拉巴力的纸皮箱


  • 首页

  • 标签

  • 归档

  • 关于

  • 搜索

MySQL笔记

发表于 2020-10-03

思维导图

InnoDB 事务隔离级别

隔离级别 脏读 不可重复读 幻读
未提交读(RUC) NO NO NO
已提交读(RC) YES NO NO
可重复读(RR) YES YES NO
可串行化 YES YES YES

InnoDB 锁类型

共享/排它锁(Shared and Exclusive Locks)

在InnoDb中实现了两个标准的行级锁,可以简单的看为两个读写锁:

  1. S-共享锁:又叫读锁,其他事务可以继续加共享锁,但是不能继续加排他锁。
  2. X-排他锁: 又叫写锁,一旦加了写锁之后,其他事务就不能加锁了。
  • 兼容性:是指事务A获得一个某行某种锁之后,事务B同样的在这个行上尝试获取某种锁,如果能立即获取,则称锁兼容,反之叫冲突。

  • 纵轴是代表已有的锁,横轴是代表尝试获取的锁。

    . X(行级) S(行级)
    X(行级) 冲突 冲突
    S(行级) 冲突 兼容

意向锁(Intention Locks)

  • InnoDB为了支持多粒度锁机制(multiple granularity locking),即允许行级锁与表级锁共存,而引入了意向锁(intention locks)。意向锁是指,未来的某个时刻,事务可能要加共享/排它锁了,先提前声明一个意向。
  1. 意向共享锁:表达一个事务想要获取一张表中某几行的共享锁。
  2. 意向排他锁:表达一个事务想要获取一张表中某几行的排他锁。
  • 事务要获得某些行的S/X锁,必须先获得表对应的IS/IX锁,意向锁仅仅表明意向,意向锁之间相互兼容;虽然意向锁之间互相兼容,但是它与共享锁/排它锁互斥
  • 如果请求事务与当前存在的锁兼容,则授予锁。如果冲突则不会授予,事务会进行等待,直到冲突的锁被释放。永远不会在冲突情况下授予锁,因为会导致数据库的死锁
  • 意向共享锁/意向排他锁属于表锁,且取得意向共享锁/意向排他锁是取得共享锁/排他锁的前置条件。
. IX IS X(表级) S(表级)
IX 兼容 兼容 冲突 冲突
IS 兼容 兼容 冲突 兼容
X(表级) 冲突 冲突 冲突 冲突
S(表级) 冲突 兼容 冲突 兼容

意向锁的意义在哪里?

  1. IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突
  2. 意向锁是在添加行锁之前添加。
  3. 如果没有意向锁,当向一个表添加表级X锁时,就需要遍历整张表来判断是否存行锁,以免发生冲突
  4. 如果有了意向锁,只需要判断该意向锁与表级锁是否兼容即可。

MVCC

  • MVCC,多版本并发控制技术。在InnoDB中,在每一行记录的后面增加两个隐藏列,记录创建版本号和删除版本号。通过版本号和行锁,从而提高数据库系统并发性能。

InnoDB 索引

  • InnoDB的主键索引与行记录是存储在一起的,故叫做聚集索引(Clustered Index):

InnoDB的表必须要有聚集索引

  1. 如果表定义了PK,则PK就是聚集索引;
  2. 如果表没有定义PK,则第一个非空unique列是聚集索引;
  3. 否则,InnoDB会创建一个隐藏的row-id作为聚集索引;
  • 聚集索引,也只能够有一个,因为数据行在物理磁盘上只能有一份聚集存储。

  • InnoDB的普通索引可以有多个,它与聚集索引是不同的:普通索引的叶子节点,存储主键(也不是指针)

索引使用

  • where条件中的and前后的顺序,不会影响索引的命中
  • 负向查询肯定不可以命中索引

InnoDB log

  • binlog 可以给备库使用,也可以保存起来用于恢复数据库历史数据。它是实现在 server 层的,所有引擎可以共用。redo log 是 InnoDB 特有的日志,用来支持 crash-safe 能力。

MySQL EXPLAIN

Extra

该列包含MySQL解决查询的详细信息,有以下几种情况:

  1. Using where:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
  2. Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
  3. Using filesort:MySQL中无法利用索引完成的排序操作称为“文件排序”
  4. Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
  5. Impossible where:这个值强调了where语句会导致没有符合条件的行。
  6. Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行

type

找到所需行的方式

  1. ALL: 扫描全表
  2. index: 扫描全部索引树
  3. range: 索引范围扫描
  4. ref: 非唯一性索引扫描
  5. eq_ref:唯一性索引扫描
  6. const:常量扫描,比如主键。

总结

  • Using filesort:当Query 中包含order by 操作,而且无法利用索引完成排序操作的时候,MySQL Query Optimizer 不得不选择相应的排序算法来实现。
  • Using temporary:在某些操作中必须使用临时表时,在 Extra 信息中就会出现Using temporary ,主要常见于 GROUP BY 和 ORDER BY 等操作中

主要命令

  1. SHOW ENGINES; 命令查看 MySQL 支持的存储引擎。
  2. SHOW TABLE STATUS; 命令查看当前库中的表使用的是什么存储引擎。
  3. select * from information_schema.INNODB_LOCKS TODO
  4. show variables like '%tx_isolation%';查看事务隔离级别
  5. show engine innodb status 输出innodb监控可以查看到意向锁的信息
  6. show status like 'innodb_row_lock%';查看锁情况

其他

  1. 在 MySQL 数据库中,Database 和 Schema 是一对一的,所以 Database 和 Schema 是一个概念。
  2. 唯一索引会降级记录锁,这么做的理由是:非唯一索引加next-key锁由于不能确定明确的行数有可能其他事务在你查询的过程中,再次添加这个索引的数据,导致隔离性遭到破坏,也就是幻读。唯一索引由于明确了唯一的数据行,所以不需要添加间隙锁解决幻读。
  3. 间隙锁之间是兼容的。插入意向锁(IX)和间隙锁冲突。间隙锁 属于S锁???这样才符合表格的描述。
  4. 删除记录时,先查询出需要删除的记录主键,通过主键索引进行删除,可以避免产生间隙锁(唯一索引会降级记录锁)。
  5. 间隙锁(gap lock)与临键锁(next-key lock) 只在RR以上的级别生效,RC下会失效

注意事项

  1. 以固定的顺序访问表和行。交叉访问更容易造成事务等待回路。
  2. 尽量避免大事务,占有的资源锁越多,越容易出现死锁。建议拆成小事务。
  3. 降低隔离级别。如果业务允许(上面4.3也分析了,某些业务并不能允许),将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
  4. 为表添加合理的索引。防止没有索引出现表锁,出现的死锁的概率会突增。
  5. 在删除之前,可以通过快照查询(不加锁),如果查询没有结果,则直接插入,如果有通过主键进行删除。如果查询的索引不含有唯一属性,不会降级成记录锁,而是间隙锁,插入时容易死锁。

Q&A

InnoDB选择什么列作为主键

  1. 不能为空的列;
  2. 不能重复的列;
  3. 很少改变的列;(行是按照聚集索引物理排序的,如果主键频繁改变,物理顺序会改变,性能会急剧降低。)
  4. 经常被检索(where key=XXX)的列; (被检索的列上要建立索引,如果该索引是聚集索引,能够避免回表,性能提升几乎一倍。)
  5. 不是太长的列;(普通索引叶子节点会存储主键值,如果主键值太长,会增加普通索引的大小。)

为什么 MySQL 索引选择了 B+树而不是 B 树?

  1. B+树更适合外部存储(一般指磁盘存储),由于内节点(非叶子节点)不存储 data,所以一个节点可以存储更多的内节点,每个节点能索引的范围更大更精确。也就是说使用 B+树单次磁盘 I/O 的信息量相比较 B 树更大,I/O 效率更高。
  2. MySQL 是关系型数据库,经常会按照区间来访问某个索引列,B+树的叶子节点间按顺序建立了链指针,加强了区间访问性,所以 B+树对索引列上的区间范围查询很友好。而 B 树每个节点的 key 和 data 在一起,无法进行区间查找。

扩展

InnoDB一棵B+树可以存放多少行数据?

  • https://www.cnblogs.com/leefreeman/p/8315844.html
  • 约2千万。(高度为3的B+树);一个高度为 3 的 B+ 树大概可以存放 1170 × 1170 × 16 = 21902400 行数据,已经是千万级别的数据量了。
  • InnoDB存储引擎最小储存单元——页(Page),一个页的大小默认是16K。
    • show variables like 'innodb_page_size';
    • innodb的所有数据文件(后缀为ibd的文件),他的大小始终都是16384(16k)的整数倍
  • 假设一行记录的数据大小为1k,实际上现在很多互联网业务数据记录大小通常就是1K左右
  • 非叶子节点能存放多少指针:假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节,我们一个页中能存放多少这样的单元,其实就代表有多少指针,即16384/14=1170。那么可以算出一棵高度为2的B+树,能存放1170 × 16=18720条这样的数据记录。
  • 根据同样的原理我们可以算出一个高度为3的B+树可以存放:1170117016=21902400条这样的记录。所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。
  • 在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。
  • 怎么得到InnoDB主键索引B+树的高度?
    • 在实际操作之前,你可以通过InnoDB元数据表确认主键索引根页的page number为3,你也可以从《InnoDB存储引擎》这本书中得到确认。
    • SELECT b.name, a.name, index_id, type, a.space, a.PAGE_NO FROM information_schema.INNODB_SYS_INDEXES a, information_schema.INNODB_SYS_TABLES b WHERE a.table_id = b.table_id AND a.space <> 0;
  • 基于现有表的总大小和总行数,算出每一行占用的大概大小
    • 每行大概大小 ≈ total_size / total_rows
    • 对于InnoDB存储引擎,可以通过执行以下SQL查询来获取表的大小
        SELECT
        table_name AS `Table`,
        ROUND(((data_length + index_length) / 1024 / 1024), 2) AS `Size (MB)`
        FROM
        information_schema.tables
        WHERE
        table_schema = 'your_database_name' -- 替换为实际的数据库名
        AND table_name = 'your_table_name'; -- 替换为实际的表名        
        
    • 通过查看InnoDB存储引擎的.ibd文件来获取表的大小。每个InnoDB表都有一个对应的.ibd文件,其中包含了该表的数据和索引。
    • 3层B+树,每行大小1k(1个页16k,则可以存16条数据):可以记录的总大小大概为1170117016

MySQL InnoDB 引擎 RR 隔离级别是否解决了幻读?

  • https://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247489575&idx=2&sn=410aa5a43cb2cdc265dcd39e31128f17&chksm=eb539d11dc24140743b6b6f1369e958417cbe86b9f53809afe2744872f86675e753d9884f9e3&xtrack=1&scene=90&subscene=93&sessionid=1559142508&clicktime=1559142541&ascene=56&devicetype=android-26&version=2700043b&nettype=WIFI&abtest_cookie=BQABAAoACwASABMAFQAHACOXHgBWmR4AyJkeANyZHgDzmR4AA5oeAAyaHgAAAA%3D%3D&lang=zh_CN&pass_ticket=W5ig5maP6tmaLevaqwsMcnXl28LHoqSmvBuqMPbg7dOQBytHnUWtVKFBwtS2hFz6&wx_header=1

  • Mysql官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读。

  • a事务先select,b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意dml操作),a事务再select出来的结果在MVCC下还和第一次select一样,接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了,实测在RR级别下确实如此。
    如果这样理解的话,Mysql的RR级别确实防不住幻读。

  • 在快照读读情况下,mysql通过mvcc来避免幻读。
    在当前读读情况下,mysql通过next-key来避免幻读。
    select * from t where a=1;属于快照读
    select * from t where a=1 lock in share mode;属于当前读

不能把快照读和当前读得到的结果不一样这种情况认为是幻读,这是两种不同的使用。所以MySQL 存储引擎 InnoDB 隔离级别 RR 解决了幻读问题。

结论

  1. MySQL 存储引擎 InnoDB 隔离级别 RR 解决了幻读问题。
  2. 不能把快照读和当前读得到的结果不一样这种情况认为是幻读,这是两种不同的使用。
  3. 如果要update,不能出现幻读的情况,之前应该加上for update查询;不需要update,只是读,快照读已经使用mvcc解决幻读问题。

Reference

  • MySQL 体系结构-概述
  • MySQL 体系结构-主要文件
  • MySQL 参数- Innodb_File_Per_Table(独立表空间)
  • 为什么开发人员必须要了解数据库锁?
  • 主键,不少人以为自己懂了,却不透彻…
  • 如何避免回表查询?什么是索引覆盖?
  • InnoDB架构,一幅图秒懂!
  • 事务已提交,数据却丢了,赶紧检查下这个配置
  • 丁奇:MySQL 中 6 个常见的日志问题
  • 再深入一点|binlog 和 relay-log 到底长啥样?
  • MySQL InnoDB 引擎中的 7 种锁类型,你都知道吗?
  • 锁机制与 InnoDB 锁算法
  • MySQL常见的七种锁详细介绍 !!
  • MySQL中InnoDB的锁分类
  • 浅谈MySQL的七种锁
  • 别废话,各种SQL到底加了什么锁?
  • MySQL Explain详解
  • 记一次 MySQL 性能优化过程
  • MySQL索引前世今生

关于CLOSE_WAIT和HttpClient的使用

发表于 2020-09-26


ESTABLISHED 表示正在进行网络连接的数量
TIME_WAIT 表示表示等待系统主动关闭网络连接的数量
CLOSE_WAIT 表示被动等待程序关闭的网络连接数量

  1. 查看系统TCP状态的命令:netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
  2. CLOSE_WAIT 是被动关闭产生的一种状态,当用户程序正常close之后将变成LAST_ACK状态。
  3. TIME_WAIT状态可以通过优化服务器参数得到解决(当然也有可能是程序处理不当产生太多连接)。而CLOSE_WAIT数目过大一般是由于程序被动关闭连接处理不当导致的。
  4. 以HttpClient为例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    try {
    String resp = "";
    HttpResponse response = client.execute(get);
    if (response.getStatusLine().getStatusCode() != 200) {
    get.abort();
    return "";
    }
    HttpEntity entity = response.getEntity();
    if (entity != null) {
    in = entity.getContent();
    resp = in.xxx;
    //xxx
    }
    return resp;
    } catch (Exception e) {
    get.abort();
    return "";
    } finally {
    if (in != null) {
    in.close();
    }
    }
    在异常时显示调用abort,直接中止本次连接,避免in未赋值导致连接未关闭的问题。
  5. HttpClient连接关闭。一种是主动,一种是被动。在代码API的使用上没进行区分。主动关闭时当调用Close(),发出FIN包由ESTABLISHED进入FIN_WAIT_1 状态;被动关闭时当调用Close(),发出FIN包由CLOSE_WAIT进入LAST_ACK状态。
  6. 使用PoolingClientConnectionManager?

扩展

httpclient 的timeout参数

  • httpclient SocketTimeout ConnectTimeout ConnectionRequestTimeout
  • ConnectionRequestTimeout
    • httpclient使用连接池来管理连接,这个时间就是从连接池获取连接的超时时间,可以想象下数据库连接池;
  • ConnectTimeout
    • 连接建立时间,三次握手完成时间;
  • SocketTimeout
    • 关于readimeout的含义: Defines a timeout for reading a response from the proxied server. The timeout is set only between two successive read operations, not for the transmission of the whole response. If the proxied server does not transmit anything within this time, the connection is closed.
    • 数据传输过程中数据包之间间隔的最大时间;
    • SocketTimeout的值表示的是“a”、”b”、”c”这三个报文,每两个相邻的报文的间隔时间不能超过SocketTimeout
    • 虽然报文(“abc”)返回总共用了6秒,如果SocketTimeout设置成4秒,实际程序执行的时候是不会抛出java.net.SocketTimeoutException: Read timed out异常的

Reference

  • 服务器TIME_WAIT和CLOSE_WAIT详解和解决办法
  • HttpClient连接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查

分布式一致性协议概览

发表于 2020-09-12
  • 本文内容基本来源:网上资料总结

共识(Consensus) 和 一致性(Consistency)

  • 分布式一致性(共识)协议 (consensus protocol)
  • Consensus != Consistency
  • CAP 定理中的 C 和数据库 ACID 的 C 才是真正的“一致性”—— consistency 问题
  • 在早些的文献中,共识(consensus)也叫做协商(agreement)
  • 共识(Consensus),很多时候会见到与一致性(Consistency)术语放在一起讨论。严谨地讲,两者的含义并不完全相同。
  • 一致性的含义比共识宽泛,在不同场景(基于事务的数据库、分布式系统等)下意义不同。具体到分布式系统场景下,一致性指的是多个副本对外呈现的状态。如前面提到的顺序一致性、线性一致性,描述了多节点对数据状态的共同维护能力。而共识,则特指在分布式系统中多个节点之间对某个事情(例如多个事务请求,先执行谁?)达成一致意见的过程。因此,达成某种共识并不意味着就保障了一致性。
  • 实践中,要保证系统满足不同程度的一致性,往往需要通过共识算法来达成。
  • 分布式之系统底层原理

共识问题

  • 在分布式系统中,共识就是系统中的多个节点对某个值达成一致。共识问题可以用数学语言来描述:一个分布式系统包含 n 个进程 {0, 1, 2,…, n-1},每个进程都有一个初值,进程之间互相通信,设计一种算法使得尽管出现故障,进程们仍协商出某个不可撤销的最终决定值,且每次执行都满足以下三个性质:
    • 终止性(Termination):所有正确的进程最终都会认同某一个值。
    • 协定性(Agreement):所有正确的进程认同的值都是同一个值。
    • 完整性(Integrity),也称作有效性(Validity):如果正确的进程都提议同一个值,那么所有处于认同状态的正确进程都选择该值。
  • 完整性可以有一些变化,例如,一种较弱的完整性是认定值等于某些正确经常提议的值,而不必是所有进程提议的值。完整性也隐含了,最终被认同的值必定是某个节点提出过的。
  • 算法共识/一致性算法有两个最核心的约束:1) 安全性(Safety),2) 存活性(Liveness):
    • Safety:保证决议(Value)结果是对的,无歧义的,不会出现错误情况。
      • 只有是被提案者提出的提案才可能被最终批准;
      • 在一次执行中,只批准(chosen)一个最终决议。被多数接受(accept)的结果成为决议;
    • Liveness:保证决议过程能在有限时间内完成。
      • 决议总会产生,并且学习者最终能获得被批准的决议。
  • 根据解决的场景是否允许拜占庭(Byzantine)错误,共识算法可以分为 Crash Fault Tolerance (CFT) 和 Byzantine Fault Tolerance(BFT)两类。
    • 对于非拜占庭错误的情况,已经存在不少经典的算法,包括 Paxos(1990 年)、Raft(2014 年)及其变种等。这类容错算法往往性能比较好,处理较快,容忍不超过一半的故障节点。
    • 对于要能容忍拜占庭错误的情况,包括 PBFT(Practical Byzantine Fault Tolerance,1999 年)为代表的确定性系列算法、PoW(1997 年)为代表的概率算法等。确定性算法一旦达成共识就不可逆转,即共识是最终结果;而概率类算法的共识结果则是临时的,随着时间推移或某种强化,共识结果被推翻的概率越来越小,最终成为事实上结果。拜占庭类容错算法往往性能较差,容忍不超过 1/3 的故障节点。
  • 副本控制协议可以分为两大类:“中心化(centralized)副本控制协议”和“去中心化(decentralized)副本控制协议”。

分布式系统的几个主要难题

  1. 网络问题
  2. 时钟问题
  3. 节点故障问题

达成共识还可以解决分布式系统中的以下经典问题

  1. 互斥(Mutual exclusion):哪个进程进入临界区访问资源?
  2. 选主(Leader election):在单主复制的数据库,需要所有节点就哪个节点是领导者达成共识。如果一些由于网络故障而无法与其他节点通信,可能会产生两个领导者,它们都会接受写入,数据就可能会产生分歧,从而导致数据不一致或丢失。
  3. 原子提交(Atomic commit):跨多节点或跨多分区事务的数据库中,一个事务可能在某些节点上失败,但在其他节点上成功。如果我们想要维护这种事务的原子性,必须让所有节点对事务的结果达成共识:要么全部提交,要么全部中止/回滚。
  • 总而言之,在共识的帮助下,分布式系统就可以像单一节点一样工作——所以共识问题是分布式系统最基本的问题。

FLP 不可能(FLP Impossibility)

  • 早在 1985 年,Fischer、Lynch 和 Paterson (FLP)在 “Impossibility of Distributed Consensus with One Faulty Process[5]” 证明了:在一个异步系统中,即使只有一个进程出现了故障,也没有算法能保证达成共识。
  • 简单来说,因为在一个异步系统中,进程可以随时发出响应,所以没有办法分辨一个进程是速度很慢还是已经崩溃,这不满足终止性(Termination)。
  • FLP给后来的人们提供了研究的思路——不再尝试寻找异步通信系统中共识问题完全正确的解法。FLP 不可能是指无法确保达成共识,并不是说如果有一个进程出错,就永远无法达成共识。

同步系统中的共识

  • Dolev 和 Strong 在论文 “Authenticated Algorithms for Byzantine Agreement[9]” 中证明了:同步系统中,如果 N 个进程中最多有 f 个会出现崩溃故障,那么经过 f + 1 轮消息传递后即可达成共识。
  • 在一个有 f 个拜占庭故障节点的系统中,必须总共至少有 3f + 1 个节点才能够达成共识。即 N >= 3f + 1。
  • 虽然同步系统下拜占庭将军问题的确存在解,但是代价很高,需要 O(N^f+1 ) 的信息交换量,只有在那些安全威胁很严重的地方使用(例如:航天工业)
  • PBFT(Practical Byzantine Fault Tolerance)[12] 算法顾名思义是一种实用的拜占庭容错算法,由 Miguel Castro 和 Barbara Liskov 发表于 1999 年。
  • 算法的主要细节不再展开。PBFT 也是通过使用同步假设保证活性来绕过 FLP 不可能。PBFT 算法容错数量同样也是 N >= 3f + 1,但只需要 O(n^2 ) 信息交换量,即每台计算机都需要与网络中其他所有计算机通讯。

一致性模型

  • 一致性(Consistency)是指多副本(Replications)问题中的数据一致性。
  1. 强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。
  2. 弱一致性:数据更新成功后,系统不承诺立即可以读到最新写入的值,也不承诺具体多久之后可以读到。
  3. 最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺立即可以返回最新写入的值,但是保证最终会返回上一次更新操作的值。
  • 分布式中一致性是非常重要的,分为弱一致性和强一致性。现在主流的一致性协议一般都选择的是弱一致性的特殊版本:最终一致性。

CAP

  • CAP是指在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)这三个要素最多只能同时实现两点,不可能三者兼顾。
  • CAP理论提出就是针对分布式环境的,所以,P 这个属性是必须具备的。
  1. Consistency 一致性
    一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。等同于所有节点拥有数据的最新版本。
    CAP中的C指的是强一致性。
  2. Availability 可用性
    可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。
    对于一个可用性的分布式系统,每一个非故障的节点必须对每一个请求作出响应。如果不考虑一致性,这个是很好实现的,立即返回本地节点的数据即可,而不需要等到数据一致才返回。
  3. Partition Tolerance 分区容忍性
    Tolerance也可以翻译为容错,分区容忍性具体指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即系统容忍网络出现分区,分区之间网络不可达的情况,分区容忍性和扩展性紧密相关,Partition Tolerance特指在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。
  • 传统数据库都是假设不保证P的,因为传统数据库都是单机或者很小的本地集群,假设网络不存在问题,出现问题手工修复。所以,损失分区容错(P)只保证CA相当于就是一个单体应用,根本不是分布式。

  • 分布式是要求单个节点故障(概率太高了)系统仍能完成运行。搭建分布式就是间接要求必须保证P,即P是现实,那C和A就无法同时做到,需要在这两者之间做平衡。

  • 像银行系统,是通过损失可用性(A)来保障CP,银行系统是内网,很少出现分区不可达故障状态,一旦出现,不可达的节点对应的ATM就没法使用,即变为不可用。同时如果数据在各分区未达到一致,ATM也是Loading状态即不可用。

  • 在互联网实践中,可用性又是极其重要的,因此大部分是通过损失一致性(C)来保障AP,当然也非完全牺牲一致性,使用弱一致性,即一定时间后一致的弱一致性,当数据还在同步时(WRITE之后),使用上一次的数据。

  • Google 2009年 在Transaction Across DataCenter 的分享中,对一致性协议在业内的实践做了一简单的总结,如下图所示,这是 CAP 理论在工业界应用的实践经验。

BASE 理论

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)
  • BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
  • BASE理论是对大规模的互联网分布式系统实践的总结,用弱一致性来换取可用性,不同于ACID,属于AP系统。

ACID

  • ACID(Atomicity原子性,Consistency一致性,Isolation隔离性,Durability持久性)是事务的特点,具有强一致性,一般用于单机事务,分布式事务若采用这个原则会丧失一定的可用性,属于CP系统。

分类

按单主和多主进行分类

  1. 单主协议,即整个分布式集群中只存在一个主节点。主备复制、2PC、 Paxos、Raft、ZAB。
    • (不允许数据分歧):整个分布式系统就像一个单体系统,所有写操作都由主节点处理并且同步给其他副本。
  2. 多主协议,即整个集群中不只存在一个主节点。Pow、Gossip协议。
    • (允许数据分歧):所有写操作可以由不同节点发起,并且同步给其他副本。
  • 单主协议由一个主节点发出数据,传输给其余从节点,能保证数据传输的有序性。而多主协议则是从多个主节点出发传输数据,传输顺序具有随机性,因而数据的有序性无法得到保证,只保证最终数据的一致性。这是单主协议和多主协议之间最大的区别。
---单主--主备复制、2PC、 Paxos、Raft、ZAB
|
---多主--Pow、Gossip

按CAP中的P分类

  • 分区容忍的一致性协议跟所有的单主协议一样,它也是只有一个主节点负责写入(提供顺序一致性),但它跟 2PC 的区别在于它只需要保证大多数节点(一般是超过半数)达成一致就可以返回客户端结果,这样可以提高了性能,同时也能容忍网络分区(少数节点分区不会导致整个系统无法运行)。分区容忍的一致性算法保证大多数节点数据一致后才返回客户端,同样实现了顺序一致性。
---非P--主备复制、2PC
|
---P   --Paxos、Raft

算法简介

1、主备复制

  • 主备复制可以说是最常用的数据复制方法,也是最基础的方法,很多其他协议都是基于它的变种。 主备复制要求所有的写操作都在主节点上进行,然后将操作的日志发送给其他副本。可以发现由于主备复制是有延迟的,所以它实现的是最终一致性。
  • 主备复制的实现方式:主节点处理完写操作之后立即返回结果给客户端,写操作的日志异步同步给其他副本。这样的好处是性能高,客户端不需要等待数据同步,缺点是如果主节点同步数据给副本之前数据缺失了,那么这些数据就永久丢失了。MySQL 的主备同步就是典型的异步复制。

2、2PC

  • 2PC 是典型的 CA 系统,为了保证一致性和可用性,2PC 一旦出现网络分区或者节点不可用就会被拒绝写操作,把系统变成只读的。
  • 由于 2PC 容易出现节点宕机导致一直阻塞的情况,所以在数据复制的场景中不常用,一般多用于分布式事务中。
  • 如果网络环境较好,该协议一般还是能很好的工作的,2PC广泛应用于关系数据库的分布式事务处理,如mysql的内部与外部XA都是基于2PC的,一般想要把多个操作打包未原子操作也可以用2PC。
|---1. Prepare(Vote Request) 
|
|---2. Global Commit (1- return (Vote Commit)) (正常流程)
|
|---2. Global Rollback (1- return (Vote Abort)) (异常流程)
  • 缺点:
    1. 性能问题(两个阶段都涉及同步等待阻塞,极大降低了吞吐量)
    2. 协调者单点故障问题
    3. 丢失消息导致的数据不一致问题

3、3PC

  • 相对于2PC,3PC主要解决的单点故障问题,并减少阻塞。在2PC的基础上增加了CanCommit阶段,并引入了超时机制。一旦事务参与者迟迟没有收到协调者的Commit请求,就会自动进行本地commit,这样相对有效地解决了协调者单点故障的问题;但是性能问题和不一致问题仍然没有根本解决。

  • CanCommit阶段: 检查下自身状态的健康性,看有没有能力进行事务操作。

  • 和2PC区别:

    1. 相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制。这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
    2. 通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
  • 无论是2PC还是3PC都无法彻底解决分布式的一致性问题。Google Chubby的作者Mike Burrows说过, there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos。意即世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。

4、MVCC

  • MVCC(Multi-version Cocurrent Control,多版本并发控制)技术。MVCC 技术最初也是在数据库系统中被提出,但这种思想并不局限于单机的分布式系统,在分布式系统中同样有效。

5、Paxos协议

  • 2PC、3PC 两个协议的协调者都需要人为设置而无法自动生成,是不完整的分布式协议,而Paxos 就是一个真正的完整的分布式算法。系统一共有几个角色:Proposer(提出提案)、Acceptor(参与决策)、Learner(不参与提案,只负责接收已确定的提案,一般用于提高集群对外提供读服务的能力),实践中一个节点可以同时充当多个角色。
  • 作者在描述Paxos时,列举了希腊城邦选举的例子,所以该算法又被称为希腊城邦算法。
  • Paxos是非常经典的一致性协议,但是因为过于理论化,难以直接工程化,因此工业界出现了诸多基于Paxos思想出发的变种。虽然这些变种最终很多都和原始的Paxos有比较大的差距,甚至完全演变成了新的协议,但是作为奠基者的Paxos在分布式一致性协议中依然持有不可撼动的地位。
  • Paxos协议的容错性很好,只要有超过半数的节点可用,整个集群就可以自己进行Leader选举,也可以对外服务,通常用来保证一份数据的多个副本之间的一致性,适用于构建一个分布式的一致性状态机。
  • Google的分布式锁服务Chubby就是用了Paxos协议,而开源的ZooKeeper使用的是Paxos的变种ZAB协议。

6、Raft协议

  • Raft协议是斯坦福的Diego Ongaro、John Ousterhout两人于2013年提出,作者表示流行的Paxos算法难以理解,且其过于理论化致使直接应用于工程实现时出现很多困难,因此作者希望提出一个能被大众比较容易地理解接受,且易于工程实现的协议。Raft由此应运而生。不得不说,Raft作为一种易于理解,且工程上能够快速实现一个较完整的原型的算法,受到业界的广泛追捧。
  • Raft协议对标Paxos,容错性和性能都是一致的,但是Raft比Paxos更易理解和实施。系统分为几种角色:Leader(发出提案)、Follower(参与决策)、Candidate(Leader选举中的临时角色)。
  • 在Raft协议出来之前,Paxos是分布式领域的事实标准,但是Raft的出现打破了这一个现状(raft作者也是这么想的,请看论文),Raft协议把Leader选举、日志复制、安全性等功能分离并模块化,使其更易理解和工程实现,将来发展怎样我们拭目以待(挺看好)。
  • Raft协议目前被用于 cockrouchDB,TiKV等项目中
  • Raft 算法实际上是 Multi-Paxos 的一个变种,通过新增两个约束:
    • 追加日志约束:Raft 中追加节点的日志必须是串行连续的,而 Multi-Paxos 中则可以并发追加日志(实际上 Multi-Paxos 的并发也只是针对日志追加,最后应用到内部 State Machine 的时候还是必须保证顺序)。
    • 选主限制:Raft 中只有那些拥有最新、最全日志的节点才能当选 Leader 节点,而 Multi-Paxos 由于允许并发写日志,因此无法确定一个拥有最新、最全日志的节点,因此可以选择任意一个节点作为 Leader,但是选主之后必须要把 Leader 节点的日志补全。
    • 基于这两个限制,Raft 算法的实现比 Multi-Paxos 更加简单易懂,不过由于 Multi-Paxos 的并发度更高,因此从理论上来说 Multi-Paxos 的性能会更好一些,但是到现在为止业界也没有一份权威的测试报告来支撑这一观点。

Paxos和Raft的对比

  • Paxos算法和Raft算法有显而易见的相同点和不同点。二者的共同点在于,它们本质上都是单主的一致性算法,且都以不存在拜占庭将军问题作为前提条件。二者的不同点在于,Paxos算法相对于Raft,更加理论化,原理上理解比较抽象,仅仅提供了一套理论原型,这导致很多人在工业上实现Paxos时,不得已需要做很多针对性的优化和改进,但是改进完却发现算法整体和Paxos相去甚远,无法从原理上保证新算法的正确性,这一点是Paxos难以工程化的一个很大原因。相比之下Raft描述清晰,作者将算法原型的实现步骤完整地列在论文里,极大地方便了业界的工程师实现该算法,因而能够受到更广泛的应用。
  • 从根本上来看,Raft的核心思想和Paxos是非常一致的,甚至可以说,Raft是基于Paxos的一种具体化实现和改进,它让一致性算法更容易为人所接受,更容易得到实现。由此亦可见,Paxos在一致性算法中的奠基地位是不可撼动的。

7、Gossip算法

  • Gossip又被称为流行病算法,它与流行病毒在人群中传播的性质类似,由初始的几个节点向周围互相传播,到后期的大规模互相传播,最终达到一致性。
  • Gossip协议与上述所有协议最大的区别就是它是去中心化的,上面所有的协议都有一个类似于Leader的角色来统筹安排事务的响应、提交与中断,但是Gossip协议中就没有Leader,每个节点都是平等的。
  • Gossip协议被广泛应用于P2P网络,同时一些分布式的数据库,如Redis集群的消息同步使用的也是Gossip协议,另一个重大应用是被用于比特币的交易信息和区块信息的传播。
  • 去中心化的Gossip看起来很美好:没有单点故障,看似无上限的对外服务能力……本来随着Cassandra火了一把,但是现在Cassandra也被抛弃了,去中心化的架构貌似难以真正应用起来。归根到底我觉得还是因为去中心化本身管理太复杂,节点之间沟通成本高,最终一致等待时间较长……往更高处看,一个企业(甚至整个社会)不也是需要中心化的领导(或者制度)来管理吗,如果没有领导(或者制度)管理,大家就是一盘散沙,难成大事啊。
  • 事实上现代互联网架构只要把单点做得足够强大,再加上若干个强一致的热备,一般问题都不大。
  • 应用:数据同步;缺点:节点之间沟通成本高,最终一致等待时间较长

8、Pow(Proof of work)

  • Proof-of-work算法又被称为Pow算法。工作量证明算法。
  • Pow最为人所熟知的应用是比特币。代表者是比特币(BTC),区块链1.0
  • PoW(Proof of Work,工作量证明)的字面意思是谁干的活多,谁的话语权就大,在一定层面上类似于现实生活中“多劳多得”的概念。以比特币为例,比特币挖矿就是通过计算符合某一个比特币区块头的哈希散列值争夺记账权。这个过程需要通过大量的计算实现,简单理解就是挖矿者进行的计算量越大(工作量大),它尝试解答问题的次数也就变得越多,解出正确答案的概率自然越高,从而就有大概率获得记账权,即该矿工所挖出的区块被串接入主链。
  • 基于PoW节点网络的安全性令人堪忧。大于51%算力的攻击。
  • 51%算力攻击目前仅在“PoW”共识机制中存在,因为“PoW”共识机制依赖算力计算获胜,也就是谁算得快,谁的胜率就高。在使用了“PoW”共识机制的区块链网络中,我们称参与计算哈希的所有计算机资源为算力,那么全网络的算力就是100%,当超过51%的算力掌握在同一阵营中时,这个阵营的计算哈希胜出的概率将会大幅提高。为什么是51%?50.1%不行吗?当然也是可以的,之所以取51%是为了取一个最接近50%,且比50%大的整数百分比,这样当算力值达到51%后的效果将会比50.1%的计算效果更明显。举个例子,如果诚实节点的算力值是50.1%,那么坏节点的算力值就是49.9%。两者的差距不算太大,这样容易导致最终的区块竞争你来我往、长期不分上下。如果算力资源分散,不是高度集中的,那么整个区块链网络是可信的。然而,当算力资源集中于某一阵营的时候,算力的拥有者就能使用算力资源去逆转区块,导致区块链分叉严重,

9、PoS(Proof of Stake)

  • 代表者是以太坊(ETH),以太坊正在从PoW过渡到PoS,区块链2.0
  • PoS(Proof of Stake,股权证明)是由点点币(PPCoin)首先应用的。该算法没有挖矿过程,而是在创世区块内写明股权分配比例,之后通过转让、交易的方式,也就是我们说的IPO(Initial Public Offerings)公开募股方式,逐渐分散到用户钱包地址中去,并通过“利息”的方式新增货币,实现对节点地址的奖励。PoS的意思是股份制。也就是说,谁的股份多,谁的话语权就大,这和现实生活中股份制公司的股东差不多。但是,在区块链的应用中,我们不可能真实地给链中的节点分配股份,取而代之的是另外一些东西,例如代币,让这些东西来充当股份,再将这些东西分配给链中的各节点。
  • PoS共识算法具有下面的优缺点:(1)优点•缩短了共识达成的时间,链中共识块的速度更快。•不再需要大量消耗能源挖矿,节能。•作弊得不偿失。如果一名持有多于50%以上股权的人(节点)作弊,相当于他坑了自己,因为他是拥有股权最多的人,作弊导致的结果往往是拥有股权越多的人损失越多。(2)缺点•攻击成本低,只要节点有物品数量,例如代币数量,就能发起脏数据的区块攻击。•初始的代币分配是通过IPO方式发行的,这就导致“少数人”(通常是开发者)获得了大量成本极低的加密货币,在利益面前,很难保证这些人不会大量抛售。•拥有代币数量大的节点获得记账权的概率会更大,使得网络共识受少数富裕账户支配,从而失去公正性。
  • 区块链2.0仍存在性能上的缺陷,难以支持大规模的商业应用开发。与支付宝在“双十一”时26.5万笔交易/秒的性能相比,像以太坊这样的区块链系统只能做到几百笔交易/秒的水平。交易需由多个参与方确认,是影响区块链性能的主要原因。

10、DPoS

  • 代表者是柚子(EOS),区块链3.0

11、

  • PBFT拜占庭容错,联盟链中常用。

Gossip算法和Pow算法对比

  • 同为去中心化算法,Gossip算法和Pow算法都能实现超大集群的一致性,但是它们的特性可谓有天壤之别。Gossip算法往往应用于超大集群快速达成一致性的目的。它的特性是如流感一般超强的传播速度,以及自身能够管理的数量可观的节点数。但是对于流传的消息没有特别的管控,无法辨别其中的虚假信息,并且只关注与最终的一致性,不关心消息的顺序性。而Pow算法则完全专注于尽可能地解决”拜占庭将军”问题,防止消息的篡改。它可以不计代价地去要求各个节点参与竞选,以付出巨大算力为代价保证平台的安全性。

应用

  • 区块链:PoW、PoS、Gossip

总结

2PC和TCC什么关系

  1. 2PC在“分布式一致性协议“的范畴,属于CA层面的一个协议,一般作为和其他协议(PAXOS,Raft)进行对比的形式出现。

  2. 2PC在“分布式事务“的范畴,属于数据库层面,XA协议的基础。TCC算是一种特殊的2PC。TCC事务的处理流程与2PC两阶段提交类似,不过2PC通常都是在跨库的DB层面,而TCC本质上就是一个应用层面的2PC,需要通过业务逻辑来实现。

  3. TCC是分布式事务的范畴,但其本质也是分布式一致性的一种协议,只是特指业务上的协议。而一般情况下我们所说的分布式一致性协议,一般是指底层系统实现上的,偏向基础服务上的。如果以后有人对TCC进行改造,描述出底层系统(非业务)的TCC,那么它也属于这篇文章所包含的其中一种分布式一致性协议。对于其他分布式事务的实现方案同理。

其他

  • 从Paxos到Raft再到EPaxos:一文总结:分布式一致性技术是如何演进的?
  • 众所周知,Paxos是出了名的晦涩难懂,不仅难以理解,更难以实现。而Raft则以可理解性和易于实现为目标,Raft的提出大大降低了使用分布式一致性的门槛,将分布式一致性变的大众化、平民化,因此当Raft提出之后,迅速得到青睐,极大地推动了分布式一致性的工程应用。
  • EPaxos的提出比Raft还早,但却长期无人问津,很大一个原因就是EPaxos实在是难以理解。EPaxos基于Paxos,但却比Paxos更难以理解,大大地阻碍了EPaxos的工程应用。不过,是金子总会发光的,EPaxos因着它独特的优势,终于被人们发现,具有广阔的前景。
  • EPaxos更适用于跨AZ跨地域场景,对可用性要求极高的场景,Leader容易形成瓶颈的场景。Multi-Paxos和Raft本身非常相似,适用场景也类似,适用于内网场景,一般的高可用场景,Leader不容易形成瓶颈的场景。

Reference

  • 分布式一致性协议概述
  • 分布式系统:一致性协议
  • 分布式一致性协议
  • 分布式事务:深入理解什么是2PC、3PC及TCC协议
  • 详解分布式一致性机制
  • 漫谈分布式共识问题!!
  • 分布式之系统底层原理!!!!
  • 深度介绍分布式系统原理与设计
  • [区块链:以太坊DApp开发实战]
  • [区块链:分布式商业与智数未来]

分布式事务简要总结

发表于 2020-09-12

前言

  • 本文涉及一些理论基础
  • 业界目前的分布式事务的解决方案
  • 简单的分类和总结
  • 基本内容都来源网上

理论基础

事务

  1. 事务:事务是由一组操作构成的可靠的独立的工作单元,事务具备ACID的特性,即原子性、一致性、隔离性和持久性。
  2. 本地事务:当事务由资源管理器本地管理时被称作本地事务。本地事务的优点就是支持严格的ACID特性,高效,可靠,状态可以只在资源管理器中维护,而且应用编程模型简单。但是本地事务不具备分布式事务的处理能力,隔离的最小单位受限于资源管理器。
  3. 全局事务:当事务由全局事务管理器进行全局管理时成为全局事务,事务管理器负责管理全局的事务状态和参与的资源,协同资源的一致提交回滚。

ACID(数据库事务4个特性)

  1. Atomicity(原子性)
  2. Consistency(一致性)
  3. Isolation(隔离性)
  4. Durablity(持久性)

分布式事务 (Distributed Transaction)

  1. 在互联网技术里面,强调追求最终一致性。异地多活就是围绕这一点来做的。
  2. 分布式事务从实质上看与数据库事务的概念是一致的,既然是事务也就需要满足事务的基本特性(ACID),只是分布式事务相对于本地事务而言其表现形式有很大的不同。

分布式一致性协议 (consensus protocol)

  1. 两阶段提交协议(The two-phase commit protocol,2PC)
  2. 3PC
  3. PAXOS
  4. Raft 等
  • 无论是2PC还是3PC都无法彻底解决分布式的一致性问题。Google Chubby的作者Mike Burrows说过, ”there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos。意即世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。

  • 详情看:分布式一致性协议概览

DTP(Distributed Transaction Processing)

  • DTP(Distributed Transaction Processing Reference Model):分布式事务处理模型。TM、RM、AP等角色的分布式事务的模型。

  • AP(Application Program,应用程序)

  • TM(Transaction Manager,事务管理器)

  • RM(Resource Manager,资源管理器)

  • DTP规范中主要包含了AP、RM、TM三个部分,其中AP是应用程序,是事务发起和结束的地方;RM是资源管理器,主要负责管理每个数据库的连接数据源;TM是事务管理器,负责事务的全局管理,包括事务的生命周期管理和资源的分配协调等。

  • XA是DTP的一部分接口规范。

分布式事务框架

  • 大多数分布式事务框架,也多借鉴了DTP(Distributed Transaction Processing)模型

  • RM负责本地事务的提交,同时完成分支事务的注册、锁的判定,扮演事务参与者角色。

  • TM负责整体事务的提交与回滚的指令的触发,扮演事务的总体协调者角色。

  • 不同框架在实现时,各组件角色的功能、部署形态会根据需求进行调整,例如TM有的是以jar包形式与应用部署在一起,有的则剥离出来需要单独部署(例如Seata中将TM的主要功能放到一个逻辑上集中的Server上,叫做TC( Transaction Coordinator ))

  • 一个好的分布式事务框架应用尽可能满足以下特性:
    业务改造成本低;1
    性能损耗低;2
    隔离性保证完整。3
    但如同CAP,这三个特性是相互制衡

  • 基于业务补偿的Saga满足1.2;TCC满足2.3;Seata(AT模式)满足1.3

分布式事务的4种模式

  1. AT - (无侵入的分布式事务解决方案)
  2. TCC (业务层的2PC)
  3. Saga (一种补偿协议,长事务解决方案)
  4. XA (DB层的2PC)
  • 阿里seata框架 实现了这四种模式。
  • 分布式事务的4种模式

  • 性能最高的是Saga,其次是TCC。
  • 隔离性最好的是XA(资源层),最差的是Saga。
  • 除了XA,其他三个为柔性事务,补偿式事务。

补偿型事务

补偿型事务处理机制构建在 事务资源 之上(要么在中间件层面,要么在应用层面),事务资源 本身对分布式事务是无感知的。

分类

  • 分布式事务实现方案从ACID的角度上,分刚性事务、柔型事务。
  • 刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务;
    柔性事务:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。
  • 刚性事务满足ACID理论;柔性事务满足BASE理论(基本可用,最终一致)
  • 刚性事务:XA 协议(2PC、JTA、JTS)、3PC;
    柔型事务:TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)。

柔性事务

  • 柔性事务(如分布式事务)为了满足可用性、性能与降级服务的需要,降低一致性(Consistency)与隔离性(Isolation)的要求,遵循 BASE 理论。
  • 柔性事务也部分遵循 ACID 规范:
    1. 原子性:严格遵循
    2. 一致性:事务完成后的一致性严格遵循;事务中的一致性可适当放宽
    3. 隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽
    4. 持久性:严格遵循

柔性事务的分类

柔性事务分为:两阶段型、补偿型、异步确保型、最大努力通知型。

  1. 两阶段型 (这个并不算柔性事务,资源层,强一致性!)
    分布式事务二阶段提交,对应技术上的 XA、JTA/JTS,这是分布式环境下事务处理的典型模式。
  2. 补偿型
    TCC 型事务(Try-Confirm-Cancel)可以归为补偿型。在 Try 成功的情况下,如果事务要回滚,Cancel 将作为一个补偿机制,回滚 Try 操作;TCC 各操作事务本地化,且尽早提交(没有两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现“补偿”行为。
    TCC 是将资源层的二阶段提交协议转换到业务层,成为业务模型中的一部分。
  3. 异步确保型
    将一些有同步冲突的事务操作变为异步操作,避免对数据库事务的争用,如消息事务机制。
  4. 最大努力通知型
    通过通知服务器(消息通知)进行,允许失败,有补充机制。
  • 针对不同的分布式场景业界常见的解决方案有2PC、TCC、可靠消息最终一致性、最大努力通知这几种。

其他

  • JTA(Java Transaction API):分布式事务的编程API,按照XA、DTP的模型和规范实现,在J2EE中,单库事务通过JDBC事务来支持,跨库事务通过JTA API来支持,通过JTA API可以协调和管理横跨多个数据库的分布式事务,一般来说会结合JNDI。

分布式事务解决方案

本地消息服务方案(ebay)

  • eBay 事件队列方案——最终一致性

  • eBay 的架构师Dan Pritchett,曾在一篇解释BASE 原理的论文《Base:An Acid Alternative》中提到一个eBay 分布式系统一致性问题的解决方案。它的核心思想是将需要分布式处理的任务通过消息或者日志的方式来异步执行,消息或日志可以存到本地文件、数据库或消息队列,再通过业务规则进行失败重试,它要求各服务的接口是幂等的。

  • (任务表,定时任务或人工自动重试)。

  • 优点:

    • 消息的时效性比较高;
    • 从应用设计的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了对MQ中间件特性的依赖;
    • 方案轻量级,容易实现。
  • 缺点:

    • 与具体的业务场景绑定,耦合性强,不可以共用;
    • 消息数据与业务数据同步,占用业务系统资源;
    • 业务系统在使用关系型数据库的情况下消息服务性能会受到关系型数据库的并发性能限制。
  • 本地消息表:一种非常经典的实现,基本避免了分布式事务,实现了“最终一致性”。但是,关系型数据库的吞吐量和性能方面存在瓶颈,频繁的读写消息会给数据库造成压力。所以,在真正的高并发场景下,该方案也会有瓶颈和限制的。

  • 其他类似方案:去哪儿网消息队列设计与实现

  • 事件驱动架构(EDA)编码实践 (事件表方式)

消息队列MQ事务

  • 2PC、3PC的时候我们说没有根本解决性能问题,而如果通过MQ的事务消息来进行异步解耦,并实现系统的数据的最终一致性的话会不会好很多呢?实际上这就是我们下一篇文章要继续讲述的《分布式事务之如何基于RocketMQ的事务消息特性实现分布式系统的最终一致性?:https://blog.csdn.net/u014532775/article/details/100830995

以支付系统为例

  1. 上游服务(支付系统)如何确保完成自身支付成功状态更新后消息100%的能够投递到下游服务(用户余额系统)指定的Topic中?
    1)在这个流程中上游服务在进行本地数据库事务操作前,会先发送一个状态为“待确认”的消息至可靠消息服务,而不是直接将消息投递到MQ服务的指定Topic。
    2)之后上游服务就会开启本地数据库事务执行业务逻辑操作,这里支付系统就会将该笔支付订单状态更新为“已成功”。
    3)如果上游服务本地数据库事务执行成功,则继续向可靠消息服务发送消息确认消息,此时可靠消息服务就会正式将消息投递到MQ服务,并且同时更新消息数据库中的消息状态为“已发送”。
    4)相反,如果上游本地数据库事务执行失败,则需要向可靠消息服务发送消息删除消息,可靠消息服务此时就会将消息删除,这样就意味着事务在上游消息投递过程中就被回滚了,而流程也就此结束了
    实现数据一致性是一个复杂的活。在这个方案中可靠消息服务作为基础性的服务除了执行正常的逻辑外,还得处理复杂的异常场景。在实现过程中可靠消息服务需要启动相应的后台线程,不断轮训消息的状态,这里会轮训消息状态为“待确认”的消息,并判断该消息的状态的持续时间是否超过了规定的时间,如果超过规定时间的消息还处于“待确认”的状态,就会触发上游服务状态询问机制。

可靠消息服务就会调用上游服务提供的相关借口,询问这笔消息的处理情况,如果这笔消息在上游服务处理成功,则后台线程就会继续触发上图中的步骤5,更新消息状态为“已发送”并投递消息至MQ服务;反之如果这笔消息上游服务处理失败,可靠消息服务则会进行消息删除。通过这样以上机制就确保了“上游服务本地事务成功处理+消息成功投递”处于一个原子操作了。

2. 下游服务(用户余额系统)如何确保对MQ服务Topic消息的消费100%都能处理成功?

在正常的流程中,下游服务等待消费Topic的消息并进行自身本地数据库事务的处理,如果处理成功则会主动通知可靠消息服务,可靠消息服务此时就会将消息的状态更新为“已完成”;反之,处理失败下游服务就无法再主动向可靠消息服务发送通知消息了。

此时,与消息投递过程中的异常逻辑一样,可靠消息服务也会启动相应的后台线程,轮询一直处于“已发送”状态的消息,判断状态持续时间是否超过了规定时间,如果超时,可靠消息服务就会再次向MQ服务投递此消息,从而确保消息能被再次消费处理。(注意,也可能出现下游服务处理成功,但是通知消息发送失败的情况,所以为了确保幂等,下游服务也需要在业务逻辑上做好相应的防重处理)。
  • 事实上,支付系统的数据一致性是一个复杂的问题,原因在于支付流程的各个环节都存在异步的不确定性,例如支付系统需要跟第三方渠道进行交互,不同的支付渠道交互流程存在差异,并且有异步支付结果回调的情况。

  • 除此以外,支付系统内部本身又是由多个不同子系统组成,除核心支付系统外,还有账务系统、商户通知系统等等,而核心支付系统本身也会被拆分为多个不同的服务模块,如风控、路由等用以实现不同的功能逻辑。某些场景我们无法通过分布式事务来实现数据一致性,只能通过额外的业务补偿手段,如二次轮训、支付对账等来实现数据最终一致性。

  • 综上所述,支付系统是一个复杂的系统,要完全实现数据的一致性单靠某一种手段是无法实现的,大部分情况下我们可以通过额外的业务补偿逻辑来实现数据最终一致性,只是这样补偿逻辑需要以更多的业务开发逻辑为代价,并且在时效性上会存在延迟的问题。

  • MQ(事务消息)(notify-两阶段提交加回调机制)- RocketMQ


RocketMQ

  • 很像阿里的notify

  • 事务发起方首先发送 prepare 消息到 MQ。
    在发送 prepare 消息成功后执行本地事务。
    根据本地事务执行结果返回 commit 或者是 rollback。
    如果消息是 rollback,MQ 将删除该 prepare 消息不进行下发,如果是 commit 消息,MQ 将会把这个消息发送给 consumer 端。
    如果执行本地事务过程中,执行端挂掉,或者超时,MQ 将会不停的询问其同组的其他 producer 来获取状态。
    Consumer 端的消费成功机制有 MQ 保证。

  • 从用户侧来说,用户需要分别实现本地事务执行以及本地事务回查方法,因此只需关注本地事务的执行状态即可;而在 service 层,则对事务消息的两阶段提交进行了抽象,同时针对超时事务实现了回查逻辑,通过不断扫描当前事务推进状态,来不断反向请求 Producer 端获取超时事务的执行状态,在避免事务挂起的同时,也避免了 Producer 端的单点故障。而在存储层,RocketMQ 通过 Bridge 封装了与底层队列存储的相关操作,用以操作两个对应的内部队列,用户也可以依赖其他存储介质实现自己的 service,RocketMQ 会通过 ServiceProvider 加载进来。
    从上述事务消息设计中可以看到,RocketMQ 事务消息较好的解决了事务的最终一致性问题,事务发起方仅需要关注本地事务执行以及实现回查接口给出事务状态判定等实现,而且在上游事务峰值高时,可以通过消息队列,避免对下游服务产生过大压力。
    事务消息不仅适用于上游事务对下游事务无依赖的场景,还可以与一些传统分布式事务架构相结合,而 MQ 的服务端作为天生的具有高可用能力的协调者,使得我们未来可以基于 RocketMQ 提供一站式轻量级分布式事务解决方案,用以满足各种场景下的分布式事务需求。

  • RocketMQ 阿里开源的消息中间件,原来叫做MetaQ; RocketMQ的各个环节,包括生产者,消费者,broker都是分布式的,所以基本可以保障由于网络原因丢掉,且RocketMQ存在重复消费的问题,所以文档明确表明了应该业务方自己实现幂等性.

  • Pulsar、RocketMQ、NSQ、RabbitMQ、Kafka TODO

注意

  • 如果由于上游程序bug,下游程序又有二次校验,会导致消息迟迟没消费成功而堆积。(如上游出仓,下游加分成等)

AT (自动模式)

  • AT 模式是一种无侵入的分布式事务解决方案。下面以Seata为例。

  • Seata实现了AT模式:Seata AT 模式

  • 在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

  • 一阶段:Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

  • 二阶段提交:因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

  • 二阶段回滚:Seata 需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就可能需要转人工处理。

  • 写隔离
    一阶段本地事务提交前,需要确保先拿到 全局锁 。
    拿不到 全局锁 ,不能提交本地事务。
    拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

  • 读隔离
    在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
    如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

回滚

  • 如果要人工处理。不太适合金钱业务,因为“after image”基本都是在变的。
  • 官方回答:
    Q: 5.脏数据回滚失败如何处理?
    A:
    1.脏数据需手动处理,根据日志提示修正数据或者将对应undo删除(可自定义实现FailureHandler做邮件通知或其他)
    2.关闭回滚时undo镜像校验,不推荐该方案。

注:建议事前做好隔离保证无脏数据

  • 实践验证:TODO

TCC

  • TCC(Try-Confirm-Cancel)的概念来源于 Pat Helland 发表的一篇名为“Life beyond Distributed Transactions:an Apostate’s Opinion”的论文。
  • TCC 提出了一种新的事务模型,基于业务层面的事务定义,锁粒度完全由业务自己控制,目的是解决复杂业务中,跨表跨库等大颗粒度资源锁定的问题。TCC 把事务运行过程分成 Try、Confirm / Cancel 两个阶段,每个阶段的逻辑由业务代码控制。
  • Try 阶段失败可以 Cancel,如果 Confirm 和 Cancel 阶段失败了怎么办?
    • TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等。
  • 相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。
  • 关键属性:应用层面的两阶段操作(应用的侵入性非常强,实现成本高), 强隔离性(预提交校验),性能并非最佳。

优点

  1. 解决了跨服务的业务操作原子性问题,例如组合支付、下订单减库存等场景非常实用
  2. TCC的本质原理是把数据库的二阶段提交上升到微服务来实现,从而避免数据库二阶段中锁冲突的长事务引起的低性能风险
  3. TCC异步高性能,它采用了try先检查,然后异步实现confirm,真正提交是在confirm方法中。

缺点

  1. 对微服务的侵入性强,微服务的每个事务都必须实现try、confirm、cancel等3个方法,开发成本高,今后维护改造的成本也高
  2. 为了达到事务的一致性要求,try、confirm、cancel接口必须实现幂等性操作
  3. 由于事务管理器要记录事务日志,必定会损耗一定的性能,并使得整个TCC事务时间拉长,可以考虑采用Redis的方式来记录事务日志

实践经验

  • 蚂蚁金服TCC实践,总结以下注意事项:
    ➢业务模型分2阶段设计
    ➢并发控制
    ➢允许空回滚
    ➢防悬挂控制
    ➢幂等控制
    
  1. 允许空回滚
    • Cancel 接口设计时需要允许空回滚。在 Try 接口因为丢包时没有收到,事务管理器会触发回滚,这时会触发 Cancel 接口,这时 Cancel 执行时发现没有对应的事务 xid 或主键时,需要返回回滚成功。让事务服务管理器认为已回滚,否则会不断重试,而 Cancel 又没有对应的业务数据可以进行回滚。

  1. 防悬挂控制
    • 悬挂的意思是:Cancel 比 Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而最终又收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作。

Saga [‘sɑɡə]

  • Saga 理论出自 Hector & Kenneth 1987发表的论文 Sagas。Saga 模式是一种长事务解决方案。

  • Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

  • 分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。

  • Saga 正向服务与补偿服务也需要业务开发者实现。因此是业务入侵的。

  • Saga可以看做一个异步的、利用队列实现的补偿事务。Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行。

  • 使用 Saga 模式开发分布式事务时,有两种协调不同服务的方式,一种是协同(Choreography),另一种是编排(Orchestration)

    1. 选择使用协同的方式处理事务时,服务之间的通信其实就是通过事件进行的,每一个本的事务最终都会向服务的下游发送一个新的事件,既可以是消息队列中的消息,也可以是 RPC 的请求,只是下游提供的接口需要保证幂等和重入
    2. 编排的方式引入了中心化的协调器节点,我们通过一个 Saga 对象来追踪所有的子任务的调用情况,根据任务的调用情况决定是否需要调用对应的补偿方案,并在网络请求出现超时时进行重试
    • (下游约束)使用 Saga 对分布式事务进行开发时,会对分布式事务的参与者有一定的约束,每一个事务的参与者都需要保证:
      1. 提供接口和补偿副作用的接口;
      2. 接口支持重入并通过全局唯一的 ID 保证幂等
  • Saga定义了两种恢复策略:

    1. 向后恢复,对整个Saga的执行结果撤销。
    2. 向前恢复,适用于必须要成功的场景。
  • 使用 Saga 实现分布式事务的优点有:

    • 微服务架构:通过对一些基础服务进行组合 / 编排来完成各种业务需求。
    • 数据库兼容性高:对每个服务使用何种数据库技术没有任何要求,服务甚至可以不使用数据库。
  • 使用 Saga 实现分布式事务的缺点有:

    • 要求服务提供补偿接口:增加了开发和维护的成本。
    • 不符合 ACID:没有涉及 Isolation 和 Durability。
  • Saga 从流程上,还可分为两种模式:Orchestration(交响乐)和 Choreography(齐舞)。

  • 关键:每一个参与者都是一个冲正补偿服务、异步。

  • 关键定义:补偿式事务的 Saga

  • 关键属性:性能较好,隔离性较差

方案例子

  1. Ebay提出的基于消息表,即可靠消息最终一致模型,但本质上这也属于Saga模式的一种特定实现。
  2. 消息队列MQ事务(可靠消息)(支持事务的消息中间件)
    • Apache开源的RocketMQ中间件能够支持一种事务消息机制,确保本地操作和发送消息的异步处理达到本地事务的结果一致。
  3. Saga Orchestration(引入了类似 XA 中的协调者的角色,来驱动整个流程);基于 Saga Orchestration 和 Kafka 的分布式事务:微服务中台技术解析之分布式事务方案和实践

Saga详细说明

  • 最直接的方法就是按照逻辑依次调用服务,但出现异常怎么办?那就对那些已经成功的进行补偿,补偿成功就一致了,这种朴素的模型就是Saga。但Saga这种方式并不能保证隔离性,于是出现了TCC。在实际交易逻辑前先做业务检查、对涉及到的业务资源进行“预留”,或者说是一种“中间状态”,如果都预留成功则完成这些预留资源的真正业务处理,典型的如票务座位等场景。
  • 当然还有像Ebay提出的基于消息表,即可靠消息最终一致模型,但本质上这也属于Saga模式的一种特定实现,它的关键点有两个:
    基于应用共享事务记录执行轨迹;
    然后通过异步重试确保交易最终一致(这也使得这种方式不适用那些业务上允许补偿回滚的场景)。
  • 仔细对比这些方案与XA,会发现这些方案本质上都是将两阶段提交从资源层提升到了应用层。
  1. Saga的核心就是补偿,一阶段就是服务的正常顺序调用(数据库事务正常提交),如果都执行成功,则第二阶段则什么都不做;但如果其中有执行发生异常,则依次调用其补偿服务(一般多逆序调用未已执行服务的反交易)来保证整个交易的一致性。应用实施成本一般。
  2. TCC的特点在于业务资源检查与加锁,一阶段进行校验,资源锁定,如果第一阶段都成功,二阶段对锁定资源进行交易逻辑,否则,对锁定资源进行释放。应用实施成本较高。
  3. 基于可靠消息最终一致,一阶段服务正常调用,同时同事务记录消息表,二阶段则进行消息的投递,消费。应用实施成本较低。

Saga 模式使用场景

  • Saga 模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
  • 事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。

优点

  • 一阶段提交本地数据库事务,无锁,高性能;
  • 参与者可以采用事务驱动异步执行,高吞吐;
  • 补偿服务即正向服务的“反向”,易于理解,易于实现;

缺点

  • Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。

实践经验

  • 与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持:
    • 空补偿:逆向操作早于正向操作时;
    • 防悬挂控制:空补偿后要拒绝正向操作
    • 幂等

注意

  • 这里要注意的是,在Saga模式中不能保证隔离性,因为没有锁住资源,其他事务依然可以覆盖或者影响当前事务。隔离性导致在金钱业务的情景中,可能出现”不够扣”的情况(给用户加钱之后,无法撤销,因为被用户花完了)。

XA (eXtended Architecture)

  • XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。
  • XA协议由Tuxedo首先提出的,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准。XA 规范主要定义了事务协调者(Transaction Manager)和资源管理器(Resource Manager)之间的接口。
  • XA则规范了TM与RM之间的通信接口,在TM与多个RM之间形成一个双向通信桥梁,从而在多个数据库资源下保证ACID四个特性。
  • TM和RM之间使用2PC协议。在XA规范的描述中,2PC是TM协调RM们完成已定义的全局事务的方法,AP找TM申请/注册全局事务的动作并不是二阶段提交的保障内容。
  • 目前MySQL中只有InnoDB存储引擎支持XA协议。
    • https://dev.mysql.com/doc/refman/5.7/en/xa.html
  • XA模式下的 开源框架有atomikos,其开发公司也有商业版本。
  • 优点:
    • 强一致性:实现了数据在多个数据库上的强一致提交。
    • 业务侵入性小:完全靠数据库本身的支持实现分布式事务,不需要改动业务逻辑。
  • 缺点:
    • 单点故障:协调者或者任意一个 XA 数据库都是能引起故障的单点(Single point of failure)。
    • 低性能:支持 XA 特性的数据库在设计上有大量的阻塞和资源占位操作, 数据体量和吞吐量扩展性差。
    • 数据库选型限制:对于服务的数据库选型引入了支持 XA 协议这个限制。
  • XA 在设计上没有考虑到分布式系统的特点,事实上是一个强一致、低可用的设计方案,对网络分隔的容忍度较差。
  • XA模式缺点:事务粒度大。高并发下,系统可用性低。因此很少使用。
  • 关键属性:DB层面(资源层)

Seata 的XA模式

  • 分布式事务如何实现?深入解读 Seata 的 XA 模式

  • Seata 已经支持的 3 大事务模式:AT、TCC、Saga 都是 补偿型 的。

  • 补偿型 事务处理机制构建在 事务资源 之上(要么在中间件层面,要么在应用层面),事务资源 本身对分布式事务是无感知的。

  • 事务资源 对分布式事务的无感知存在一个根本性的问题:无法做到真正的 全局一致性 。

  • Seata 1.2.0 版本重磅发布新的事务模式:XA 模式,实现对 XA 协议的支持。

  • XA 的价值:与 补偿型 不同,XA 协议 要求 事务资源 本身提供对规范和协议的支持。因为 事务资源 感知并参与分布式事务处理过程,所以 事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。

  • 除了 全局一致性 这个根本性的价值外,支持 XA 还有如下几个方面的好处:

    1. 业务无侵入:和 AT 一样,XA 模式将是业务无侵入的,不给应用设计和开发带来额外负担。
    2. 数据库的支持广泛:XA 协议被主流关系型数据库广泛支持,不需要额外的适配即可使用。
    3. 多语言支持容易:因为不涉及 SQL 解析,XA 模式对 Seata 的 RM 的要求比较少,为不同语言开发 SDK 较之 AT 模式将更 薄,更容易。
    4. 传统基于 XA 应用的迁移:传统的,基于 XA 协议的应用,迁移到 Seata 平台,使用 XA 模式将更平滑。
  • 从编程模型上,XA 模式与 AT 模式保持完全一致。上层编程模型与 AT 模式完全相同。只需要修改数据源代理,即可实现 XA 模式与 AT 模式之间的切换。

最大努力通知型

  • 回调客户端确认:支付宝会每间隔一段时间后,再向客户方发起回调请求,直到输出成功标识为止。

其他

  1. 为了交易系统更可靠,我们一般会在类似交易这种高级别的服务代码中,加入详细日志记录的,一旦系统内部引发类似致命异常(如超时情况),会有邮件通知。同时,后台会有定时任务扫描和分析此类日志,检查出这种特殊的情况,会尝试通过程序来补偿并邮件通知相关人员。
  2. 在某些特殊的情况下,还会有“人工补偿”的,这也是最后一道屏障。

Seata

  • 阿里开源分布式事务解决方案 Fescar:https://mp.weixin.qq.com/s/TFGRcHV6EgeLB45OEJPRXw
  • https://github.com/seata/seata
  • https://seata.io/zh-cn/docs/overview/what-is-seata.html

四种模式分析

四种分布式事务模式,分别在不同的时间被提出,每种模式都有它的适用场景

  1. AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学习成本。
  2. TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。
  3. Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。
  4. XA模式是分布式强一致性的解决方案,但性能低而使用较少。

总结

  1. 金钱业务的冻结&结算 接口,其实就是TCC模式,而消费&退款 就是Saga模式。
  2. 数据库事务对应刚性事务,以XA模式为例;业务事务对应柔性事务,以TCC,Saga,AT模式为例。
  3. 2PC一般是分布式事务的基础协议,具有普适性。
  4. 要多考虑“防悬挂控制“、”允许空回滚“。

分类归纳

  1. 分布式事务两种大分类

    1. 按模式分类:XA、AT、TCC、Saga。
    2. 按柔性事务分类:补偿型、异步确保型、最大努力通知型。
  2. 补偿型(资源之上-业务层或中间件):AT、TCC、Saga。

  3. 业务无侵入:XA、AT;业务侵入:TCC、Saga。

  4. 性能:Saga > TCC> XA、AT。

  5. 隔离型:XA>TCC、AT>Saga。

  6. 刚性事务满足ACID理论;柔性事务满足BASE理论(基本可用,最终一致)。

  7. 刚性事务:XA;柔性事务:AT、TCC、Saga。

  8. 异步确保型(可靠消息最终一致性):本地消息服务方案(ebay)、消息队列MQ事务;异步回调:最大努力通知型。

  9. Saga:普通RPC重试、异步确保型(主动)、异步回调(被动)。(异步只是提升性能的一种方式)

  10. 总结

    1. 一致性保证:XA > TCC = SAGA > 事务消息
    2. 业务友好性:XA > 事务消息 > SAGA > TCC
    3. 性能损耗:XA > TCC > SAGA = 事务消息
  11. 分布式事务的解决方案都很难做到有高一致性的同时,也有高性能,同时在实现上也有一定的难度。在业务允许的情况下,我们通常处理分布式事务的一般原则应是:业务规避 > 最终一致 > 强一致。

Q&A

XA跟2PC什么关系?

  1. XA规范中2PC是TM协调RM的方式。

XA VS TCC

  1. XA是数据库的2PC,TCC是业务层的2PC
  2. XA是数据库的分布式事务,强一致性,在整个过程中,数据一张锁住状态,即从prepare到commit、rollback的整个过程中,TM一直把持着数据库的锁,如果有其他人要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险。
  3. TCC是业务的分布式事务,最终一致性,不会出现长事务的锁风险,try是本地事务,锁定资源后就提交事务,confirm/cancel也是本地事务,可以直接提交事务,所以多个短事务不会出现长事务的风险。

XA如何在分布式事务中如何保证隔离性?

  1. 由RM直接连接各个数据源(支持XA协议),RM一般是个单独的服务?
  2. 后续通过Seata的XA模式深入了解TODO

分布式事务一致性与Paxos一致性的思考

  • 聊一聊分布式事务
  • 分布式事务解决方案,如TCC、Saga、本地消息表等,其本质都是2PC。
  • Paxos算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。
  • 2PC和Paxos都是解决关于“一致性”的问题,其实细想它们解决的问题不在一个层面。
    • 2PC要求分布式系统中的每个节点要不全部成功,要不全部失败,强调的是原子性。
    • Paxos要求多个副本之间的数据一致性,其实这里用“一致性”并不准确,应该用“共识(Consensus)”才对。例如2PC中的协调者单点的问题可以用Paxos算法通过选举出新的协调者来解决。

扩展

  1. GTS-阿里巴巴全新分布式事务解决方案
    • GTS既不在资源层也不在应用层,它是在中间件层解决事务的问题,这是它们本质的区别。
    • GTS有两个使用模式,AT和MT。AT是自动模式,可以完全自动回滚,可以覆盖90%左右的业务场景,所以比较推荐使用AT模式,它对业务无侵入,高效,强一致性。还有一种MT模式是GTS推出的兼容TCC的模式,因为有一些情况下是无法避免的要使用TCC模式。
    • 强一致?感觉不是。

Reference

  • XA 规范与 TCC 事务模型
  • 分布式事务(4)XA规范
  • 初识Open/X XA
  • Distributed Transaction Processing: The XA Specification
  • TCC、XA 、DTP区别
  • XA 规范与 TCC 事务模型
  • 柔性事务的定义与分类
  • MySQL 中基于 XA 实现的分布式事务
  • 分布式事务的4种模式
  • 分布式事务如何实现?深入解读 Seata 的 XA 模式
  • 讲清楚分布式事务选型:XA、2PC、TCC、Saga、阿里Seata
  • 分布式事务:深入理解什么是2PC、3PC及TCC协议
  • 一种提高微服务架构的稳定性与数据一致性的方法
  • 分布式系统事务一致性解决方案
  • 分布式事务中的三种解决方案详解
  • 分布式事务的实现原理
  • 关于如何实现一个TCC分布式事务框架的一点思考
  • 还不理解“分布式事务”?这篇给你讲清楚!
  • RocketMQ 4.3正式发布,支持分布式事务
  • 事件驱动架构(EDA)编码实践
  • 微服务中台技术解析之分布式事务方案和实践

金融业务容灾切换后监控和修正方案

发表于 2020-09-08

前言

  • 接关于MHA_Consul_MySQL高可用方案的简单总结和思考
    中所讲,该方案无法严格保证主从数据一致或者不丢数据,那么对数据准确性非常严格的业务,则需要在业务层面进行相应的对账和修正。

  • 对账的前提当然是要有流水,可以是业务流水(跟余额表事务绑定)或者是binlog的变更流水。

  • 既然是对准确性要求严格的业务,一般都应该会有业务流水。

方案描述

  • 方案以数据库切换事件为线索,描述切库前的事先准备、切库中的止损、切库后的监控和修正。

一、切库前

  1. 余额写redis缓存(如果有多机房,需要异步多写对应机房的redis,或nsq通知)

二、切库中

  1. 发生切库事件
  2. (切库之后的一段时间内)若有用户余额变动操作,比对redis和DB余额是否一致,不一致则阻断一段时间。
  • 缺点:不一致有可能是误伤(redis刚好写失败导致切库前就不一致)
  • 折中:针对一些大额变更才阻断,增加一些更细致的策略

三、切库后

  1. 指定主库和从库,对比流水日志
  2. 修复数据(若有不一致),并关闭冻结开关
  3. 无法修复的订单如何处理(用户余额不足)?
    • 考虑当坏账处理;通过余额调整(邮件审批方式同步团队成员);再按流水订单重新扣除

对比流水和修复大致思路

  1. 读旧主库和新主库的余额变更流水日志
  2. 对比得到旧主库存在而新主库不存在的余额变更流水日志
  3. 获取受影响的用户的余额,重放流水日志模拟分析,得到受影响人数,受影响金额,不可修复订单(比如余额不足)等情况
  4. 按照流水日志中的业务类型重新执行相应的业务sql(充值,消费,提现等)
  5. 修复完成之后,重建旧主库,并挂为主库

金融业务容灾体系简述

发表于 2020-09-08

概述

  • 本文针对金融类(如用户虚拟货币)这类对数据准确性很敏感的业务的容灾架构;
  • 案例中的服务架构为同城主备架构;
  • 内容包括:容灾架构、故障切换、故障恢复、演练方案等;
  • 涉及技术点:MySQL、MHA、Consul等;
  • 本文基于
    1. 关于MHA-Consul-MySQL高可用方案的简单总结和思考
    2. 如何解决脑裂问题
  • 由于某种原因,某些细节不会描述。
  • 关于系统可用性,收藏了一张网图
  • MTTF 是 Mean Time To Failure,平均故障前的时间,即系统平均能够正常运行多长时间才发生一次故障。系统的可靠性越高,MTTF 越长。(注意:从字面上来说,看上去有 Failure 的字样,但其实是正常运行的时间。)
  • MTTR 是 Mean Time To Recovery,平均修复时间,即从故障出现到故障修复的这段时间,这段时间越短越好。
  • 故障是正常的,而且是常见的。
    故障是不可预测突发的,而且相当难缠。
    我们要干的事儿就是想尽一切手段来降低 MTTR——故障的修复时间。
    – 陈皓

容灾架构

  1. 红色线条为正常情况下的服务主链路;
  2. 同城主备架构: 主-IDC-A;从-IDC-B;
  3. 域名灾备:前端通过主域名访问后端接口,当响应超时或者返回50x的时候,前端将切换到备用域名访问后端接口;
    • 异常情况可能是后端服务异常或者用户所在地区网络不稳定等原因;
    • 针对不幂等的接口,应设置成只切换域名不进行重试;
    • 后端服务可以根据自身服务的异常情况,返回50x,让前端切换到备用域名
  4. 主域名为CDN域名,连接北京两个机房,备用域名是非CDN域名,直接连接北京第三个机房;
    • 使用CDN域名的原因:不把回源点暴露出去,从而把核心机房暴露出去。
  5. 使用MHA+Consul作为DB的切换方案;
  6. 每个IDC的gateway配置服务的主权重都是在IDC-A;发生故障切换时,gateway将把服务主权重切换到IDC-B,而通过外网进入IDC-A的流量,服务通过返回50x由前端切换到备用域名。

详细描述

故障切换

触发故障切换的场景

  1. 机房孤岛 (MHA+Consul出发切换)
  2. 网络抖动 (由决策服务通过健康检查手段决策)
  3. 服务自身异常或机器问题等 (MHA+Consul出发切换)

1) MHA+Consul切换

  • 触发条件:1或3 (主库不可访问)
  • MHA+Consul切换后,决策服务切换gateway的服务权重

2) 决策服务切换

  • 触发条件:2 (网络抖动)
  • 决策服务健康检查异常后,通过MHA+Consul主动切换主库,并切换gateway的服务权重

机房容灾场景(单机房故障)

1) 机房内外网不通

  1. IDC-A故障
    1. 服务权重通过gateway切换到IDC-B
    2. 外网无法进入IDC-A,只能进入IDC-B
    3. CDN域名检测异常后剔除IDC-A的服务节点
    4. 故障瞬间由于IDC-A不通,cdn结点会重试到IDC-B或者返回前端50x,前端访问备用域名。
  2. IDC-B故障
    1. 服务权重无需调整
    2. 外网无法进入IDC-B,只能进入IDC-A
    3. CDN域名检测异常后剔除IDC-B的服务节点
    4. 故障瞬间由于IDC-B不通,cdn结点会重试到IDC-A或者返回前端50x,前端访问备用域名。

2) 机房孤岛(内网不通,外网通)

  1. IDC-A故障
    1. 服务权重通过gateway切换到IDC-B
    2. 通过外网进入IDC-A的流量,服务自杀返回50x,前端访问备用域名
      • 服务自杀参考:如何解决脑裂问题
  2. IDC-B故障
    1. 服务权重无需调整
    2. 通过外网进入IDC-B的流量,服务自杀返回50x,前端访问备用域名

3) 机房网络抖动

机房服务异常(内外网都通,但该机房服务有问题,网络不稳定,丢包率高等,需机房切换)。
流量进入服务之后处理同2)只是切换策略不同。

决策服务

  1. 通过Consul判断是否孤岛
  2. 通过接口调用失败率,apm,监控网络丢包率等指标判断
  3. 相关服务同步切

单元化

  1. 切换需要把相互依赖的服务整体进行切换,尽量避免跨机房调用。

实现

gateway如何实现

  • 可以考虑结合服务发现,以localproxy代替nginx代替服务权重配置

其他注意事项

  1. 定时任务是否要切换
  2. MQ消费或生产是否要切换
  3. 服务强依赖要单元化,比如强依赖方主备,自己的服务做双活也没用
  4. MQ类发送异步进行
  5. 留意nginx重试机制,如以下配置:proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504;,501是不重试的,超时和其他50x会重试。
    • nginx的重试机制

其他技巧

  1. 服务所依赖的外部服务分为强依赖和弱依赖。弱依赖可以继续降级,而强依赖则不可以。
  2. 强依赖可以进一步划分成读依赖和写依赖。通常情况下写依赖是几乎不可能优化的,只能依赖其高可用,比如扣费服务。而读依赖则有优化空间,读依赖可以分为实时性依赖和非实时性依赖,后者可以通过缓存来解决强依赖的问题,比如用户的信息。这部分数据通常的强依赖的,但是又可以缓存下来。
  3. 强实时性读依赖的,故障情况下,可以考虑降级成非实时性依赖。比如采取先读远端数据,失败时降级成读缓存的策略。

演练方式

  1. 前期:tcpcpoy线上流量,复制线上服务部署单独一台演练环境,包括应用和db
  2. kill服务,killDB
  3. iptables屏蔽端口
    • iptable和kill应用不一样。前者会有readtimeout,后者直接reset peer
    • kill应用的方式基本是平稳的,使用iptable屏蔽端口的方式,可能会有connectTimeout或readTimeout。
  4. 线上停机房
    • 停专线一定是孤岛,不存在一边内网通,另一边内网不通的情况,是一个内网(看上图的网络拓扑图)。

Q&A

  1. 备用机房(IDC-B)正常情况下无流量?
    • 配置万分之一的流量,确保切换前已经可用。
    • 确保切换之前备机房是可用的,是否可用通过观察日志情况。
    • 备用机房异常应该及时告警。
  2. 为什么跨机房同步不使用MySQL主从同步而使用otter?
    • 一般主从同步只在同机房或者同城进行。
    • otter是阿里开源的一个分布式数据库同步系统,尤其是在跨机房数据库同步方面,有很强大的功能。
    • ????
  3. 相关服务一同切换是否合理
    1. 机房异常一同切换可行;
    2. 如果只是单个服务异常,别的服务也要跟着切库不一定合理。决策服务应该支持业务自行配置切换策略。

如何解决脑裂问题

发表于 2020-09-05

什么是“Split Brain”(脑裂)问题?

Split Brain 是指在同一时刻有两个认为自己处于 Active 状态的 NameNode。对于HA集群而言,确保同一时刻只有一个NameNode处于active状态是至关重要的。否则,两个NameNode的数据状态就会产生分歧,可能丢失数据,或者产生错误的结果。

CAP理论

  • Consistency (一致性): 副本一致性特指强一致性;

  • Availiablity(可用性): 指系统在出现异常时已经可以提供服务;

  • Tolerance to the partition of network (分区容忍): 指系统可以对网络分区这种异常情 况进行容错处理;

  • CAP理论,对于P(分区容忍性)而言,是实际存在 从而无法避免的。因为,分布系统中的处理不是在本机,而是网络中的许多机器相互通信,故网络分区、网络通信故障问题无法避免。因此,只能尽量地在C 和 A 之间寻求平衡。

  • 对于数据存储而言,为了提高可用性(Availability),采用了副本备份。某数据块所在的机器宕机了,就去该数据块副本所在的机器上读取。但是,当需要修改数据时,就需要更新所有的副本数据,这样才能保证数据的一致性(Consistency)。因此,就需要在 C(Consistency) 和 A(Availability) 之间权衡。

  • Quorum机制,就是这样的一种权衡机制,一种将“读写转化”的模型。

Quorum机制

  1. Write-all-read-one(简称 WARO).(极端的方案)
    • 当Client请求向某副本写数据时(更新数据),只有当所有的副本都更新成功之后,这次写操作才算成功,否则视为失败。
    • WARO牺牲了更新服务的可用性,最大程度地增强了读服务的可用性。而Quorum就是更新服务和读服务之间进行一个折衷。
  2. Quorum 机制
    • 将 WARO 的条件进行松弛,从而使得可以在读写服务可用性之间做折中,得出 Quorum 机制。
    • Quorum机制是“抽屉原理”的一个应用。定义如下:假设有N个副本,更新操作wi 在W个副本中更新成功之后,才认为此次更新操作wi 成功。称成功提交的更新操作对应的数据为:“成功提交的数据”。对于读操作而言,至少需要读R个副本才能读到此次更新的数据。其中,W+R>N ,即W和R有重叠。一般,W+R=N+1
    • 仅仅依赖 quorum 机制是无法保证强一致性的。因为仅有 quorum 机制时无法确 定最新已成功提交的版本号,除非将最新已提交的版本号作为元数据由特定的元数据服务器或元数 据集群管理,否则很难确定最新成功提交的版本号。
      • 1)如何读取最新的数据?—在已经知道最近成功提交的数据版本号的前提下,最多读R个副本就可以读到最新的数据了。
      • 2)如何确定 最高版本号 的数据是一个成功提交的数据?—继续读其他的副本,直到读到的 最高版本号副本 出现了W次。
    • Quorum 机制的三个系统参数 N、W、R 控制了系统的可用性,也是系统对用户的服务承诺:数 据最多有 N 个副本,但数据更新成功 W 个副本即返回用户成功。对于一致性要求较高的 Quorum 系 统,系统还应该承诺任何时候不读取未成功提交的数据,即读取到的数据都是曾经在 W 个副本上成 功的数据。

Lease 机制

  • Lease 是由颁发者授予的在某一有效期内的承诺。颁发者一旦发出 lease,则无论接受方是否收到,也无论后续接收方处于何种状态,只要 lease 不过期,颁发者一 定严守承诺;另一方面,接收方在 lease 的有效期内可以使用颁发者的承诺,但一旦 lease 过期,接 收方一定不能继续使用颁发者的承诺。
  • Lease 机制依赖于有效期,这就要求颁发者和接收者的时钟是同步的。对于这种时钟不同步,实践中的通常做法是 将颁发者的有效期设置得比接收者的略大,只需大过时钟误差就可以避免对 lease 的有效性的影响。

解决脑裂的关键

raft是如何解决的?

  1. 核心是“大多数成功”机制。Raft就是基于Quorum机制下实现的。
  2. 看下以下这篇文章:为 Raft 引入 leader lease 机制解决集群脑裂时的 stale read 问题
    • 这种方法牺牲了一定的可用性(在脑裂时部分客户端的可用性)换取了一致性的保证。
    • 多数派的网络分区挂了,岂不是直接不可写了
  3. 如何解决多数派的网络分区挂了,服务就不可用的问题?
    • 我的答案是部署不止两个网络分区,至少三个网络分区;这样任意一个分区挂了,服务都可用,除非三个分区的大多数分区挂了(挂了2个);
  4. 副本控制协议分为两大类:“中心化(centralized)副本控制协议”和“去中心化(decentralized) 副本控制协议”。解决脑裂的其中一个关键点是哪种 “去中心化的协议”,paxos和raft都是。

使用非分布式存储的应用如何解决?

  1. 上述处理脑裂问题的前提是:服务本身就是一个分布式存储服务,使用raft等一致性协议;那么普通的业务服务,使用MySQL的的情况下,怎么解决脑裂问题?
  2. MySQL主从服务复制,这种存储服务本身就不是去中心的分布式数据库;
  3. 解决问题的核心点是:引入第三方组件作为整个业务集群的“选主服务”(比如Consul、Etcd,都是基于Raft协议);结合MySQL的半同步复制机制:支持配置n个从库ack才响应客户端(类似Quorum机制)和MHA,可以很大程度的避免丢数据导致不一致。
  4. Consul本身就是基于raft协议,可以利用其去中心化的特性,作为选主服务,业务通过Consul识别到哪个集群是主集群之后,可定制进行相应的业务处理。

选主服务

  1. Consul、Etcd (Raft)
    • 在本次案例中是通过判断是否能获得Consul集群中的leader来判断孤岛的(调获取某个node的信息接口,如果没有leader会返回“No cluster leader”);而不是通过Consul分布式锁的方式选主。
  2. ZooKeeper (ZAB、Paxos )
  3. Chubby 分布式锁;Google的锁,才是分布式锁?

使用Consul解决脑裂问题

如何判断“孤岛”?

  1. 在三个IDC部署一个Consul集群,该集群会存在一个leader,且每个IDC的service在网络正常的情况下,都能检测到哪个是leader;依赖Consul的raft协议选举判断网络孤岛;(为了后续描述简单,假设每个IDC只部署一个Consul结点)
  2. 每个IDC的service只访问当前IDC对应的Consul结点;当访问返回“No cluster leader”时,说明该网络分区已经是”孤岛”;
    curl 'http://consul:8500/v1/health/service/nodeName?passing&wait=1s&index=1&tag=master'
    No cluster leader
    
  3. 通过“No cluster leader”来判断孤岛的前提是,假设Consul是高可用的。因为当有两个Consul结点无法访问或挂掉的时候,也有能误判成没有leader(实际上并未出现网络孤岛,只是Consul集群异常);
  4. 引入双重检查机制,解决Consul集群因为可用性问题导致的误判的可能性;在Consul判断为网络孤岛的情况下,通过ping异地IDC的service的健康检查接口,二次确认;双重检查机制只能解决误判问题,若Consul集群不能高可用,那么判断孤岛的机制将失效。

如何处理“孤岛”?

  1. 判断为孤岛的网络分区的服务,如果接收到外网请求(因为是孤岛情况下其他IDC内网不通),可以拒绝服务,也可以返回特殊的业务错误码或HTTP错误码,由上层服务或客户端重试到其他的机房。

“孤岛”消失后如何恢复?

  1. 手动恢复。”孤岛”的网络分区服务不恢复提供服务,由开发人员确认DB等无异常后再手动恢复;
  2. 自动恢复。需做好充分的检测手段才能启用自动恢复:DB是否发生切换、主从DB数据是否一致(如比对脚本)、判断网络是否已经恢复到策略(防止来回切)等。

Q&A

为什么每个IDC的service只访问当前IDC对应的Consul结点?(前提是Consul高可用性)

  1. 假设IDC-A出现网络孤岛的情况下,IDC-A的service本身就无法访问IDC-B的Consul结点;
  2. 假设IDC-B未出现网络孤岛,IDC-A的service只需要访问IDC-A的Consul结点即可判断;

选主服务一定要选择基于去中心一致性协议的组件吗?

  1. 不一定。使用选主服务只是利用组件高可用性和选举机制解决脑裂问题。Raft等去中心一致性的协议解决的的一致性问题及其leader的选举机制。换句话,只要该组件能高可用且在结点大多数存活(网络互通)的情况下,能选举出一个Leader即可,数据的一致性并不重要(因为没用到组件提供的数据存储功能)。
  2. 一般情况下,业务使用MHA-Consul-MySQL作为DB切换的高可用方案;那么解决脑裂问题,也使用Consul组件,也可以减少服务对其他组件的依赖。

扩展

  1. Group Replication
    • Group Replication由至少3个或更多个节点共同组成一个数据库集群,事务的提交必须经过半数以上节点同意方可提交,在集群中每个节点上都维护一个数据库状态机,保证节点间事务的一致性。Group Replication基于分布式一致性算法Paxos实现,允许部分节点故障,只要保证半数以上节点存活,就不影响对外提供数据库服务,是一个真正可用的高可用数据库集群技术。 Group Replication支持两种模式,单主模式和多主模式。在同一个group内,不允许两种模式同时存在,并且若要切换到不同模式,必须修改配置后重新启动集群。 在单主模式下,只有一个节点可以对外提供读写事务的服务,而其它所有节点只能提供只读事务的服务,这也是官方推荐的Group Replication复制模式。
  2. NewSQL
    • 关于NewSQL的定义是:这是一类现代关系型的DBMS,旨在为NoSQL的OLTP读写负载提供相同的可扩展性能,同时仍然提供事务的ACID保证。
    • TiDB。TiDB 是一个分布式 NewSQL 数据库。它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适OLAP 场景的混合数据库。

Reference

  • 刘杰:分布式原理介绍
  • 分布式系统理论之Quorum机制
  • 关于MHA_Consul_MySQL高可用方案的简单总结和思考
  • MySQL · 引擎特性 · Group Replication内核解析
  • 我们听到的TiDB到底是什么?

关于MHA_Consul_MySQL高可用方案的简单总结和思考

发表于 2020-08-31

概述

MHA + Consul + MySQL的高可用方案,网上已经有很多资料,这里只是做一下简要的总结和思考。文章部分文字摘自Reference中的链接。
直接先上一张图

  1. 图中的灾备架构适用于同城主备架构的业务,比如金钱业务;
  2. DB层是一主多从,主库至在其中一个机房,并在DB集群上搭建跨IDC的MHA;同样Consul也是跨IDC的集群;
  3. 业务的写主流量配置在主库机房的一侧,当主库故障发生DB切换产生新主库时,流量也跟从新主库一同切换;
  4. 主库切换通过MHA完成,并通过Consul集群和应用的SDK通知到业务服务。
  5. MySQL采用默认的异步复制方式

解决什么问题

  1. 解决MySQL的单点问题。当主库发生故障时能提升从库为新的主库,且应用层能自动识别;从而缩短故障的处理时间;
  2. 通过Consul能支持从库的故障切换以及从库扩容或下线;

每个组件各自的作用

  1. MHA:监控主节点,可以在故障时自动或手动切换、非故障时手动主从切换。切换时会进行数据补齐,并将新的master信息更新到consul中。
  2. Consul:MySQL服务注册并提供健康检查、记录最新的master;还可以提供其他配置性的服务,比如dns,可解析数据库域名。
  3. SDK: sdk通过consul-api监控MySQL ip列表的变化,能连接新的库和去掉已下线的库。

下面的部分将对使用到的技术逐一简要描述和总结。

MHA

介绍

MySQL MHA架构介绍:

MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就职于Facebook公司)开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中,MHA能做到在0~30秒之内自动完成数据库的故障切换操作,并且在进行故障切换的过程中,MHA能在最大程度上保证数据的一致性,以达到真正意义上的高可用。

该软件由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)。MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。整个故障转移过程对应用程序完全透明。

在MHA自动故障切换过程中,MHA试图从宕机的主服务器上保存二进制日志,最大程度的保证数据的不丢失,但这并不总是可行的。例如,如果主服务器硬件故障或无法通过ssh访问,MHA没法保存二进制日志,只进行故障转移而丢失了最新的数据。使用MySQL 5.5的半同步复制,可以大大降低数据丢失的风险。MHA可以与半同步复制结合起来。如果只有一个slave已经收到了最新的二进制日志,MHA可以将最新的二进制日志应用于其他所有的slave服务器上,因此可以保证所有节点的数据一致性。

目前MHA主要支持一主多从的架构,要搭建MHA,要求一个复制集群中必须最少有三台数据库服务器,一主二从,即一台充当master,一台充当备用master,另外一台充当从库,因为至少需要三台服务器,出于机器成本的考虑,淘宝也在该基础上进行了改造,目前淘宝TMHA已经支持一主一从。(出自:《深入浅出MySQL(第二版)》)
MHA工作原理总结为以下几条:
(1)从宕机崩溃的master保存二进制日志事件(binlog events);
(2)识别含有最新更新的slave;
(3)应用差异的中继日志(relay log) 到其他slave;
(4)应用从master保存的二进制日志事件(binlog events);
(5)提升一个slave为新master;
(6)使用其他的slave连接新的master进行复制。
自带脚本:
自动切换脚本:/usr/local/bin/master_ip_failover
手动切换脚本:/usr/local/bin/master_ip_online_change

图中的MHA阐述

  1. 服务为主备架构,部署在IDC-A和IDC-B,其中主流量在IDC-A;MHA MANAGER则部署在IDC-C(第三个机房),这样才能保证任意一边的IDC出问题,都不影响MHA的切换;
  2. 为了防止来回切导致严重数据问题。可以设置,当MHA发生自动切换时,MHA MANAGER无论成功与否都会退出;
  3. MHA在故障时可以进行数据补齐。

MySQL 半同步复制

主从复制的基本原理

  1. [master] SQL操作存入binLog中;
  2. [slave] 连接master,进行数据同步;
  3. [master] dump thread 把binlog数据发送到slave中;
  4. [slave] 创建I/O线程读取 master传输过来的binlog内容并写入到relay Log;
  5. [slave] 创建SQL线程,从relay Log读取并执行。

主从复制的方式

  1. 异步复制(Asynchronous replication)

    • MySQL默认的复制即是异步的,主库先提交事务,然后立即响应客户端。
    • 如果主库crash,且主库上已经提交的事务还没有同步到相应的从库上,那么当从库提升为主时,会导致新主上的数据不完整。
    • 性能最好
  2. 全同步复制(Fully synchronous replication)

    • 当主库执行完一个事务,且所有的从库都同步完之后才响应客户端。
    • 性能差
  3. 半同步复制(Semisynchronous replication)

    • AFTER_COMMIT;即参数 rpl_semi_sync_master_wait_point = after_commit
    • 介于异步复制和全同步复制之间,主库提交完事务之后不立即响应客户端,而是等待至少一个从库接收到并写到relay log中才响应客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。
    • 等待的时间以rpl_semi_sync_master_timeout参数为准,默认为10秒。在这等待的10秒里,对其他会话该事务是可见的;所以一旦master发生宕机,对外就会产生不一致的影响
    • Slave ACK超时,将退化为异步复制模式 (所以半同步复制并不是严格意义上的半同步复制)
  4. 增强半同步(Loss-less Semi-Synchronous)

    • AFTER_SYNC;即参数 rpl_semi_sync_master_wait_point = after_sync
    • after sync是MySQL5.7官方新加以解决MySQL5.6半同步缺陷的选项,也是官方推荐的方式。
    • 原理:客户端发送过来一个请求,经过mysql的SQL分析,存储引擎处理,写入binlog,然后 写入从库的relaylog,存储引擎层提交,最后返回给客户端。
    • 优点:主库把SQL操作先发给从库的relay log,然后再提交,再响应给客户端,这个过程即使 在storage commit之后主crash了,日志也已经写入到relay log中,从库和主库数据一致。
    • 在commit之前等待Slave ACK,可以堆积事务,利于group commit,有利于提升性能。
    • 在master接受到Slave ACK之前,数据的变化对其他会话时不可见的,因为此时并未提交,从 而也不会产生数据不一致的影响。
    • 同样,Ack超时,也将退化为异步复制模式
  5. 组复制 MySQL Group Replication(MGR)

    • MGR内部实现了分布式数据一致性协议,paxos通过其来保证数据一致性。

after_commit VS after_sync


  • after_commit:master把每一个事务写到二进制日志并保存到磁盘上,并且提交(commit)事务,再把事务发送给从库,开始等待slave的应答。响应后master返回结果给客户端,客户端才可继续。
  • after_sync :master把每一个事务写到二进制日志并保存磁盘上,并且把事务发送给从库,开始等待slave的应答。确认slave响应后,再提交(commit)事务到存储引擎,并返回结果给客户端,客户端才可继续。
  • 半同步和增强半同步都是等待slave的ACK后才给客户端返回成功(也就是整个流程完成)

总结

  • 一致性要求高的,比如金融类的(相比其他业务TPS较低),可以考虑开启增强半同步复制

其他

  1. MySQL 5.7新增了rpl_semi_sync_master_wait_for_slave_count系统变量,可以用来控制主库接收多少个从库写事务成功反馈,给高可用架构切换提供了灵活性。 当该变量值为2时,主库需等待两个从库的ACK。

Consul

介绍

  • Consul is a tool for service discovery and configuration. Consul is distributed, highly available, and extremely scalable
  • Consul是一个服务管理软件。支持多数据中心下,分布式高可用的,服务发现和配置共享。采用 Raft 算法,用来保证服务的高可用。
  • Consul使用Gossip Protocol来管理成员资格并向集群广播消息。所有这些都是通过使用Serf库提供的。
  • 关于raft算法原理,可以后续再讲。

和MHA的结合使用

  1. checkmysql 脚本部署到每台 consul server 中, 实现了多点检测 MySQL 是否正常;
  2. checkmysql 脚本在超过半数的情况下调用 masterha_manager_consul 脚本进行主从切换;

主从切换

  • MHA 是切换工具,控制数据库主从切换和数据补齐;
  • MHA 进行故障检测,故障时进行切换并通知Consul下发新的主库配置到应用服务。

从库上下线

  • Consul可以对从库进行健康检查,通过配置下发控制从库上下线。

扩展

CMDB

  • 配置管理数据库( Configuration Management Database,CMDB)
  • 自动化运维立足之本。在容灾切换管理工具中,可以直接一键从CMDB中同步所有业务系统,并能够非常灵活的定义每个业务系统的切换过程环节以及每个环节所有执行的具体操作。
  • CMDB与容灾备份的关联
  • 好的CMDB建设,应该具备这些要素
  • 【运维探讨】如何实现更加简单、高效、安全的灾备切换管理?

脑裂

  • 脑裂问题是分布式多活建设必须面临的问题之一。
  • 以上面的架构进行描述,当IDC-A和IDC-B、IDC-C网络不通时,其实IDC-A就是一个网络孤岛。
    这时候时IDC-B中的从库就会提升为主库,并开始接收写操作。因为IDC-A已经是个数据孤岛,服务的主从库并未发生改变(接收不到consul下发的配置),此时也接受外部的写操作请求,那么就会造成两边数据都有写操作,错误的双主架构导致错误的数据问题。

如何避免脑裂问题

  1. 如何让IDC的服务可以判断自身是否孤岛,从而拒绝服务,避免数据问题呢?
  2. 其中一个方案使用Consul作为选主服务来解决。后续另开文章叙述。
  3. 使用etcd:etcd 实现故障时主备秒级切换高可用架构
  4. 分布式锁服务Chubby(参见文献[Bur06])集群锁服务提供一个与文件系统类似的API用来操作锁。Chubby 可以处理异地、跨机房级别的锁请求。Chubby 使用Paxos协议来提供分布式一致性(具体描述参看第23章)。Chubby 是实现主实例选举(Master-Election)过程的关键组件。例如,一个服务有5个冗余实例在同时运行,但是只有一个实例可以处理对外请求,一般的做法是通过Chubby进行自动选举,选择一个实例成为主实例。Chubby适合存放那种对一致性要求非常高的数据。—《SRE:Google运维解密》

结合raft的思考

  • 为了避免脑裂,需要有选主机制,一般超半数的投票才能成为leader;Consul就是基于raft是实现的;
  • 在线上部署consul必须至少是三个机房,因为如果只有两个机房,其中一个机房挂了(刚好是大多数机器的机房),服务将不可用;
  • 分布式存储系统,需要解决数据的一致性问题和脑裂问题。
    1. raft有实现,但是首先得基于raft协议的文件存储;
    2. 使用MySQL作为存储,Consul作为选主服务的业务:Consul可以解决脑裂问题
      • MySQL的增强半同步复制机制 支持配置n个从库ack才响应客户端(其实和raft大多数结点写成功才算成功有点类似[Quorum机制]),可以很大程度的避免丢数据导致不一致。
  • 一般金钱业务使用同城两个机房,主备架构,那么不能完全和raft三机房匹配(三机房意味着一定每个机房至少有一个结点接收到数据才算成功)。当然每个机房可以有多个DB,比如同城的两个机房A合B,A有一主一丛,B有两从。增强半同步可以根据实际情况配置rpl_semi_sync_master_wait_for_slave_count参数,指定必须多少个从库成功。
    • MySQL使用异步复制的,一般情况下,切完机房保持数据一致性或检查监控数据一致性的方案,需要业务方自行监控和修正。
    • 就算使用了增强半同步,理论上数据也只是落盘到relay log,极端情况下,从库也可以立马故障,甚至无法恢复。这种极端情况基本不考虑了,只能业务自己权衡,允不允许丢数据,有没有其他修复数据机制(日志文件或对账等),要不要继续提供服务了。

Q&A

为什么Dump和I/O线程不能多线程?

  1. dump线程和IO线程都是负责网络传输,如果将这里扩展为多线程那么会将一份Binlog日志分割为多份,并行传输,那么在slave端将会要额外的增加代码考虑如何将多份日志还原为原来的Binlog,大大增加难度。
  2. 性能瓶颈不在IO,扩展后也没有多大效率提升。
  3. 为什么Redis 6.0使用IO多线程增强性能,MySQL这里使用IO多线程却不行?
    • Redis是多个Client节点一个Server节点(暂且这么看),IO线程需要处理多个不同Client来源的请求;MySQL主从复制,本质上是1个Client端一个Server端,增大IO线程也无济于事。

增强半同步是否会导致从有数据而主却没有?

  • 是。在Loss-less Semi-Synchronous模式下,master在调用binlog sync之后,engine层commit之前等待Slave ACK(需要收到至少一个Slave节点回复的ACK后)。这样只有在确认Slave收到事务events后,master事务才会提交,然后把结果返回给客户端。此时此事务才对其他事务可见。在这种模式下解决了after_commit模式带来的幻读和数据丢失问题,因为主库没有提交事务。但也会有个问题,假设主库在存储引擎提交之前挂了,那么很明显这个事务是不成功的,但由于对应的Binlog已经做了Sync操作,从库已经收到了这些Binlog,并且执行成功,相当于在从库上多了数据,也算是有问题的,但多了数据,问题一般不算严重。这个问题可以这样理解,作为MySQL,在没办法解决分布式数据一致性问题的情况下,它能保证的是不丢数据,多了数据总比丢数据要好。

MHA本身就支持自动切,为什么还要使用Consul?

  • MHA本身提供了自动切换主库的功能,但是MHA本身没有提供通知应用等机制。因此采用比较成熟的方案MHA+consul,并以SDK的方式接入。

自动切换安全吗?

  1. 直接启用masterha_manager 自动切换脚本并不安全,主要因为在网络抖动的情况下并不能保证数据库真的不能访问。不能仅通过一个点的检测就判断数据库不可访问。
  2. 通过 Consul( Consul 提供 dns 接口)集群的特性, 增加多点检测机制, 在 n 个集群的环境中, 有超过半数的检测点检测到数据库有问题, 就认为数据库不可访问, 这时调用 masterha_manager 脚本进行切换。
  3. 网络问题千变万化,在发生切换事件之后,需有相应的方案对主从流水数据进行对账或修正(确定基于binlog与应用日志的重要数据校验与补偿机制)。
  4. 发生切换事件之后,在确定数据已经无异常之前,需要防止再自动切回去,造成严重的数据异常。所以一般情况下,只能自动切一次,直到人工介入确认无异常,重新设置为自动切模式。当然,有完善的监控比对数据异常机制的情况下,可以考虑做成自动化,无需依赖人工介入。

MHA切换之后,主库禁写后,现有的连接是否还能继续写入?


mha切库禁写时,直接锁住整个实例,新操作无法写入,直接就设置read_only。

自动切的时机如何把握?

  1. 自动切的最佳时机很难人为,必须经过多种故障场景的测试,确定合适尽可能安全的切换策略和参数。
  2. 在实践中,初期可以先告警人工介入决定是否切换;确定好合适的切换策略和参数后,自动切在非核心业务中稳定正确运行一段时间后才在核心业务运用。

MHA能保证数据一定不丢吗?

  • 在MHA自动故障切换过程中,MHA试图从宕机的主服务器上保存二进制日志,最大程度的保证数据的不丢失。但如上所述,网络等问题时无解的,理论上还是存在丢的可能性。一致性要求高的,比如金钱类的(相比其他业务TPS较低),可以考虑开启半同步复制,大大降低数据丢失的风险。

新的主库和原主库数据一致性问题如何解决?

  1. MHA只能尽量保证数据补齐;
  2. 主从延迟较大时,切主库有风险;
  3. 开启半同步复制可以大大降低丢数据的风险,但也带来一定的性能损耗;
  4. 要做好切库后,主从日志流水对比修复方案

不一致如何止损?

  1. 根据业务情况进行实现,比如在发生切换事件的一端时间内(比如一个小时),阻断大额交易操作,等待开发确认后再恢复;
  2. 具体实现可以使用redis存储数据快照,执行前和数据库的数据进行对比判断是否阻断,或其他可行方案;
  3. 是否应该阻断,公司的利益和用户的利益,这是个哲学问题。。。

扩展

  • 云数据库使用数据库代理进行连接:读写分离扩展云数据库 MySQL 性能
  • 切换网络

Reference

  1. 基于 consul 架构的 MHA 自动切换
  2. 基于MHA+consul的MySQL高可用设计
  3. MySQL高可用之MHA的搭建
  4. MHA官方介绍
  5. 京东MySQL数据库主从切换自动化
  6. MySQL数据库的授权原则
  7. MySQL MHA 搭建&测试
  8. MySQL半同步复制
  9. MySQL - 异步复制,半同步复制,增强半同步复制,组复制,全告诉你
  10. mysql 半同步复制
  11. 增强半同步复制
  12. 【MySQL】5.7增强半同步AFTER SYNC&AFTER COMMIT

使用Redis实现榜单

发表于 2020-08-31

一、前言

  1. 使用redis实现
  2. 基于直播间业务场景
  3. 不阐述详细实现细节

二、相关Redis数据结构和命令

Redis 集合(Set)

  1. Sismember 命令判断成员元素是否是集合的成员。

Redis 有序集合(sorted set)

  1. Zrevrank 命令返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
  2. Zincrby 命令对有序集合中指定成员的分数加上增量 increment
  3. Zrevrangebyscore 返回有序集中指定分数区间内的所有的成员。有序集成员按分数值递减(从大到小)的次序排列
  4. Zremrangebyrank 命令用于移除有序集中,指定排名(rank)区间内的所有成员。

其他

  1. 查找特定前缀key:scan命令

三、如何实现

榜单保存

  1. 使用 Redis 有序集合(sorted set)保存榜单数据
  2. 如果是按时间排序的榜单,把时间戳存到score字段;如果是按礼物数量排序,把数量存到score;其他排序场景同理

榜单添加数据操作幂等

  1. 使用数据库日志表(唯一索引) (最严格可靠)
  2. 使用一个set保存所有的消息ID,并使用sismember防止重复处理(并发场景可能不幂等)

数据清理

  1. 使用一个set保存现有所有在线的房间榜单;定时任务检查房间是否在播,不在线的进行清理(同时可以监听房间下播时间)
  2. 保存一定数量的redis榜单:Zremrangebyrank 命令用于移除有序集中,指定排名(rank)区间内的所有成员
  3. 使用scan命令 查找特定前缀key的榜单,同1或2操作进行清理

四、扩展

  1. 房间榜单的标识是房间id有时可能不够,因为通过榜单有时是和本场开播挂钩的,同一个房间多次开播,可能导致不同场次的数据导致榜单错误。
    增加房间开播id标识可以解决。
  2. 老生常谈的哲学问题:二八定理。花费大量时间来得到较低的收益,实现时要从业务看是否值得。但不代表在做设计方案时不去考虑,这是严谨性的问题。
    按极端的方式考虑,实际实现按业务需要进行选择折中。

关于CDN缓存总结摘要

发表于 2020-08-20

CDN缓存的几点总结

  1. CDN资源的标识: url
  2. 跟HTTP缓存无关的请求头不影响CDN缓存(亲测)
  3. 不仅是静态资源,接口的数据可以做CDN缓存(亲测)
  4. CDN服务一般都有默认的缓存配置(比如一分钟),服务端可以通过设置HTTP缓存相关的Header控制是否适用CDN缓存
  5. HTTP缓存和CDN缓存分别作为客户端缓存和服务端缓存

HTTP缓存

关键字段有Expires,Cache-Control ,Last-Modified ,Etag 四个字段,Expires和Cache-Control用来确定确定缓存的存储时间,Last-Modified 和Etag则用来确定缓存是否要被更新

强制缓存

控制强制缓存的字段分别是Cache-Control和Expires,其中Cache-Control优先级比Expires高

  1. Expires: HTTP1.0的,已废弃
  2. Cache-Control: HTTP1.1中用来控制缓存时间的参数 (Cache-Control:max-age=30;xxx;)
    public: 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
    private: 表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。
    max-age=seconds: 设置缓存存储的最大周期,相对于请求的时间缓存seconds秒,在此时间内,访问资源直接读取本地缓存,不向服务器发出请求。(与expires同时出现时,max-age优先级更高)
    s-maxage=seconds: 规则等同max-age,覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。(与expires或max-age同时出现时,s-maxage优先级更高)
    no-store: 不缓存服务器响应的任何内容,每次访问资源都需要服务器完整响应
    no-cache: 缓存资源,但立即过期,每次请求都需要跟服务器对比验证资源是否被修改。(等同于max-age=0)
    

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。

  1. Last-modified: 源头服务器认定的资源做出修改的日期及时间。精确度比Etag低。包含有If-Modified-Since或 If-Unmodified-Since首部的条件请求会使用这个字段。
  2. Etag: HTTP响应头是资源的特定版本的标识符。

如何控制不使用缓存

  • F5刷新:
    (Cache-Control: max-age=0)
    浏览器会设置max-age=0,跳过强缓存判断,会进行协商缓存判断【浏览器直接对本地的缓存文件过期,但是会带上If-Modifed-Since,If-None-Match(如果上一次response带Last-Modified, Etag)这就意味着服务器会对文件检查新鲜度,返回结果可能是304,也有可能是200.】
  • 强制刷新 (command+shift+R):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。
    ctrl+F5强制刷新:
    (Cache-Control: no-cache)
    跳过强缓存和协商缓存,直接从服务器拉取资源。【浏览器不仅会对本地文件过期,而且不会带上If-Modifed-Since,If-None-Match,相当于之前从来没有请求过,返回结果是200.】
  • 如何不缓存
    Cache-Control其他字段:
    no-cache: 虽然字面意义是“不要缓存”。但它实际上的机制是,仍然对资源使用缓存,但每一次在使用缓存之前必须向服务器对缓存资源进行验证。
    no-store: 不使用任何缓存
    禁止缓存:
    Cache-Control: no-cache, no-store, must-revalidate
    Expires:设为当前时间之前
  • 强缓存存在两种形式:from memory cache 与 from disk cache (浏览器F12查看)

CDN缓存

  • cdn缓存是一种服务端缓存
  • 与http缓存规则不同的是,这个规则并不是规范性的,而是由cdn服务商来制定
  • 回源的意思就是返回源站,何为源站,就是我们自己的服务器;CDN回源,没有资源就去源站读取,有资源就直接发送给用户。
  • cdn缓存配置,整体来说,建议和http缓存配置保持统一

不一致的影响

cdn的缓存配置会受到http缓存配置的影响,而且各个cdn服务商并不完全一致,以腾讯云为例,在缓存配置的文档中特别有以下说明。
这会对我们有什么影响呢?

  1. 如果我们http缓存设置cache-control: max-age=600,即缓存10分钟,但cdn缓存配置中设置文件缓存时间为1小时,那么就会出现如下情况,文件被访问后第12分钟修改并上传到服务器,用户重新访问资源,响应码会是304,对比缓存未修改,资源依然是旧的,一个小时后再次访问才能更新为最新资源
  2. 如果不设置cache-control呢,在http缓存中我们说过,如果不设置cache-control,那么会有默认的缓存时间,但在这里,cdn服务商明确会在没有cache-control字段时主动帮我们添加cache-control: max-age=600。
    注:针对问题1,也并非没有办法,当我们必须要在缓存期内修改文件,并且不向想影响用户体验,那么我们可以使用cdn服务商提供的强制更新缓存功能,主要注意的是,这里的强制更新是更新服务端缓存,http缓存依然按照http头部规则进行自己的缓存处理,并不会受到影响。

缓存配置

  • cdn缓存的配置并不复杂, 复杂的情况在于cdn缓存配置会受到http缓存配置的影响,并且不同的cdn运营商对于这种影响的处理也都不一致,实际使用时,建议去对应的cdn服务商文档中找到对应的注意事项。
  • CDN缓存控制:如果源站设置了 no-cache 、private、 max-age = 0 都遵循源站,CDN 是不会进行缓存的。
  • 又拍云为开发者执行缓存刷新提供了主动更新和被动更新两种方式。
    1. 主动更新主要是指同名资源在源服务器更新之后,开发者手动刷新文件。又拍云提供了可视化的操作台供用户执行缓存刷新操作,同时支持 URL 刷新和规则刷新。此外开发者也可通过 API 接口完成刷新操作。
    2. 被动刷新则是等文件在 CDN 节点的缓存过期之后,节点回源拉取源服务器上最新的文件。这个过程由 CDN 自动完成,无需手动操作。

扩展

  • 动态CDN加速

  • CDN的全称是Content Delivery Network,即内容分发网络

  • 动态CDN加速:非静态数据,通过CDN的加速来起到快速回源的效果的。使用到的就是CDN的快速传输的能力。其实也就是DSA(Dynamic Site Acceleration)

    • 传统的DSA有:
      • TCP 优化:设计算法来处理网络拥堵和包丢失,加快这些情况下的数据从cdn的恢复以及一些常见的TCP瓶颈
      • Route optimization:就是优化从源到用户端的请求的线路,以及可靠性,就是不断的测量计算得到更快更可靠的路线
      • Connection management:就是边缘和源之间,包括CDN之前的线路,采用长连接,而不是每一个请求一个连接
      • On-the-fly compression:就是数据在刚刚离开源的时候就进行压缩,可以缩短在整个网络之中的流通时间
      • SSL offload:加速或者说减少一些安全监测,减少原服务器执行这种计算密集型的压力
      • Pre-fetching:有的服务可以解析HTML文件,并将原始服务器预取缓存对象嵌入到文件中
    • 更可靠的连接(只要负责连接边缘服务器,如果直接走回源线路的话,线路会很长,不可靠)
  • 使用CDN隐藏服务器真实IP

    • 隐藏服务器真实IP是解决问题最好和最快的方法,但只针对小流量,大流量同样会扛不住。
      服务器前端加CDN中转,比如阿里云、百度云加速、360网站卫士、加速乐、安全宝等,如果资金充裕的话,可以购买高防的盾机,用于隐藏服务器真实IP,域名解析使用CDN的IP,所有解析的子域名都使用CDN的IP地址。此外,服务器上部署的其他域名也不能使用真实IP解析,全部都使用CDN来解析。
    • 另外防止服务器对外传送信息泄漏IP,最常见的是,服务器不使用发送邮件功能,如果非要发送邮件,可以通过第三方代理(例如sendcloud)发送,这样对外显示的IP是代理的IP。

Reference

  • 聊聊 CDN 缓存与浏览器缓存
  • http缓存与cdn缓存配置指南
  • CDN 基础架构及缓存控制
  • 从HTTP响应头看各家CDN缓存技术
  • HTTP 缓存机制
<1…111213…16>

154 日志
165 标签
RSS
© 2025 Kingson Wu
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4