zoukankan      html  css  js  c++  java
  • 带你走进缓存世界(4):缓存之缓

            缓存二字,从字面上分为两块:“缓”与“存”。上节我们提到的缓存原理,其实是在讲的一个“存”字,如何存取。大致回顾下是key对应的hashcode,根据hashcode作为数组下标来存取,因为存在hash冲突,速度虽达不到O(1),但也是非常之快。今天就说下“缓”的策略。

      缓,便意味着“暂时”的意思,过一段时间就不再存在或被替换掉了,所以我们要说的其实是缓存的过期策略。在缓存入门篇中,主要提到了Cache类的Insert的方法,其中的几个变化的参数寓意着各种缓存策略,有具体依赖的有按时间的,一一来看。

      按过期时间缓存
      这种缓存策略最为简单,只要判断当前时间是否超过了指定的过期时间就remove掉该缓存项即可,一般用于不影响大碍的数据,比如论坛帖子列表,热门板块会更新极其频繁,缓存起来最为合适。但是又不能不更新缓存,不然有人发帖和回帖就看不到了,但可以缓存个一两分钟,两分钟后自动过期,重新加载新的列表,这样就不用管了,所以这种缓存策略更倾向于“不用管”的缓存。既然如此,那么我们就自己写一个按时间过期的缓存类吧。下面的这个类非常基础:

        /// <summary>
        /// 按时间缓存类
        /// </summary>
        public class CacheByDateTime<TKey,TValue>
        {
            /// <summary>
            /// 内部缓存项
            /// </summary>
            class CacheItem
            {
                /// <summary>
                /// 缓存的值
                /// </summary>
                public TValue value { get; set; }
                /// <summary>
                /// 过期时间
                /// </summary>
                public DateTime dateTime { get; set; }
            }
    
            /// <summary>
            /// 缓存数据词典
            /// </summary>
            private readonly Dictionary<TKey, CacheItem> _dict;
    
    	//为了线程安全,需要对dict的操作加锁
            private static readonly object LockDict = new object();
    
            public CacheByDateTime()
            {
                _dict = new Dictionary<TKey, CacheItem>();
            }
            /// <summary>
            /// 添加一个缓存
            /// </summary>
            /// <param name="key"></param>
            /// <param name="value"></param>
            /// <param name="dateTime">过期时间</param>
            public void Add(TKey key, TValue value, DateTime dateTime)
            {
                lock (LockDict)
                {
                    if (_dict.ContainsKey(key))
                    {
                        _dict[key].value = value;
                        _dict[key].dateTime = dateTime;
                    }
                    else
                    {
                        _dict.Add(key, new CacheItem { value = value, dateTime = dateTime });
                    }
                }
            }
            /// <summary>
            /// 获取缓存
            /// </summary>
            public TValue Get(TKey key)
            {
                if (_dict.ContainsKey(key))
                {
                    var val = _dict[key].value;
    		//判断缓存项是否过期
                    if (_dict[key].dateTime > DateTime.Now)
                    {
                        return val;
                    }
                    else
                    {
                        Remove(key);
                        return val;//这里可以酌情是否返回Value,因为毕竟可以省去一次查询
                    }
                }
                return default(TValue);
            }
            /// <summary>
            /// 移除缓存
            /// </summary>
            public void Remove(TKey key)
            {
                lock (LockDict)
                {
                    if (_dict.ContainsKey(key))
                    {
                        _dict.Remove(key);
                    }
                }
            }
        }


            按间隔时间缓存
            这个相对上面的绝对过期时间来说更有趣一些,他的策略是只要被访问,就延迟该缓存的绝对过期时间(间隔时间比如是5分钟就延长5分钟)。这种过期策略似乎十分精明,但对缓存的数据类型也是极其讲究,这种策略一般来缓存什么合适呢?如果说缓存永不过期的数据最为合适,但不存在这样的数据,像网站的配置这种数据极少改动,但访问量巨大,如果用这种缓存策略,不管管理员怎么修改配置,估计这缓存都是更新不了了,反而用上面的缓存合适,而像文章内容这种数据,访问的随机性比较大,拿捏不准啥时候过期,但文章内容极少会被更新,而网站的访问量基本上又属内容页比较大,所以这种缓存缓存文章内容比较合适。可以有效的延长热门内容的过期时间,而冷门的文章自然而言就自动过期了。具体的代码实现只需要在上面的类的Add方面做些改动就可实现:

            /// <summary>
            /// 添加一个缓存
            /// </summary>
            /// <param name="key"></param>
            /// <param name="value"></param>
            /// <param name="timeSpan">间隔时间</param>
            public void Add(TKey key, TValue value, TimeSpan timeSpan)
            {
                lock (LockDict)
                {
                    if (_dict.ContainsKey(key))
                    {
                        _dict[key].value = value;
                        _dict[key].dateTime.Add(timeSpan);
                    }
                    else
                    {
                        _dict.Add(key, new CacheItem { value = value, dateTime = DateTime.Now.Add(timeSpan) });
                    }
                }
            }


            依赖项缓存
            依赖缓存相对以上两个来说是非常复杂的处理过程,比如文件依赖,会有相应的监测程序(FileMonitor)来管理dependency对象。这里我们便不讲解,了解其用处即可,着实因为太过复杂。有兴趣的可以看.Net源码。


            LRU(Least Recently Used)缓存
            从名字便知其意,其主要用于限定容量(比如内存大小或缓存数量)的缓存,需要在缓存容器满了之后踢出过期缓存的策略,是使用次数最少或很久没使用的缓存项策略。
            实现原理一般使用链表方式把所有缓存项连起来,每当有新的缓存进入则把缓存放入链表前端,如果缓存被使用则把他提到链表前端,那么没被使用的将慢慢趋于链表后端,所以当容量满了以后,就优先移除链表末尾的缓存项。当然,也有其他更为复杂的过期策略,比如同时使用缓存时间。虽然此策略和上面的按时间间隔延长缓存有点相像,但这个更侧重于缓存容器大小的管理,毕竟内存是有限的,此策略多用于公共缓存服务。下面的类是个简单的LRU实现,只限定的缓存的长度并没有大小限制,如果要做大小限制则需要计算每一个value的大小。
     

        /// <summary>
        /// LRUCache
        /// </summary>
        public class LRUCache<TKey,TValue>
        {
            /// <summary>
            /// 缓存项
            /// </summary>
            class CacheItem
            {
                public TKey Key { get; set; }
                public TValue Value { get; set; }
                public CacheItem Left { get; set; }
                public CacheItem Right { get; set; }
    
                public CacheItem(TKey key, TValue value)
                {
                    Key = key;
                    Value = value;
                }
            }
    
            private readonly static object LockDict = new object();
    
            private readonly IDictionary<TKey, CacheItem> _dict;
    
            public int Length { get; private set; }
    
            public LRUCache(int maxLength)
            {
                _dict = new Dictionary<TKey, CacheItem>();
                Length = maxLength;
            }
    
            //链表头部
            private CacheItem _first;
            //链表末端
            private CacheItem _last;
    
            public bool HasKey(TKey key)
            {
                return _dict.ContainsKey(key);
            }
    
            /// <summary>
            /// 添加一个缓存项
            /// </summary>
            public void Add(TKey key, TValue value)
            {
                var item = new CacheItem(key, value);
    
                lock (LockDict)
                {
                    //如果没有缓存项,则item既是first也是last
                    if (_dict.Count == 0)
                    {
                        _last = _first = item;
                    }
    
                    //如果只有一个缓存项,则item是first,first和last变为last
                    else if (_dict.Count == 1)
                    {
                        _last = _first;
                        _first = item;
    
                        _last.Left = _first;
                        _first.Right = _last;
                    }
                    else
                    {
                        //item为first,之前的前端向后移位
                        item.Right = _first;
                        _first.Left = item;
                        _first = item;
                    }
    
                    //如果超过的链表长度
                    if (_dict.Count >= Length)
                    {
                        //断开last并移除
                        _last.Left.Right = null;
                        _dict.Remove(_last.Key);
                      
                        _last = _last.Left;
                    }
    
                    //将item放入dict
                    if (_dict.ContainsKey(key))
                        _dict[key] = new CacheItem(key, value);
                    else
                        _dict.Add(key, new CacheItem(key, value));
                }
            }
    
            /// <summary>
            /// 获取一个缓存项
            /// </summary>
            public TValue Get(TKey key)
            {
                if (!_dict.ContainsKey(key))
                {
                    return default(TValue);
                }
    
                var item = _dict[key];
    
                lock (LockDict)
                {
                    if (_dict.Count == 1)
                    {
                        return item.Value;
                    }
    
                    //如果item左侧有缓存项,则将左侧的缓存指向item的右侧
                    if (item.Left != null)
                    {
                        item.Left.Right = item.Right;
                    }
                    else
                    {
                        //否则说明item是first
                        return item.Value;
                    }
    
                    //如果item右侧有缓存项,则将右侧的缓存指向item的左侧
                    if (item.Right != null)
                    {
                        item.Right.Left = item.Left;
                    }
                    else
                    {
                        //否则说明item是last
                        //将last的左侧的右侧断开,让其成为last
                        _last.Left.Right = null;
                        _last = _last.Left;
                    }
                    //断开item的左侧,让item成为first,让first成为item的右侧项
                    item.Left = null;
                    item.Right = _first;
                    _first.Left = item;
                    _first = item;
                }
                return item.Value;
            }
    
            public void Remove(TKey key)
            {
                if (!_dict.ContainsKey(key))
                {
                    return;
                }
    
                var item = _dict[key];
    
                lock (LockDict)
                {
                    //如果item左侧有值,则将左侧的右侧指向item的右侧
                    if (item.Left != null)
                    {
                        item.Left.Right = item.Right;
                    }
                    else
                    {
                        //否则item则是first,所以将item的右侧赋值给first
                        _first = item.Right;
                    }
    
                    //如果item的右侧有值,则将item的右侧的左值指向item的左侧
                    if (item.Right != null)
                    {
                        item.Right.Left = item.Left;
                    }
                    else
                    {
                        _last = item.Left;
                    }
    
                    _dict.Remove(key);
                }
            }
    
        }
    




    以上提到的是我们常用的几种缓存策略,当然还有其他的策略,我们后面也会提到。今天就先到这吧。

  • 相关阅读:
    ubantu安装pip3
    ubantu更换镜像源
    git 快速上手
    python zmq(ZeorMQ)
    用python连接SQL server数据库
    Django模板url需要注意的地方
    希尔排序记录--最好写的排序
    口腔溃疡要对症-------阴虚火旺和阳虚火旺
    与大学室友,保持一定的距离
    取指 间址 执行 中断 FE IND EX INT四个触发器
  • 原文地址:https://www.cnblogs.com/mad/p/2123870.html
Copyright © 2011-2022 走看看