Redis(Remote Dictionary Server) 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Reids 使用场景:
热点数据的缓存限时业务的运用,例:手机验证码、限时优惠活动信息计数器相关问题,例:限制接口请求次数,短信发送数量等排行榜相关问题分布式锁消息队列Redis的操作都是在内存中进行的,由于内存的速度快,CPU也并不是其性能瓶颈,所以使用单线程以减少设计的复杂度。
注: Redis的6.0版本引入了多线程以解决读写网络的read/write操作占用了太多的CPU时间,减少多主线程的阻塞时间,多线程只是处理读写网络的read/write操作,执行命令的依然是单线程。
缓存穿透:频繁查询一个Redis和数据库都不存在的数据,很有可能是恶意攻击,会对数据库产生很大的压力。
解决方案:
接口增加用户权限校验,id做校验,例如 id<0 的直接拦截。在数据库中取不到的数据存一个空值到redis,设置缓存有效时间较短,例如30秒后过期。使用布隆过滤器,布隆过滤器可以做到的效果:一个一定不存在的数据会被拦截掉,可能存在的数据才会去查数据库。因为这个特性,只要设置合理的参数就可以极大的缓解缓存穿透问题。缓存击穿:一条热点数据的缓存过期了,导致一瞬间大量请求打到了数据库。
解决方案:
热点数据的缓存永不过期(这个在实际场景中很少遇到,很多场景热点数据变化很快,而且不容易确定)
加互斥锁,且不能使用synchronized,要做到获取不到锁不自动等待锁,所以可以用 ReentrantLock 的tryLock 加锁,获取不到锁就返回失败,自己控制等待一段时间再获取数据。
注意: 此方式不适合分布式系统,分布式系统需要将 ReentrantLock 换成分布式锁。
public String getData(String key) throws InterruptedException { //从缓存获取数据 String result = getDataByRedis(key); //从缓存中取不到数据 if (result == null){ //获取锁,获取成功去数据据取数据 if (reentrantLock.tryLock()){ //从数据库取数据 result = getDataByMySQL(key); if (result != null){ //更新缓存数据 setDateToRedis(key, result); } reentrantLock.unlock(); }else { //未获取到锁,等待100毫秒再重新获取数据 Thread.sleep(100); result = getData(key); } } return result; }缓存雪崩:一批数据同时过期了,导致大量的请求打到了数据库。
解决方案:
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。如果遇到必须要同时过期的情况,例如每日固定时间更新排行榜数据,无法分散过期时间,这时候可以从业务层考虑,service层查询数据的时候设置个随机延迟时间,这样一部分请求访问过数据库后将数据存入缓存,另一部分请求再访问就能命中缓存了。string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(sorted set:有序集合)。
Jedis、Redisson、Lettuce等。Redis官方推荐使用Redisson。
Jedis 是老牌的 Redis 实现客户端,提供了较全的 Redis 命令支持。
Redisson 实现了分布式和可扩展的Java数据结构,和 Jedis 相比不支持 Redis 的一些特性,促使使用者对 Redis 的关注分离,从而让使用者更加关注于业务本身。
Redis 有两种方式对数据进行持久化。
RDB(Redis Database):指定时间间隔对数据进行快照保存。AOF(Append Only File):将对数据的修改操作追加到日志文件中,重启 Redis 后通过解析日志恢复数据。Redis中AOF有三种同步方式,分别是每秒同步,每次操作同步,不同步。Redis 分布式锁的实现方式其实就是通过一个原子性操作(setnx命令)往 Redis 存一条数据(占一个“坑“),且此数据不存在才能存入成功(抢到了”坑“位),如果数据已经存在就不能存入此数据(没抢到”坑“位)。
只有占”坑“成功了才说明拿到了锁,可以继续执行后续操作,未占”坑“成功的则放弃或稍后重试。
拿到锁的程序在执行完操作后要及时释放锁(让出”坑“位)。
这里有一个问题,如果占”坑“成功的程序挂掉了怎么办?不主动释放锁会导致锁一直存在,后续程序一直等待获取锁,所以一般的解决方式是给锁加一个过期时间,防止因意外情况导致锁无法释放。
这里会出现新的问题,如果在锁的有效期内程序未能执行完操作怎么办?此时就需要一个程序监控锁的状态,如果锁快到期了任务还没执行完,就要重置锁的过期时间(给锁续期)。
Master 写内存快照,此操作会阻塞主线程工作,当快照比较大时对性能的影响会很大,有可能会导致短暂性的暂停服务。
Master 启用AOF持久化,当AOF文件过大时,影响Redis 的重启速度。
Master 主从复制的问题,Redis的主从复制,第一次Slave向Master请求同步时,Master会先建立内存快照,然后全量传输给Slave,然后再将缓存的命令发送给Slave,初次同步完成以后,后面的同步就是将变量的快照依次发送给Slave。
解决方法:
Master不要启用持久化,选一台Slave启用持久化。Master和Slave最好在同一个局域网内。避免在压力较大的Master上增加Slave。不使用图状结构使用链式结构,即:Master -> Slave1 -> Slave2 -> Slave3…