Redis内核原理及读写一致企业级架构深入剖析-综合组件环境实战

本套技术专栏是作者(秦凯新)平时工作的总结和升华,并深度整理大量网上资源和专业书籍。通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。QQ邮箱地址:[email protected],如有任何学术交流,可随时联系。

1 Redis 工作模型

redis实际上是个单线程工作模型,其拥有较多的数据结构,并支持丰富的数据操作,redis目前是原生支持cluster模式。如果需要缓存能够支持更复杂的结构和操作,基于以上原因,选择线上使用Redis会是不错的选择。

1.1 Redis 高效的原因:

  • Redis高效的原因:

    1)纯内存操作

    2)核心是基于非阻塞的IO多路复用机制

    3)单线程反而避免了多线程的频繁上下文切换问题

  • mysql单机支撑到2000qps就会出现性能问题了,所以要是你有个系统,高峰期一秒钟过来的请求有1万,那一个mysql单机绝对会死掉。因为 redis单机支撑的并发量轻松一秒几万十几万,支撑高并发so easy。单机承载并发量是mysql单机的几十倍。

  • 使用Redis可能会遇到的问题:

    • 缓存与数据库双写不一致
    • 缓存雪崩
    • 缓存穿透
    • 缓存并发竞争

1.2 文件事件处理流程

  • (1) redis基于reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器,file event handler。这个文件事件处理器,是单线程的,redis才叫做单线程的模型,采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器来处理这个事件。

  • (2) 文件事件处理器的结构包含4个部分:多个socket,IO多路复用程序,文件事件分派器,事件处理器(命令请求处理器、命令回复处理器、连接应答处理器,等等)。

  • (3) 多个socket可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,但是会将socket放入一个队列中排队,每次从队列中取出一个socket给事件分派器,事件分派器把socket给对应的事件处理器。

  • (4) 一个socket的事件处理完之后,IO多路复用程序才会将队列中的下一个socket给事件分派器。文件事件分派器会根据每个socket当前产生的事件,来选择对应的事件处理器来处理。

1.3 文件事件

  • 如果一个客户端跟redis发起连接,此时会产生一个AE_READABLE事件,然后由连接应答处理器来处理跟客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READABLE事件跟命令请求处理器关联起来。

  • 如果是客户端要连接redis,那么会为socket关联连接应答处理器。

  • 如果是客户端要写数据到redis,那么会为socket关联命令请求处理器。

  • 如果是客户端要从redis读数据,那么会为socket关联命令回复处理器。

1.4 客户端与redis通信流程

  • 创建客户端对应的socket

    在redis启动初始化的时候,redis会将连接应答处理器跟AE_READABLE事件关联起来,接着如果一个客户端跟redis发起连接,此时会产生一个AE_READABLE事件,然后由连接应答处理器来处理跟客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READABLE事件跟命令请求处理器关联起来。

  • 请求处理,socket产生一个AE_READABLE事件

    当客户端向redis发起请求的时候(不管是读请求还是写请求,都一样),首先就会在socket产生一个AE_READABLE事件,然后由对应的命令请求处理器来处理。这个命令请求处理器就会从socket中读取请求相关数据,然后进行执行和处理。

  • 客户端这读取响应数据

    接着redis这边准备好了给客户端的响应数据之后,就会将socket的AE_WRITABLE事件跟命令回复处理器关联起来,当客户端这边准备好读取响应数据时,就会在socket上产生一个AE_WRITABLE事件,会由对应的命令回复处理器来处理,就是将准备好的响应数据写入socket,供客户端来读取。

  • 命令回复处理器写完之后,就会删除这个socket的AE_WRITABLE事件和命令回复处理器的关联关系。

2 redis 过期策略及内存淘汰机制

  • 用内存当缓存。内存是无限的吗?内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个G的内存,但是可以有几个T的硬盘空间。redis主要是基于内存来进行高性能、高并发的读写操作的。

      set key value 过期时间(1小时)
      set进去的key,1小时之后就没了,就失效了,但是数据明明都过期了,怎么还占用着内存呢?
    复制代码
  • 定期删除+惰性删除

    所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了,cpu负载会很高的,消耗在你的检查过期key上了。注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。

    所谓惰性删除了,指的是在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。

    如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,也会出现严重的线上事故。

  • 内存淘汰

    如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:

      redis 10个key,现在已经满了,redis需要删除掉5个key
      1个key,最近1分钟被查询了100次
      1个key,最近10分钟被查询了50次
      1个key,最近1个小时倍查询了1次
    复制代码

    1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

    2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)

    3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的key给干掉啊。

    4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)

    5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key

    6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

猜你喜欢

转载自juejin.im/post/5c8cd67bf265da2dc006a168