redis是一个内存数据库,使用key-value形式在内存中管理数据。
一、redis使用场景
1、热数据存储。对于需要频繁读写的数据,可以放到redis中,不用频繁的请求数据库。再设置策略持久化到数据库中。
2、token管理。用户登录成功后,生成token,存储到redis中,并设置过期时间,可以模拟session,用于做认证中心。
3、缓解数据库压力。大量并发请求访问数据库会造成数据库崩溃,可以使用redis做缓冲,先访问redis。
二、redis为啥这么块
主要有3个原因:
- 纯内存操作。所有的操作都是在内存中进行。
- 单线程操作。单线程操作避免了频繁的上下文切换。
- 使用非阻塞I/O多路复用机制。客户端会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将socket置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
三、redis数据结构
- string 就是常规的set/get操作
- list 队列
- set 不重复的集合
- sorted set 有序的不重复集合
- hash 结构化的对象。
四、redis持久化
在因为故障等原因导致服务停止时,内存的数据就会丢失。因此,需要将内存的数据持久化到硬盘上。
redis持久化有两种方式:RDB、AOF
1、RDB方式持久化
是通过快照来完成的。当满足一定条件时,redis会将内存中的数据进行快照并存储到硬盘上。
进行快照的条件在配置文件中配置,由两个参数构成:时间、改动键的个数。当在指定时间内,被改动键的个数大于指定个数时,则触发RDB持久化。
配置文件中默认有3个条件,这些条件是或的关系。
1 save 900 1 # 15分钟内至少有一个键被更改 2 save 300 10 # 5分钟内至少有10个键被更改 3 save 60 10000 # 1分钟内至少有10000个键被更改
默认的rdb文件在当前的目录,文件名是dump.rdb。
可以对Redis执行手动快照操作,命令是:save、bgsave。两者有区别:save是由主进程进行快照,会阻塞其他请求。bgsave通过fork子进程进行快照。
注意:由于Redis使用fork来复制一份当前进程,那么子进程就会占有和主进程一样的内存资源,比如说主进程8G内存,那么在备份的时候,必须保证有16G的内存,要不然会启用虚拟内存,性能非常的差。
2、AOF方式持久化
是将发送到Redis服务端的每一条命令都记录下来,并且保存在硬盘的AOF文件中。
可以通过参数appendonly来设置是否启用AOF。AOF文件的位置和RDB的位置相同,都是通过dir参数设置,默认的文件名是appendonly.aof,可以通过appendfilename参数修改。
五、redis过期删除策略以及内存淘汰机制
过期删除策略有:定时删除、惰性删除。
定时删除:有一个定时器在定时随机扫描一些key,key过期了则删除。这种策略因为定时器一直在扫描,很消耗系统资源。同时有一些key到了过期时间也没有删除。
惰性删除:在使用key的时候,再检查是否过期,过期了就删除。
定时删除和惰性删除可以配合使用。
有个问题是:如果有些key没有被定时删除任务扫描到,同时又长时间没有被调用到,那么这些key是不会被删除的。长期这样会占用大量内存。
这就需要使用到内存淘汰机制。
内存淘汰机制在配置文件中开启和配置:
在redis.conf中有一行配置
# maxmemory-policy volatile-lru
有6种内存淘汰机制,一般使用的是:allkeys-lru。当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
六、问题
1、redis和数据库一致性问题
数据库和缓存双写,必然存在数据不一致问题,对数据有强一致性要求的不能将数据放到缓存中。
2、缓存穿透。
大量的请求没有请求缓存,而是都请求数据库了。一般发生在黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
解决方案:
(一)利用互斥锁。当请求从缓存中得不到数据时,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试,这样能避免大量的请求数据库。
(二)采用异步更新策略。无论请求是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
(三)提供一个能迅速判断请求是否有效的拦截机制。比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出请求所携带的Key是否合法有效。如果不合法,则直接返回。
3、缓存雪崩
缓存在同一时间大面积失效,这时候又有大量的请求,则这些请求会请求到数据库,导致数据库异常。
解决方案:
(一)给缓存的失效时间加上一个随机值,避免集体失效。
(二)使用互斥锁,避免大量数据库请求。
(三)双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。然后细分以下几个小点:
- 从缓存A读数据库,有则直接返回。
- A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
- 更新线程同时更新缓存A和缓存B。
4、redis并发竞争key
多个子系统去set一个value。
解决方案:
1、设置分布式锁,抢到锁的就去set。
2、设置队列,根据实际需求将set操作变成串行。