缓存

定义: 缓存顾名思义就是将常用的东西存储起来,为了下一次的获取更快速。在互联网上,一个常用数据存储在硬盘中查询可能消耗很多时间,但是存储在内存中就能节约很多时间,因为内存的响应速度是硬盘的百倍速度,所以提升是巨大的,将常用的数据存储在内存中的手段就是缓存。

缓存在web中的表现位置:

数据不止可以存储再内存中快速响应,亦可以在服务器,用户的本地等等。在用户本地的速度指定是最快的,毕竟一次网络请求消耗的时间是很大的。

20_C#之缓存 - 图1

由上图可知,缓存在不同的位置都发挥着巨大的作用,但是不同的位置的缓存发挥的用处不同。但是缓存的目的是一致的,都是为了提升用户的使用体验,当使用缓存的时候,可以加快服务器的响应效率与页面的渲染时间,这样可以减少在用户量大的时候服务器的压力,也可让用户减少等待时间。

其中客户端缓存一般是缓存前端的一些样式文件或者图片、js文件等等。前端是暴露在用户的面前得,是不安全的,所以一些重要的数据是不能缓存在前端的,以免非法用户的篡改。

CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。这个是需要还钱购买的,买完配置就好了。

反向代理缓存通常是应对集群并发,负载均衡的情况,为了服务器的压力减少,对客户端更快速的响应而使用的缓存技术。

我们要学习的就是常见的数据缓存——本地缓存。这种通常是应对数据频繁查询的情况,将数据放在服务器内存中,而使用的技术,当数据量达到一定程度,就可以使用分布式缓存来应对,开多台服务器做集群,都用来缓存,这样就可以应对数据量大的问题了。

本地缓存

一般的缓存都是设置一个本地的静态数组来存储缓存数据的,在fw框架中内置了缓存数组集,框架自带的缓存就是一个静态的keyvaluepaire的集合,相当于字典,这里用框架的memorycache来实现接口中定义的功能。

首先线引入框架的缓存组件:

20_C#之缓存 - 图2

设计缓存接口

系统给与的功能过于繁杂,可能有些功能并用不到,也可能有某些功能会用错,所以自己定义缓存接口,去实现一些常用功能是必要的,接口是规约,可扩展的。这里设计了ICache接口:

  1. namespace Tools.CacheTools
  2. {
  3. // 缓存的功能
  4. internal interface ICache
  5. {
  6. int Count { get; }
  7. object this[string key] { get; set; }
  8. T Get<T>(string key);
  9. void Add(string key, object data, int cacheTime = 30);
  10. bool Contains(string key);
  11. void Remove(string key);
  12. void RemoveAll();
  13. }
  14. }

接口中定义了缓存的常用功能。那么来实现这个接口吧:(用缓存组件来实现接口中的功能)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Text.RegularExpressions;

namespace Tools.CacheTools
{
    /// <summary>
    /// 封装了框架的cache,需引用: using System.Runtime.Caching;
    /// </summary>
    class MemoryCacheOp : ICache
    {
        /// <summary>
        /// 构造
        /// </summary>
        public MemoryCacheOp() { }

        protected ObjectCache Cache
        {
            get
            {
                return MemoryCache.Default;
            }
        }
        /// <summary>
        /// 索引
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object this[string key] 
        {
            get { return Cache.Get(key); }
            set { Add(key, value); }
        }
        /// <summary>
        /// 获取缓存中数据的总数
        /// </summary>
        public int Count { get { return (int)(Cache.GetCount()); } }
        /// <summary>
        /// 添加时效缓存
        /// </summary>
        /// <param name="key">数据key</param>
        /// <param name="data">数据value</param>
        /// <param name="cacheTime">数据时效(分钟);默认30分钟</param>
        public void Add(string key, object data, int cacheTime = 30)
        {
            if (data == null)
            {
                return;
            }

            var policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime);
            Cache.Add(new CacheItem(key, data), policy);
        }
        /// <summary>
        /// 比较缓存中是否有key值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Contains(string key)
        {
            return Cache.Contains(key);
        }
        /// <summary>
        /// 获取缓存数据
        /// </summary>
        /// <typeparam name="T">泛型数据</typeparam>
        /// <param name="key">key</param>
        /// <returns>value</returns>
        public T Get<T>(string key)
        {
            if (Cache.Contains(key))
            {
                return (T)Cache[key];
            }
            else
            {
                return default(T);
            }
        }
        /// <summary>
        /// 获取由key返回类型为object类型的数据
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object Get(string key)
        {
            return Cache[key];
        }
        /// <summary>
        /// 移除对应key的value
        /// </summary>
        /// <param name="key">key</param>
        public void Remove(string key)
        {
            if (Contains(key))
            {
                Cache.Remove(key);
            }
            else
            {
                throw new Exception("无此数据");
            }
        }
        /// <summary>
        /// 清除全部缓存
        /// </summary>
        public void RemoveAll()
        {
            KeyValuePair<string, object>[] keyValuePairs = Cache.ToArray();
            foreach (var item in keyValuePairs)
            {
                Cache.Remove(item.Key);
            }
        }
        /// <summary>
        /// 正则删除
        /// </summary>
        /// <param name="pattern"></param>
        public void RemoveByPattern(string pattern)
        {
            var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);

            var keysToRemove = new List<String>();
            foreach (var item in Cache)
            {
                if (regex.IsMatch(item.Key))
                {
                    keysToRemove.Add(item.Key);
                }
            }

            foreach (string key in keysToRemove)
            {
                Remove(key);
            }
        }
    }
}

这样一个可以使用的本地缓存组件就可以使用了。我们也可以不用系统的,而是自己定义一个静态的本地数组来实现缓存的功能:这里用MyCacheOp实现了ICache接口。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Tools.CacheTools
{
    /// <summary>
    /// 自制缓存工具
    /// </summary>
    class MyCacheOp : ICache
    {
        /// <summary>
        /// 缓存的本质就是一个静态的key-value数组,缓存的静态数组
        /// </summary>
        private static Dictionary<string, KeyValuePair<DateTime,object>> _MemoryCacheDictionary = new Dictionary<string, KeyValuePair<DateTime, object>>();
        /// <summary>
        /// 被动过期:长时间运行
        /// </summary>
        static MyCacheOp()
        {
            Task.Run(() =>
            {
                while (true)
                {
                    if (_MemoryCacheDictionary.Count > 0)
                    {
                        List<string> list = new List<string>();
                        foreach (var item in _MemoryCacheDictionary)
                        {
                            KeyValuePair<DateTime, object> keyValuePair = item.Value;
                            if (DateTime.Now > keyValuePair.Key)
                            {
                                list.Add(item.Key);
                            }
                        }
                        foreach (var item in list)
                        {
                            _MemoryCacheDictionary.Remove(item);
                        }
                    }
                    Thread.Sleep(1000);
                }
            });
        }
        public object this[string key] {
            get { return _MemoryCacheDictionary[key]; }
            set { Add(key, value); }
        }
        /// <summary>
        /// 获取缓存总数s
        /// </summary>
        public int Count { get { return _MemoryCacheDictionary.Count(); } }
        /// <summary>
        /// 添加键值对如缓存,并设置缓存保留时长
        /// </summary>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="cacheTime"></param>
        public void Add(string key, object data, int cacheTime = 30)
        {
            if (data==null)
            {
                return;
            }

            _MemoryCacheDictionary.Add(key, new KeyValuePair<DateTime, object>(DateTime.Now.AddMinutes(cacheTime),data));
        }
        /// <summary>
        /// 做缓存中对比是否有对应key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Contains(string key)
        {
            return _MemoryCacheDictionary.ContainsKey(key);
        }
        /// <summary>
        /// 获取key对应的value
        /// </summary>
        /// <typeparam name="T">泛型</typeparam>
        /// <param name="key">key</param>
        /// <returns>value</returns>
        public T Get<T>(string key)
        {
            if (_MemoryCacheDictionary.ContainsKey(key))
            {
                KeyValuePair<DateTime, object> keyValue = _MemoryCacheDictionary[key];
                if (DateTime.Now > keyValue.Key)
                {
                    _MemoryCacheDictionary.Remove(key);
                    return default(T);
                }
                else
                {
                    return (T)keyValue.Value;
                }
            }
            else
            {
                return default(T);
            }
        }

        public object Get(string key)
        {
            return _MemoryCacheDictionary[key];
        }
        /// <summary>
        /// 清楚key对应的value
        /// </summary>
        /// <param name="key"></param>
        public void Remove(string key)
        {
            if (!Contains(key))
            {
                return;
            }
            _MemoryCacheDictionary.Remove(key);
        }
        /// <summary>
        /// 将所有的键值清除
        /// </summary>
        public void RemoveAll()
        {
            _MemoryCacheDictionary.Clear();
        }
    }
}

这里有一些线程安全的问题,但是由于是字典,所以当是多线程的时候可以换成线程安全的字典。

缓存的问题

缓存是什么?

缓存在不同的地方作用不同,但是目的都是为了加快服务器的响应速度,提升用户使用产品体验。

缓存的作用?

为了加快服务器的响应速度

缓存在语言中的体现?

语言中缓存可以是本地缓存,也可以是远程服务缓存(redis)。

哪里用?什么时机用?

20_C#之缓存 - 图3

缓存穿透?

20_C#之缓存 - 图4

以上是本地缓存字典,小项目缓存的东西少,本地的cache就足够了,当缓存的东西超出了本地能承受的范围,就可以用分布式缓存redis。