缓存:redis pipeline 和事务,redis 运行机制,redis 数据淘汰,问题排查

pipeline 和事务

  1. pipeline 支持 redis 客户端一次发送多条命令给服务端,服务端全部执行完成后一并返回,这种方式避免了多次的网络通信和多次 io 处理的上下文切换,相比每次只发送一条命令,吞吐量更高,但命令返回延时更高,客户端等待时间更长;

  2. pipeline 中多个命令并不是原子的,可能出现部分成功,部分失败的情况,且 pipeline 中命令也不宜过多,避免单次网络通信数据量太大,命令耗时太高;pipeline 适用于客户端有大量命令需要执行,为提高吞吐量,组合成多个 pipeline 来完成,更多参考:https://blog.csdn.net/w1lgy/article/details/84455579;

  3. redis 事务是指一次性、顺序性、排他性的执行一系列命令,通过 multi 命令开启事务,之后 redis 服务端会将该连接发送的命令都缓存起来,收到 exec 命令后,取出缓存的命令,一次性执行,中间不会插入其他命令,最后将所有命令的执行结果一次性返回给客户端;

  4. redis 事务执行的中间状态对其他客户端是不可见的,但不能保证原子性,如果事务中命令有编译性错误会导致整个事务终止,单条命令的语法错误不会影响其他命令执行,事务中收到 discard 命令也会终止事务,事务不支持回滚,这样的设计能保证 redis 简单快速;

  5. watch 命令负责监控对指定键值的变化,一旦发生改变,会终止事务的执行,类似于乐观锁的作用,下例中,watch key 的变化,事务中对 key 做了修改,导致事务不被执行,key 的值没有发生变化,更多查看:https://www.cnblogs.com/DeepInThought/p/10720132.html;

watch key
set key 1
multi
set key 3
exec
get key // 得到的值仍是 1,事务没有执行
  1. redis 执行多个命令是非原子的,但可以使用 lua 语言编写脚本来自定义原子命令,如原子化生成一个分布式 id,原子化扣减库存等,lua 脚本执行有两种方式:使用 eval 命令,或者先执行 script load 加载脚本,然后执行 evalsha,第二种方式适合脚本需要反复执行或者脚本内容复杂的场景,更多参考:https://blog.csdn.net/a_helloword/article/details/81878759;
// 语法 eval lua-script key-num [key1...] [value1...]; lua-script是脚本内容,key-num参数个数,key和value都是参数值
eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
script load "redis.call('set',KEYS[1],ARGV[1])"
123456XXX
evalsha 123456XXX

Redis 运行机制

  1. redis 单机可支持10万+的并发,速度如此快的主要原因是:完成基于内存操作、单线程命令执行(避免了线程切换的开销)、基于非阻塞多路复用的 io 模型、以及对基础的数据结构做了大量优化;

  2. redis 是单线程来执行客户端发来的指令,但 4.0 之后的版本除了主线程外,也会创建若干后台线程进行数据清理,连接释放等耗时的操作,在 6.0 版本之后采用多线程来处理连接的网络 io,但 redis 命令的执行仍是单线程的,因此要避免阻塞耗时的命令执行;

Redis 数据淘汰机制

  1. redis 会采用:惰性删除、定期删除和阈值删除几种手段进行数据淘汰;惰性删除是依赖客户端从 redis 服务端查询键值对时,服务端检查判断键值是否过期,如果过期进行删除,返回客户端 null;

  2. 定期删除是 redis 将有过期时间的键值统一存放,然后定时(默认 100 ms)随机抽选 20 个键值,判断是否过期来删除,如果一次抽查删除比例超过 1/4,会再次抽查 20 个键值,否则等待第二次抽查时进行删除;因此有过期时间的键值并不会到期后立即被删除;

  3. 阈值删除是当 redis 存储超过 maxmemory 时,会依照指定的策略淘汰键值对,默认策略是不淘汰旧数据,但不接收新的写入操作;其他策略有:对全量 key 进行 lru 删除或者随机删除,只对有过期时间的 key 进行 lru 删除或者随机删除,对有过期时间的 key 删除即将过期的,对全量 key 或只对有过期时间的,删除使用频率最少的 key 等策略;

noeviction:不淘汰任何key,返回错误

allkeys-lru:针对所有 key,通过 LRU 算法淘汰最久没有使用的 key
allkeys-random:针对所有 key,随机删除

volatile-lru:针对有过期时间的 key,通过 LRU 算法淘汰最久没有使用的 key
volatile-random:针对有过期时间的 key,随机删除

volatile-ttl:针对有过期时间的 key,删除马上要过期的 key
volatile-lfu:针对有过期时间的 key,删除使用频率最少的 key
allkeys-lfu:针对所有 key,删除使用频率最少的 key
  1. lru 淘汰算法是选择最近最少使用的,具体实现:可以用一个定长的双向链表,来保存缓存的访问记录,每当新增的缓存,或最新使用的缓存,都移动放置在链表头,这样保证链表的顺序,就是从最近使用的,到最长时间没有使用的;链表长度固定,这样每次链表头增加元素,就会移除链尾元素,即最近最少使用的元素;

问题排查

  1. 热点缓存,关键在于建立监控,客户端监控:记录 key 和调用次数,服务端监控:使用 monitor 等命令统计;然后做针对性优化:分散热点 key 的存储,加入二级缓存等;对于热点缓存的重建,要注意加锁,避免缓存击穿;或者设置热点缓存不过期,在 value 中记录过期时间,依靠后台线程检查并更新;

  2. 大 key 问题,定期使用 scan + debug,redis-cli --bigkeys 命令找出大 key,使用 redis-rdb-tools 工具分析 rdb 快照得到 redis 的使用情况,然后尝试对大 key 进行压缩和拆分,注意删除大 key 使用 unlink 命令以非阻塞的方式,或者 scan 命令来删除;

  3. redis 阻塞-慢查询,执行时间超过配置 slowlog-log-lower-than 和 slowlog-max-len 的阈值即是慢查询,建议设置为 1 和 1000 毫秒以上,redis 提供 slowlog get [n],slowlog len,slowlog reset 的命令获取慢查询,慢查询语句需要定期保存,否则会丢失;

  4. redis 阻塞-fork 线程,执行 bgsave 或 aof 文件重写时需要 fork 线程,该过程是阻塞的,排查阻塞可以查看 cpu 使用率,qps 等指标,使用 redis-cli-h{ip}-p{port}–stat 命令查看使用情况,aof 持久化磁盘和 hugePage 写操作时也可能阻塞;

  5. 对于敏感命令:keys,flushall,shutdown 等需要重写,避免误执行;删除数据时,string 类型使用 del,其他数据类型建议使用 scan + del,避免误删;

  6. mater 节点尽量不做任何持久化工作,避免阻塞,尽量将 master 和 slave 分布在同一局域网内,主从结构集群尽量使用单向链表形式: master-slave1-slave2,耗时操作可以在不对外的 redis 从节点上进行;建议 redis 单实例大小控制在 10G 以内;

  7. redis 延迟,使用命令 redis-cli --latency 测试,命令 redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000 -t get 表示使用 50 个并发线程,执行 10000 次 get 命令进行压测;

  8. 命令 set key value,如果 redis 中已有该 key,那么会覆盖之前值,并清除其过期时间;连接超时和读写超时,可能是超时时间设置不合理,如果 lua 脚本执行超过 lua-time-limit,会执行 script kill;

  9. 获取不到连接:could not get a resource,原因可能是连接泄露,慢查询等;输出缓冲区占满:unexpected end of stream,假设 redis 客户端设置输出缓冲区为 1M,那么超过 1M 的 bigKey 就会出现这个异常;

  10. redis 主从节点,如果主节点内存占用比从节点大很多,首先主从节点分别执行 dbsize,检查主节点的输入和输出缓冲区,通过 info clients 查看最大的缓冲区,如果发现有节点在执行 monitor,导致其输出缓冲区变得巨大,使用 client kill 杀掉该客户端;

你可能感兴趣的:(每日一学,缓存,redis,java)