【redis正传】redis淘汰+过时双向保证高可用 | 单线程如何作到快速响应

这是我参与更文挑战的第25天,活动详情查看: 更文挑战 redis为啥单线程还能作到高响应?java

前言

  • 【redis前传】持续更新!各类穿插更新!嘿嘿嘿

redis和数据相比除了他们的结构型颠覆之外!还有他们存储位置也是不相同。传统数据库将数据存储在硬盘上每次数据操做都须要IORedis是将数据存储在内存上的。这里稍微解释下IO是啥意思。IO就是输入流输出流方式将数据在硬盘和内存之间进行交互!而redis直接在内存上就剩下了IO操做。这也是redis快的缘由之一吧程序员

  • 内存相对于硬盘来讲很宝贵。咱们平时的电脑也是硬盘是内存的几百倍。既然内存很宝贵而redis又将数据存储在内存上那么redis确定不能肆无忌惮的进行存储 。这就须要redis和开发者们做出相应的优化
  • 首先redis在配置文件(redis.conf)中经过maxmemory参数指定redis 设置整个对内存的支配大小!
  • 其次就是要求咱们开发者在想redis中填值的时候根据本身的需求设置相应的key过时时间。这样没必要要的数据就会被redis过时驱逐策略清楚。从而节省内存供别人使用

本文凌驾于redis基础之上,这里笔者默认你们都已经安装了redis . 并实际使用过redisredis

内存分配

  • maxmemory 指定大小。在redis.conf配置文件中能够直接指定。他的单位时byte。

image-20210617144838455

  • 上图中注释部分是给你们的解释,实际中#配置须要换行哈!!!友情提示
  • 另外咱们能够链接上redis经过config命令来设置

image-20210617145851812

  • 两种方式均可以设置,前者是全局设置重启以后仍然有效!后者是临时设置重启以后就会从新加载redis.conf中的配置。

键过时

  • 上面咱们从redis自己角度出发设置了内存限制,这样不用担忧他们吞噬系统内存!下面就须要咱们开发者设计角度约束本身了。

设置过时时间

expire key time
复制代码
  • 设置过时时间默认单位时S 。数据库

  • 而后经过ttl 命令能够查看剩余过时时间小程序

image-20210617151445320

  • 通过屡次执行ttl可以观察到剩余时间在不断的减小!当减小到0的时候就被给驱逐策略驱逐!注意这里说的是驱逐策略驱逐并非意味着立马被删除

更新过时时间

  • del key 直接将key删除了那么该key对应的过时天然也就不存在了!这种状况笔者也算做是更新过时时间
  • set getset等命令从新设置key、value方式会覆盖过时时间 , 直接被覆盖成-1

image-20210617152121178

  • setgetset包括del严格意义是覆盖过时时间。真正作到更新过时时间的仍是expire .在expire是已最新为准的!
  • 上面其实都修改了key才会应发本来的过时时间失效的!由于此key非彼key 。 可是appendincr 等命令是改变值这种命令是不会影响到原来的过时设置的

image-20210617152731013

淘汰策略

  • 根据上面配置咱们能够将咱们的redis最大内存设置为1MB , 设置大小随便最好能小点。而后咱们经过Java小程序不断向redis中填充。最终当内存不够使用时就会报错

image-20210617161613440

  • 报错就是由于内存满了,新增的key被redis拒绝了!不只仅是新增的被拒绝,就算此时咱们想改变已经在redis中的key的值也是不可用的
public static void main(String[] args) {
    Jedis jedis = new Jedis("39.102.60.114", 6379);
    jedis.auth("Qq025025");
    Integer index = 1;
    while (true) {
        String uuid = UUID.randomUUID().toString();
        jedis.set(index.toString(), uuid);
        System.out.println(index++);
    }
}
复制代码

image-20210617162804867

  • 无论是append 仍是set 都是报OOM command not allowed when used memory > maxmemory 。代码中打印和redis键个数一致;说明咱们默认的淘汰策略是直接拒绝缓存

  • 总结下来就是:当redis内存被使用满了后,任何的写操做都会被拒绝!markdown

  • 当没有足够内存时难道就这么直接拒绝吗?上面也提到了须要咱们程序员本身根据需求设置键过时已释放内存供其余有须要的key使用!那么设置了过时key以后这些key又是怎么被清除的呢? 这就牵扯出咱们的淘汰策略并发

image-20210617163430236

volatile-lru【最近不多使用】

当内存告警时redis会将近期不多使用且设置了过时时间的key剔除出去,即便该key尚未到过时时间。若是没有符合的key也就是执行以后内存仍然不足时将会和默认淘汰策略noeviction抛出同样的错误OOM command not allowed when used memory > maxmemoryapp

  • 首先咱们在redis.conf中配置咱们最近不多使用策略. maxmemory-policy volatile-lru 。 而后重启咱们的redis服务 。重启以后flushall清空全部数据,咱们在经过上面的Java程序从新生成下数据!

image-20210617165407096

  • Java程序中咱们设置前100个key添加过时时间
public static void main(String[] args) {
    Jedis jedis = new Jedis("39.102.60.114", 6379);
    jedis.auth("Qq025025");
    Integer index = 1;
    while (true) {
        String uuid = UUID.randomUUID().toString();
        if (index < 100) {
            jedis.setex(index.toString(),360, uuid);
        } else {
            jedis.set(index.toString(), uuid);
        }
        System.out.println(index++);
    }
}
复制代码

image-20210617171334220

  • 简单分析下为何程序计数器大于redis库中的key数量!就是由于咱们为前100设置了过时时间。当内存不足时redis就会将当前设置了过时时间的key中最近最少使用的key进行剔除!因此咱们计数器会大于键数量。由于有部分键被清除了!咱们获取前100的key都是null , 说明被删除了! 那么为何本次计数器不是比上次多100 。 那是由于咱们每次存储进来的是uuid, 所占长度都不是固定的。还有自己淘汰策略也是占用内存的

image-20210617173746769

策略总结

  • 上面演示了最近最少使用的淘汰策略!除此以外还有其余的策略
noeviction:拒绝写请求,正常提供读请求,这样能够保证已有数据不会丢失(默认策略);
2. volatile-lru:尝试淘汰设置了过时时间的key,虽少使用的key被淘汰,没有设置过时时间的key不会淘汰;
3. volatile-ttl:跟volatile-lru几乎同样,可是他是使用的key的ttl值进行比较,最早淘汰ttl最小的key;
4. volatile-random:其余同上,惟一就是他经过很随意的方式随机选择淘汰key集合中的key;
5. allkeys-lru:区别于volatile-lru的地方就是淘汰目标是所有key,没设置过时时间的key也不能幸免;
6. allkeys-random:这种方式同上,随机的淘汰全部的key。
复制代码
  • 使用哪一种淘汰策略须要咱们结合本身的项目场景来配合使用!!!

过时删除

  • 上面咱们从【键过时】、【淘汰策略】两个角度分析了redis 。 仅仅这两方面尚未彻底高效使用内存!淘汰策略是濒临内存不足时触发。那么当设置了过时时间的键真正到了过时时间而此时内存尚够使用?这种场景是否是须要将过时键删除呢?
  • 由于redis是单线程,那么在键过时的时候如何不影响对外服务的同时清除过时键呢?答案是【不行】。严格意义是没法解决的由于单线程同时只能作一件事!既然没法解决那么咱们能够达到一种协调状态!若是同一时刻出现一个过时键那么清除键很快这时候阻塞外部服务的时间很短可能毫秒级设置纳秒级!
  • 可是如何同一时间发生上万键过时,若是想要删除上万键那确定须要花费必定时间这时候就会阻塞对外服务!这确定是不能接受的,阻塞时间过长会致使客户端链接超时报错的。这在并发场景下更是没法接受的!因此redis如何应对同一时间过多数据过时的场景,他的删除过时键的方法略有不一样!

定时清除

  • 针对每一个过时键设置一个定时器,在过时时就会进行清理该键!
  • 该作法可以作到数据实时被清理从而保证内存不会被长期占用!提升了内存的使用率!
  • 可是问题也随之而来,每个key须要设置一个定时器进行跟踪。redis这里笔者猜想应该是启用另外线程来进行定时跟踪!这里有清除的还请帮忙解答下?
  • 当同一时间过时key不少的时候!咱们的CPU就须要不断的执行这些定时器从而致使CPU资源紧张。最终会影响到redis服务的性能

按期清除

image-20210618101552195

  • 按期删除就是上面咱们图示效果,redis会每隔100ms执行一次定时器,定时器的任务就是随机抽取20个设置过时的key 。 判断是否进行清除。上面图示中说明中写错了不是10S , 而是每隔100ms 。请原谅个人粗心!!!

image-20210618102943955

  • 按期删除和定时删除做用是相反的!按期删除是将key集中进行处理同时为了保证服务的高可用在处理时加入的时间限制。每次执行总时长不能超过25ms 。 也就是说对于客户端来讲服务端的延迟不会超过25ms
  • 他的优势就是不须要CPU频繁的进行操做key清除!由于他是按期进行清除因此就会致使一部分数据没有来得及清除从而致使内存使用上会被一直占用!

惰性清除

  • 关于惰性删除咱们在平时开发中也常用这种方式!当数据过时时redis并不急着去清除这些数据,而是等到该key被再次请求时进行删除!这样在最终效果上是没有问题的。
  • 优势和按期清除同样他保证了CPU没必要频繁的进行切换!可是缺点也很明显会致使不少已通过期的key任然在redis中。

惰性清除+按期清除

  • 咱们开头说过了既要高可用又要实时清理过时key 这是没法作到的!既然没法作到咱们就须要在CPU和内存中间作一个权衡!redis内部是使用惰性清除和按期清除两种方式结合使用,最终保重CPU和内存之间的一种平衡!

总结

  • 相信你们对上面三个概念有点模糊了。【键过时】、【淘汰策略】、【过时清除】
  • 首先【键过时】是redis给咱们开发者提供的功能。咱们能够根据本身的业务需求合理的设置键的过时时间,从而保证内存的高可用
  • 其次【过时清除】在咱们以前设置的过时的key如何进行合理的清除,并不能一股脑一会儿进行清除由于数据过大会致使服务的卡顿。这个时候咱们须要经过按期清除减缓清除key代码的卡顿。在redis.conf中咱们能够设置 hz 10 表明1S中平均执行几回这也是咱们上面所说的100MS的由来。可是仅仅按期删除会产生遗漏数据因此咱们还须要加上惰性清除,最终保证对客户端来讲数据是准确实时清除的。
  • 那么关于【淘汰策略】又是啥呢?在上面过时清除是若是用户一直不请求过时的key ,而且随着业务产生愈来愈多的过时key . 这时候redis服务中还会堆积不少过时的无效key 。这个时候若是内存不够用了的话那又该怎么办呢?这时候咱们须要设置淘汰策略好比果volatile-lru . 就会将最近最少使用的设置过时key进行清除从而保证尽量的接收更多的有效数据!
  • 这就是为何会设计三者的缘由!好好理解上面三个主题咱们再去想一想为何会发生【缓存雪崩】、【缓存奔溃】、【缓存击穿】就好理解一点了呢?
  • 关于上述的redis常见的灾难场景,咱们下章节继续分析如何产生的、而且如何进行修复进行展开讨论。
相关文章
相关标签/搜索