如何保证redis与数据库一致性?
根据业务需求,如果需要保证数据则选择强一致性方法,保证响应速度则选择最终一致性方法。
1.强一致性
更新数据之前增加缓存锁,然后更新数据库,接着更新缓存,都更新成功后释放锁。
缺点:高并发的情况下,会存在大量请求等待锁释放而阻塞。
2.最终一致性
方法1:延时双删
先删除缓存,再更新数据库,休眠1秒再删除缓存。
方法2:利用消息队列
先删除缓存,再更新数据库,触发异步写入串行化mq,接收mq消息删除缓存。
3.利用第三方工具
canal [kəˈnæl]
应用于mysql与其他软件的数据同步的阿里开源项目,基于Java实现,通过配置就能实现数据同步。
主要是通过监听binlog日志进行同步数据。
Redis模式
1.主从同步架构模式
为什么采用主从模式?
性能问题:对于大量操作,一台Redis服务不够用。
安全和稳定问题:当主服务器宕机时候,可以用从服务器代替原来的主服务器,保证系统可以正常工作。
设计思路是什么?
主从模式的设计思路主要是:
- 在多台服务器中,只有一个主服务器,主服务器只负责写入数据,不负责外部程序读取。
- 在多台服务器中,从服务器不写入数据,只负责同步主服务器的数据,并让外部程序读取数据。
- 主服务器在写入数据后,即刻将写入数据的命令发送给从服务器,从而使得主从数据同步。
- 当某个从服务器宕机的时候,整个系统将不受影响;当主服务器宕机的时候,可以让从服务器切换成主服务器。
2.哨兵架构模式
为什么采用哨兵模式?
针对主从切换问题,即主服务器宕机,需要手动把一台从服务器切换为主服务器,这种方法不仅需要人工干预,还会造成一段时间服务不可用。所以就引入了哨兵模式。
哨兵模式:在主从复制的基础上,哨兵引入了主节点的自动故障转移。
哨兵模式的作用
- 监控:哨兵会不断地检查主节点和从节点是否运作正常。
- 自动故障转移:当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个节点升级为主节点,并让其他节点改为复制新的主节点数据。
- 通知:哨兵可以将故障转移的结果发送给客户端。
哨兵模式的结构
哨兵结构由两部分组成,哨兵节点和数据节点
哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点时特殊的redis节点,不存储数据。
数据节点:主节点和从节点都是数据节点。
故障转移机制:
1.由哨兵节点定期监控发现主节点是否出现了故障。
2.当主节点出现故障,哨兵节点会通过Raft算法(选举算法)实现选举机制共同选举一个哨兵节点作为leader,来负责处理主节点的故障转移和通知。所以整个运行哨兵的集群的数量不少于3个节点。
3.由leader哨兵节点执行故障转移,过程如下:
- 将某个从节点升级为新的主节点,让其他从节点指向新的主节点。
- 若原主节点恢复也变成从节点,并指向新的主节点。
- 通知客户端主节点已经更换。
哨兵的启动依赖主从模式,必须把主从模式安装好再去做哨兵模式。
3.集群模式
- 集群即Redis Cluster,是Redis3.0开始引入的分布式存储方案。
- 集群由多个节点(Node)组成,Redis的数据分布再这些节点中。
- 集群中的节点分为主节点和从节点,只有主节点负责读写请求和集群信息的维护,从节点只进行主节点数据和状态信息的复制。
集群的作用:
1.数据分区,数据分区(数据分片)是集群最核心的功能
集群将数据分散到多个节点,一方面突破了Redis单机内存大小的限制,存储容量大大增加,另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。
2.高可用
集群支持主从复制和主节点的自动故障转移,当任一节点发生故障,集群仍然可以对外提供服务。
集群模式的数据分片
Redis引入了哈希槽的概念,总共有16384个哈希槽,每个节点负责一部分哈希槽,例如有3个节点组成集群,节点A包含0到5460哈希槽,节点B包含5461到10922号哈希槽,节点C包含10923到16383号哈希槽。
集群模式的主从复制模型
集群中有A、B、C3个节点,如果节点B宕机了,整个集群就会因缺少5461到10922这个范围的槽而不可以用,所以为每个节点添加一个从节点,就是3个主节点和3个从节点,节点B宕机后,集群选举B的从节点为主节点继续服务,当转换为主节点后的B节点也宕机了,集群将不可用。
Redis事务
什么是Redis的事务?
Redis的事务时一个单独隔离的操作,它会将一系列的指令按需排队并顺序执行,期间不会被其他客户端的指令插队。
Redis事务的指令
multi:开启事务
exec:执行事务
discard:取消事务
通过multi,当前客户端会开启事务,后续的指令都会按序存到队列中。当用户键入exec后,这些指令都会按顺序执行。若在multi后输入指令,再键入discard,则之前输入的指令通通取消执行。
在Java代码中的实现:
Jedis jedis = new Jedis("localhost");
Transaction transaction = jedis.multi();
transaction.set("key1", "value1");
transaction.set("key2", "value2");
transaction.exec();
事务的错误和回滚
组队时错误
如果我们在开启事务后,输入了错误的命令,那么Redis会让之前输入的指令全部失效。
执行时错误
如果是在执行时遇到了错误,因为Redis无回滚机制,所以Redis会继续往下执行,直到命令结束。
回滚
在组队错误发生时,Redis是有回滚操作的。但是在执行时,因为情况未知,所以Redis是不支持回滚的。
Redis事务的其他实现方式
lua脚本可以保证Redis指令一次性按顺序执行完成,并且不会被其他客户端打断,但是这种方式无法实现事务回滚。
Redis实现分布式锁
分布式锁:分布式锁指的是,所有服务中的所有线程都去获取同一把锁,但只有一个线程可以成功的获得锁,其他没有获得锁的线程必须全部等待,直到持有锁的线程释放锁。
1)基本方案:Redis提供了setXX指令来实现分布式锁。
设置分布式锁后,能保证并发安全,但是如果执行过程中出现异常,程序就直接抛出异常退出,即使将锁放在finally中释放,但是假如执行到中途系统宕机,锁还是没有成功的释放掉,依然会死锁。
2)方案改进:给锁设置一个超时时间,到时自动释放锁。
但是如果有多个线程,线程1在锁到期时间后还没执行完,此时线程2可以获得锁执行代码,等线程1执行完之后删除锁,那么删除的就是线程2的锁。
3)方案改进:让线程只能删除自己的锁,即给每个线程上的锁添加唯一标识,删除锁时判断这个标识。
注意在对redis操作时也要注意按原子性进行操作,防止极端情况的发生。可用Redis事务或者lua脚本实现。
4)使用框架,如果Redission,Redission是redis官网推荐实现分布式锁的一个第三方类库。
Redission执行流程如下:线程加锁成功就会启动一个watch dog,他是一个后台线程,每隔10秒检查一下,如果线程还持有锁,就会不断延长锁key的生存时间,因此解决了锁过期释放问题。
什么是缓存雪崩,怎么解决?
缓存雪崩:在项目运行中,如果缓存突然宕机,那么大量请求可能直接打在数据库上,导致数据库宕机,进而整个系统宕机。
解决方案:
- Redis做高可用,如用主从+哨兵,集群模式,避免全局崩溃。
- 使用熔断器(例如hystrix)进行限流和降级,避免数据库宕机。
- Redis做持久化,保证数据不丢失。
什么是缓存穿透,怎么解决?
缓存穿透:大量查询的数据在缓存中和数据库中都没有,请求直接打在数据库上导致数据库宕机。
解决方案:
数据如果在数据库中没有查到,可以设置固定值到缓存中并设置一个过期时间。
什么是缓存击穿,怎么解决?
缓存击穿:大量请求一个刚刚失效的key,导致请求直接打在数据库上导致数据库宕机。
分情况解决:
缓存数据基本不更新:可以设置该key永不过期。
缓存数据更新不频繁:可以采用Redis、Zookeeper等分布式中间件的分布式互斥锁,或者本地互斥锁保证少量请求进入数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
缓存数据更新频繁:利用定时器在缓存过期前更新缓存或者延后缓存时间。