Redis
Redis数据类型
- String:key-value
- list:有序列表
- set:无序列表并且去重
- zset:有序列表去重
- hash:哈希表接口
Redis键过期时间
Redis是存储在内存中的,我们内存空间是有限的,所以我们需要定时的去清理Redis数据,这个时候就用到了Redis过期时间清除
过期策略
根据上面所讲的我们知道当redis过期的是会删除,但是什么时候删除呢?redis有三种过期策略
- 定时删除:到时间就把所有过期键进行删除
- 惰性删除:每次从键空间取键的时候都会判断一下该键是否过期,如果过期则删除
- 定期删除:每隔一段时间就去删除过期键
Redis默认是采用惰性删除和定期删除两种策略,所以当redis键过期的时候不会立刻去删除。
内存淘汰策略
刚刚我们说redis使用惰性删除和定期删除,所以当使用惰性删除的时候,其实我们有很多过期数据是没有被删除的,停留在Redis内存中,这样也会导致Redis内存耗尽,所以我们制定了内存淘汰策略,但是redis内存不够的时候触发内存淘汰策略,总共有6种内存淘汰策略
- lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
- ttl:从已设置过期时间的数据集中挑选将要过期的数据进行淘汰
- random:从已设置过期时间的数据集中随机挑选数据进行淘汰
- allkeys-lru:从所有数据集中挑选最近最少使用的数据淘汰,当为了提供缓冲命中率可以使用、
- allkeys-random:从所有数据集中挑选任意数据
- noeviction:禁止驱逐数据
Redis持久化
由于Redis是基于内存的,所以为了保证数据不丢失,Redis的数据必须保留在硬盘上,Redis保留数据在硬盘上有两种方式:1AOF:当Redis执行写命令的时候会把数据写到AOF文件中,2 RDB:基于快照,将某一时刻的数据快照保存到RDB文件中
- RDB
RDB文件触发保存有两种方式,1 SAVE命令,这个命令会阻塞Redis服务进程,2BGSAVe命令,这个命令不会阻塞,是因为他创建子进程去创建RDB文件,Redis服务器可以继续接受请求。
- AOF
aof是通过保存写命令来记录到数据库中。1 首先命令会写入aof_buf缓冲区中 2 缓冲区中的文件写入到AOF文件中3文件同步内存缓冲区数据写入到硬盘
aof还有一个重要概念是AOF重写,指的是多条指令可以合并成一条指令,这样还节约了空间,aof文件体积也变小了
RDB和AOF优缺点
RDB载入数据非常快,到时容易丢失数据
AOF丢失数据非常少,恢复数据比较慢,因为文件体积比较大。
为什么Redis是单线程却这么快
- 利用IO多路复用优点:操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功,多路指的是多个socket,复用指的是一个线程
- 内存操作
- 避免多线程频繁的上下文切换
Redis主从架构
主从架构的优点
- 主服务器负责接收写请求
- 从服务器负责接收读请求
- 提高并发量
- 高可用,主挂了还有从接收请求。
主从如何保证数据一致
既然设置了主从,那么一定要保证数据的一致性,避免出现脏数据,在Redis中可以通过命令SALVEOF命令,复制有两部操作,1同步,2命令传播
主服务挂了
主服务器是接收写请求的,Redis提供了哨兵机制,如果主服务器挂了则把从服务器升为主服务器
哨兵工作
- 检测服务器状况
- redis有故障发送给管理员
- 主服务器挂了,则从服务器升级为主
- 提供当前主服务器配置信息
如何判断主服务挂了
- 1 主观下线:哨兵会每秒主动给各个服务发送ping命令,如果ping命令判断是否下线
- 2 客观下线:如果其中一个哨兵发现主服务挂了,则会通知其他哨兵,如果一半以上的哨兵都ping不通则进行故障转移操作。
Redis缓存雪崩
- 1 加入所有缓存都过期失效了
- 2 加入缓存挂掉了
以上两种情况可能导致所有所有请求都跑到我们MySQL数据中执行,如果大量的请求打数据库压力会非常大,可能造成极端情况,数据库被打垮,导致真个系统崩溃
解决方法:
- 1 Redis使用主从+哨兵模式提高可用性,高可用
- 2 本地缓存+限流方式实现
- 3 恢复数据,利用redis持久化AOF文件,恢复缓存数据
缓存穿透
缓存穿透指的是缓存中没有数据,数据库中也没有数据,所有每次查询都查询的是不存在的数据,缓存失去意义。
解决方法
- 1 使用过滤器把请求不合法的数据阻挡在外面
- 2 如果数据库为null则存储到redis中,这样避免每次都走数据库
缓存与数据库不一致
读数据
在读数据的时候,首先会判断缓存中是否存在,如果存在则直接使用,如果不存在则去数据库中读取,然后存储到Redis缓存中
修改数据流程
- 先删除缓存再更新数据库
- 线程A删除缓存
- 线程B发现缓存为空,数据库旧值
- 线程A更新数据
- 线程B把旧值更新到redis
这个时候缓存与数据库就不一致了,通过分布式锁来解决这个问题。
- 先更新库在删除缓存
- 缓存失效
- 线程AB读取缓存
- 先A把数据写入数据,在删除Redis缓存
- 先B把旧数据写入缓存
这种几率一般不高,缓存要同时失效
- mysql的binlog日志更新到redis中
推荐使用最后一种方法。
Redis有哪些适合的场景
- session共享
- 队列
- 排行榜
- 发布/订阅
Redis锁
- setnx
- redlock
- redisson
setnx
setnx表示当获取key的时候不存在则赋值,返回1,存在则返回0
- 进程Asetnx发现没有数据则获取锁
- 线程Bsetnx发现有数据则阻塞等待
- 进程A执行完,则del锁
- 进程B获取锁执行
以上是正常情况,下面描述的是不正常的情况
- 问题1
假如进程A获取锁不释放则会一直持有锁,导致进程B获取不到锁,所以需要设置超时时间,但超时超时时间则释放错,避免死锁现象
- 问题2
假如进程A设置了超时时间并且执行时间超过超时时间,这样就会导致,当进程A超过超时超时间,另一个进程会获取锁,等线程A执行完之后执行finally删除锁的时候,删除的是另一个进程,另一个进程过来则获取锁
Redis有一个GetSet 命令,表示赋值之后,把旧值返回。
- A获取锁,之后超时
- BC读取发现A超时,B检测到A超时,比较当前时间和redis中的时间发现超时
- B执行GetSet命令设置时间戳,通过比较旧值是否小于当前时间,判断进程是否获取锁,这时候B已经获取锁
- C执行GetSet命令发现时间大于当前时间,则等待。
所以在释放锁的时候,需要判断锁是否过期,如果过期是否会删除其他线程的锁。