索引
概念:索引是为了加快数据查询的一种数据结构
索引类型
类型分类 | |
---|---|
存储结构维度划分 | B + Tree索引、B-Tree索引、Hash索引 |
应用层次维度划分 | 主键索引,普通索引、唯一索引、全文索引、空间索引 |
索引键值类型维度划分 | 主键索引、辅助索引(二级索引) |
数据存储和索引键值逻辑关系维度划分 | 聚集索引(聚簇索引)、非聚集索引(非聚簇索引) |
索引组成维度划分 | 组合索引(复合索引)、单一索引 |
特别注意:以上实现步骤考虑到了使用分布式锁需要考虑的互斥性、防死锁、加锁和解锁必须为同一个进程等问题,但是锁的续期无法实现。所以,博主采用 Redisson 实现 Redis 的分布式锁,借助 Redisson 的 WatchDog 机制 能够很好的解决锁续期的问题,同样 Redisson 也是 Redis 官方推荐分布式锁实现方案,实现起来较为简单。
当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,这里我认为以下几点是必须要考虑的。
在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,这是最基本的一点。
在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。
所以分布式非常有必要设置锁的有效时间
,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。
对于访问量大的共享资源,需要考虑减少锁等待的时间,避免导致大量线程阻塞。
所以在锁的设计时,需要考虑两点。
锁的颗粒度要尽量小
。比如你要通过锁来减库存,那这个锁的名称你可以设置成是商品的ID,而不是任取名称。这样这个锁只对当前商品有效,锁的颗粒度小。锁的范围尽量要小
。比如只要锁2行代码就可以解决问题的,那就不要去锁10行代码了。我们知道ReentrantLock是可重入锁,那它的特点就是:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用。
sentinal,哨兵,主要负责:
(1) 集群监控,负责监控redis master和salve进程是否正常工作
(2) 消息通知,如果某个redis有故障,哨兵负责发送消息作为报警通知管理员
(3) 故障转移,如果master挂掉,自动转移到salve 节点上
(4) 配置中心.如果故障转移发生,通知客户端新的master地址
哨兵本身也可以是分布式的,作为一个哨兵集群,这样当部分哨兵节点挂掉,集群依然可以正常工作
数据丢失
主从复制导致丢失
集群脑裂导致丢失
某个master所在机器突然脱离了正常的网络,跟其他slave节点不能连接,但实际上master还运行着,此时哨兵可能会认为master宕机,重新选举了master,此时集群里会存在两个master
在某个slave选举成为master之后,客户端还没来得及切换到新的master上面,继续向旧的master写入数据,此时旧master恢复正常,会作为一个slave挂到新的master上面,自己的数据就会被清空,重新复制数据
解决办法
min-salves-to-write
min-slaves-max-lag
设置要求最少数量的slave,复制和同步最大延迟不能超过多长时间.超过min-slaves-max-lag指定的时间后未收到ack则master拒绝写请求
sdown和odown失败状态
- sdown是主观宕机,即就一个哨兵自己觉得master宕机了.如果一个哨兵ping一个master,超过is-master-after-milliseconds指定的时间后,没有收到回复则主观认为master宕机了
- odowm,客观宕机,如果quorum数量的哨兵都觉得master宕机了.如果一个哨兵指定时间内,收到了quorum指定数量的其他哨兵也认为master宕机了,那就是客观宕机
自动发现
- 哨兵间的相互发现是通过redis的pub/sub机制实现的
每个哨兵都会往 sentinel:hello这个channel发送消息,这个时候其他哨兵可以订阅这个消息感知其他哨兵的存在
- 每隔两秒钟,哨兵就会往自己监控的某个master+slaves对应的 sentinel:hello channel里发送一个消息,内容是自己的host ip runid 和对这个master的监控配置
选举算法
- 如果一个master被认为odown了,而且majority哨兵都允许主备涉黄,那么某个哨兵就会执行主备切换操作
- 首先需要quorum数量的哨兵认为odowm了,然后选举出来一个哨兵来做切换,这个哨兵需得到majority哨兵的授权
如quorum < majority ,比如有5个哨兵,majority就是3,quorum设置为2,那么需要3个哨兵授权才能执行切换
如quorum >= majority, 那么必须quorum数量的哨兵授权才能执行
- 执行切换的那个哨兵会从切换到的新master哪里得到一个 configuration epoch(一个版本包version),进行传播
优点
- 基于主从模式,具有主从的有点
- 故障可以转移,可用性好
缺点
- 配置复杂
- 在线扩容较难
Redis先执行写命令之后才将命令记录到AOF日志中(主进程执行命令写入内存-Redis数据,再写日志到AOF缓冲,缓冲后落盘)
优势:
风险(AOF日志回写硬盘的时机有关):
数据丢失风险
执行写操作命令和记录日志是两个过程,当Redis还没来得及将命令写入到硬盘时,服务器发生宕机,那么数据就会丢失
给下一个命令带来阻塞风险
由于写命令执行成功后才会记录AOF日志,所以不会阻塞当前写操作的命令执行,当会给下一个命令带来阻塞风险。写日志时,服务器的硬盘IO压力太大就会导致写硬盘速度很慢进而阻塞住
并发请求A、B。
A请求先更新数据库的值为1,然后在更新缓存前,请求B将数据库的值更新为2,紧接着也把缓存更新为2,然后A请求更新缓存为1。于是出现数据库缓存不一致,数据库为2,缓存为1
并发请求A、B。
A请求先将缓存数据更新为1,然后在更新数据库前,B请求将数据库的值更新为2,紧接着将数据库更新为2,此时A请求将数据库的数据更新为1。于是出现了数据库缓存数据不一致
并发请求A、B。
请求A要将数据库值更新为21,所以请求A会删除缓存,此时请求B要读取该值,它查询缓存后没有命中,会从数据库中读取原值20,并写入到缓存中,然后请求A继续更改数据值为21。此时依然会出现数据库缓存不一致的现象
不更新缓存,而是删除缓存,然后后读取数据时,发现缓存中没有数据,在从数据库中读取数据更新到缓存中。分为【读策略】和【写策略】
写策略步骤:更新数据库数据,删除缓存中的数据
读策略步骤:如果读取的数据命中缓存,则直接返回数据;如果没有命中则从数据库中读取数据,然后更新缓存并返回
并发请求A、B。
请求A查询某个值(假设缓存中不存在),此时请求A从数据中查询到值为20,在未写入缓存时请求B更新该值,先将数据库更新为21,并删除缓存。这是请求A把从数据库中读取到的数据20写入缓存。最终还是可能出现数据库缓存不一致的现象。
概率较低,缓存速度理论上不会出现该问题,但若删除缓存失败会导致缓存不一致。
不更新缓存,而是删除缓存,然后后读取数据时,发现缓存中没有数据,在从数据库中读取数据更新到缓存中。分为【读策略】和【写策略】
写策略步骤:更新数据库数据,删除缓存中的数据
读策略步骤:如果读取的数据命中缓存,则直接返回数据;如果没有命中则从数据库中读取数据,然后更新缓存并返回
两步操作,没有并发控制,因此遇到并发场景时,由于执行顺序不同导致数据不一致
分布式锁
同一时间同一数据只由一个请求操作更新缓存
过期时间
数据不一致 兜底方案
保证数据库与缓存两个操作均成功
①Cache Aside 旁路更新策略(不保证数据一致性)
释义:更新数据时,不更新缓存,而是删除缓存中的数据,在读取数据时,发现缓存中没有,再从数据库中读取数据,更新到缓存中。
大量Key同时过期或Redis宕机,请求直接访问数据库造成巨大压力,影响数据库性能及系统稳定性
大量Key同时过期
redis宕机
某个热点数据过期,大量请求访问无法直接在缓存获取数据,导致直接访问数据库
访问Redis与Database中都不存在的数据,无法根据当前请求构建缓存数据,造成应用压力
1 | @PostConstruct |
1 | /** |
1 | public class OrderAllServiceImpl implements OrderAllService{ |
1 | public class OrderAllServiceImpl implements OrderAllService { |
1 | @Component |