网站架构之缓存应用(摘录)

网站缓存这个话题并不新颖,但是能否将它用好,可是一门学问,同一件工具在不同人的手中会做出不同的事情来。这里我来分享总结下我对于网站架构中缓存应用的一些看法和经验,大家有好的想法可以补充 
    第一:缓存的一些基本概念。
        1:缓存(CACHE)与缓冲(BUFFER)的区别,我认为缓存可以在某种程序上理解成一级缓存(Primary Cache),数据全局共享。缓冲则属于二级缓存,只对一部分对象共享数据,二级缓存在某种程序上主要是降低一级缓存组件的访问压力以及提高缓存的存取效率。
        2:缓存的一些基本属性:命中率,表示缓存命中的次数/总的请求数,这是缓存设计的重要质量指标之一;缓存执行效率,例如GET,INSERT,DELETE等;容量,即缓存介质的容量最大值;成本,即开发成本,部署成本,软硬件成本。
        3:缓存的问题,存储介质的选择往往左右缓存的设计,缓存在不命中时往往会使性能下降。
    第二:网站中缓存的应用场景:
        1:可以缓存整个页面的html,提高访问响应能力;
        2:针对局部页面元素进行缓存;
        3:对复杂数据的结果进行缓存,例如一个查询需要结合多个数据集,然后根据这些数据集进行相应的运算,即使每个子集查询有缓存,但还是需要额外的运算,这种情况可以考虑缓存计算后的结果。
        4:对耗时的查询进行缓存,例如产品列表页的查询。
        5:和上下文相关的用户数据,例如用户从订单埴写页进入到订单成功页,或者是从产品列表页点击详细产品进行预订时的订单填写页,此时这两个页面之间都需要传递大量的相关数值,我们可以把所有的数值封装在一个类中,然后通过缓存进行通信。
    第三:影响缓存命中率的因素。
        1:数据时实性,每个业务系统都对自己的数据有相应的要求,有些数据的实时性非常强,像每日的股票信息,这种情况如果设置了缓存,缓存的命中率会特别低。
        2:缓存粒度问题,一般来说是缓存的跨度太大,即此时的KEY值包含的条件太多,会出现缓存命中率特别低的情况。
    第四:提高缓存命中率的方法:
        1:增大存储介质的容量;
        2:对非常热点的数据进行捕捉,可以采用实时更新缓存的方式来平衡缓存与实时性的问题,例如可以单独开启一个后台服务来定时做更新缓存的工作。
        3:调整缓存KEY值的算法,尽量保证缓存KEY的细粒度,KEY-VALUE就是很好的细粒度例子。
        4:根据业务调整缓存的过期策略。
       
    第五:如何实现缓存组件:
        1:采用二级缓存架构,即在web server上设置二级缓存,这里的二级缓存即上面的提到的缓冲,只对具体的webserver进行数据共享,二级缓存可以考虑采用微软企业库的缓存组件来完成。由于一级缓存的实现往往都是单独的服务器,为了减少缓存服务器的压力,在webserver上对数据进行缓冲,在降低缓存服务器压力的情况下最大的好处在于缓存的存取速度上。
        2:一级缓存由单独的缓存服务器来完成,至于缓存服务器采用哪种缓存方案,可以根据不同的场景来决定。如果考虑到部署的方便性,可以采用微软企业库来完成一级缓存,如果服务器允许,可以采用memcached来实现。
        cache服务器采用微软企业库实现的优缺点:
        1:优点,开发以及部署都非常容易,不需要安装第三方软件等等;
        2:缺点,需要自己开发客户端功能以实现分布式,这里我们可以采用一致性hash算法来实现,同时如果服务以WCF形式公布开,在访问效率上也不是最优的,比起memcached的通信方式要差一些。
        memcached的优缺点:
        1:优点,通信方式比起wcf要高;
        2:缺点,需要第三方服务的支持,需要在服务器上安装memcached服务,这好像也不是什么重要的缺点。


        最后,贴出网站网页在数据访问上的流程图,供大家参考,在下面的文章中我会把实现的方案分享出来。

 

上一篇我主要总结了网站缓存中的一些基本概念,以及我对于网站架构缓存应用的架构实现思路,这篇主要分享下如何利用微软企业库来实现一二级缓存的缓存服务。

   
    为了能够有效的管理缓存,需要对使用缓存方法上做一些规范,即要想使用缓存组件提供的服务,需要在指定的配置文件中按照一定的规则来配置缓存条目,不允许在配置之处使用缓存。下面先展示下一条Cache条目的配置:

<Region name="MyBlog">
    <SubRegion name="default">  
      <Cache CacheMode="LocalCacheOnlyMode" Key="BlogListConfigKey" BufferType="AbsoluteTime" BufferTimeSeconds="300" CacheType="AbsoluteTime" CacheTimeMinutes="30" CachePriority="Normal"/>    
    </SubRegion>
  </Region>

 

  
     上面的代码中,其实由三部分构成:
     1:主分区:Regin,如果一个网站分很多子系统,可以为每个子系统定义一个这样的主分区,例如食品频道Food,手机频道Mobile等;
     2:子分区:SubRegion,主分区下面的子分区,即对子系统更加小的划分,可以根据子系统的功能来划分,例如产品列表页List,详细页Detail等;
     3:缓存条目:Cache,指具体的一则缓存条目规则,这里的缓存条目规则并不是指某一条缓存设置,而是指一条缓存规则,与具体的缓存条目是一对多的关系。
        <1>:CacheMode,设置缓存模式,是只有二级缓存还是即有一级缓存也有二级缓存,例如用户页面之间的信息沟通就只需要二级缓存,即缓存在web server上。而产品列表页的数据属于全局数据,就需要即采用二级缓存也需要一级缓存。
        <2>:BufferType,指二级缓存的过期方式,分为绝对过期,滑动过期,文件依赖。
        <3>:BufferTimeSeconds,二级缓存Timespan中的秒。
        <4>:CacheType,一级缓存的过期方式,类型同BufferType.
        <5>:CacheTimeMinutes,一级缓存Timespan中的分钟。
        <6>:CachePriority,缓存的优先级。


     二级缓存实现:
     第一:IWebCacheProvider,缓存提供者接口,它公布了所有缓存组件需要的方法,接口之所以加上了ServeiceContract标签,是由于下面的一级缓存WCF服务也继承此接口的原因。小提示:WCF服务契约对于方法重载的实现和普通方式有小小区别,请注意OperationContract标签的定义。
    

 [ServiceContract]
    public  interface IWebCacheProvider
    {
        [OperationContract(Name = "Add")]
        void Insert(string key, object value, string region, string subRegion);

        [OperationContract(Name = "AddByAbsoluteTime")]
        void Insert(string key, object value, string region, string subRegion, MyCacheItemPriority scavengingPriority, AbsoluteTimeCacheDependency absoluteTimeCacheDependency);

        [OperationContract(Name = "AddBySlidingTime")]
        void Insert(string key, object value, string region, string subRegion, MyCacheItemPriority scavengingPriority, SlidingTimeCacheDependency slidingTimeCacheDependency);

        [OperationContract(Name = "AddByFile")]
        void Insert(string key, object value, string region, string subRegion, MyCacheItemPriority scavengingPriority, FileCacheDependency fileCacheDependency);

        [OperationContract]
        void Delete(string key, string region, string subRegion);

        [OperationContract]
        object Get(string key, string region, string subRegion);

        [OperationContract]
        void Clear(string region);

        [OperationContract]
        int Count(string region);
    }


 

   
    第二:EntLibWebCacheProvider,微软企业库实现缓存实现类。代码并不贴了,基本就是利用企业库的缓存组件实现上面的接口。
   
     第三:MyWebCacheServiceClient,提供缓存客户端的实例。包含了两个重要的属性:一级缓存实例,二级缓存实例。余下的就是调用EntLibWebCacheProvider来完成缓存的调用,以及根据缓存规则,选择操作一级缓存以及二级缓存。

           说明:下面代码中的GetMemcachedWebCacheProvider是下篇文章会提到的利用memcached实现一级缓存,由于需要支持一级缓存在企业库以及memcached之间的切换才出现的逻辑。
     

      //一级缓存
        IWebCacheProvider PrimaryCacheProvider;
        //二级缓存
        IWebCacheProvider SecondaryCacheProvider;
         /// <summary>
        /// 实例化二级缓存
        /// </summary>
        /// <param name="configFilePath"></param>
        /// <returns></returns>
        private IWebCacheProvider GetSecondaryCacheProvider()
        {
            IWebCacheProvider provider = null;
            provider = WebCacheProviderFactory.GetEntLibWebCacheProvider(configFilePath);
            return provider;
        }
        /// <summary>
        /// 获取一级缓存
        /// </summary>
        /// <param name="hashKey"></param>
        /// <param name="configFilePath"></param>
        /// <returns></returns>
        private IWebCacheProvider GetPrimaryCacheProvider(uint hashKey)
        {
            IWebCacheProvider provider = null;
            string cacheType = WebConfig.ChannelConfig["CacheType"].ToString().ToLower();
            switch (cacheType)
            {
                case "memcached":
                    provider = WebCacheProviderFactory.GetMemcachedWebCacheProvider(configFilePath);
                    break;
                case "entlib":
                    provider = servicePool.GetServiceClient(hashKey) as IWebCacheProvider;
                    break;
            }
          
            return provider;
        }

 

       
       一级缓存的实现:由于一级缓存也采用微软企业库实现,而企业库本身是不具备分布式功能的,就算是memcached,本身也不具备分布式,而在于客户端的实现,所以企业库我们也可以实现分布式。
        首先:我们把实现了缓存的组件以WCF服务形式分布出来,在多个服务器上部署,形成一个服务器群;
        其实:实现分布式的客户端,让服务的请求均匀的分布到之前部署的WCF缓存服务器上。这里采用一致性hash算法实现,主要是根据key的hash值以及服务器数量来选择存储的服务器,这里贴些主要的实现代码,供参考:
       
        1:定义一个hash的服务器实例集合,为每个服务器分配250个hash值,这里的值可以根据实际情况调整。

private Dictionary<uint, isRoc.Common.Cache.CacheProvider.IWebCacheProvider> hostDictionary;

 

        2:定义一个hash的服务实例key集合,用来统计所有服务器实例中的hash key。 

private uint[] hostKeysArray;

 

        3:创建服务器列表的hash值。

/// <summary>
        /// 重新设置服务器列表
        /// </summary>
        /// <param name="hosts">服务器列表</param>
        internal void Setup(List<WebCacheServerInfo> hosts)
        {
            hostDictionary = new Dictionary<uint, isRoc.Common.Cache.CacheProvider.IWebCacheProvider>();
            List<isRoc.Common.Cache.CacheProvider.IWebCacheProvider> clientList = new List<isRoc.Common.Cache.CacheProvider.IWebCacheProvider>();
            List<uint> hostKeysList = new List<uint>();
            foreach (WebCacheServerInfo host in hosts)
            {
                //创建客户端

                isRoc.Common.Cache.CacheProvider.IWebCacheProvider client = ServiceProxyFactory.Create<isRoc.Common.Cache.CacheProvider.IWebCacheProvider>(host .HostUri );
                //Create 250 keys for this pool, store each key in the hostDictionary, as well as in the list of keys.
                for (int i = 0; i < 250; i++)
                {
                    uint key = 0;
                    switch (this.HashAlgorithm)
                    {
                        case EHashAlgorithm.KetamaHash:
                            key = (uint)Math.Abs(KetamaHash.Generate(host.HostUri + "-" + i));
                            break;
                        case EHashAlgorithm.FnvHash32:
                            key = BitConverter.ToUInt32(new ModifiedFNV1_32().ComputeHash(Encoding.UTF8.GetBytes(host.HostUri + "-" + i)), 0);
                            break;
                    }

                    if (!hostDictionary.ContainsKey(key))
                    {
                        hostDictionary.Add(key, client);
                        hostKeysList.Add(key);
                    }
                }

                clientList.Add(client);
            }

            //Hostlist should contain the list of all pools that has been created.
            clientArray = clientList.ToArray();

            //Hostkeys should contain the list of all key for all pools that have been created.
            //This array forms the server key continuum that we use to lookup which server a
            //given item key hash should be assigned to.
            hostKeysList.Sort();
            hostKeysArray = hostKeysList.ToArray();

        }

        4:如何根据key值来查找具体的缓存服务实例呢,即具体的key存储在哪一台服务器上呢?根据传入的key值,计算出对应的hash值,然后经过特定的算法计算出服务器实例地址。 

 internal isRoc.Common.Cache.CacheProvider.IWebCacheProvider GetServiceClient(uint hash)
        {
            //Quick return if we only have one host.
            if (clientArray.Length == 1)
            {
                return clientArray[0];
            }

            //New "ketama" host selection.
            int i = Array.BinarySearch(hostKeysArray, hash);

            //If not exact match...
            if (i < 0)
            {
                //Get the index of the first item bigger than the one searched for.
                i = ~i;

                //If i is bigger than the last index, it was bigger than the last item = use the first item.
                if (i >= hostKeysArray.Length)
                {
                    i = 0;
                }
            }
            return hostDictionary[hostKeysArray[i]];
        }

        总结:本文简单的介绍了如何利用微软企业库来实现具有两级缓存的缓存组件,上篇我提到过,实现一级缓存也可以采用memcached,采用memcached可以不用自己开发分布式客户端,目前有两个成熟的解决方案:1:Memcached.ClientLibrary2:EnyimMemcached。下篇我来介绍一级缓存如何通过memcached实现,以及如何让组件在一级缓存上即支持企业库也支持memcached。 

这篇来讲如何利用memcached实现一级缓存,以及如何让一级缓存组件支持在企业库,memcached或者其它第三方实施方案之间的切换。memcached本人并没有太多经验,如果文中有说的不对的地方,还希望批评指出,且文中关于memcached的代码大多来自网络。

  
     创建memcached实现类MemcachedWebCacheProvider,由它来继承缓存提供者接口IWebCacheProvider,主里memcached客户端我采用.NET memcached client library ,这个类库很久没有更新这过了,没有和java版同步,有部分功能目前没有实现。
     1:初始化memcached服务,这段初始化代码在程序中保证执行一次就够,一般可以放在gloabl文件中,或者是设置一个静态变量来存储服务的状态。
 

 private void Setup()
        {
            String[] serverlist = { "127.0.0.1:11211" };
            this._pool = SockIOPool.GetInstance("default");
            this._pool.SetServers(serverlist); //设置服务器列
            //各服务器之间负载均衡的设置
            this._pool.SetWeights(new int[] { 1 });
            //socket pool设置
            this._pool.InitConnections = 5; //初始化时创建的连接数
            this._pool.MinConnections = 5; //最小连接数
            this._pool.MaxConnections = 250; //最大连接数
            //连接的最大空闲时间,下面设置为6个小时(单位ms),超过这个设置时间,连接会被释放掉
            this._pool.MaxIdle = 1000 * 60 * 60 * 6;
            //通讯的超时时间,下面设置为3秒(单位ms),.NET版本没有实现
            this._pool.SocketTimeout = 1000 * 3;
            //socket连接的超时时间,下面设置表示连接不超时,即一直保持连接状态
            this._pool.SocketConnectTimeout = 0;
            this._pool.Nagle = false; //是否对TCP/IP通讯使用Nalgle算法,.NET版本没有实现
            //维护线程的间隔激活时间,下面设置为60秒(单位s),设置为0表示不启用维护线程
            this._pool.MaintenanceSleep = 60;
            //socket单次任务的最大时间,超过这个时间socket会被强行中断掉(当前任务失败)
            this._pool.MaxBusy = 1000 * 10;
            this._pool.Initialize();
        }

 

        2:获取一个memcached客户端。
       

        private MemcachedClient GetClient()
        {
            MemcachedClient client = new MemcachedClient();
            client.PoolName = "default";
            return client;
        }

 

        3:根据memcached提供的功能实现IWebCacheProvider,代码就不贴了,大家可以自己去试试。
       
        到此我们就利用memcached实现了一级缓存,由于.NET memcached client library 实现了分布式,我们只需要在多台服务器上安装上memcached服务,在初始化memcached代码中增加了服务器相关配置即可。String[] serverlist = { "127.0.0.1:11211" };
       
        如何让一级缓存组件支持多实现方案之间的切换。
        MyWebCacheServiceClient:客户端缓存组件实例,它来完成一级缓存与二级缓存之间的联系,以及根据配置文件来选择一级缓存的实施方案。
        第一:CacheServiceMode,根据它就可以决定缓存是只缓存二级缓存还是两级都缓存。

                 1:LocalCacheOnlyMode,只启用web server上的二级缓存。

                 2:BufferedLCacheServerMode,即启用web server上的二级缓存也启用cache server上的缓存。

                 3:Off,关闭缓存功能。
        第二:IWebCacheProvider service = this .GetPrimaryCacheProvider(hashKey);方式决定了一级缓存的实施方案。
        

/// <summary>
        /// 获取一级缓存
        /// </summary>
        /// <param name="hashKey"></param>
        /// <param name="configFilePath"></param>
        /// <returns></returns>
        private IWebCacheProvider GetPrimaryCacheProvider(uint hashKey)
        {
            IWebCacheProvider provider = null;
            string cacheType = WebConfig.ChannelConfig["CacheType"].ToString().ToLower();
            switch (cacheType)
            {
                case "memcached":
                    provider = WebCacheProviderFactory.GetMemcachedWebCacheProvider(configFilePath);
                    break;
                case "entlib":
                    provider = servicePool.GetServiceClient(hashKey) as IWebCacheProvider;
                    break;
            }
          
            return provider;
        }

 

         插入缓存的逻辑:原理就是根据配置文件中的CacheMode来完成缓存级别的判定以及一级缓存的方案。

public void Insert(string key, object value, string region, string subRegion, CacheItemConfig cacheItemConfig)
        {
            if (string.IsNullOrEmpty(key) || value == null)
                return;
            //关闭模式,不使用缓存
            if (Options.CacheServiceMode == ECacheServiceMode.Off)
            {
                return;
            }
            else if (Options.CacheServiceMode == ECacheServiceMode.BufferedLCacheServerMode
                || Options.CacheServiceMode == ECacheServiceMode.LocalAndCacheServerAndSql
                || Options.CacheServiceMode == ECacheServiceMode.LocalCacheOnlyMode)
            {//使用带缓冲的模式
                if (Options.BufferType == ECacheDependencyType.SlidingTime)
                {
                    SecondaryCacheProvider.Insert(key, value, region, subRegion, MyCacheItemPriority.Normal, Options.BufferSlidingTime);
                }
                else if (Options.BufferType == ECacheDependencyType.AbsoluteTime)
                {
                    SecondaryCacheProvider.Insert(key, value, region, subRegion, MyCacheItemPriority.Normal, Options.BufferAbsoluteTime);
                }

                if (Options.CacheServiceMode == ECacheServiceMode.LocalCacheOnlyMode)
                {//只使用本地缓存
                    return;
                }
            }

            checkKey(key);
            uint hashKey = hash(key);

            try
            {
                if (Options.CacheServiceMode == ECacheServiceMode.CacheServerMode
                    || Options.CacheServiceMode == ECacheServiceMode.BufferedLCacheServerMode
                    || Options.CacheServiceMode == ECacheServiceMode.CacheServerAndSql
                    || Options.CacheServiceMode == ECacheServiceMode.LocalAndCacheServerAndSql)
                {//CacheServer模式使用Cache服务器保存Cache                                     
                    IWebCacheProvider service = this .GetPrimaryCacheProvider(hashKey);
                    byte[] byteValue = SerializationHelper.SaveToBinaryBytes(value);
                    var cachePriority = ModelConverter.ToRefClass(cacheItemConfig.CachePriority);
                    if (cacheItemConfig.CacheType == ECacheDependencyType.AbsoluteTime)
                    {
                        AbsoluteTimeCacheDependency absTime = new AbsoluteTimeCacheDependency();
                        absTime.AbsoluteTime = DateTime.Now.AddMinutes(cacheItemConfig.CacheTimeMinutes);
                        service.Insert(key, byteValue, region, subRegion, cachePriority, absTime);
                    }
                    else if (cacheItemConfig.CacheType == ECacheDependencyType.SlidingTime)
                    {
                        SlidingTimeCacheDependency slTime = new SlidingTimeCacheDependency();
                        slTime.SlidingTime = new TimeSpan(0, cacheItemConfig.CacheTimeMinutes, 0);
                        service.Insert(key, byteValue, region, subRegion, cachePriority, slTime);
                    }
                }
            }
            catch (Exception ex)
            {//出现异常,保存到数据库中
                servicePool.ReplaceServiceClient(hashKey);
                this.SendLogEmail(ex);
            }
          

        }


 

       
        客户端调用代码:为了调用方便,创建一个CacheHelper来帮助完成:

public class CacheHelper
    {
        /// <summary>
        /// 主分区
        /// </summary>
        public const string REGION = "MyBlog";
        /// <summary>
        /// 子分区
        /// </summary>
        public const string SUB_REGION = "default";
        public const string BlogListConfigKey = "BlogListConfigKey";
        #region 页面间数据传递
        /// <summary>
        /// 新增页面间传递数据到WebCache
        /// </summary>
        /// <returns>返回PageKeyID,用于页面间传递的键值</returns>
        public static string InsertPageParams(string configKey, object obj,string pageKey)
        {
            string result = null;

            MyWebCacheServiceClient cacheClient = CacheClientFactory.GetWebCacheServiceClient(REGION, SUB_REGION, configKey);
            cacheClient.Insert(
                MyWebCacheServiceClient.BuildKey(configKey,pageKey),
                obj,
                REGION,
                SUB_REGION);

            return result;
        }
        /// <summary>
        /// 从Cache里获取页面传递Cache
        /// </summary>
        /// <param name="key">FlightCacheKey里的常量</param>
        /// <param name="pageKeyID">页面传递的键值</param>
        public static object GetPageParams(string configKey, string pageKey)
        {
            object result = null;
            MyWebCacheServiceClient cacheClient = CacheClientFactory.GetWebCacheServiceClient(REGION,
                SUB_REGION, configKey);
            result = cacheClient.Get(
                MyWebCacheServiceClient.BuildKey(configKey, pageKey),
                REGION,
                SUB_REGION);

            return result;

        }
        #endregion
    }

对于web系统中增加缓存服务,使用起来还是挺方便的,目前可采用的方案比较多,有微软的企业库,memcached等等。但如果需要很好的对项目中的缓存进行监控管理,也不是一件特别容易的事情,例如:监控缓存服务器上都有哪些项目使用了缓存,具体都有多少个key,大小,单个key的命中率以及过期时间等信息。有了这些信息,就非常容易排查内存为什么快用完的问题,如果再提供手动过期缓存的服务,就更好了,有的时候由于数据出错,需要紧急让缓存失效,此种办法影响最小。

   
    这篇我来总结了针对memcached的缓存管理。
   
    其实memcached本身也提供了一些缓存统计信息,例如:当前总共的缓存数量,使用的内存量,总获取次数,总的写入次数,总的命中次数等等,但这种统计信息粒度太大:

    1:无法具体到单个key,如果我们想针对某一个key统计它的命中率情况,就不好办了。

    2:无法分析系统中都有哪些项目使用了key,哪个项目占用的key多,内存多。

    3:无法实现手工过期,这种需求某些特殊情况下也是很有帮助的。

 

    既然memcached本身不提供,我这里采用了一种变通的方式来记录我们特定的信息。
   
    首先我们引进一个概念:分区,这个分区可以理解成电脑上的硬盘分区,用户可以把不同的文件放在不同的分区上,这样在管理上也容易些,同样分区底下有子分区,就像电脑上的文件一样,子分区下面就是具体的key了,对于我们的cache后台管理,可以这样理解,一个项目可以分配为一个分区,按项目功能模块可以分为不同的子分区,子分区下来分散着N多key。

 

 

实现方案:我们可以对每个key的访问记录下它的一些信息,例如:大小,所属分区名,过期时间,访问命中率,然后把这些信息在每个memcached 实例上创建一个特殊key,用于存储key的访问信息。
   
    注意点:

       1:由于记录访问信息都需要更新特殊key,如果过于频繁,会影响正常的cache性能,所以可以考虑形成一个内存队列,当数量达到多少后(如果key使用频率不高,还可以设定时间,当过了这个时间,即使数量不够也进行更新),统一更新特殊key内容。
       2:由于memcached有单个key大小限制,所以对于这种统计信息key,不能过大,记录key访问信息时,尽量以文本形式存储,这样能保证最小。

       3:每个实例中对应一个用于存储key访问信息的key,这样可以统计更多的key。

          
    监控视图:通过上面的努力,我们可以形成三个视图:
    第一:memcached 实例视图,以某个具体cache实例为单位,呈现memcached服务本身所提供的统计信息,还包含此实例中包含了多少个分区,即实例上包含了多少个项目使用的缓存。
    第二:分区视图,根据分区名称,集合所有节点的数据,最终汇总出统计数据,例如可以统计酒店项目总共使用了多少个key等,这对分析key的分布情况比较有帮助。
    第三:key视图,呈现具体key的访问信息,以及手工过期功能。

 

    总结:上面的方案虽然能实现需求,但在实际生产环境中,尽量不要打开这种监控功能,需要的时候再打开,尽量让cache的效率最高。

 

原文来自:雨枫技术教程网 http://www.fengfly.com
原文网址:http://www.fengfly.com/plus/view-197176-1.html

 

 

转载于:https://www.cnblogs.com/lzjsky/archive/2012/09/05/2671464.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/494703.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++校招常见面试题(2019年校招总结)

总结了语法、数据结构、常见排序算法、操作系统、网络五大块常见校招面试题。欢迎补充与修正。 ★★语法知识★★ 一、C与C的区别 面向对象与面向过程的区别 面向过程 面向过程编程是就分析出解决问题题的步骤&#xff0c;然后把这些步骤一步一步的实现&#xff0c;使用的时…

3分钟了解物联网三大技术的未来争夺战!

来源&#xff1a;传感器技术摘要&#xff1a;物联网通过通信技术将人与物、物与物进行连接&#xff0c;在智能家居、工业数据采集等区域网通信场景一般采用短距离通信技术&#xff0c;对于广范围、远距离的连接则需要远距离通信技术。物联网通过通信技术将人与物、物与物进行连…

❤ 想知道大厂面试都问什么吗,附最强面试技巧!!(大数据开发岗)❤

作者简介 蓝桥签约作者、大数据&Python领域优质创作者。维护多个大数据技术群&#xff0c;帮助大学生就业和初级程序员解决工作难题。 我的使命与愿景&#xff1a;持续稳定输出&#xff0c;赋能中国技术社区蓬勃发展&#xff01; 粉丝福利&#xff1a;免费下载海量【PPT…

机器怎样才能有意识

来源&#xff1a;应行仁科学网博客摘要&#xff1a;意识&#xff0c;没有一个客观的科学定义&#xff0c;难以观测认定&#xff0c;但它是每个人都能主观感受到的真实。意识&#xff0c;没有一个客观的科学定义&#xff0c;难以观测认定&#xff0c;但它是每个人都能主观感受到…

OD使用教程3(下) - 调试篇03|解密系列

OD使用教程3(下) - 调试篇03 让编程改变世界 Change the world by program 逻辑运算 [caption id"attachment_706" align"aligncenter" width"358"]逻辑运算[/caption] 关于test指令 test指令格式&#xff1a;test dest, src 这个指令和…

❤『面试知识集锦100篇』1.面试技巧篇丨HR的小心思,你真的懂吗?

作者&#xff1a;不吃西红柿 简介&#xff1a;CSDN博客专家、蓝桥签约作者、大数据&Python领域优质创作者。 目录 一、企业考察要点 技巧一&#xff1a;了解自己的专业技能与 JD 中的匹配点 技巧二、把控好企业「味道」 技巧三、精彩的自我介绍 技巧四、常见问题回答…

机器人行业发展方向预测报告

来源&#xff1a;招商证券摘要&#xff1a;随着中国人口红利消失&#xff0c;机器人不仅在制造业上正在替代工人&#xff0c;还将在军事、服务、娱乐等领域取代人类&#xff0c;“钢铁侠”已不仅仅存在于美国科幻电影中&#xff0c;而正走入我们的生活。本篇报告对机器人行业及…

在BingoCC上面解析域名实现智能路由

天朝的网络强大无人能及&#xff0c;世界上最大的Ping值不是从中国到美国而是从电信到联通。当你搭建了一个网站需要为南方北方的客户服务的话少不得你就要选择有中国特色的双线机房了。现在假如你已经把你的服务器已经部署在双线机房了&#xff0c;并且申请到了双线IP。为了让…

数据结构 - 队列(图解+源码)

队列 概念 队列是一种特殊的线性表&#xff0c;特殊之处在于它遵循先入先出&#xff08;FIFO&#xff09;原则&#xff0c;只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;进行插入操作&#xff0c;和栈一样…

学界 | 清华AMiner团队发布53页计算机图形学研究报告

来源&#xff1a;AI科技评论摘要&#xff1a;清华 AMiner 团队近日发布新一期研究报告——《计算机图形学研究报告》&#xff0c;报告全文共 53 页&#xff0c;从概念、技术、人才、会议、应用及相应趋势详细介绍了计算机图形学的相关内容。报告内容速览概述篇&#xff1a;计算…

爱犯错的智能体 – 视觉篇(五):火星人脸的阴影

来源&#xff1a;张军平科学网博客摘要&#xff1a;人类对外星文明的寻找和痴迷自古就有记载。所以&#xff0c;每每看到拍摄于外星球的照片&#xff0c;必然会情绪激动&#xff0c;试图从中获取存在外星人的蛛丝马迹。一、火星人脸人类对外星文明的寻找和痴迷自古就有记载。所…

链表简单实现(增删查改)

链表 关于链表的原理已经有一篇链表文章写的很详细了&#xff0c;这篇文章主要侧重于代码的实现&#xff0c;主要使用go实现。 单链表实现 package Listtype listNode struct {val intnext *listNode }func newNode(val int) *listNode {node : new(listNode)node.val val…

今天不发技术文,发点粉丝福利

一、资料 1、100套小编购买的简历模板&#xff08;部分截图&#xff09; 2、1000套精品PPT模板&#xff08;部分截图&#xff09; 3、大数据-学习资料&#xff08;1.3G 硬核PDF&#xff0c;官方指南&#xff09; 4、python学习全集 5、Java基础、高级和面试资料 6、大数据-行业…

剑指 Offer 35. 复杂链表的复制(哈希/衍生拆分图解)

题目描述 请实现 copyRandomList 函数&#xff0c;复制一个复杂链表。在复杂链表中&#xff0c;每个节点除了有一个 next 指针指向下一个节点&#xff0c;还有一个 random 指针指向链表中的任意节点或者 null。 题目分析 题中每个节点新增了 random 指针&#xff0c;指向链表…

自动驾驶技术发展的5个阶段和现状

来源&#xff1a;智车科技摘要&#xff1a;自动驾驶的级别从L1到L5&#xff08;SAE&#xff09;&#xff0c;清晰而直观&#xff0c;是大家讨论自动驾驶行业的一个基准。但是&#xff0c;它也很容易误导人。让人以为自动驾驶的技术会一级一级获得突破&#xff0c;最终迎来一辆L…

❤『知识集锦』一文搞懂mysql索引!!(建议收藏)

作者&#xff1a;不吃西红柿 简介&#xff1a;CSDN博客专家、蓝桥签约作者、大数据领域优质创作者。 以我的资历和文凭&#xff0c;将来这个城市的大街&#xff0c;都归我扫。 【系列课程介绍】 『面试知识集锦』系列课程包括以下20个系列&#xff0c;超过100篇文章。每篇文章…

AI芯片的过去、现在与未来

来源&#xff1a;华尔街见闻摘要&#xff1a;AI芯片似乎没能表现得比人类更智能&#xff0c;但它们的学习能力很强&#xff0c;未来可以变得更聪明。算法和芯片系统的设计都可以进步&#xff0c;这需要AI芯片具备更高级的记忆系统和连接机制&#xff0c;以及承载深度学习数据流…

链表(图文详解)

链表的概念 链表是一种物理存储结构上非连续&#xff0c;非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。   链表的结构是多式多样的&#xff0c;当时通常用的也就是两种&#xff1a;   无头单向非循环列表&#xff1a;结构简单&…

sql join中on条件后接and和where

目录 场景1&#xff1a;left join on a.xx b.xx and a.xx2 aa 场景2&#xff1a;left join on a.xx b.xx and b.xx2 aa 场景3&#xff1a;left join on a.xx b.xx where b.xx2 aa 场景4&#xff1a;inner join on a.xx b.xx where a.xx2 aa 场景5&#xff1a;…

❤「和平精英」被python爬虫了?看看你最适合什么配件!❤

作者简介&#xff1a;不吃西红柿&#xff0c;CSDN博客专家、蓝桥签约作者。 困难像弹簧&#xff0c;你弱它就强&#xff0c;你强它更强。 求点赞、求关注 写作目的&#xff1a; 1、练习爬虫技术&#xff1b; 2、深入了解和平精英枪械属性&#xff0c;提高吃鸡概率。 一、爬前…