分布式常见问题
1.分布式CAP定理
- CAP定理:对于一个分布式系统来说,不可能同时满足以下三点
- 一致性(C):分布式中所有数据备份,在同一时刻具有相同的值
- 可用性(A):保证每个请求不管成功或失败都有响应
- 分区容忍性(P):系统中任意信息的丢失或失败不会影响系统的继续运作
- 所有分布式系统只能满足两个:
- CP:当分布式系统中无副本,必然就满足C,此时CP具备,因为无副本就无法满足高可用性
- AP:与上面恰好相反,使用副本就难以满足一致性,但可以保证可用性。
- Base理论:是Bascially Available(基本可用),soft state(软状态),Eventually consistent(最终一致性)的缩写。虽然根据CAP定理我们无法做到强一致性(一般都会有副本),但每个应用都可以根据自身业务特点,采用适当方式使系统达到最终一致性。Base三元素:
- 基本可用:分布式系统在常出现不可预知的故障时,允许损失部分可用性。不等于不可用,知识可能服务慢了或者降级。
- 软状态:允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的副本之间进行数据同步的过程中存在延迟。
- 最终一致性:强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能达到一个一致的状态。
2.数据库分库分表的坑------用于扩容
- 概念:
- 分库:以为一个数据库支持的最高并发数是有限的,可以将一个数据库的数据拆分到多个库中,来增加最高并发访问数。(增加并发能力)。这里又要考虑集群了吧,怎么确定什么数据在哪里
- 分表:当一张表的数据量太大,用于索引来查询数据都搞不定的时候,可以将一张表的数据拆分,拆分后相当于只用在部分数据中查询,(增加查询效率)这里是不是也要考虑如何确定在哪个表里啊
- 水平拆分:表的结构不变,一个表中的数据拆分到多个数据库,比如按月拆分,(会导致跨分片的关联查询性能差,维护量大)
- 垂直拆分:将一个有很多字段的表,拆分为多个表放到同一个或者多个库上面。高频访问字段放在一起,低频字段放在一起。(部分表可能无法关联查询,只能通过接口聚合方式解决,增加开发复杂度)
- 第一个坑----ID怎么确定
- 分库分表的时候,必须要考虑主键ID是全局唯一的,比如一张订单表,分到了A库和B库中,如果两张表都是从1开始递增,那么查询订单数据的时候就乱了。
- 分库的一个期望就是将访问数据的次数分摊到各个库中,有些场景需要均匀分摊,那么数据插入到多个数据库的时候需要交替生成唯一的ID来保证请求的均匀分摊到所有库。
- 唯一ID生成的方式有什么呢?怎么保证在多个库之间的唯一呢?
- 数据库自增ID:每个库增加一条记录时将ID加1。(必然不满足多个库之间保持唯一)
- UUID:科普一下,UUID是基于当时时间,硬件标识等数据计算生成, 是一个唯一标识(比较长,占用空间大;不具有有序性)
- 获取系统当前时间作为唯一ID:字面意思(高并发时,可能1ms产生多个相同ID,所以也不行,还不如UUID)
- Twitter的snowflake(雪花算法):Twitter开源的分布式id生成算法,64位的id,分为4部分:(位数可以自行调整)
- 1bit:不用,统一为0,
- 41bit:毫秒时间戳,可以表示69年的时间
- 10bit:5bit表示机房id,5bit表示机器id,可以表示32个机房,每个机房32个主机
- 12bit:同一毫秒内的id,最多4096个不同id,自增
- 毫秒在高位自增在低位,整体递增趋势有序:可以根据自身特性分配中间的10bit,灵活:但强依赖机器,如果机器始终回拨,会导致重复。
- 百度的UIDGenerator算法:(基于雪花算法,解决时钟问题)
- 先介绍简单版的uidGenerator算法
- delta seconds:这个数为当前时间与epoch时间的时间差,单位为秒,epoch时间为集成UIDGenerator分布式ID服务第一次上线的时间,也可以自行配置。
- worker id:UidGenerator需要创建一个表,在产生分布式ID的实例启动的时候会往表中插入一行数据,得到的id就是workid的值。(因为默认22位,所以UidGenerator生成分布式ID的所有实例启动次数不许超过2^22 - 1,多了表生成的id就超出22位能存的范围了)
- sequence:每秒自增
- UIDGenerator升级版-CachedUidGenerator
- 本质是数组,数组中的每项称为slot,UidGenerator设计了两个RingBuffer,一个保存唯一ID,一个保存flag,尺寸为2^n
- 保存flag这个RingBuffer的每个slot值都是0或者1,0表示CanPutFlag,1表示CanTakeFlag。每个slot要么是0要么是1。
- 保存UID的这个RingBuffer有两个指针,tail和Cursor
- Tail表示最后一个生成的唯一ID,如果这个指针追上了Cursor则意味着RingBuffer已经满了,不再允许继续生成ID了。拒绝策略可以设置。
- Cursor指针表示最后一个已经被消费的唯一ID。如果Cursor追上了Tail,则以为着RingBuffer已经空了,不再允许获取ID了。拒绝策略可设置。
- 过程:
- 根据BoostPower值确定RingBuffer 的size
- 构造RingBuffer,PaddingFactory默认为50,意思是当RingBuffer中剩余的可以用ID少于百分之50的时候,就会出发一个异步线程往里填充新的唯一ID。
- 初始化各种设置,
- 初始化填满RingBuffer中的所有的slot,
- 注:每次不再在每次取ID的时候实时计算分布式ID,而是利用RingBuffer结构预先生成了多个分布式ID并保存;这些ID也是通过之前的方法获得的;有一个问题就是分布式ID中的时间信息可能不是这个ID真正用到时候的时间点。
- 美团的leaf-snowflake算法:沿用snowflake方法的bit结构设计
- 通过代理服务访问数据库获得一批ID
- 双缓冲:当前一批id使用百分之10,再访问数据库获取新的一批id缓存起来,等上批的id用完后直接用。
3.负载均衡的实现方式
- 所谓负载均衡就是让任务可以按照你的预想一样分配到各个实例上,并不定非要55开。
- 软件负载均衡:最常用,便宜且灵活简单,买个主机,装个软件,配置一下就能用了,但是性能一般,流量很大的企业就顶不住,没有防火墙或防DDos攻击的功能(软件负载均衡分四层和七层负载,四层负载均衡是在网络层利用IP地址端口进行请求的转发,基本上就是起一个转发分配的作用,而七层负载均衡就是可以根据用户HTTP的请求头,URL信息将请求发送到特定的主机)常见软件如下:
- Nginx:可四可七,万级别,通常用作七层负载
- LVS:只能四,十万级别,流量太大也可以组合使用(见下图)
- VIPServer:我们用的是这个
- DNS实现:最简单的实现方式,不同地域的用户通过DNS的解析可以返回不同的IP地址,缺点是扩展性差,控制权在域名商
- 例如哈尔滨的人访问百度,就返回离它最近的机房的IP,海南的访问百度就返回离海南最近的机房IP,实现地理级别的负载均衡。
- 硬件负载均衡就是用一个硬件(类似于交换机之类的硬件),常见的有F5,A10
- 功能强大,支持全局负载均衡,提供全面的负载均衡算法,支持百万界别以上的并发,提供安全功能
- 但是,贵贵贵贵贵贵贵贵贵贵贵贵。
- 选择:DNS负载均衡是地理级别的,硬件是集群级别的,软件是机器级别的,一般公司选择软件就够用了。难不成一个服务布置了多少个集群?(xhs的悟空才两个集群)