一、构建一种死锁的场景
线程T1需要先后访问资源A和B,线程T2需要先后访问资源B和A;
T1先对A进行上锁处理,T2先对B进行上锁处理;T1在访问B时,B被占用,进入阻塞;T2在访问A时,A被占用,进入阻塞
二、延迟队列实现方法
1、zset,将timestamp+延迟时间作为sorce,轮询任务每秒只轮询 score 大于当前时间的 key即可
2、rabbitMQ,TTL(time to live)和DLX(Dead letter exchange)是两个关键的点
1、如果有事件需要延迟那么将该事件发送到MQ 队列中,为需要延迟的消息设置一个TTL;
2、TTL到期后就会自动进入设置好的DLX,然后由DLX转发到配置好的实际消费队列;
3、消费该队列的延迟消息,处理事件。
三、限流
1、固定窗口
三个值,当前时间curTime,标记时间markTime,时间窗口长度windowSize
如果curTime>markTime+windowSize,返回0,matkTime=curTime;
如果curTime<markTime+windowSize,查看计数器是否超过阈值,超过返回false,丢弃;否则正常处理
2、滑动窗口
在固定窗口的基础上,增加一个分片参数,将这个大的窗口分的更细
3、漏斗
设置一个最大容量的队列,有请求就进入队列,当队列达到最大容量时,废弃请求
4、令牌
请求来到时会向桶(redis list)中取令牌,取到就对请求进行处理,否则丢弃。
自我理解时,没当消费掉一个请求时,向桶中再写入令牌。
四、读写分离如何保证数据一致性
1、缓存标记
在进行写操作时,将唯一标识(用户id+业务id)写入数据库,并设置过期时间(需要预估主从同步时间)
用户在读取数据时,先从redis中查看是否有缓存标志,有的话读主库,否则读从库
2、本地缓存标记
这种方式还是会存在数据不一致,就是本地用户在写操作时,在客户端本地设置一个redis标志,本地用户在进行读操作的时候,检查该标志是否存在,存在,读主库;否则读从库
3、数据库同步写方案
数据库写操作成功返回前,等同步结果;同步成功,才进行返回
4、选择性强制读主
对于一些对一致性要求较高的场景,强制读主
5、中间件
同缓存标记,只是这个操作由中间件来做
五、主主复制冲突
不同机器的增长步长设置的不一致
六、分布式锁
使用redis的setnx来做分布式锁,客户端加锁时,使用setnx lockname value(如果lockname不存在则设置lockname值为value,否则返回0,表示操作不成功),进行具体的业务操作
改进方式1:如果客户端在加锁过程中崩溃,会导致锁无法释放,此时可以给锁设置一个过期时间
改进方式2:设置过期时间后,如果在业务代码执行过程中就到了过期时间,怎么办呢,起一个守护进程,对锁进行续期,检测到业务代码正常执行时,不断给锁续期
七、事务特性及隔离级别
https://www.jianshu.com/p/081a3e208e32
ACID 原子性、一致性、隔离性、持久性
redo log可以保证持久性
undo log 可以保证一致性
隔离级别
隔离级别 | 脏读(A未提交的事务,B可以读到) | 不可重复读 (针对update操作, A读到1000,B读到1000,B优先A进行消费,A读不到) |
幻读 (针对insert和delete操作 B读到80,A在消费,B最终打印出1080) |
备注 |
读未提交 | 是 | 是 | 是 | |
读已提交 | 否 | 是 | 是 | 一般sql、oracal采用这种方式 |
可重复读 | 否 | 否 | 是 | |
序列化 | 否 | 否 | 否 |
不可重复读:行锁可以解决
幻读:表锁可以解决
八、悲观锁和乐观锁
https://www.jianshu.com/p/d2ac26ca6525
1、乐观锁:适用于多读少写场景
不需要借助数据库的锁机制,但可以有版本的概念,在表的末尾增加一列,表示这一行数据的修改版本号,每次有修改,版本号+1;在提交修改时,比较上次读取的版本号和当前版本号是否一致,一致进行修改,否则丢弃或者重试
2、悲观锁:适用于多写少读场景
包括共享锁和排他锁,
共享锁:读锁,S锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
排他锁:写锁, X 锁,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁。获取排他锁的事务可以对数据行读取和修改。
悲观锁需要借助数据库的锁机制,要注意锁的级别,MySQL InnoDB 默认行级锁。行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。