Redis
Redis是一个数据库,不过与传统数据库不同的是redis的数据存储在内存中,并且是键值对数据库,因此读写速度非常快,应用于缓存方向,但是redis不支持事务回滚
Redis数据类型
字符串:String
哈希:Hash
列表:List
集合:Set
有序集合:ZSet
对于服务端程序来说,线程切换和锁通常是性能杀手,单线程避免了线程切换和竞争所带来的消耗
Redis大部分操作在内存上完成,所以性能很高
Redis采用了IO多路复用机制,使其在网络IO操作中能并发处理大量客户端请求,实现高吞吐率
保证双写一致性(缓存和持久数据库的数据一致)
1.先删除缓存在更新数据库
-
在进行更新操作时,先删除缓存,然后再更新数据库,后续请求再次读取时,会从数据库读取完后再将新数据库更新到缓存。
-
存在的问题:删除缓存数据之后,更新数据库完成之前,如果有新的请求,就会从数据库读取旧数据重新写到缓存中,再次造成不一致,并且之后读的都是旧数据。
先更新数据库再删除缓存
进行更新操作时,先更新MySQL,成功之后,删除缓存,后续读取请求时再将新数据回写缓存。
存在的问题:更新MySQL和删除缓存的这段时间,请求读取的还是缓存的旧数据,不过等数据库更新完成之后,就会恢复一会,影响比较小
异步更新缓存
数据库的更新操作完成之后不直接操作缓存,而是把操作指令封装成消息发送给消息队列,由redis自己去消费更新数据,消息队列可以保证数据操作顺序一致性,以确保缓存系统的数据正常
Redis使用场景
缓存 -> 穿透、击穿、雪崩、双写一致性、持久化、数据过期策略,数据淘汰策略
分布式锁 -> setnx、redisson
缓存穿透
缓存穿透是传递一个不存在的数据,redis中查不到,mysql中也查不到,不会写回redis所有请求都打进数据库导致数据库压力过大宕机
解决方案: 缓存空数据,但是会数据不一致
解决方案:布隆过滤器
缓存击穿
缓存击穿是给一个key设置了过期时间,当key过期的时候,恰好由大量请求发送过来,这些并发请求会瞬间把数据库压垮
解决方案:使用互斥锁,强一致性,性能差
解决方案: 逻辑过期策略,高可用,性能好,不能保证数据绝对一致
互斥锁解决缓存击穿
当缓存失效时,不会立即去查找数据库,先使用redis的setnx去设置一个互斥锁,操作成功返回时,在进行load db的操作并回写缓存
设置当前key逻辑过期策略
在设置key的时候设置一个时间字段表示过期时间,一起存入缓存,不设置key的过期时间
查询的时候,从redis取出数据判断时间是否过期
如果过期,就开一个线程进行数据同步,当前返回过期数据,当然这个数据不是最新的
什么是缓存雪崩,如何解决?
缓存雪崩是指很多缓存采用同样的过期时间,在同一时间大量缓存失效,请求打回数据库,导致数据库压力过大,造成雪崩,雪崩时多个key同时过期,击穿时一个key过期但是有很多并发请求
解决方案:
- 不同key设置不同的过期时间,过期时间根据key的实际业务用途来设置
- 利用redis基础群提高服务的可用性,哨兵、集群
- 给缓存业务添加降级限流策略 nginx或者gateway
- 给业务添加多级缓存 Guava或者Caffeine
redis作为缓存,聊一聊双写一致性问题
当要求数据库和redis保持高度一致时,可以采用读写锁保证强一致性,使用redisson实现读写锁,在读的时候加一把共享锁,保证读读不互斥,读写互斥。当更新数据库时,添加一个排他锁,读写和读读都互斥,这样就能保证在写数据的同时不会让其他线程读数据,避免了脏数据。(读和写方法必须使用同一把锁)
当数据同步可以由一定延迟时,可以采用canal组件实现数据同步;不需要更改业务代码,部署一个canal服务。当数据库更新后,canal会读取binlog数据,然后通过canal的客户端获取到数据,更新缓存即可。
Redis持久化的问题
redis持久化有两种方式RDB和AOF
RDB叫redis快照,会把redis内存存储的数据写到磁盘上,当redis宕机的时候,方便从RDB快照文件中恢复数据。
触发RDB快照的方式有两种
- 手动触发比如执行save(有主进程执行RDB,会造成阻塞)或者bgsave命令(开启一个紫禁城执行RDB,不影响主进程)
- 自动触发,可以在配置文件中修改设置,save 900 1(900秒内,如果至少有一个key被修改,则执行bgsave)
AOF追加文件
当redis操作写命令时,会把命令存储到文件中,当redis宕机时,会读取这个文件再次执行这个命令,来恢复数据。
RDB因为是一个二进制文件,体积小会快很多,但是有可能会丢失数据,通常在项目中也会使用AOF恢复数据,虽然AOF恢复的慢,但是它丢失数据的风险要小很多,可以将RDB和AOF配合使用
redis的数据过期策略
redis有两种数据过期策略:
- 一种是惰性删除:设置一个key的过期时间后,不用再去管他,当我们需要这个key的时候,会自动检查是否过期,如果过期就删除
- 第二种是定期删除:每隔一段时间,对一些key进行检查,删除里面过期的key
定期删除有两种方式:
- SLOW模式:定时任务,执行频率默认10hz,每次不超过25ms,通过配置文件redis.conf选项来调整参数
- FAST模式:执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms(不能占用主进程过多时间)
- redis过期删除策略一般采用惰性删除和定期删除两种策略同时使用
redis如何实现分布式锁
-
在redis中提供了一个setnx(set if not
exists)命令,用了这个命令之后只能有一个客户端对key设置值,在key没过期或者没有被删除时,其他客户端不能设置这个key -
setnx命令:
获取锁:set lock value nx ex 10(nx是互斥,ex是设置超时时间)
释放锁:del key
redis实现分布式锁如何控制锁的有效时长
要使用setnx控制比较麻烦,许哟啊考虑业务需要的时间,如果网络波动业务没走完,锁被释放就会完蛋,可以考虑用redisson实现
redission中需要手动去改加锁,并且可以控制所得失效时间和等待时间,其中有一个WatchDog看门狗的机制,每隔一段时间就回去查看业务是否还持有这把锁,如果持有就增加锁时间,直到业务执行完毕。
redisson实现分布式锁时可重入吗
可以重入,为了避免死锁的发生,重入就是在内部判断是否当前这个线程持有锁,如果当前线程持有锁,就会计数,如果锁释放就会计数减1.
在存储时用hash结构,大key可以按照自己的业务定制,小key时当前线程唯一标志,value时当前线程重入的次数。
数据淘汰策略
redis数据淘汰策略默认是noeviction,不删除任何数据,内存不足直接报错。策略可以在配置文件中设置,有两个重要的概念LRU和LFU。
LRU最近最少使用,当前时间减去最后一次访问时间,值越大淘汰优先级越高。
LFU最少频率使用,key的访问频率越小淘汰优先级越高。
数据库中有1000万条数据,redis只能缓存一小部分,如何保证都是热点数据,可以使用allkeys-lru淘汰策略,访问频率低的淘汰留下来的就是访问频率高的了