<b>类型</b><br><ul><li>String(字符串)</li><li>List(列表)</li><li>Hash(哈希)</li><li>Set(集合)</li><li>ZSet(有序集合)</li></ul>
过期删除策略<br><ul><li><font color="#a6a6a6">定时删除:设置key过期时间同时创建一个定时器,定时器负责key过期时删除(对CPU不友好,时间换空间)。</font></li><li>惰性删除:<font color="#e74f4c">在key被使用时才判断是否过期</font>,对内存不友好,用存储空间换处理器性能(对内存不友好,空间换时间)。</li><li>定期删除:<font color="#e74f4c">周期性的轮询查找时效性数据</font>,默认1秒10次(100毫秒/次,通过hz配置),采用随机抽取的策略,利用过期数据占比的方式控制删除频度(随机抽查)。<br>1)测试随机的20个keys进行相关过期检测。<br>2)删除所有已经过期的keys。<br>3)如果还有多于25%的keys过期,重复步奏1。<br><br></li><li>假设Redis当前存放30万个key,并且都设置了过期时间,如果每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉。因此,<font color="#e74f4c">redis采取的是定期过期</font>,每隔100ms就随机抽取一定数量的key来检查和删除。但是呢,最后可能会有很多已经过期的key没被删除。<font color="#e74f4c">这时候,redis采用惰性删除</font>。在获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除。</li><li>但是呀,如果定期删除漏掉了很多过期的key,然后也没走惰性删除。就会有很多过期key积在内存,直接会导致内存爆掉。或者有些时候,业务量大起来了,redis的key被大量使用,内存直接不够了,运维小哥哥也忘记加大内存了。难道redis直接这样挂掉?不会的!Redis用8种<font color="#e74f4c">内存淘汰策略</font>保护自己~</li></ul>
<b>内存淘汰策略<br></b><ul><li>触发时机:当内存超过maxmemory限制时,使用maxmemory-policy指定的策略进行内存回收。<br>1、noevication : 不会驱逐任何 key (默认)<br>2、<font color="#e74f4c">allkeys-lru: 对所有的 key 使用 lru 算法进行删除<br></font>3、volatile-lru: 对所有的设置了过期时间的 key 进行 lru 算法进行删除<br>4、allkeys-random: 对所有 key 随机删除<br>5、volatile-random: 对所有设置了过期时间的 key 随机删除<br>6、volatile-ttl :马上删除要过期的 key<br>7、allkeys-lfu: 对所有 key 进行 lfu 算法进行删除<br>8、volatile-lfu: 对所有设置了过期时间的 key 使用 lfu 算法进行删除<br><br></li><li>LRU算法(最近最少使用):以<font color="#e74f4c">访问时间</font>为参考,淘汰最早被访问的数据(LRU=哈希+链表,可以用LinkedHashMap实现LRU)。</li><li>LFU算法(最不经常使用):以<font color="#e74f4c">访问频率</font>为参考,淘汰最少访问频率的数据。、<br><br></li><li>配置:<br> 1、配置文件:maxmemory-policy allkeys-lru<br> 2、客户端命令:config set maxmemory-policy allkeys-lru(设置)<br> config get maxmemory-policy(查看)</li></ul>
<b>RDB持久化<br></b><ul><li><font color="#e74f4c">RDB(默认开启)</font>:在某一些刻以(全量)快照形式将内存中的数据写入到磁盘(忽略已过期key),生成dump.rdb(经过压缩的二进制数据文件)。<br>0、fork()会产生一个和父进程完全相同的子进程(除了pid)。<br>1、RDB持久化时(bgsave),父进程会fork出一个子进程,由子进程对数据进行I/O写到磁盘。如果在这个过程中发生写操作,Redis会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,依然可以正常处理写操作,同时当前主进程写操作的内容也会写入RDB。<br>2、恢复RDB数据:如果指定目录中存在dump.rdb文件(主:忽略已过期key,从:不忽略),重启redis服务时(需要将AOF关闭)。<br><br></li><li>配置<br>1、save 900 1 :#900秒(15分钟)内,如果有1个key发生变化,自动触发bgsave命令创建快照。<br>2、关闭RDB:注释所有save规则,设置为空save "",删除dump.rdb,重启redis服务。<br>3、dbfilename :设置快照的文件名,默认是 dump.rdb。<br>4、dir:设置快照文件的存放路径(目录)。<br><br></li><li><span style="font-size:inherit;">命令<br></span>lastsave:获取最后一次成功执行快照的时间。<br>save:阻塞进程,直到RDB完成,阻塞期间不能处理任何命令。、<br>bgsave:派生出一个子进程处理RDB,不会影响父进程处理其他命令。</li></ul><br><b><span style="font-size:inherit;">AOF持久化</span><br></b><ul><li><span style="font-size:inherit;">AOF:根据指定的策略,<font color="#e74f4c">以日志形式将改变数据集的操作命令追加到appendonly.aof文件中</font>。<br></span>1、AOF持久化时,父进程会fork出一个子进程,由子进程对数据进行IO写到磁盘。为了提升写入效率,不会将内容直接写入到磁盘中,而是将其放到一个内存缓存区(buffer)中,等到缓冲区写满或调用fsync()函数时才真正将缓存区中的内容写入到磁盘里。<br>2、恢复AOF数据:Redis重启,如果RDB、AOF同时存在,默认加载AOF。<br><br></li><li><span style="font-size:inherit;">配置<br></span>1、appendonly no # 是否开启aof<br>2、appendfilename "appendonly.aof" # 文件名<br>3、磁盘同步策略(默认每秒一次)<br>appendfsync always # 每次对数据集操作执行一次fsync<br>appendfsync everysec # 每秒执行一次fsync<br>appendfsync no # 由操作系统执行,默认Linux配置最多丢失30秒<br>4、AOF重写策略,AOF文件体积过大时,将重复指令优化成1条指令<br>auto-aof-rewrite-percentage 100 #百分比,第一次64mb时重写,第二次128mb,第三次等再次增加64mb(100%)</li><li><span style="font-size:inherit;">auto-aof-rewrite-min-size 64mb #表示触发AOF重写的最小文件体积,大于或等于64MB自动触发。<br><br></span></li><li><span style="font-size:inherit;">命令<br></span>1、bgrewriteaof重写AOF<br><br>Redis4.0后,<font color="#e74f4c">混合持久化策略(必须开启AOF)</font>,相当于AOF升级版,区别在于当AOF重写时,会将已有的内容优化成RDB二进制的内容存储。<br>aof-use-rdb-preamble yes #开启混合持久化</li></ul>
<b>扩展<br></b><ul><li>Redis是单线程还是多线程?<br>1、6.0前:网络I/O、读写命令由单线程完成,持久化与集群数据同步是多线程。<br>2、6.0后:读写命令仍然是单线程处理,而网络I/O采用了多线程。<br><br></li><li>Redis单线程为什么这么快?<br>1、命令执行基于内存操作<br>2、没有线程切换开销<br>3、基于IO多路复用<br>4、底层高效存储结构<br><br></li><li>删除Key会阻塞Redis吗?<br>1、O(N),N为被删除key的数量<br>2、删除单个字符串,时间复杂度O(1)<br>3、删除单个列表、集合、有序集合、哈希,时间复杂度O(M),M为元素数量<br>结论:集合会逐个元素遍历删除(阻塞),超大字符串(阻塞)<br><br></li><li>缓存穿透<br><font color="#e74f4c">表示请求的数据在缓存、数据库都不存在,穿过了缓存,透过了数据库,还是没有数据。<br></font>场景:遭到恶意攻击,传入一个根本不存在的值产生大量请求,如id=-1,可能导致DB压力过大挂掉。<br>解决:<br>1)做参数校验,过滤掉非法无效参数。<br>2)缓存空值,如id=-1不存在,也缓存一个key=empty:-1,同时设置过期时间。<br>3)BloomFilter(布隆过滤器),系统启动时将需要检查的缓存数据存入,请求来的时候先去BloomFilter查询key是否存在,不存在则直接返回,存在则查缓存、查DB,布隆过滤器也存在一定的误判。<br><br></li><li>缓存击穿<br><font color="#e74f4c">请求将Redis击穿,到达DB层,可以看做小规模雪崩(击穿是一个人的雪崩,雪崩是一群人的击穿)。<br></font>场景1:某时刻一批热点数据同时过期失效,那么这一刻的所有请求将访问DB,使DB压力剧增。<br>场景2:突然爆发大量并发请求一个未被缓存的冷门数据,如微博热点事件。<br>解决:<br>1)对热点数据的过期时间设置尽可能分散,基础值+2分钟内的随机时间(具体随机范围看实际业务),避免同一时间大批量缓存失效。<br>2)增加分布式DCL读写锁。<br>3)在业务层监控热点数据是否即将过期,如果即将过期则去数据库获取最新数据进行更新并延长该热点key在缓存系统中的时间。<br><br></li><li>缓存雪崩<br><font color="#e74f4c">缓存雪崩的主要原因是缓存系统不够高可用,间接导致整个系统不可用。<br></font>场景1:在单节点主从架构,突发的大量并发请求(10W+)直接访问Redis,导致Redis承载不住挂掉。<br>解决:<br>1)限流:各级负载均衡层根据后端相关接口的压测指标针对性的做限流控制,避免同时处理大量的请求。<br>2)多级缓存架构:如在JVM层做一个进程级缓存,当该缓存没有数据时才查询Redis。<br>3)Redis集群避免单点故障。<br>场景2:大量并发请求场景,缓存大面积过期失效,未被Redis缓存承接住,直接请求到DB,导致DB挂掉。<br>解决:对热点数据的过期时间设置尽可能分散,一个较大固定值+一个较小的随机值(具体随机范围看实际业务),避免同一时间大批量缓存失效。<br><br></li><li>分布式锁<br>分布式锁需要解决的问题:<br>1、使用setnx实现分布式锁,并在finally中释放锁<br>2、问题 - 在finally前程序终止,锁未释放<br> 解决 - 设置锁超时时间。<br>3、问题 - 如果先加锁再设置超时时间可能存在加锁后,设置超时时间之前程序down了<br> 解决 - 使用setex保证原子操作<br>4、问题 - 锁5s过期,A线程执行业务逻辑用了6秒,第5秒释放锁后B线程拿到锁,这时A线程会删除B线程的锁。<br> 解决 - 加锁线程设置唯一标识到value中,解锁前先检查,是自己加的锁才允许删除。<br>5、问题 - 锁检查与锁释放不是原子操作<br> 解决 - Lua表达式:检查、删除封装成一条脚本执行,保证原子性<br>6、问题 - 业务执行时间大于锁过期时间,导致线程A任务还未执行完但锁被释放,其他线程获取锁后进行业务操作<br> 解决 - 锁续期(Redisson:默认30s超时,10s一次续期),加锁redisson.lock();解锁redison.unlock();<br>7、问题 - 在高并发情况下,使用redisson.unlock()解锁可能出现IllegalMonitorStateException异常(需要创建锁的线程进行解锁)<br> 解决 - if(redisson.isLocked() && redisson.isHeldByCurrentThread()){redisson.unlock();}</li></ul>
Redis分布式集群倾斜问题
1、数据存储容量倾斜,数据存储总是落到集群中少数节点<br>2、QPS请求倾斜,QPS总是落到少数节点<br>
倾斜的影响
1、QPS集中到少数redis节点,引起少数节点过载,会拖垮整个服务,同时集群处理QPS能力不具备可扩展性;<br>2、数据容量倾斜,导致少数节点内存爆增,出现OOM Killer和集群存储容量不具备可扩展性;<br>3、运维管理变复杂,类似监控告警内存使用量、QPS、连接数、redis cpu busy等值不便统一;<br>4、因集群内其他节点资源不能被充分利用,导致redis服务器/容器资源利率低;<br>5、增大自动化配置管理难度;单集群节点尽量统一参数配置;
倾斜的常见原因
1、系统设计时,redis键空间(keyspace)设计不合理,出现”热点key",导致这类key所在节点qps过载,集群出现qps倾斜;<br>2、系统存在大的集合key(hash,set,list等),导致大key所在节点的容量和QPS过载,集群出现qps和容量倾斜;<br>3、DBA在规划集群或扩容不当,导致数据槽(slot)数分配不均匀,导致容量和请求qps倾斜;<br>4、系统大量使用[Keys hash tags](http://redis.io/topics/cluster-spec), 可能导致某些数据槽位的key数量多,集群集群出现qps和容量倾斜;<br>5、工程师执行monitor这类命令,导致当前节点client输出缓冲区增大;used_memory_rss被撑大;导致节点内存容量增大,出现容量倾斜;
倾斜问题的排查方式
1、排查节点热点key,确定top commands.<br>当集群因热点key导致集群qps倾斜,需快速定位热点key和top commands。可使用开源工具[redis-faina]<br>2、系统是否使用较大的集合键<br>系统使用大key导致集群节点容量或qps倾斜,比如一个5kw字段的hash key, 内存占用在近10GB,这个key所在slot的节点的内存容量或qps都很有可能倾斜。<br>3、检查集群每个分片的数据槽分配是否均匀<br>4、系统是否大量使用keys hash tags<br>5、是否因为client output buffer异常,导致内存容量倾斜
如何有效避免Redis集群倾斜
1、系统设计redis集群键空间和query pattern时,应避免出现热点key, 如果有热点key逻辑,尽量打散分布不同的节点或添加程序本地缓存;<br>2、系统设计redis集群键空间时,应避免使用大key,把key设计拆分打散;大key除了倾斜问题,对集群稳定性有严重影响;<br>3、redis集群部署和扩缩容处理,保证数据槽位分配平均;<br>4、系统设计角度应避免使用keys hash tag;<br>5、日常运维和系统中应避免直接使用keys,monitor等命令,导致输出缓冲区堆积;这类命令建议作rename处理;<br>6、合量配置normal的client output buffer, 建议设置10mb,slave限制为1GB按需要临时调整(警示:和业务确认调整再修改,避免业务出错)<br>在实际生产业务场景中,大规模集群很难做到集群的完全均衡,只是尽量保证不出现严重倾斜问题。