我们都知道系统中使用缓存其实就两个主要的用途高并发和高性能。那么用了缓存后会有哪些弊端呢1. redis为什么是单线程模型效率为什么高Redis 内部使用文件事件处理器file event handler这个文件事件处理器是单线程的所以 Redis 才叫做单线程的模型。文件事件处理器的结构包含4 个部分1、多个 socket2、IO 多路复用程序3、文件事件分派器4、事件处理器效率高1、纯内存操作2、核心是基于非阻塞的 IO 多路复用机制3、单线程反而避免了多线程的频繁上下文切换问题2. 有哪些数据类型Redis 主要有以下几种数据类型Strings字符串最简单的数据类型用于存储键值对。Hashes哈希表类似于map用于存储键值对Lists有序列表可以存储排行榜、文章评论列表之类的东西Sets无序集合不能存储重复元素Sorted Sets排序的set去重且排序3. redis的过期策略有哪些淘汰机制呢LRU了解吗过期策略就是定期删除惰性删除1、定期删除就是redis默认每隔100ms随机抽取一些过期时间的key删除过期的keyredis不是查找所有的key进行检查2、 惰性删除就是当访问一个key时如果发现key过期了就删除这个key这样总会有漏网之鱼怎么办内存淘汰机制淘汰机制有以下几个1、allkeys-lru全局最近最少使用2、allkeys-ttl全局过期时间最短的3、allkeys-random全局随机选择4、volatile-ttl过期时间最短的5、volatile-random随机选择6、volatile-lru最近最少使用手写LRU算法第一种使用哈希表双向链表实现LRU缓存复杂第二种JDk底层自己实现的LRU缓存简单实例/** * 继承 LinkedHashMap 快速实现 LRU Cache * param K key * param V value */publicclassLRUCacheLinkedHashMapK,VextendsLinkedHashMapK,V{// 缓存最大容量privatefinalintmaxCapacity;/** * 构造器开启访问顺序排序 * param capacity 缓存容量 */publicLRUCacheLinkedHashMap(intcapacity){// initialCapacity: 初始容量loadFactor: 负载因子accessOrdertrue 开启LRU访问排序super(capacity,0.75f,true);this.maxCapacitycapacity;}/** * 钩子方法put 执行完毕后回调 * 返回 true → 删除最久未使用的元素链表头部 */OverrideprotectedbooleanremoveEldestEntry(Map.EntryK,Veldest){// 当前元素个数超过最大容量则淘汰returnsize()maxCapacity;}// 测试入口publicstaticvoidmain(String[]args){// 缓存容量为3LRUCacheLinkedHashMapInteger,StringlrunewLRUCacheLinkedHashMap(3);lru.put(1,一号数据);lru.put(2,二号数据);lru.put(3,三号数据);System.out.println(存入1,2,3lru);// 新增第4个超出容量淘汰最久未使用的1lru.put(4,四号数据);System.out.println(存入4淘汰1lru);// 访问key22变为最新元素移到末尾lru.get(2);System.out.println(访问2之后lru);// 新增5此时最久未使用是3淘汰3lru.put(5,五号数据);System.out.println(存入5淘汰3lru);// 更新key4的值4变为最新lru.put(4,四号更新);System.out.println(更新4之后lru);}}4. 什么是 Redis 的雪崩、穿透和击穿问题**雪崩**大量key同时过期失效或者redis服务挂了大量请求直接打到数据库导致数据库压力很大甚至崩溃解决事前主从复制➕哨兵模式事中本地缓存➕服务层限流、降级事后持久化数据避免数据丢失**穿透**查询数据库中根本不存在的数据绕过缓存直接查询数据库比如传-1或者空值解决1、缓存空值数据库查询为空时让redis写入key:null,设置过期时间为5分钟2、使用布隆过滤器**击穿**缓存中有超高并发热点key刚刚好过期解决1、使用互斥锁确保只有一个线程查询数据库2、使用缓存预热提前查询数据库将数据缓存起来避免直接查询数据库3、设置永不过期5. Redis 的持久化有哪几种方式**RDB**快照持久化按照时间点快照把当前内存中全部数据一次性保存到磁盘二进制文件中。1、自动触发#900秒至少1次修改、300秒至少10次、60秒至少10000次修改自动bgsave save9001save30010save60100002、手动触发fork子进程子进程写RDB父进程继续处理请求。**AOF**记录每一条修改命令set/hset/del等追加写入.aof 文本文件重启时重放命令恢复数据。默认关闭开启配置appendonly yes appendfilenameappend.aofAOF 重写机制AOF 持续追加会文件越来越大重写fork 子进程读取当前内存数据生成精简命令替换旧 AOF删除冗余操作。触发条件配置文件比上次重写后增大100%且超过64MB自动重写。 auto-aof-rewrite-percentage100auto-aof-rewrite-min-size64mb6. 如何保证redis的高可用和高并发呢高并发1、主从复制一主多从主负责写并且将数据复制到其它的 slave 节点所有的读请求全部走从节点。原理及流程全量复制➕增量复制1从节点发起连接握手认证发送replconf配置2发送PSYNC命令核心PSYNCrunid:主节点唯一运行ID每次重启变更offset:复制偏移量记录从节点上次复制到的偏移量用于断线重连2种情况1、首次链接/runid不匹配主节点执行全量复制2、断线重连/runid一致、offset在主节点复制积压缓冲区增量复制3主节点执行全量复制生成一份RDB持久化文件同时写入所有命令存入复制积压缓冲区生成完毕通过socket发送给从节点4从节点接收RDB文件加载数据同时从主节点订阅写命令持续增量同步无磁盘复制主节点在内存中直接创建RDB 然后发送给从节点不会在自己本地落地磁盘了。只需要在配置文件中开启 repl-diskless-sync yes 即可。过期key处理slave 不会过期key只会等待 master 过期key。如果 master 过期了一个key或者通过 LRU 淘汰了一个key那么会模拟一条 del 命令发送给 slave。高可用1、使用哨兵模式哨兵sentinel是 Redis 集群架构中非常重要的一个组件主要有以下功能集群监控负责监控 Redis master 和 slave 进程是否正常工作。消息通知如果某个 Redis 实例有故障那么哨兵负责发送消息作为报警通知给管理员。故障转移如果 master node 挂掉了会自动转移到 slave node 上。配置中心如果故障转移发生了通知 client 客户端新的 master 地址。此模式导致数据丢失的情况**1、异步复制导致**主从复制是异步的有部分数据还没复制到从节点主节点就宕机了**2、脑裂导致:**主节点突然脱离了正常的网络和其他从节点不连接但是自己还运行着此时哨兵认为你已经挂了会选举新的主节点集群里就会有两个主节点这就是脑裂。此时某个从节点被选择为了主节点但是client还没来及切换还在往旧的主节点写数据。因此就主节点再次恢复的时候会被认为是新的从节点从主节点同步数据但是新的主节点并没有client写入的数据所以就丢失了。解决方法至少有1个 slave数据复制和同步的延迟不能超过10秒 min-slaves-to-write1min-slaves-max-lag10如何保证缓存与数据库的双写一致性场景1读多写少、允许短暂不一致90% 普通业务标准实现延迟双删 过期兜底更新数据库删除缓存异步延迟 N ms 再次删除缓存缓存统一设置过期时间兜底最终一致。// 1. 更新数据库updateDb(entity);// 2. 立即删缓存redis.del(key);// 3. 异步延迟删除threadPool.execute(()-{Thread.sleep(500);redis.del(key);});场景2强一致性要求金融、订单、库存不允许脏数据分布式锁串行化读写写流程加锁 → 更新 DB → 删除缓存 → 释放锁读流程加锁 → 查询缓存无则查 DB 写入缓存 → 释放锁11. 写在最后喜欢的点赞、收藏、转发呀