- Q: CAP分别指什么?
- Q: 为什么说CAP无法同时满足? 能讲清楚3种情况吗?
- Q: BASE解决方案是什么?
- Q: 分布式事务种的2PC是什么?
- Q: 3PC又是什么?
- Q: TCC又是什么?
- Q:数据库主从复制的延时问题如何解决?
- Q:讲一下分布式锁的实现?
- Q: 详细讲讲如何用数据库实现锁?
- Q: redis的红锁是什么?解决什么问题的?
- Q: 如何实现分布式的负载均衡?
- Q: 主备数据库如何实现主备切换?
- Q: 如何防止上面的主备切换过程中的新数据丢失?
- Q: 如何防止主备切换时的数据不同步? 和上面的数据丢失不同, 这里指的是master节点已经收到数据, 但是还没有往备节点同步时就挂掉了
- Q: 如何生成分布式ID?
[toc]
Q: CAP分别指什么?#
A:
-
C 一致性Consistency —— 多台节点之间数据一致 (响应准确度)
-
A 可用性Availability —— 能快速响应结果,没有延迟或者等待 (响应速度,不需要等待)
-
P 分区容错性PartitionTolerance—— 如果有一部分节点挂了, 其他区节点还能提供服务 ( 时刻能响应,不会挂)
Q: 为什么说CAP无法同时满足? 能讲清楚3种情况吗?#
A:
-
CA 无P :
不支持分区处理请求, 则仅1个节点, 或者全部是时刻联通, 1个挂了,则认为系统不可用。
意味着分布式系统的意义不存在。无法扩展。违背初衷
传统的关系型数据库RDBMS:Oracle、MySQL就是CA。 -
CP 无A:
没有可用性。
意味着我会尽可能保证数据同步, 不同步的话我就不返回。
如果有节点挂了,就用另外正在同步的节点做。
例子: redis、hbase 这类和业务实时性强相关较弱的分布式数据库
他们要保证一致性,但不一定要马上能返回结果, -
AP 无C
缺失一致性。
就是因为节点同步延迟, 你看到的可能和别人的页面不一样,但是至少会马上给你结果。
一般用于不重要的广告、 网页推送、推荐之类的功能。
举个例子:
以这个图为例
如果必须满足P
则当DB1和DB0的网络通信断开(需要1分钟才能恢复)
N2仍旧要能够返回结果。
这时候一致性和可用性无法同时满足
如果要求有一致性,则必须等待1分钟才会恢复, 则无法立刻响应结果
如果要求可用性, 则必须立刻返回结果, 那么无法保证DB0和DB1是一致的。
Q: BASE解决方案是什么?#
A:
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
- 基本可用(Basically Available)
指系统故障时,能保障核心功能可用,接口性能适当降低 - 软状态(Soft state)
允许存在中间状态,例如支付中、同步中, 也就是允许数据延时 - 最终一致(Eventually Consistent)
经过一段时间后,所有节点数据都将会达到一致。如订单的"支付中"状态,最终会变 为“支付成功”或者"支付失败",使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。
Q: 分布式事务种的2PC是什么?#
A:
2PC( two-phase commit protocol)
两阶段提交
- 第一阶段:请求/表决阶段(点击放大)
问一下这些参与节点"这件事你们能不能处理成功了",参与者节点打开本地数据库事务,完成后并不会立马提交数据库本地事务,而是先向Coordinator报告说:“我这边可以处理了/我这边不能处理” - 第二阶段:提交/执行阶段(正常流程)
所有参与者节点都向协调者报告说“我这边可以处理”,协调者向所有参与者节点发送“全局提交确认通知(global_commit)”,参与者节点就会完成自身本地数据库事务的提交,并最终将提交结果回复“ack”消息给Coordinator,然后Coordinator就会向调用方返回分布式事务处理完成的结果。 - 第二阶段:提交/执行阶段(异常流程)
参与者节点向协调者节点反馈“Vote_Abort”的消息。此时分布式事务协调者节点就会向所有的参与者节点发起事务回滚的消息(“global_rollback”),此时各个参与者节点就会回滚本地事务,释放资源,并且向协调者节点发送“ack”确认消息,协调者节点就会向调用方返回分布式事务处理失败的结果。
缺点:性能(阻塞等待)、协调者故障、
Q: 3PC又是什么?#
A:
在两阶段提交的基础上增加了CanCommit阶段 并引入了超时机制 ,一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调者单点故障的问题。
第一阶段:CanCommit阶段(确认、检查各节点状态)
第二阶段:PreCommit阶段(事务预提交,有执行节点的超时机制)
第三阶段:DoCommit阶段(同样引入超时)
Q: TCC又是什么?#
A:
补偿事务TCC协议 (Try-Confirm-Cancel)
有3个阶段 Try、confirm、cancel
- Try阶段:主要是对业务系统做检测及资源预留。
- Confirm阶段:确认执行业务操作。
- Cancel阶段:取消执行业务操作。
2PC通常都是在跨库的DB层面,而TCC本质上就是一个应用层面的2PC,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。
TCC的不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。
Q:数据库主从复制的延时问题如何解决?#
A:
分情况讨论
- 如果是写操作太多,导致binlog过多,以至于主库写和从库写都很慢——那么可以通过水平扩容的方式,打散写请求。 或者用高版本mysql支持并行binlog复制
- 过大的事务,导致主从延时——拆分大事务语句到若干小事务中,这样能够进行及时提交,减小主从复制延时
- 对大表进行alter table操作,导致了表会重新生成并进行迁移。——避免业务高峰执行表修改操作,尽量安排在业务低峰期执行
- 从库机器规格、配置和主库不一致。 ——从库有时候规格应该比主库配置要高。
- 数据库的表缺少主键或者合适索引,导致更新时的主从复制延时。 —— 去检查表结构,保证每个表都有显式自增主键,并协助用户建立合适索引
- 从库的查询请求过多,导致性能下降——增加从库数量,打散从库的查询请求。
https://zhuanlan.zhihu.com/p/92077345
Q:讲一下分布式锁的实现?#
A:
分布式锁实现
https://blog.csdn.net/wuzhiwei549/article/details/80692278
- 从理解的难易程度角度(从低到高)
数据库(最简单) > 缓存 > Zookeeper
- 从实现的复杂性角度(从低到高)
Zookeeper >= 缓存 > 数据库
- 从性能角度(从高到低)
缓存(最快) > Zookeeper >= 数据库
- 从可靠性角度(从高到低)
Zookeeper(最可靠) > 缓存(怕主节点突然挂了,导致锁失效) > 数据库(无失效时间,挂了就gg)
Q: 详细讲讲如何用数据库实现锁?#
A:
有个locker表
分别有4个字典
- 锁名
- 持有锁的机器id
- version
- 超时时间
先查出这个锁名所在的行数据
判断这个锁的id是否为空。
如果不为空,且机器id也不是自己,说明被人持有了,返回false。
如果为空, 则会尝试去更新, 使用 update 机器id where lockname=‘xxx’ and version = 刚才拿到的version+1
如果update返回的结果不为0,说明更新成功, 返回true,持有锁成功。
如果update结果为0, 说明抢锁失败, 因为version被人改了,导致where条件不成立,没更新任何一条
抢到锁的人完成自己的事务操作后, 释放锁,即把锁id清理即可。
没抢到的人自己选择等一段时间再获取,或者频繁查询。
利用的行锁和MVCC的特性实现。
图片如下:
Q: redis的红锁是什么?解决什么问题的?#
A:
解决的问题:
Redis的master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点;master故障,发生故障转移,slave节点升级为master节点,导致锁丢失。
如何解决:
- 获取当前时间(单位是毫秒)。
- 轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。
- 客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
- 如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
- 如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。
Q: 如何实现分布式的负载均衡?#
A:
nginx的负载均衡方式
反向代理,作为代理服务器进行请求转发。
轮询: 指定1个服务器ip列表, 依次按顺序分配
weight权重: 根据指定权重, 分配的概率会变高(和服务器不同性能相关)
ip哈希算法: 让特定ip都导向同一个服务器(避免不同服务器频繁获取某个用户信息)
fair响应时间算法: 根据响应时间,动态调整分配优先级
url哈希: 类似ip哈希,根据url哈希,一般是某个服务器会做特定接口缓存的情况。
Q: 主备数据库如何实现主备切换?#
A:
两种方式
- 配置中心实现。 当监控系统发现异常后, 运维人员手动修改配置中心的数据源信息。 shark支持了基于zk的配置中心客户端。
- 给主备节点部署keepalive程序。 需要主备机器配置虚拟ip(类似于浮动ip),支持机器进行ip切换。
运行过程中, master和slave机器上的keepalived程序会互相发心跳,确认对方是否存货。 一旦master实例出现异常, 主节点的keeplive会自杀, 同时slave节点开始接管这个虚拟ip。
Q: 如何防止上面的主备切换过程中的新数据丢失?#
A:
数据优先插入到缓存服务,再通过消息队列插入到数据库, 如果主节点挂了,可以通过failover机制重发,当切换成功后,就能插入到更新后的master节点上了( 前提是failover的总时间大于主备切换的时间)
Q: 如何防止主备切换时的数据不同步? 和上面的数据丢失不同, 这里指的是master节点已经收到数据, 但是还没有往备节点同步时就挂掉了#
A:
-
方法1: 如果要求数据强一致, 可以开启半同步复制模式, 即事务提交到master时,master会先发binlog给slave,当slave响应成功后,master才会完成这个事务。 (TPS较高场景不适合该模式)
-
方法2: 就是上面提到的缓存机制,先缓存,再落库。 然后再依靠 GTID(全局事务id)来保证主备数据的最终一致性。
GTID即全局事务ID (global transaction identifier), 其保证为每一个在主上提交的事务在复制集群中可以生成一个唯一的ID。GTID最初由google实现,官方MySQL在5.6才加入该功能。mysql主从结构在一主一从情况下对于GTID来说就没有优势了,而对于2台主以上的结构优势异常明显,可以在数据不丢失的情况下切换新主
如图, Server1(Master)崩溃,根据从上show slave status获得Master_log_File/Read_Master_Log_Pos的值,Server2(Slave)已经跟上了主,Server3(Slave)没有跟上主。这时要是把Server2提升为主,Server3变成Server2的从。这时在Server3上执行change的时候需要做一些计算。
这个问题在5.6的GTID出现后,就显得非常的简单。由于同一事务的GTID在所有节点上的值一致,那么根据Server3当前停止点的GTID就能定位到Server2上的GTID。甚至由于MASTER_AUTO_POSITION功能的出现,我们都不需要知道GTID的具体值,直接使用CHANGE MASTER TO MASTER_HOST=‘xxx’, MASTER_AUTO_POSITION命令就可以直接完成failover的工作。
Q: 如何生成分布式ID?#
A:
- UUID
UUID.randomUUID()
UUID有5个版本,第一个版本比较好理解
基于时间戳、随机数、机器MAC地址(java中改成ip地址)生成UUID
随机性过强,不连续 - 数据库自增ID
需要一个单独的MySQL实例用来生成ID,给id字段加上auto_increment关键字,自动id,只不过可能会不连续(可能挂掉) - 数据库多主模式
设置两个Mysql实例都能单独的生产自增ID
2个实例的自增大小相同,但是起始值不同,就能保证隔开了
不方便扩容 - 号段模式
从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存
多业务端可能同时操作,所以采用版本号version乐观锁方式更新,这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。
主流 - Redis
利用redis的 incr命令实现ID的原子性自增
RDB备份可能导致id重复
AOF备份可能导致重启时间过长 - 雪花算法Snowflake
Snowflake ID组成结构:正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特组成的一个Long类型
序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID