欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

缓存cache(擦车)

程序员文章站 2022-05-03 14:27:39
第一次接触到Cache的时候,是在WebForm中,第一次接触,我就再也没能忘记,cache(擦车,的拼音) 客户端浏览器缓存https://blog.csdn.net/y874961524/article/details/61419716 CDN缓存原理https://www.cnblogs.co ......

第一次接触到cache的时候,是在webform中,第一次接触,我就再也没能忘记,cache(擦车,的拼音)

客户端浏览器缓存

cdn缓存原理

阿里云cdn开启设置

有句话叫做,系统性能优化的第一步,就是使用缓存,所以,缓存真的很重要

缓存:

  实际上是一种效果&目标,就是获取数据点时候,第一次获取之后找个地方存起来,后面直接用,这样一来可以提升后面每次获取数据的效率。读取配置文件的时候把信息放在静态字段,这个就是缓存。缓存是无处不在的。

缓存cache(擦车)

 

 

我们来请求一个网站,打开开发人员工具

缓存cache(擦车)

 

 

 客户端缓存的好处:

  1、缩短网络路径,加快响应速度

  2、减少请求,降低服务器压力

浏览器缓存究竟是怎么做到的?

  打开一个网页,浏览器-----请求---服务器---处理请求会发响应------浏览器展示

  http协议,数据传输的格式(协议,就像是两人交流,都用什么语言)

  信息是否缓存,一定是服务器控制的。responseheader--cache---control来指定下缓存策略,浏览器看到了这个,就去存储一下。

第一次请求服务器:

缓存cache(擦车)

 

 

 再一次请求服务器

 缓存cache(擦车)

 

 

 dns是互联网的第一跳,dns缓存就是cdn,内容分发网络,cdn就是加速缓存的

没有用cdn的请求:

缓存cache(擦车)

 

 

 使用了cdn缓存

缓存cache(擦车)

 

 

 反向代理:

  1、隔离网络,保护服务器(节约公共ip)

  2、网络加速,反向代理双网卡

  3、负载均衡

  4、缓存(跟cdn,也是识别一下header,压缩到一个物理路径/内存)

  为什么叫反向代理?因为他就是一个代理,一般的代理,是客户端和服务器之间,有一个代理,去做处理的。但是这个代理是安装在服务器端的。

缓存cache(擦车)

 

 

 

几种缓存套路相同,但是位置不同,影响的范围也不同。

  客户端缓存:只影响当前用户

  cdn缓存:针对一批用户

  反向代理缓存:针对全部用户。

  客户端缓存,存在内存或者硬盘,下次直接用。cookie,存在内存或者硬盘,浏览器每次请求服务器都会带上的信息。

什么时候用缓存?

  1、重复请求,100人访问首页,每个人其实做的都一样,不就是重复

  2、耗时好资源

  3、结果没变的

下面有一个第三方数据存储和获取的地方:

 /// <summary>
 /// 第三方数据存储和获取的地方
 /// </summary>
 public class customcache
 {
     /// <summary>
     /// private:私有一下数据容器,安全
     /// static:不被gc
     ///   字典:读写效率高
     /// </summary>
     private static dictionary<string, object> customcachedictionary = new dictionary<string, object>();

     public static void add(string key, object ovaule)
     {
         customcachedictionary.add(key, ovaule);
     }

     /// <summary>
     /// 要求在get前做exists检测
     /// </summary>
     /// <typeparam name="t"></typeparam>
     /// <param name="key"></param>
     /// <returns></returns>
     public static t get<t>(string key)
     {
         return (t)customcachedictionary[key];
     }

     public static bool exists(string key)
     {
         return customcachedictionary.containskey(key);
     }

     public static t gett<t>(string key, func<t> func)
     {
         t t = default(t);
         if (!customcache.exists(key))
         {
             t = func.invoke();
             customcache.add(key, t);
         }
         else
         {
             t = customcache.get<t>(key);
         }
         return t;
     }
 }

存取数据的唯一标识:1 唯一的  2 能重现

for (int i = 0; i < 5; i++)
{
    console.writeline($"获取{nameof(dbhelper)} {i}次 {datetime.now.tostring("yyyymmdd hhmmss.fff")}");
    //list<program> programlist = dbhelper.query<program>(123);
    list<program> programlist = null;
    string key = $"{nameof(dbhelper)}_query_{123}";
    //存取数据的唯一标识:1 唯一的  2 能重现
    //if (!customcache.exists(key))
    //{
    //    programlist = dbhelper.query<program>(123);
    //    customcache.add(key, programlist);
    //}
    //else
    //{
    //    programlist = customcache.get<list<program>>(key);
    //}
    programlist = customcache.gett<list<program>>(key, () => dbhelper.query<program>(123));
}

 

 /// <summary>
 /// 数据库查询
 /// </summary>
 public class dbhelper
 {
     /// <summary>
     /// 1 耗时耗资源
     /// 2 参数固定时,结果不变
     /// </summary>
     /// <typeparam name="t"></typeparam>
     /// <param name="index"></param>
     /// <returns></returns>
     public static list<t> query<t>(int index)
     {
         console.writeline("this is {0} query", typeof(dbhelper));
         long lresult = 0;
         for (int i = index; i < 1000000000; i++)
         {
             lresult += i;
         }
         list<t> tlist = new list<t>();
         for (int i = 0; i < index % 3; i++)
         {
             tlist.add(default(t));
         }

         return tlist;
     }

 }

  缓存优化性能,核心就是结果重用,下次请求还是上一次的结果。如果数据库中有变化,岂不是用了一个错误的数据?是的,缓存是难免的,缓存难免会有脏数据,当然了,我们也会分门别类的去尽量减少脏数据。

  用户--角色--菜单,用户权限查的多+比较耗资源+相对稳定,非常适合缓存,缓存方式应该是用户id为key,菜单列表作为value。

   string name = "bingle";
   list<string> menu = new list<string>();
   if (!customcache.exists(name))
   {
       menu = new list<string>() { "123", "125553", "143", "123456" };
       customcache.add(name, menu);
   }
   else
   {
       menu = customcache.get<list<string>>(name);
   }

假如bingle的权限变化了,缓存应该失效。数据更新影响单挑缓存,常规做法是remove而不是更新,因为缓存只是用来提升效率的,而不是数据保存的,因此不需要更新,只需要删除就好,如果真的下次用上了,到时候再去初始化。

customcache类增加删除缓存的方法:
 public static void remove(string key)
 {
     customcachedictionary.remove(key);
 }
 string name = "bingle";
 customcache.remove(name);

 list<string> menu = new list<string>();
 if (!customcache.exists(name))
 {
     menu = new list<string>() { "123", "125553", "143" };
     customcache.add(name, menu);
 }
 else
 {
     menu = customcache.get<list<string>>(name);
 }

删除了某个菜单,影响了一大批用户。根据菜单--昭觉寺---找用户---每一个拼装key然后去remove(最准确)。但是这种方式不行,为了缓存增加数据库的任务,最大的问题是数据量的问题,缓存是二八原则,只有20%的热点用户才缓存,这样做的成本太高。

可以选择加上一个removeall的方法

 public static void removeall()
 {
     customcachedictionary.clear();
 }

或者,菜单删除了,能不能只影响一部分的缓存数据呢?

  1、添加缓存时,key带上规则,比如权限包含_menu_

  2、清理时,就只删除key含_menu_的

 /// <summary>
 /// 按条件删除
 /// </summary>
 /// <param name="func"></param>
 public static void removecondition(func<string, bool> func)
 {
     list<string> keylist = new list<string>();
     lock (customcache_lock)
         foreach (var key in customcachedictionary.keys)
         {
             if (func.invoke(key))
             {
                 keylist.add(key);
             }
         }
     keylist.foreach(s => remove(s));
 }

第三方修改了数据,缓存并不知道,这个就没办法了

  a 可以调用接口清理缓存,b系统修改数据,调用c西永通知下缓存更新,b就只能容忍了,容忍脏数据,但是可以加上时间限制,减少影响时间。

时间,过期策略:

  永久有效----目前就是

  绝对过期:

    有个时间点,超过就过期了

  滑动过期:

    多久之后过期,如果期间更新/查询/检查存在,就再次延长多久。

 /// <summary>
 /// 主动清理
 /// </summary>
 static customcache()
 {
     task.run(() =>
     {
         while (true)
         {
             try
             {
                 list<string> keylist = new list<string>();
                 lock (customcache_lock)
                 {
                     foreach (var key in customcachedictionary.keys)
                     {
                         datamodel model = (datamodel)customcachedictionary[key];
                         if (model.obslotetype != obslotetype.never && model.deadline < datetime.now)
                         {
                             keylist.add(key);
                         }
                     }
                     keylist.foreach(s => remove(s));
                 }
                 thread.sleep(1000 * 60 * 10);
             }
             catch (exception ex)
             {
                 console.writeline(ex.message);
                 continue;
             }
         }
     });
 }

多线程问题:

list<task> tasklist = new list<task>();
for (int i = 0; i < 110000; i++)
{
    int k = i;
    tasklist.add(task.run(() => customcache.add($"testkey_{k}", $"testvalue_{k}", 10)));
}
for (int i = 0; i < 100; i++)
{
    int k = i;
    tasklist.add(task.run(() => customcache.remove($"testkey_{k}")));
}
for (int i = 0; i < 100; i++)
{
    int k = i;
    tasklist.add(task.run(() => customcache.exists($"testkey_{k}")));
}
//thread.sleep(10*1000);
task.waitall(tasklist.toarray());

多线程操作非现场安全的容器,会造成冲突

  1、线程安全容器concurrentdictionary

  2、用lock---add/remove/遍历,可以解决问题,但是性能呢?

    怎么降低影响,提升性能呢?多个数据容器,多个锁,容器之间可以并发

为了解决多线程问题,customcache 类最终修改成如下:

 public class customcache 
 {
     //concurrentdictionary

     private static readonly object customcache_lock = new object();

     /// <summary>
     /// 主动清理
     /// </summary>
     static customcache()
     {
         task.run(() =>
         {
             while (true)
             {
                 try
                 {
                     list<string> keylist = new list<string>();
                     lock (customcache_lock)
                     {
                         foreach (var key in customcachedictionary.keys)
                         {
                             datamodel model = (datamodel)customcachedictionary[key];
                             if (model.obslotetype != obslotetype.never && model.deadline < datetime.now)
                             {
                                 keylist.add(key);
                             }
                         }
                         keylist.foreach(s => remove(s));
                     }
                     thread.sleep(1000 * 60 * 10);
                 }
                 catch (exception ex)
                 {
                     console.writeline(ex.message);
                     continue;
                 }
             }
         });
     }

     /// <summary>
     /// private:私有一下数据容器,安全
     /// static:不被gc
     ///   字典:读写效率高
     /// </summary>
     //private static dictionary<string, object> customcachedictionary = new dictionary<string, object>();

     private static dictionary<string, object> customcachedictionary = new dictionary<string, object>();

     public static void add(string key, object ovaule)
     {
         lock (customcache_lock)
             customcachedictionary.add(key, new datamodel()
             {
                 value = ovaule,
                 obslotetype = obslotetype.never,
             });
     }
     /// <summary>
     /// 绝对过期
     /// </summary>
     /// <param name="key"></param>
     /// <param name="ovaule"></param>
     /// <param name="timeoutsecond"></param>
     public static void add(string key, object ovaule, int timeoutsecond)
     {
         lock (customcache_lock)
             customcachedictionary.add(key, new datamodel()
             {
                 value = ovaule,
                 obslotetype = obslotetype.absolutely,
                 deadline = datetime.now.addseconds(timeoutsecond)
             });
     }
     /// <summary>
     /// 相对过期
     /// </summary>
     /// <param name="key"></param>
     /// <param name="ovaule"></param>
     /// <param name="duration"></param>
     public static void add(string key, object ovaule, timespan duration)
     {
         lock (customcache_lock)
             customcachedictionary.add(key, new datamodel()
             {
                 value = ovaule,
                 obslotetype = obslotetype.relative,
                 deadline = datetime.now.add(duration),
                 duration = duration
             });
     }

     /// <summary>
     /// 要求在get前做exists检测
     /// </summary>
     /// <typeparam name="t"></typeparam>
     /// <param name="key"></param>
     /// <returns></returns>
     public static t get<t>(string key)
     {
         return (t)(((datamodel)customcachedictionary[key]).value);
     }

     /// <summary>
     /// 被动清理,请求了数据,才能清理
     /// </summary>
     /// <param name="key"></param>
     /// <returns></returns>
     public static bool exists(string key)
     {
         if (customcachedictionary.containskey(key))
         {
             datamodel model = (datamodel)customcachedictionary[key];
             if (model.obslotetype == obslotetype.never)
             {
                 return true;
             }
             else if (model.deadline < datetime.now)//现在已经超过你的最后时间
             {
                 lock (customcache_lock)
                     customcachedictionary.remove(key);
                 return false;
             }
             else
             {
                 if (model.obslotetype == obslotetype.relative)//没有过期&是滑动 所以要更新
                 {
                     model.deadline = datetime.now.add(model.duration);
                 }
                 return true;
             }
         }
         else
         {
             return false;
         }
     }

     /// <summary>
     /// 删除key
     /// </summary>
     /// <param name="key"></param>
     public static void remove(string key)
     {
         lock (customcache_lock)
             customcachedictionary.remove(key);
     }

     public static void removeall()
     {
         lock (customcache_lock)
             customcachedictionary.clear();
     }
     /// <summary>
     /// 按条件删除
     /// </summary>
     /// <param name="func"></param>
     public static void removecondition(func<string, bool> func)
     {
         list<string> keylist = new list<string>();
         lock (customcache_lock)
             foreach (var key in customcachedictionary.keys)
             {
                 if (func.invoke(key))
                 {
                     keylist.add(key);
                 }
             }
         keylist.foreach(s => remove(s));
     }

     public static t gett<t>(string key, func<t> func)
     {
         t t = default(t);
         if (!customcache.exists(key))
         {
             t = func.invoke();
             customcache.add(key, t);
         }
         else
         {
             t = customcache.get<t>(key);
         }
         return t;
     }
 }
/// <summary>
/// 缓存的信息
/// </summary>
internal class datamodel
{
    public object value { get; set; }
    public obslotetype obslotetype { get; set; }
    public datetime deadline { get; set; }
    public timespan duration { get; set; }

    //数据清理后出发事件
    public event action dataclearevent;
}

public enum obslotetype
{
    never,
    absolutely,
    relative
}
 public class customcachenew
 {
     //动态初始化多个容器和多个锁
     private static int cpunumer = 0;//获取系统的cpu数
     private static list<dictionary<string, object>> dictionarylist = new list<dictionary<string, object>>();
     private static list<object> locklist = new list<object>();
     static customcachenew()
     {
         cpunumer = 4;
         for (int i = 0; i < cpunumer; i++)
         {
             dictionarylist.add(new dictionary<string, object>());
             locklist.add(new object());
         }

         task.run(() =>
         {
             while (true)
             {
                 thread.sleep(1000 * 60 * 10);
                 try
                 {
                     for (int i = 0; i < cpunumer; i++)
                     {
                         list<string> keylist = new list<string>();
                         lock (locklist[i])//减少锁的影响范围
                         {
                             foreach (var key in dictionarylist[i].keys)
                             {
                                 datamodel model = (datamodel)dictionarylist[i][key];
                                 if (model.obslotetype != obslotetype.never && model.deadline < datetime.now)
                                 {
                                     keylist.add(key);
                                 }
                             }
                             keylist.foreach(s => dictionarylist[i].remove(s));
                         }
                     }


                 }
                 catch (exception ex)
                 {
                     console.writeline(ex.message);
                     continue;
                 }
             }
         });
     }

缓存究竟哪里用?满足哪些特点适合用缓存?

  1、访问频繁

  2、耗时耗资源

  3、相对稳定

  4、体积不那么大的

  不是说严格满足,具体的还要看情况,存一次能查三次,就值得缓存(大型想换标准)

  下面应该用缓存

    1、字典数据

    2、省市区
    3、配置文件
    4、网站公告信息
    5、部门权限,菜单权限
    6、热搜
    7、类别列表/产品列表
    8、用户,其实session也是缓存的一种表现

  股票信息价格/彩票开奖信息,这些不能用缓存,即时性要求很高。图片/视频,这些也不行,太大了。商品评论,这个可以用缓存的,虽然评论汇编,但是这个不重要,我们不一定非要看到最新的,而且第一页一般不变。

  可以测试下customcache的性能,十万/百万/千万  插入/获取/删除的性能。