- 1. 缓存穿透
- 2. 缓存雪崩
- 3. 缓存更新策略
- 4. 主从同步
- 5. 集群架构模式
- 6. 优化redis性能
- 7. 持久化
- 8. Redis的淘汰策略
- 9. Redis的删除策略
- 10. Bigkey
- 11. 内存上限
- 12. 分布式锁的实现
- 13. bitmap
1. 缓存穿透
缓存穿透是指缓存中查询一个不存在的数据,需要去数据库中获取,每次都需要DB交互,缓存失效。
1. bloom filter
将所有查询的参数都存储到一个 bitmap 中,在查询缓存之前,先再找个 bitmap 里面进行验证。
如果 bitmap 中存在,则进行底层缓存的数据查询; 如果 bitmap 中不存在查询参数,则进行拦截,不再进行缓存的数据查询。
2. 缓存null对象
如果查询返回的数据为空,仍然把这个空结果进行缓存。那么再次用相同 key 获取数据的时候,即使不存在的数据,缓存也可以直接返回空值,避免重复访问 DB。
缓存空对象有两个不足之处:
- 缓存层将存储更多的键值对,如果是恶意的随机访问,将造成很多内存空间的浪费。这个不足之处可以通过将这类数据设置很短的过期时间来控制。
- DB 与缓存数据不一致。这种可以考虑通过异步消息来进行数据更新的通知,在一定程度上减少这类不一致的时间。
2. 缓存雪崩
在集中的一段时间内,有大量的缓存失效,导致大量的访问没有命中缓存,从而将所有查询进行数据库访问,导致数据库的压力增大,从而造成了缓存雪崩。
1. 范围内随机设置缓存过期时间
分析缓存数据的特点,尽量将热点缓存的失效时间均匀分布。 比如说将相同类型的缓存的失效时间设置成一个在一定区间内的随机值。从而有效的分散失效时间。
2. DB 访问限制
对数据的访问进行限流性质的操作。比如说对数据库访问进行加锁的处理或者限流相关的处理。
3. 两级缓存
一级缓存为基础缓存,缓存失效时间设置一个较长时间, 二级缓存为应用缓存,失效时间正常设置,一般会比较短。 当二级缓存失效的时候,再从一级缓存里面获取
3. 缓存更新策略
1.先更新数据库,再更新缓存
- 这种策略会导致线程安全问题
例如:线程 1 更新了数据库,线程 2 也更新数据库, 这时候由于某种原因,线程 2 首先更新了缓存,线程 1 后续更新。 这样就导致了脏数据的问题。 因为目前数据库中存储的线程 2 更新后的数据,而缓存存储的是线程1更新的老数据。
- 更新缓存的复杂度相对较高
数据写入数据库之后,一般存入缓存的数据都要经过一系列的加工计算,然后写入缓存。 这时候更新缓存相比较于直接删除缓存要比较复杂。
2.先删除缓存,再更新数据库
这种策略可能导致数据不一致的问题。线程 1 写数据删除缓存;这时候有线程 2 查询该缓存,发现不存在,则去访问数据库,得到旧值放入缓存;线程 1 更新数据库。这时候就出现了数据不一致的问题。 如果缓存没有过期时间,这个脏数据一直存在。
解决方案:在写数据库成功之后, 再次淘汰缓存一次。
3.先更新数据库,再删除缓存
可能会造成比较短暂的数据不一致。在更新完成数据库, 还没有删除缓存的时刻,如果有缓存数据访问, 就会造成数据不一致的情形。 但这种如果数据同步机制比较科学,一般都会比较快, 不一致的影响比较小。
4. 主从同步
1. 全量同步
全量同步一般发生在 Slave 机器初始化阶段, 这时候 Slave 需要将 Master 上的所有数据都进行同步复制。
- 从库连接到主库,并发送一条SYNC命令;
- 主库接收到SYNC命令后,开始执行BGSAVE命令生成RDB快照文件,并使用缓冲区记录此后执行的所有写命令;
- 主库执行完BGSAVE之后,将快照文件发送到所有从库,在此期间,仍继续将所有写命令记录到缓冲区;
- 从库在接收到快照文件后,丢弃所有旧数据,载入快照文件中的新数据;
- 主库继续向从库发送缓冲区中的写命令;
- 从库将快照文件中的数据载入完毕后,继续接收主库发送的缓冲区中的写命令,并执行这些写命令以更新数据。
完成上面的步骤之后,从库可以开始接收来自用户的读数据请求。
2. 增量同步
增量复制是指,在slave初始化完成后的工作阶段,主库将新发生的写命令同步到从库的过程。主库每执行一条写命令,都会向从库发送相同的写命令,从库会执行这些写命令。
总结:主库和从库初次建立连接时,进行全量复制;全量复制结束后,进行增量复制。但是当增量复制不成功时,需要发起全量复制。
5. 集群架构模式
- Redis 单节点单机器部署
- Redis 主从节点部署
- Redis Sentinel(哨兵)模式部署
- Redis 集群模式
主从模式
- 优点:保证高可用,各个节点自动故障转移
- 缺点:主从模式,依旧存在主节点单点风险,主从切换有丢失数据的问题
哨兵模式
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵有两个作用
-
监控:通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
-
故障迁移:当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
哨兵模式故障迁移过程
- 假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。
- 当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。
- 切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
哨兵模式优缺点
- 优点:保证高可用,各个节点自动故障转移
- 缺点:主从模式,依旧存在主节点单点风险,主从切换有丢失数据的问题
集群模式
Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容。
每个集群中至少需要三个主数据库才能正常运行。主从不用配置,集群会自己选。
集群的工作方式
在 Redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的的取值范围是:0-16383。还有一个就是cluster,可以理解为是一个集群管理的插件。当我们的存取的 Key到达的时候,Redis 会根据 crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
为了保证高可用,redis-cluster集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了。
######集群的特点
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 节点的fail是通过集群中超过半数的节点检测失效时才生效。
- 客户端与 Redis 节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
6. 如何优化 Redis 服务的性能?
- Master 节点禁止持久化工作
- 持久化策略要有正确的选择,关键数据可以采用 slave 节点 AOF 备份
- 主从节点部署在同一个局域网内,保证复制速度与稳定性
- 设置或者增加从库需要考虑主库现有的压力
- 从复制一定要单向结构,避免使用图状结构
7. 持久化
Redis 持久化机制
- RDB 持久化:原理是将 Reids 在内存中的数据库记录定时 dump 到磁盘上的 RDB 持久化。
- AOF(append only file)持久化:原理是将 Redis 的操作日志以追加的方式写入文件。
RDB 持久化机制的优缺点
优点
RDB 是紧凑的二进制文件,比较适合备份,全量复制等场景
RDB 恢复数据远快于 AOF
缺点
RDB 无法实现实时或者秒级持久化;
最后一次持久化后的数据可能丢失;
fork的时候,内存中的数据被fork一份,导致两倍的内存消耗;
AOF 持久化机制的优缺点
优点
可以更好地保护数据不丢失;
appen-only 模式写入性能比较高;
适合做灾难性的误删除紧急恢复。
缺点:
对于同一份文件,AOF 文件要比 RDB 快照大;
AOF 开启后,会对写的 QPS 有所影响,相对于 RDB 来说 写 QPS 要下降;
数据库恢复比较慢, 不合适做冷备。
启动会首先找aof,如果aof被破坏了,redis服务启不起来。Redis-aof-check --fix可以自动修复aof文件。
8. 淘汰策略
- noeviction:当内存使用达到上限,所有需要申请内存的命令都会异常报错。
- allkeys-lru:先试图移除一部分最近未使用的 key。循环读写所有的key, 或者各个key的访问频率差不多。
- volatile-lru:淘汰一部分最近使用较少的(LRC),但只限于过期设置键。
- allkeys-random:随机淘汰某一个键。循环读写所有的key, 或者各个key的访问频率差不多。
- volatile-random:淘汰任意键,但只限于有过期时间设置的键。
- volatile-ttl:优先移除具有更早失效时间的 key。
9. 删除策略
定时删除策略
在设置 key 的过期时间的同时,为该 key 创建一个定时器,让定时器在 key 的过期时间来临时,对 key 进行删除。
优点:保证内存尽快释放。
缺点:若 key 过多,删除这些 key 会占用很多 CPU 时间, 而且每个 key 创建一个定时器,性能影响严重。
惰性删除策略
key 过期的时候不删除,每次从数据库获取 key 的时候去检查是否过期,若过期,则删除,返回 null。
优点:CPU 时间占用比较少。
缺点:若 key 很长时间没有被获取, 将不会被删除,可能造成内存泄露。
定期删除策略
每隔一段时间执行一次删除(在 redis.conf 配置文件设置 hz,1s 刷新的频率)过期 key 操作。
优点:可以控制删除操作的时长和频率,来减少 CPU 时间占用,可以避免惰性删除时候内存泄漏的问题。
缺点:对内存友好方面,不如定时策略;对 CPU 友好方面,不如惰性策略
Redis 一般采用:惰性策略 + 定期策略两个相结合。
10. Bigkey
怎么发现 bigkey?
- redis-cli-bigkeys 命令可以统计 bigkey 的分布。
- 生产环境下执行 debug object key 查看 serializedlength 属性,表示 key 对应的 value 序列化之后的字节数。
bigkey 的影响主要体现在:
- 造成内存空间不平衡:如果 bigkey 存储量比较大,同一个 key 在同一个节点或者服务器中存储,造成一定的影响
- 超时阻塞:由于占用空间比较大,那么操作起来效率肯定比较低,也就表示出现阻塞可能性增加
- 网络阻塞:获取 bigkey 的时候,自然传输的数据量比较大,导致宽带的压力。
11. Redis 的内存上限?
Maxmemory 参数可以限制最大可用内存。
12. 分布式锁
lock
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
unlock
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
- 互斥性:任意时刻,只有一个资源能够获取到锁。
- 容灾性:在未成功释放锁的的情况下,一定时限内能够恢复锁的正常功能。
- 统一性:加锁和解锁保证同一资源来进行操作。
13. bitmap
- BitMap是什么
就是通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身。我们知道8个bit可以组成一个Byte,所以bitmap本身会极大的节省储存空间。
SETBIT key offset value
bitcount
与或非操作: bitop and destkey key1 key2
- 应用场景
- 用户签到: 日期做偏移量
- 统计活跃用户:用户id 做偏移量
- 统计用户在线状态:用户id做偏移量