Redis学习七(日常积累).

一、 为什么 Redis 那么快?

Redis 是基于内存的单进程单线程模型的 KV 数据库,由 C 语言编写,官方提供的数据是可以达到 100000+ QPS。

  • 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速;
  • 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
  • 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  • 使用多路 I/O 复用模型,非阻塞 IO;
  • 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

二、Redis 主从之间的数据是怎么同步的?

你启动一台 slave 的时候,它会发送一个 psync 命令给 master ,如果是这个 slave 第一次连接到 master,它会触发一个全量复制。master 就会启动一个线程,生成 RDB 快照,还会把新的写请求都缓存在内存中,RDB 文件生成后,master 会将这个 RDB 发送给 slave 的,slave 拿到之后做的第一件事情就是写进本地的磁盘,然后加载进内存,然后 master 会把内存里面缓存的那些新命令都发给 slave。

repl-blacklog-size 复制积压缓冲区过小

主从复制过程中,复制积压缓冲区里面存放的数据为以下三个时间点的数据:
1)master 执行 rdb bgsave 产生 snapshot 的时间
2)master 发送 rdb 到 slave网络传输时间
3)slave load rdb 文件把数据恢复到内存的时间

如果在主从复制过程中(rdb 全量同步),在上面 3 个时间点产生的数据大于 repl-blacklog-size,则主从复制失败,需要重新进行全量复制,所以要合理的设置 repl-blacklog-size 的大小。

repl-timeout 主从复制超时

以下三种情况认为复制超时:
1)slave 角度,在 repl-timeout 时间内没有收到 master SYNC 传输的 rdb snapshot 数据;
2)slave 角度,在 repl-timeout 没有收到 master 发送的数据包或者 ping;
3)master 角度,在 repl-timeout 时间没有收到 REPCONF ACK 确认信息。

当 redis 检测到 repl-timeout 超时(默认值 60s),将会关闭主从之间的连接,redis slave 发起重新建立主从连接的请求,对于内存数据集比较大的系统,可以增大 repl-timeout 参数。

三、Redis 的 AOF 重写?

Redis 的 AOF 机制有点类似于 Mysql binlog,是 Redis 的提供的一种持久化方式(另一种是RDB),它会将所有的写命令按照一定频率(no, always, every seconds)写入到日志文件中,当 Redis 停机重启后恢复数据库。

随着 AOF 文件越来越大,里面会有大部分是重复命令或者可以合并的命令(100次incr = set key 100), 重写减少 AOF 日志尺寸,减少内存占用,加快数据库恢复时间。

redis fork 的子进程完成 AOF 重写工作之后,它会向父进程发送一个信号,父进程在接收到该信号之后,会调用一个信号处理函数,并执行以下工作:

  • 将 AOF 重写缓冲区中的所有内容写入到新的 AOF 文件中,保证新 AOF 文件保存的数据库状态和服务器当前状态一致。
  • 对新的 AOF 文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换
  • 继续处理客户端请求命令。

在整个 AOF 后台重写过程中,只有信号处理函数执行时可能会对 Redis 主进程造成阻塞,在其他时候,AOF 后台重写都不会阻塞主进程。解决方向大概有:

  1. 将 no-appendfsync-on-rewrite 设置为 yes,表示在日志重写时,不进行命令追加操作,而只是将命令放在重写缓冲区里,避免与命令的追加造成磁盘 IO 上的冲突,但是在 rewrite 期间的 AOF 有丢失的风险。
  2. 设置 auto-aof-rewrite-percentage = 0,关闭自动重写,选择在低峰期定时执行。
  3. 给当前 Redis 实例添加 slave 节点,当前节点设置为 master, 然后 master 节点关闭 AOF 重写,slave 节点开启 AOF 重写。这样的方式的风险是如果 master 挂掉,尚没有同步到 slave 的数据会丢失。

六、redis-server 的 timeout、tcp-keepalive 分析

redis 的 timeout、tcp-keepalive 两个参数默认值都是0。

tcp-keepalive:TCP 心跳包,如果设置为非零,则在与客户端缺乏通讯的时候使用 SO_KEEPALIVE 发送 tcp acks 给客户端;如果设置成零值,redis-server 将不使用 TCP 协议栈提供的保活机制,会认为客户端连接一直存在,不会有 TCP 的 KEEPALIVE 报文在 redis 客户端和服务端传输。下面一段话摘自 TCP/IP 详解:

timeout:指定在一个 client 空闲多少秒之后关闭连接,该值是 redis-server 应用程序的行为,与 tcp 协议栈无关。

七、其他

redis 集群中,如果从节点与主节点的断开时间过长,会触发全量同步,全量同步时主节点会 dump rdb 文件,可能触发主从切换,影响集群稳定性。

注意 redis 号称的单线程只是处理我们的网络请求或文件事件分派时只有一个线程来处理,redis-server 肯定不止只有一个线程。

IO 多路复用,这里的多路指的是多个请求,复用指的是复用同一个线程。

常见的过期策略:

  • FIFO(First In First On,先进先出):根据缓存被存储的时间,离当前最远的数据优先被淘汰;
  • LRU(Least Recently Used,最近最少使用):根据最近被使用的时间,离当前最远的数据优先被淘汰;
  • LFU(Least Frequently Used,最不经常使用):在一段时间内,缓存被使用次数最少的会被淘汰;

因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是集器内存的大小或者网络带宽。

Redis Cluster 划分了 16384 个槽位,每个节点负责其中的一部分数据,都会存储槽位的信息。访问某个具体的数据 Key,可以根据本地的槽位来确定需要连接的节点。

Spring 封装的 stringRedisTemplate.getConnectionFactory().getConnection() 获取 pool 中的 redisConnection 后,不会释放或者退还到连接池中,虽然业务已处理完毕 redisConnection 已经空闲,但是 pool 中的 redisConnection 的状态还没有回到 idle 状态。我们可以使用下面代码来执行:

stringRedisTemplate.execute(new RedisCallback<Cursor>() { 
 
     @Override 
     public Cursor doInRedis(RedisConnection connection) throws DataAccessException { 
 
       return connection.scan(options); 
     } 
   }); 

或者使用完 connection 后执行来释放 Connection。

RedisConnectionUtils.releaseConnection(conn, factory); 
posted @ 2020-11-04 22:05  JMCui  阅读(470)  评论(0编辑  收藏  举报