Rediscover数据类型
- String(key和value都是字符串)
- List(key是字符串value是List)
- Set(无序集合,元素不可以重复)
- zSet(可排序的set集合,元素不可重复)
- Hash(key是字符串,value是map集合)
Redis事务
redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序执行: (1)事务在执行过程中,不会被其他客户端发送的命令请求打断 (2)redis事务的主要作用是:串联多个命令,防止别的命令插队 基本操作: 从输入Multi 命令开始,输入的命令都会一次进入命令队列中,但不会执行,直到输入Exec 后,Redis会将之前的命令队列中的命令依次执行 组队的过程中可以通过命令:discard 来放弃组队
Redis持久化机制
官方提供了两种持久化机制:
- 快照(snapshot):保存某一时刻的数据状态
- AOF(append only file):只追加日志文件
快照持久化
快照方式是将某一时刻的所有数据都写入到磁盘中,是redis默认开启的数据持久化机制。以一个.rdb文件形式进行保存,因此也称为RDB方式
客户端实现
- BGSAVE(后台保存,异步处理,推荐使用)
- SAVE(同步处理)
BGSAVE(异步)
使用BGSAVE命令创建快照,当客户端接到命令时,redis会调用fork指令来创建一个子进程来负责将快照写入磁盘,而父进程将继续处理命令请求。 刚开始的时候,父子进程使用相同的共享内存,直到父进程对内存进行写之后,父子进程的内存共享就会结束,分别分配一定的内存大小进行各自的处理
SAVE(同步)
客户端在接收到SAVE命令之后,redis在快照创建完成之前不再处理其他请求 其他触发快照生成的情况有: 客户端接收到shutdown命令
配置文件实现
满足配置项自动触发(在redis.conf文件中进行配置save命令触发条件)
AOF持久化
在redis的配置中AOF持久化为: appendonly yes 开启持久化 appendfilename “appendonly.aof” 指定生成的文件名
日志追加同步频率
always 【谨慎使用】
- 说明:每个redis写命令都要同步写入硬盘,严重降低redis速度
- 解释:如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少,需要大量命令写入到磁盘,redis处理命令的速度会受到磁盘性能的限制
- 注意:机械硬盘在这种频率下200个左右命令处理/s,固态硬盘(SSD)几百万个命令/s
- 警告:谨慎使用always,可能会引发严重的写入放大问题,导致将固态硬盘寿命降低
everysec【推荐】
- 每秒执行一次同步显式的将多个写命令同步到磁盘
- 解释:为了兼顾数据安全和写入性能,推荐使用everysec方式,每次一秒的频率同步AOF,
- 优点:可以保证即使系统崩溃,最多丢失一秒之内产生的数据
no【不推荐】
- 说明:由操作系统决定何时同步
- 解释:完全由操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据;另外如果硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘的数据填满时,redis就会处于阻塞状态,并导致redis处理命令的速度变慢。
AOF文件重写
AOF持久化机制带来的问题
持久化文件会越来越大,为了压缩持久化文件,Redis提供了重写AOF机制(ReWriter)
AOF重写触发方式
客户端命令
BGREWRITEAOF命令,不会阻塞redis请求
服务器配置方式自动触发
AOF重写原理
重写AOF文件的操作,并没有读取旧的AOF文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的AOF文件,最后替换原有的文件,这点与快照类似
重写的流程
- redis调用fork创建一个子进程,子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
- 与此同时父进程继续处理客户端请求,除了把命令写入到原来的AOF文件中,还将收到的写命令缓存起来。这样就能保证如果子进程重写失败并不会出问题
- 当子进程把快照内容写入到临时文件后,子进程发信号通知父进程。然后父进程把缓存中的写命令也写入到临时文件中。
- 现在父进程可以使用临时文件替换原来的AOF文件,并重命名,后面收到的写命令会往新的AOF文件中追加
什么是缓存穿透?
缓存和数据库中都没有的数据,被大量请求,比如订单ID没有-1的数据,但是用户大量请求ID为-1的订单(一般是被攻击) 解决:
- 对空值进行缓存,如果一个查询返回的数据为空,把null进行缓存,设置空结果的过期时间会很短,最长不超过5分钟(如:mybatis的解决方案)
- 设置可访问的名单(白名单):使用bitmaps类型定义一个可访问的白名单,名单ID作为bitmaps的偏移量,每次访问和bitmaps中的ID进行比较,如果bitmaps中不存在则一定不存在,则进行拦截不允许访问
- 采用布隆过滤器(Bloom Filter)1970年布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数(哈希函数)。可以用于检索一个元素是否存在一个集合中。优点是空间效率和查询时间都远远超过一半算法,缺点是有一定的误识别率和删除困难
- 进行实时监控:当发现redis中的命中率开始急速降低,需要排查访问对象和数据,可以设置白名单限制服务
什么是缓存雪崩?
缓存中有大量的数据,在同一时间点或者较短的时间内大面积或全部过期,这个时候请求过来,缓存中没有数据,全部请求都打到数据库的时候,数据库的压力就会突增,扛不住就会宕机 解决:
- 对一些热点数据可以考虑设置为永不过期
- 缓存的过期时间除非很严格,可以考虑设置为波动随机值
- 可以考虑构建多级缓存:nginx缓存+redis缓存+其他缓存
什么是缓存击穿?
对存在的同一条数据进行大量请求,一般是缓存中的某一条数据突然失效,这时候如果大量用户请求该数据,因为缓存中没有,就全部请求打到数据库,压力突增,甚至瞬间打垮 解决:
- 如果是热点数据,可以考虑设置为永不过期
- 如果数据一定会过期,那么就需要在缓存中数据为空的时候,设置一个互斥锁,只让一个请求通过,请求之后将数据存放到缓存中,后续的请求打到缓存中取数据
Redis主从复制
主从复制仅仅是用来解决数据冗余备份,从节点仅仅是用来同步数据 存在问题:无法解决:主节点(master)出现故障时的故障自动转移 。 于是出现了哨兵机制
Redis哨兵机制
(Sentinel)哨兵是Redis高可用的解决方案:由一个或多个Sentinel实例组成。 系统可以监视任意多个主服务器,以及这些服务器属下的所有服务器,在被监视的主服务器进入下线状态时,自动将属下的某个从服务器升级为新的主服务器。简单来说,哨兵机制就是带有故障自动转移的主从架构 存在问题:无法解决:单节点并发压力问题、单节点内存和物理磁盘容量上限问题 于是就又出现了一种:
Redis集群架构
在Redis3.0之后就开始支持集群模式(Cluster ),目前redis集群支持节点的自动发现,支持主从节点的选举和容错,支持在线分片等
Redis集群搭建
- redis集群节点数最好为奇数
- 搭建集群至少需要三个主节点和三个从节点
- 启动节点:将节点以集群方式启动,此时节点是独立的
- 节点握手:将独立的节点连成网络
- 槽指派:将16384个槽位分给主从节点,以达到分片保存数据库键值对的效果
- 主从复制:为从节点指定主节点
CRC16算法特点
- 对集群模式下的所有key进行CRC16算法,计算的结果始终是0-16383之间
- 同一个key经过CRC16计算的结果一致
- 对客户端的不同key进行CRC16计算,计算的结果会出现不同key的结果一致
注:1、所有redis节点间的互联是通过 PING - PONG机制,内部使用二进制协议优化传输速度和宽带;2、节点的fail是通过集群中超过半数的节点检测失效时才生效;3、客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群中所有的节点,连接集群中任何一个节点即可;4、redis集群把所有的物理节点映射到【0-16383】slot上,负责维护node - slot - value
Redis和mysql如何保持数据一致性
在高并发的场景下,大量的请求直接访问mysql很容易造成性能问题 所以一般都会使用redis来做数据的缓存,减轻数据库的访问压力
导致数据不一致的原因:
- 在高并发的业务场景下,数据库大多数情况下都是用户并发访问最薄弱的环节
- 所以,就需要使用redis做一个缓冲操作,让请求先打到redis,而不是直接访问数据库
- 让用户读取缓存数据一般没什么问题,但是一旦涉及到更新,就容易出现mysql数据库和redis缓存中的数据一致性问题
缓存先后删除问题
先删除缓存
如果先删除缓存,但是还没来得及写入数据库,另一个线程就来读取这个时候发现缓存中没有数据,则去数据库中读取,在将数据又写入到缓存中,这时候缓存中的数据就是脏数据了,然后数据更新后发现缓存中的数据和mysql中的数据不一致的问题
后删除缓存
如果先写入数据库,然后再更新缓存,但是一旦更新缓存的线程挂了,这时候就会出现另一个线程直接取旧缓存的数据,也存在数据不一致问题
解决方法:
一、延时双删策略
在写库前后都进行redis.del(key),删除缓存操作,并设定合理的超时时间
public void write(String key, Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
设置缓存过期时间是关键点
- 从理论上来说,设置缓存超时时间是保证最终一致性的解决方案
- 所有写操作以数据库为准,只要达到缓存过期时间,就删除缓存
- 如果后面还有请求的话,就从数据库中读取并重新写入到缓存
缺点: - 在缓存过期时间内发生数据不一致,同时又增加了写请求的耗时
二、异步更新缓存(基于mysql的binlog同步机制)
整体思路
- 涉及到更新的数据操作,利用mysql binlog进行增量订阅消费
- 将消息发送到消息队列
- 通过消息队列消费将增量数据更新到redis上
- 操作情况:
- 读取redis缓存:热数据都在redis上
- 写mysql:增删改都是在mysql进行操作
- 更新redis数据:mysql的操作都记录到binlog,通过消息队列及时更新到redis上
Redis更新过程
数据操作主要分为两种:
- 全量(一次性将所有数据写入到redis)
- 增量(实时更新)
mysql的update、delete、insert操作 读取binlog后分析,利用消息队列,推送更新各台redis的数据 - 这样一旦mysql中数据产生新的写入、更新和删除等操作,就可以把binlog相关的消息推送到redis
- redis在根据binlog中的记录,对redis进行更新(类似mysql的主从备份机制也适用binlog来实现数据的一致性)
总结:
- 在高并发应用场景下,如果是对数据一致性要求高的情况下,首先要定位好导致数据不一致的原因
- 解决方案一般有两种:
- 另外,设置缓存的过期时间是保证数据一致性的关键操作,需要结合业务需求进行合理的设计
|