客户需求

客户的环境是一个网络音乐播放系统,根据网络情况提供给手机用户收听各种码率的MP3歌曲,在客户没购买歌曲的情况下提供一个三十秒内的试听版本。这样一个系统非常明确地一个需求就是会定期需要将一批从音乐版商手中获取到的高比特率音乐文件转换成各种低码率的MP3文件和试听文件,由于收到版商的文件数量和时间都不确定,所以长期部署大量的转码服务器为系统提供转码服务显然非常浪费资源,但是如果不准备好足够的转码服务器的话,当大批量文件需要转码时又没法能够快速完成任务,在现在这个时间比金钱更加重要的互联网时代显然是不可接受的。这时候选择公有云这样高弹性、按需计费的计算平台就显得非常合适了。

技术选型

使用Azure Fuctions+Azure Batch+Azure Blob Storage方案,全部都是基于PaaS平台,无需对服务器进行管理,省去服务器在日常维护中各种补丁安全管理要求。

方案架构图:

方案实现:

利用Azure Function监控Blob文件变化,Azure Functions的一大优点就是提供了不同类型的触发器(http Trigger,Blob Trigger,Timer Trigger,Queue Trigger…),这里我们正好利用上Blob Trigger用来监控Blob文件的变化。

首先是创建一个Azure Functions的Project

然后指定Function是用Blob Trigger的。

创建ListeningBlob函数,

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Queue; namespace MS.CSU.mp3encoder
{ public static class ListeningBlob
{ static string key_Convert = Environment.GetEnvironmentVariable("KeyConvert") ?? "-i \"{0}\" -codec:a libmp3lame -b:a {1} \"{2}\" -y";
static string work_Dir = Path.GetTempPath();
static string targetStorageConnection = Environment.GetEnvironmentVariable("targetStorageConnection");
static string sourceStorageConnection = Environment.GetEnvironmentVariable("sourceStorageConnection");
static string bitRates = Environment.GetEnvironmentVariable("bitRates") ?? "192k;128k;64k";
static string keyPreview = Environment.GetEnvironmentVariable("keyPreview") ?? "-ss 0 -t 29 -i \"{0}\" \"{1}\"";
static CloudBlobClient blobOutputClient; static string blobOutputContainerName = Environment.GetEnvironmentVariable("outputContainer") ?? "output"; static CloudBlobContainer blobOutputContainer; static CloudBlobClient blobInputClient; static CloudBlobContainer blobInputContainer; [FunctionName("ListeningBlob")]
[return: Queue("jobs")]
public static void Run([BlobTrigger("source/{name}", Connection = "sourceStorageConnection")]Stream myBlob, string name, Uri uri, TraceWriter log)
{
AzureBatch batch = new AzureBatch(sourceStorageConnection);
//保证每个音频文件都有自己的处理文件夹,避免冲突
Guid jobId = Guid.NewGuid();
log.Info($"Job:{jobId},C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes,Path:{uri.ToString()}");
//将源Blob剪切到TargetBlob,将源文件移出监控Blob容器,避免误触发
try
{
initBlobClient();
CloudBlockBlob sourceBlob = blobInputContainer.GetBlockBlobReference($"{name}");
name = Path.GetFileNameWithoutExtension(name);
CloudBlockBlob targetBlob = blobOutputContainer.GetBlockBlobReference($"{name}_{jobId}/{name}.mp3");
targetBlob.StartCopy(sourceBlob);
sourceBlob.Delete();
uri = targetBlob.Uri;
}
catch (Exception err)
{
log.Error($"删除源Blob错误!Err:{err}"); return ;
}
List<EncodeJob> jobs = new List<EncodeJob>(); string url = Uri.EscapeUriString(uri.ToString());
log.Info($"需要转换的码率:{bitRates}");
string[] bitsRateNames = bitRates.Split(';');
Dictionary<string, bool> status = new Dictionary<string, bool>(); foreach (var s in bitsRateNames)
{
if (string.IsNullOrWhiteSpace(s))
continue;
var job = new EncodeJob()
{
OutputName = $"{name}{s}.mp3",
Name = name,
Command = string.Format(key_Convert, name, s, $"{name}{s}.mp3"),
id = jobId,
InputUri = uri
};
batch.QueueTask(job);
} var previewJob = new EncodeJob()
{
Name = name,
OutputName = $"{name}preview.mp3",
Command = string.Format(keyPreview, name, $"{name}preview.mp3"),
InputUri = uri,
id = jobId,
}; batch.QueueTask(previewJob); //Directory.Delete($"{work_Dir}\\{jobId}",true); } static void initBlobClient()
{
CloudStorageAccount storageOutputAccount = CloudStorageAccount.Parse(targetStorageConnection);
// Create a blob client for interacting with the blob service. blobOutputClient = storageOutputAccount.CreateCloudBlobClient(); blobOutputContainer = blobOutputClient.GetContainerReference(blobOutputContainerName);
blobOutputContainer.CreateIfNotExists();
//初始化输入的Storage容器
CloudStorageAccount storageInputAccount = CloudStorageAccount.Parse(sourceStorageConnection);
// Create a blob client for interacting with the blob service. blobInputClient = storageInputAccount.CreateCloudBlobClient(); blobInputContainer = blobInputClient.GetContainerReference("source"); }
}
}

创建Batch服务账号,并且获取Batch Account的相关信息。

https://ffmpeg.zeranoe.com/下载最新的ffmpeg程序,安装后将ffmpeg.exe单独压缩成zip文件,然后上传到Batch中,为程序调用做准备,

构建Azure Batch类用于调用Azure Batch进行ffmpeg进行转换

using Microsoft.Azure.Batch;
using Microsoft.Azure.Batch.Auth;
using Microsoft.Azure.Batch.Common;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace MS.CSU.mp3encoder
{
public class AzureBatch
{
//ffmpeg相关信息;
string env_appPackageInfo = Environment.GetEnvironmentVariable("ffmpegversion")??"ffmpeg 3.4";
string appPackageId = "ffmpeg";
string appPackageVersion = "3.4";
// Pool and Job constants
private const string PoolId = "WinFFmpegPool";
private const int DedicatedNodeCount = 0;
private const int LowPriorityNodeCount = 5;
//指定执行转码任务的VM机型
private const string PoolVMSize = "Standard_F2";
private const string JobName = "WinFFmpegJob";
string outputStorageConnection;
string outputContainerName = "output";
string batchAccount = Environment.GetEnvironmentVariable("batchAccount");
string batchKey = Environment.GetEnvironmentVariable("batchKey");
string batchAccountUrl = Environment.GetEnvironmentVariable("batchAccountUrl");
string strMaxTaskPerNode = Environment.GetEnvironmentVariable("MaxTaskPerNode") ?? "4";
//设置每个计算节点能同时处理的任务数量,可根据选择的VM类型和任务类型适当调整
int maxTaskPerNode=4;
public AzureBatch(string storageConnection)
{
outputStorageConnection = storageConnection;
}
//用于单元测试时创建Batch对象
public AzureBatch(string storageConnection, string _batchAccount, string _batchAccountUrl, string _batchKey)
{
outputStorageConnection = storageConnection;
batchAccount = _batchAccount;
batchAccountUrl = _batchAccountUrl;
batchKey = _batchKey;
maxTaskPerNode = int.TryParse(strMaxTaskPerNode, out maxTaskPerNode) ? maxTaskPerNode : 4;
appPackageId = env_appPackageInfo.Split(' ')[0] ?? "ffmpeg";
appPackageVersion = env_appPackageInfo.Split(' ')[1] ?? "3.4";
} /// <summary>
/// Returns a shared access signature (SAS) URL providing the specified
/// permissions to the specified container. The SAS URL provided is valid for 2 hours from
/// the time this method is called. The container must already exist in Azure Storage.
/// </summary>
/// <param name="blobClient">A <see cref="CloudBlobClient"/>.</param>
/// <param name="containerName">The name of the container for which a SAS URL will be obtained.</param>
/// <param name="permissions">The permissions granted by the SAS URL.</param>
/// <returns>A SAS URL providing the specified access to the container.</returns>
private string GetContainerSasUrl(CloudBlobClient blobClient, string containerName, SharedAccessBlobPermissions permissions)
{
// Set the expiry time and permissions for the container access signature. In this case, no start time is specified,
// so the shared access signature becomes valid immediately. Expiration is in 2 hours.
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy
{
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(2),
Permissions = permissions
}; // Generate the shared access signature on the container, setting the constraints directly on the signature
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
string sasContainerToken = container.GetSharedAccessSignature(sasConstraints); // Return the URL string for the container, including the SAS token
return String.Format("{0}{1}", container.Uri, sasContainerToken);
} // BATCH CLIENT OPERATIONS - FUNCTION IMPLEMENTATIONS /// <summary>
/// Creates the Batch pool.
/// </summary>
/// <param name="batchClient">A BatchClient object</param>
/// <param name="poolId">ID of the CloudPool object to create.</param>
private void CreatePoolIfNotExist(BatchClient batchClient, string poolId)
{
// if (batchClient.PoolOperations.GetPool(poolId) != null)
// {
// return;
// }
CloudPool pool = null;
try
{
ImageReference imageReference = new ImageReference(
publisher: "MicrosoftWindowsServer",
offer: "WindowsServer",
sku: "2012-R2-Datacenter-smalldisk",
version: "latest"); //ImageReference imageReference = new ImageReference(
// publisher: "MicrosoftWindowsServer",
// offer: "WindowsServer",
// sku: "2016-Datacenter-samlldisk",
// version: "latest"); VirtualMachineConfiguration virtualMachineConfiguration =
new VirtualMachineConfiguration(
imageReference: imageReference,
nodeAgentSkuId: "batch.node.windows amd64"); // Create an unbound pool. No pool is actually created in the Batch service until we call
// CloudPool.Commit(). This CloudPool instance is therefore considered "unbound," and we can
// modify its properties.
pool = batchClient.PoolOperations.CreatePool(
poolId: poolId,
targetDedicatedComputeNodes: DedicatedNodeCount,
targetLowPriorityComputeNodes: LowPriorityNodeCount,
virtualMachineSize: PoolVMSize, virtualMachineConfiguration: virtualMachineConfiguration);
pool.MaxTasksPerComputeNode = maxTaskPerNode; // Specify the application and version to install on the compute nodes
// This assumes that a Windows 64-bit zipfile of ffmpeg has been added to Batch account
// with Application Id of "ffmpeg" and Version of "3.4".
// Download the zipfile https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-3.4-win64-static.zip
// to upload as application package
pool.ApplicationPackageReferences = new List<ApplicationPackageReference>
{
new ApplicationPackageReference
{
ApplicationId = appPackageId,
Version = appPackageVersion
}
}; pool.Commit();
}
catch (BatchException be)
{
// Accept the specific error code PoolExists as that is expected if the pool already exists
if (be.RequestInformation?.BatchError?.Code == BatchErrorCodeStrings.PoolExists)
{
// Console.WriteLine("The pool {0} already existed when we tried to create it", poolId);
}
else
{
throw; // Any other exception is unexpected
}
}
} /// <summary>
/// Creates a job in the specified pool.
/// </summary>
/// <param name="batchClient">A BatchClient object.</param>
/// <param name="jobId">ID of the job to create.</param>
/// <param name="poolId">ID of the CloudPool object in which to create the job.</param>
private void CreateJobIfNotExist(BatchClient batchClient, string jobId, string poolId)
{
//if (batchClient.JobOperations.GetJob(jobId) != null)
// return;
try
{
Console.WriteLine("Creating job [{0}]...", jobId); CloudJob job = batchClient.JobOperations.CreateJob();
job.Id = $"{JobName}";
job.PoolInformation = new PoolInformation { PoolId = poolId }; job.Commit();
}
catch (BatchException be)
{
// Accept the specific error code JobExists as that is expected if the job already exists
if (be.RequestInformation?.BatchError?.Code == BatchErrorCodeStrings.JobExists)
{
Console.WriteLine("The job {0} already existed when we tried to create it", jobId);
}
else
{
throw; // Any other exception is unexpected
}
}
} /// <summary>
///
/// </summary>Creates tasks to process each of the specified input files, and submits them
/// to the specified job for execution.
/// <param name="batchClient">A BatchClient object.</param>
/// <param name="jobId">ID of the job to which the tasks are added.</param>
/// <param name="inputFiles">A collection of ResourceFile objects representing the input file
/// to be processed by the tasks executed on the compute nodes.</param>
/// <param name="outputContainerSasUrl">The shared access signature URL for the Azure
/// Storagecontainer that will hold the output files that the tasks create.</param>
/// <returns>A collection of the submitted cloud tasks.</returns>
private List<CloudTask> AddTasks(BatchClient batchClient,EncodeJob job, string outputContainerSasUrl)
{ // Create a collection to hold the tasks added to the job:
List<CloudTask> tasks = new List<CloudTask>(); // Assign a task ID for each iteration
var taskId = String.Format("Task{0}", Guid.NewGuid()); // Define task command line to convert the video format from MP4 to MP3 using ffmpeg.
// Note that ffmpeg syntax specifies the format as the file extension of the input file
// and the output file respectively. In this case inputs are MP4.
string appPath = String.Format("%AZ_BATCH_APP_PACKAGE_{0}#{1}%", appPackageId, appPackageVersion);
string inputMediaFile = job.Name;
string outputMediaFile = job.OutputName; string taskCommandLine = String.Format("cmd /c {0}\\ffmpeg.exe {1}", appPath, job.Command);
// Create a cloud task (with the task ID and command line) and add it to the task list
CloudTask task = new CloudTask(taskId, taskCommandLine);
task.ApplicationPackageReferences = new List<ApplicationPackageReference>
{
new ApplicationPackageReference
{
ApplicationId = appPackageId,
Version = appPackageVersion
}
};
task.ResourceFiles = new List<ResourceFile>();
task.ResourceFiles.Add(new ResourceFile(Uri.EscapeUriString(job.InputUri.ToString()), inputMediaFile)); // Task output file will be uploaded to the output container in Storage. List<OutputFile> outputFileList = new List<OutputFile>();
OutputFileBlobContainerDestination outputContainer = new OutputFileBlobContainerDestination(outputContainerSasUrl,$"{job.Name}_{job.id}/{job.OutputName}");
OutputFile outputFile = new OutputFile(outputMediaFile,
new OutputFileDestination(outputContainer),
new OutputFileUploadOptions(OutputFileUploadCondition.TaskSuccess));
outputFileList.Add(outputFile);
task.OutputFiles = outputFileList;
tasks.Add(task); // Call BatchClient.JobOperations.AddTask() to add the tasks as a collection rather than making a
// separate call for each. Bulk task submission helps to ensure efficient underlying API
// calls to the Batch service.
batchClient.JobOperations.AddTask($"{JobName}", tasks); return tasks;
}
private CloudBlobClient initBlobClient()
{
CloudStorageAccount storageOutputAccount = CloudStorageAccount.Parse(outputStorageConnection);
// Create a blob client for interacting with the blob service. var blobOutputClient = storageOutputAccount.CreateCloudBlobClient();
return blobOutputClient;
//blobOutputContainer = blobOutputClient.GetContainerReference(blobOutputContainerName);
//blobOutputContainer.CreateIfNotExists(); }
public void QueueTask(EncodeJob job)
{
BatchSharedKeyCredentials sharedKeyCredentials = new BatchSharedKeyCredentials(batchAccountUrl, batchAccount, batchKey);
var blobClient = initBlobClient();
var outputContainerSasUrl = GetContainerSasUrl(blobClient, outputContainerName, SharedAccessBlobPermissions.Write);
using (BatchClient batchClient = BatchClient.Open(sharedKeyCredentials))
{
// Create the Batch pool, which contains the compute nodes that execute the tasks.
CreatePoolIfNotExist(batchClient, PoolId); // Create the job that runs the tasks.
CreateJobIfNotExist(batchClient, $"{JobName}", PoolId); // Create a collection of tasks and add them to the Batch job.
// Provide a shared access signature for the tasks so that they can upload their output
// to the Storage container.
AddTasks(batchClient,job,outputContainerSasUrl); }
} public async Task<Tuple<string,int>> GetStatus()
{
BatchSharedKeyCredentials sharedKeyCredentials = new BatchSharedKeyCredentials(batchAccountUrl, batchAccount, batchKey);
string result = "正在获取任务信息...";
int total = 0;
using (BatchClient batchClient = BatchClient.Open(sharedKeyCredentials))
{
var counts =await batchClient.JobOperations.GetJobTaskCountsAsync(JobName);
total = counts.Active + counts.Running + counts.Completed; result = $"总任务:{total},等待的任务:{counts.Active},运行中的任务:{counts.Running},成功的任务:{counts.Succeeded},失败的任务:{counts.Failed}";
}
return new Tuple<string,int>(result,total);
}
}
}

由于Azure Functions的最大Timeout时间为10分钟,当执行一些大型的文件转换时如果是同步执行往往会导致超时错误,所以我们需要在调用完Batch的任务后即可返回,让Batch Task后台执行。为了监控这些Task的完成状况,我们需要构建一个定时的Functions来检查任务状态。然后将获取到的状态信息写到output Blob Container的status.html中就好了

using System;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob; namespace MS.CSU.mp3encoder
{
/// <summary>
/// 用于更新任务处理状态
/// </summary>
public static class StatusUpdate
{
static int lastTotal=0;
static DateTime lastSubmitTime;
static string targetStorageConnection = Environment.GetEnvironmentVariable("targetStorageConnection");
static CloudBlobClient blobOutputClient; static string blobOutputContainerName = Environment.GetEnvironmentVariable("outputContainer") ?? "output"; static CloudBlobContainer blobOutputContainer;
[FunctionName("StatusUpdate")]
public async static Task Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer, TraceWriter log)
{
string strStatus = "";
int jobCount = 0;
try
{
AzureBatch batch = new AzureBatch(targetStorageConnection);
var result=await batch.GetStatus();
strStatus = result.Item1;
jobCount = result.Item2 - lastTotal;
if (lastTotal != result.Item2)
{
lastTotal = result.Item2; lastSubmitTime = DateTime.Now;
} }
catch (Exception err)
{
strStatus = Uri.EscapeDataString(err.ToString());
};
initBlobContainer();
var statusBlob = blobOutputContainer.GetBlockBlobReference("status.html");
string htmlStatus =$@"<html>
<head>
<meta http-equiv=""refresh"" content=""5"">
< meta charset=""utf-8"">
</head>
<body>
<h1>{strStatus}</h1><br/>
<h1>最后更新 :{DateTime.Now.AddHours(8)}</h1>
<h1>上次任务提交时间:{lastSubmitTime.AddHours(8)}<h1>
<h2>上次任务最后五分钟内提交了{jobCount}<h2>
</body>
</html>";
await statusBlob.UploadTextAsync(htmlStatus); }
private static void initBlobContainer()
{
CloudStorageAccount storageOutputAccount = CloudStorageAccount.Parse(targetStorageConnection);
// Create a blob client for interacting with the blob service. blobOutputClient = storageOutputAccount.CreateCloudBlobClient(); blobOutputContainer = blobOutputClient.GetContainerReference(blobOutputContainerName);
blobOutputContainer.CreateIfNotExists(); }
}
}

最终效果:

引用资料:

Azure Blob storage bindings for Azure Functions

Timer trigger for Azure Functions

Azure Batch .NET File Processing with ffmpeg

FFmpeg MP3 Encoding Guide

Azure Functions + Azure Batch实现MP3音频转码方案的更多相关文章

  1. 技术博客:Azure Functions + Azure Storage 开发

    Azure GitHub wiki 同步发布 传送门 Azure Functions 通过 Functions(一个事件驱动型无服务器计算平台,还可以解决复杂的业务流程问题)更加高效地进行开发.在本地 ...

  2. 使用 Visual Studio 开发、测试和部署 Azure Functions(一)开发

    1,什么是Azure functions Azure Functions 是 Microsoft Azure 提供的完全托管的 PaaS 服务,用于实现无服务器体系结构. Azure Function ...

  3. 使用VS code 创建 Azure Functions,从blob触发,解析,发送至Service Bus

    更多内容,关注公众号:来学云计算 场景: 某设备定时于每天23:00左右将一天的运行日志.devicelogtxt上传到Azure Blob,期待Blob文件上传后, 自动通过Azure Functi ...

  4. 设备数据通过Azure Functions 推送到 Power BI 数据大屏进行展示(2.Azure Functions实战)

    本案例适用于开发者入门理解Azure Functions/ IoT Hub / Service Bus / Power BI等几款产品. 主要实战的内容为: 将设备遥测数据上传到物联网中心, 将遥测数 ...

  5. 设备数据通过Azure Functions 推送到 Power BI 数据大屏进行展示(1.准备工作)

    本案例适用于开发者入门理解Azure Functions/ IoT Hub / Service Bus / Power BI等几款产品. 主要实战的内容为: 将设备遥测数据上传到物联网中心, 将遥测数 ...

  6. 通过流量管理器和 Azure Functions(作为代理)为全球用户提供最靠近的认知服务(或自定义API)

    本实战是一个中等复杂度的综合性实战,涉及到的内容有TrafficManager,AzureFunctions,域名/域名解析等几个内容. 本案例基础介绍: https://www.bilibili.c ...

  7. 使用 Visual Studio 开发、测试和部署 Azure Functions(二)测试,部署

    1,引言 上一篇介绍了使用使用 Visual Studio 开发 "Azure Functions" 函数,此篇介绍 “Azure Functions” 的测试以及直接从 Vist ...

  8. 使用Azure Functions 在web 应用中启用自动更新(一)分析基于轮询的 Web 应用的限制

    1,引言 上一篇介绍了使用使用 Visual Studio 开发 "Azure Functions" 函数,此篇介绍 “Azure Functions” 的测试以及直接从 Vist ...

  9. 利用Azure Functions和k8s构建Serverless计算平台

    题记:昨晚在一个技术社区直播分享了"利用Azure Functions和k8s构建Serverless计算平台"这一话题.整个分享分为4个部分:Serverless概念的介绍.Az ...

随机推荐

  1. java_web学习(五) JSTL标准标签库

    1.什么是JSTL JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能. JSTL支持通用的.结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签,SQL标签. ...

  2. 在visual studio的工程项目应用中打开console控制窗口

    在visual studio的工程项目应用中打开console控制窗口,这个可以方便我们在console中输出参数的值检查错误. 只需要在需要打开console的地方加入下面的代码即可. AllocC ...

  3. loadrunner-获取返回值和自定义参数(参数运算)

    实例:手机端操作,A新增了一条事件(返回结果:事件id,例如:1), A这时需要获取新增产生的事件id,并作为参数进行传递,才能将这条事件上报给B(返回结果:事件id不变,步骤id等于事件id加1), ...

  4. 详解css3弹性盒模型(Flexbox)

    目前没有浏览器支持 box-flex 属性. Firefox 支持替代的 -moz-box-flex 属性. Safari.Opera 以及 Chrome 支持替代的 -webkit-box-flex ...

  5. Gym100814B Gym100814F Gym100814I(异或) ACM International Collegiate Programming Contest, Egyptian Collegiate Programming Contest (2015) Arab Academy for Science and Technology

    今日份的训练题解,今天写出来的题没有昨天多,可能是因为有些事吧... Gym100814B 这个题就是老师改卷子,忘带标准答案了,但是他改了一部分卷子,并且确定自己改的卷子没出错,他想从改过的卷子里把 ...

  6. opencv+vs配环境

    首先,一定要注意debug和release下配的项目设置是有区分的!!!!!!!!!!! 1.注意自己的电脑是64位还是32位 2.要在环境变量中设置环境变量,环境变量从前向后扫描,用64位环境变量时 ...

  7. 关于PHP 开启zlib gzip配置

    一般有两种方法:一种是使用ob_start("ob_gzhandler") ,是开启php自带的zlib配置,这里要说的是后者,此两种方法只能选其一,否则会报错 一.打开php.i ...

  8. 003_JS基础_面向对象基础

    3.1 对象   引入:在js中表示一个人的信息(name, gender, age)通过var申明三个变量,但是这样使用基本数据类型的变量,他们是互相独立的,没有联系:  此时就需要使用对象,对象是 ...

  9. 01 整合IDEA+Maven+SSM框架的高并发的商品秒杀项目之业务分析与DAO层

    作者:nnngu 项目源代码:https://github.com/nnngu/nguSeckill 这是一个整合IDEA+Maven+SSM框架的高并发的商品秒杀项目.我们将分为以下几篇文章来进行详 ...

  10. xml报错 Parse Fatal Error :在实体引用中,实体名称必须紧跟在'&'后面

    修改jndi配置文件中的密码后,重启tomcat报错如下  实际问题是xml中默认’&’是非法字符,用     &   替代