分布式系统中的脑裂,如何预防?
脑裂(split-brain):主从结构的多节点集群(如:zk、es、nacos)出现网络分区(partition),两个分区分别选举出master同时对外提供服务,造成数据的一致性遭到破坏。解决方法
- 法定人数(quorum):zk、nacos、es的解决方式,集群中节点数必须到达法定人数(大于一半)才能对外提供服务,通常zk集群节点总数为奇数2n+1,法定人数为n+1,出现分区后必然有一个分区达到法定人数,另一分区不满足法定人数,避免了分区后脑裂
- 冗余通信(Redundant communications):集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信。
- 共享资源(Fencing):能看到共享资源就表示在集群中,能够获得共享资源的锁的就是Leader,看不到共享资源的,就不在集群中
DCL双重检查锁存在的问题
instance = New Instance();实际有3步操作
- 分配内存
- 初始化对象
- 设置instance指向内存地址
指令重排序的情况下,2、3可能是颠倒的,如果顺序是1->3->2则会导致未被初始化完毕的对象提前暴露给其他线程。
对instance使用volatile修饰可以解决此问题,但DCL依然是不建议使用的方式(《Java并发编程实战》推荐使用延迟初始化占位类模式)
MySQL 事务auto_commit
事务是默认提交的,即:一条sql语句自动开启事务(一个select也是一个事务),执行完毕后自动提交事务。想要禁用自动提交,则需要设置当前会话auto_commit=false,另一个编程中实际用的方式是手动的begin 或 start transition开启事务,用commit提交事务或者rollback回滚事务
Java NIO
- BIO阻塞式IO
基于流的方式,对流的操作是阻塞式的,必须等待操作完成才会接触阻塞。服务端处理并发请求,通常为每个连接分配单独的线程,线程的创建成本很高且有数量限制,导致不能支持大量访问
- NIO
单个线程不会因为处理单个请求而阻塞住,而是借助操作系统IO多路复用select、poll、epoll等机制,让单个线程同时处理大量并发连接的请求
- NIO2
比NIO的改进是支持异步方式,IO操作完成会通过回调的方式由特定的CompletionHandler处理,不需要手动控制Selector监听Channel上的操作
数据库死锁
两个/多个事务,等待对方占有的资源(锁),导致无限制的相互等待称为死锁(dead lock),满足以下条件
- 每个事务占有了一部分资源
- 每个事务互相想占用对方已经占用的资源
- 每个事务都不会主动释放已经占有的资源
基本步骤:T1占有资源a,T2占有资源b,T1想要占用资源b(等待),T2想要占用资源a(死锁)
MySQL会自动检测死锁(默认启用innodb_deadlock_detect),处理方法是牺牲一个事务,保证其他事务可以执行
| T1 | T2 | 备注 |
|---|---|---|
| begin | begin | 开启事务 |
| update t_price set price = price + 100 where id = 1 | update t_price set price = price + 100 where id = 2 | T1,T2分别占用id=1和id=2的锁 |
| update t_price set price = price + 150 where id = 2 | T1尝试占用id=2的锁(已被T2占用) | |
| 等待... | update t_price set price = price + 130 where id = 1 | T1等待id=2被T2占用的锁 |
| 死锁 | 死锁 | 数据库检测到死锁,终止其中一个事务T2 |
最终结果
T2被终止,报错:ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction,
T1解除等待,update成功,可以选择commit或者rollback事务
如果禁用死锁检测(通常不会),则会在innodb_lock_wait_timeout秒后事务等待超时,提示:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction