Azure PaaS 快速实践 3 - Azure Caching 优化云服务

前面的实践中,基于Azure PaaS编程模式(role based)的应用已经实现了,但是其运算效率可能不高,比如,第一个用户求取了10000以内的质数之后,第二个用户后续如果求取10100以内的质数,该云服务会重新计算从1到10100之间的所有质数,显然,云服务可以借鉴前面已计算得出的10000以内的质数,然后快速计算10000到10100间的质数,最后一起返回给第二个用户,这样效率会高不少。

在Azure托管服务(即云服务)中,不建议使用asp.net本身的缓存机制,原因有三:

1. Web Role可以包含多个实例虚机,如A和B,虚机A中的缓存不会共享给虚机B,由于外部用户的请求会通过Azure云服务的负载均衡被分发到web role下的所有虚机,很有可能出现:计算10000以内质数的请求被虚机A处理,后续计算10100以内质数的请求被分发给虚机B,此时A中的缓存对B虚机中的计算没有帮助。

2. Web Role属于PaaS(平台即是服务),一旦开发者将服务部署到云端以后,该服务的环境维护完全由Azure平台来负责,因此,当Web Role实例虚机需要进行系统更新、平台更新、物理故障、发布包故障时,Azure平台会对该实例虚机进行自修复,自修复中的一种方式就是重新初始化(recycling),即Azure平台会使用开发者上传的服务包重新部署到一台新的虚拟机上,来取代有问题的虚拟机。在此Recycling的过程中,如果用户使用了虚拟内部的缓存,缓存的内容会丢失。

3.当Web Role实例虚机需要进行系统更新、平台更新、物理故障、发布包故障时,Azure平台可能会重启该实例虚机来恢复/更新服务,重启操作同样会导致虚拟内部的缓存丢失。

 

因此,对于Azure托管服务开发,有几点重要的要素:

1.每一个Role(如web role或者worker role)都至少有2个实例:这样Azure平台会将这些虚机部署在完全隔离的物理设备上,上游配以负载均衡来对外服务。且对该role进行管理时,会逐一操作,而不会同时操作,如此可以保证该web role的服务可用性(SLA

>99.95%)。

2.不要将永久使用的数据存放在PaaS虚拟上:由于Role对应的虚机可能会被重启甚至Recycling,临时存储在虚机上的数据、文件可能会丢失。因此,对于服务运行过程中需要保留的数据,请使用虚机意外的独立的永久(长久)存储。如Azure Storage, SQL Azure, Azure Caching等。

本节将使用Azure Caching来优化之前的托管服务。

 

步骤八:优化设计( Azure Caching

双击Azure项目中的WorkerRole1,然后选择Caching页面,选择(Enable Caching --> Co-located Role),并配置使用已有的Azure Storage帐号。

 

如上图配置完成后使用 “Ctrl+S”快捷键键保存整个工程。

 

从Visual Studio中确认已安装的Azure SDK版本,Visual Studio --> Help --> About Microsoft Visual Studio,如图:

 

若当前开发机安装的是SDK2.0,在worker role项目中通过Library package manager安装Caching需要的库。如下图。

 

Install-Package Microsoft.WindowsAzure.Caching -Version 2.0.0.0

若当前开发机使用的AzureSDK版本为2.1,请选用以下方法安装Caching库:

右键选择WorkerRole项目,点击Manage Nuget Packages

 

在线搜索关键字azure,并找到windows azure caching安装包,点击install完成安装。

 

 

使用上述方法中的任何一种,安装Caching库完成后,在WorlerRole1项目中的app.config文件中,可以看到自动添加进来的配置。如下图,将identifier对应的内容改为当前worker role的项目名称:WorkerRole1

 

 

完成后使用或者 “Ctrl+S”快捷键键保存整个工程。

 

双击WorkerRole1项目中的WorkerRole.cs文件,添加一个引用。

using Microsoft.ApplicationServer.Caching;

 

更新WorkerRole.cs文件中的Run函数为:

        public override void Run()

        {

            // This is a sample worker implementation. Replace with your logic.

            Trace.TraceInformation("WorkerRole1 entry point called", "Information");

 

            //Azure存储帐号

            var storageAccount = CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=portalvhds303zv6wrv9j9q;AccountKey=WNydsi2FU6GRg/KfPV7aF0P86NdXt2AS6twm+qo6zhepr580EP2T7V7PGXsXYOxK/SlKHuq5smnHVsR/1R4klQ==");

 

            var queueStorage = storageAccount.CreateCloudQueueClient();

            //检查名为 helloworldqueue 的队列是否被创建,如果没有,创建它

            var queue = queueStorage.GetQueueReference("helloworldqueue");

            queue.CreateIfNotExists();

 

            while (true)

            {

                try

                {   //read message from the queue

                    var message = queue.GetMessage();

                    string strNum = message.AsString.Substring(0, message.AsString.IndexOf("@") - 1);

                    //process message and save result into blob storage

                    string strResult = getZhishu(Convert.ToInt32(strNum.Trim()));

 

                    int count = strResult.Split(';').Count();

                    count = count - 1;

 

                    strResult = "There are " + count.ToString() + " prime numbers no greater than " + strNum + "  : " + strResult;

                    writeBlobStorage(strNum, strResult);

                    //delete message from the queue

                    queue.DeleteMessage(message);

                }

                catch (Exception ee)

                {

                }

                Thread.Sleep(3000);

                Trace.TraceInformation("Working", "Information");

            }

        }

 

更新WorkerRole.cs文件中求质数对应的函数代码,为:

        public string getZhishu(int Input)

        {

            if (Input < 2)

            {

                return "invalid input or there is no prime number less than the input number.";

            }

 

            int maxCacheInt = getCachingRef(Input);

            if (maxCacheInt > 0)

            {

                System.IO.File.AppendAllText("./workerrole.log", "\r\n" + DateTime.Now.ToString() + " : cache is called. @" + Input.ToString());

                string output0 = "";

                if (maxCacheInt >= Input)

                {

                    string content = cache.Get(maxCacheInt.ToString()) as string;

                    string[] numbers = content.Split(';');

                    for (int m = 0; m < numbers.Count() - 1; m++)

                    {

                        if (Convert.ToInt32(numbers[m]) <= Input)

                        {

                            output0 += Convert.ToInt32(numbers[m]) + ";";

                        }

                        else

                        {

                            break;

                        }

                    }

                    return output0;

                }

                else //maxCacheInt < Input

                {

                    output0 = cache.Get(maxCacheInt.ToString()) as string;

 

                    int i = maxCacheInt+1, j;

                    while (i <= Input && i >= maxCacheInt)

                    {

                        j = 2;

                        while (j <= i / 2)

                        {

                            if (i % j == 0)

                                break;

                            j++;

                        }

                        if (j > i / 2)

                        {

                            output0 += i.ToString() + ";";

                            //Console.Write("{0}\t", i);

                        }

                        i++;

                    }

 

                    //cache update

                    cache.Remove("maxint");

                    cache.Remove(maxCacheInt.ToString());

 

                    cache.Add("maxint", Input.ToString());

                    cache.Add(Input.ToString(), output0);

 

                    return output0;

                }

            }

            else

            {

                System.IO.File.AppendAllText("./workerrole.log", DateTime.Now.ToString() + " : cache is not called. @" + Input.ToString());

                string res = Calculate(Input);

                cache.Add("maxint", Input.ToString());

                cache.Add(Input.ToString(), res);

                return res;

            }

        }

 

在WorkerRole.cs文件中添加两个新的函数和定义,如下:

        public int getCachingRef(int input)

        {

            // check if the name specified is in cache 

            var maxint = (string)cache.Get("maxint");

            if (maxint != null)

            {

                try

                {

                    System.IO.File.AppendAllText("./workerrole.log", "\r\n" + DateTime.Now.ToString() + " : cached max int is " + maxint);

                    return Convert.ToInt32(maxint);

                }

                catch (Exception ex)

                {

                }              

            }

 

            return -1;

        }

 

        public static DataCacheFactory factory = new DataCacheFactory();

        public static DataCache cache = factory.GetDefaultCache();

 

        public string Calculate(int Input)

        {

            int i = 2, j, n = 0;

            string output = "";

            while (i <= Input)

            {

                j = 2;

                while (j <= i / 2)

                {

                    if (i % j == 0)

                        break;

                    j++;

                }

                if (j > i / 2)

                {

                    n += 1;

                    output += i.ToString() + ";";

                    //Console.Write("{0}\t", i);

                }

                i++;

            }

            //output = "There are " + n.ToString() + " prime numbers no greater than " + Input.ToString() + "  : " + output;

            return output;

        }

 

代码更新后,WorkerRole.cs文件结构如图:

 

 

完成后本地Rebuild,并F5调试,确认workerrole运行正常。

 

同步骤五,发布到Azure Cloud中。

 

发布完成后,登录Azure 管理主页,选中刚刚发布的云服务,同之前的动手实践一样,配置remote连接功能。

 

 

配置Remote后,访问刚刚发布的云服务,进行几次测试,确认前端工作正常。

 

从管理主页上远程连接到刚刚发布的Worker Role虚机上,在E:\approot下可以找到项目code打出的日志文件workerrole.log,如图,日志中记录了之前的请求,并显示cache被调用过。

 

 

至此,一个简单的依赖于Azure Caching的实践已经完成了。

此实验对于求素数的业务逻辑没有深入研究,在算法效率和代码重用方面投入的精力较少,开发者可以结合本次整个培训学习到的知识,进一步的改进和完善。