改造MySQL实现跨IDC的多主数据库

1、引子

现在的系统大都是7 * 24不间断运行的,对高可用的要求很高。除此之外,系统需要保证不同城市,甚至于不同国家的可用性。

MySQL数据库一般来说是主从结构,通过跨地域部署主从复制查询可以保证跨地域的可用性。但是写操作一旦跨机房,网络的延迟和不可靠可能会造成执行时间超长或者执行失败。而这对于那些对高可用系统有跨地域的要求的系统来说,就是不能接受的了。

MySQL的多主复制

mysql本身具备多主复制的能力,但是我们不能在多个mysql上进行毫无顾忌的写,一旦两个mysql实例在同一个表的同一行记录都进行了写操作就会造成复制问题,或者是实例之间数据的不一致。

MySQL Group Replication

从MySQL 5.7.17开始,发布了MySQL Group Replication,Group Replication具备了多主的能力,它是一种强一致的实现,多个MySQL实例能够实时保证数据的一致性,但是它对网络延时要求高,而且比价适合于金融业务场景。一旦跨地域性能非常低,网络的延迟和抖动甚至于会造成整个系统的不可用。

2、跨IDC业务常见的模式

大多数配置了多主复制的mysql集群,在业务上我们也是写一个点,或者严格按照主键把请求哈希路由到不同的实例上来写。

当然大多数业务都是选择单点写,这样业务上好控制。当业务跨IDC部署时,跨IDC的写操作延时就会很高,影响业务性能。目前多IDC的业务部署有以下几种:

对称部署

1.png

这种模式能写性能非常高,但是对业务程序的要求更高,不仅增加了开发难度,而且部署成本会偏高,需要上层有对应的路由功能。而且一旦写请求对应的数据不属于本IDC,一样需要跨机房路由到其他机房去写。
而且这种模式一旦扩展到3个IDC的时候,往往需要在MySQL上部署环形复制,这种模式极易造成数据的不一致。

非对称部署

2.png

这种模式下,只有一个真正的主库,所有的写操作必须落到这个主库上,是最常见的模式。它不需要有路由来根据不同的key路由到不同的服务程序上,但是对于不在主库所在的IDC的服务程序需要实现读写分离。
这种模式顾名思义非对称,就是说不同IDC的服务程序部署有一定的差异性,一旦我们需要切换主库时很多情况下就需要手工操作,并且容易造成切换时的数据不一致。
同时,由于IDC2中的写操作是跨机房的,由于跨机房的网络延时和丢包影响,会对应用程序的服务性能产生巨大的影响。写操作过多,就不可取。

代理层提供的折中方案

3.png

从图中可以看出,虽然采用代理层能够解决对称部署,但是无法解决跨机房写延时的问题。代理层是一个支持多套MySQL集群的中间件平台,详细了解请参考:(http://www.jianshu.com/p/bc50221972ca)。

3、改造MySQL支持多主多写模式

目前MySQL的主从复制是一种强一致的数据库部署方案,我们公司内部有很多业务都是没有强一致性需求,而更亲赖于高可用性,需要容忍机房故障,在任何一个机房出现故障时,也不能影响服务的正常进行。所以这种场景,我们一般采用最终一致性数据库模型。

MyShard是一种最终一致性数据库,它一个支持多主多写模式的存储,是一个跨机房的对等部署的典范,但是MyShard的问题在于系统过重,部署和运维都是一件非常头痛的事情,目前我们数据库团队已经不再对外推广MyShard了,只是对线上已有的MyShard还是提供技术支持和维护。

但是目前我们公司还是有很多业务是对于这种多机房同时写有强需求,如何解决这个问题呢?我在去年就提出要开发一套基于MySQL的多写方案,最近花时间突击研究了一下终于有了突破,基本上实现了想要的轻量级的多主MySQL集群。

MySQL5.7的多源复制

从MySQL5.7开始,有了通信渠道的概念,每一个通信渠道都是一个从服务器从主服务器获得二进制日志的链接。这意味着每个通信渠道都得有一个IO_THREAD .我们需要运行不同的 “CHANGE MASTER” 命令, 对于每一个主服务器。我们需要用到 “FOR CHANNEL”这个参数来提供通信链接的名字。
举个例子:

MySQL > CHANGE MASTER to MASTER_HOST='127.0.0.1',MASTER_PORT=6301 , MASTER_USER='repl', MASTER_PASSWORD='repl',MASTER_AUTO_POSITION = 1 FOR CHANNEL="idc1";
MySQL > start slave for channel='idc1' ;

改造后多主存储的架构

多主MySQL是基于MySQL 5.7.19改造,充分利用了多源复制功能,每个MySQL实例都会成为集群中的其他的实例的从库。

4.png

改造后的MySQL是通用和多写的混合版本,对于不同的表采用不同的策略,支持多写的表有一定的限制:

1、存储在__mm 数据库下
2、必须添加一个 __version字段,并且是在表的第一个字段__version字段用来处理版本冲突,其中最后一个bit用来描述是否该记录以删除。

    最后一位为0:有效数据
    最后一位为1:删除数据

在该模式下,各个IDC的应用程序,都可以往机房内的MySQL实例进行读写操作。

改造点

  • 给MySQL添加新的语法 这里借用了MyShard的语法,给MySQL添加了SET语义的两个语法
写:HASET table set col1 = ? AND col2=? WHERE K1=? AND k2=? 
如果对应主键数据不存在,就插入数据,存在就更新数据
删:HADELETE FROM table where k1=? AND k2=? 
如果对应主键数据不存在,插入一条删除记录,存在,就改为带删除标记的数据
  • 给MySQL添加新的函数

mmversion(seed,delete_flag):函数代两个参数,第一个参数是版本生成的种子,如果为0,采用系统时间自动生成种子;第二个参数是删除标记。

mysql> select mmversion(0,0) ; 
+--------------------------------+
| mmversion(0,0)              |
+--------------------------------+
| 3232157937589817394 |
+--------------------------------+
1 row in set (0.00 sec)

mmversion_is_local(version):该函数用来返回是否是在本实例产生

mysql> select mmversion_is_local(3232157937589817394) ;
+-----------------------------------------+
| mmversion_is_local(3232157937589817394) |
+-----------------------------------------+
|                                1        |
+-----------------------------------------+

注意:该版本没有对MySQL基本的语法做任何逻辑上的变更,所以基本的查询语法、DML语法都是可以使用的,并且也可以用在多写的表上面。

4、如何使用多主MySQL

首先创建一个表:

CREATE TABLE `b` (
  `__version` bigint(20) unsigned NOT NULL,
  `id` bigint(20) auto_increment NOT NULL,
  `name` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

表的第一个字段必须是__version字段,bigint类型。

下面我们同过DML语法在这种多主表中的写法:

插入数据语法,可以采用自动生成id的功能:

mysql> insert into b ( __version, id, name) values ( mmversion(0, 0 ), mmuuid(),  '123') ;           
Query OK, 1 row affected (0.04 sec)

mysql> select last_insert_id() ;
+---------------------+
| last_insert_id()    |
+---------------------+
| 1617051614604954626 |
+---------------------+
1 row in set (0.00 sec)

mysql> select * from b where id= 1617051614604954626 ;
+---------------------+---------------------+------+
| __version           | id                  | name |
+---------------------+---------------------+------+
| 3234103229209909252 | 1617051614604954626 | 123  |
+---------------------+---------------------+------+
1 row in set (0.00 sec)

更新数据

按照最终一致性的原理,版本号大的会胜出,更新语法中一定要判断版本号,并更新版本号:

mysql> update b set name='234',__version=mmversion(0,0) where id=1 and __version < mmversion(0,0) ;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

也可以支持复杂的update

mysql> update b set name='4444', __version=mmversion(0,0) where name like '1%' and  __version < mmversion(0,0) ;           
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

删除数据(其实是给数据加删除标记)

mysql> update b set __version=mmversion(0,1) where name like '44%' and  __version < mmversion(0,1) ;
Query OK, 1 row affected (0.07 sec)
Rows matched: 1  Changed: 1  Warnings: 0

采用SET语法操作数据:

  • 在没有数据的情况下, 执行haset会插入数据,有数据的情况,haset会更新数据:
mysql> haset b set name='123' where id=1 ;
Query OK, 1 row affected (0.03 sec)

mysql> haset b set name='234' where id=2 ;
Query OK, 1 row affected (0.03 sec)

mysql> select * from b ;
+--------------------------------+----+--------+
| __version                       | id   | name |
+--------------------------------+----+--------+
| 3232159101525960754 |  1   | 123   |
| 3232159110115897394 |  2   | 234    |
+--------------------------------+----+--------+
2 rows in set (0.00 sec)
  • HADELETE删除数据
mysql> hadelete from b where id=1 ;
Query OK, 2 rows affected (0.23 sec)

mysql> select * from b ;
+--------------------------------+----+--------+
| __version                        | id  | name |
+--------------------------------+----+--------+
| 3232159183130343475 |  1  |   123  |
| 3232159110115897394 |   2  |   234  |
+--------------------------------+----+--------+
2 rows in set (0.01 sec)

hadelete不会实际的删除一条记录,而是把__version字段的删除标记为标位1。

查询数据:

mysql> select * from b where id=1 and __version % 2 = 0 ;
Empty set (0.00 sec)

mysql> select * from b where __version %2 = 0 ;      
+--------------------------+----+--------+
| __version               | id   | name |
+--------------------------+----+--------+
| 3232159110115897394 |  2 | 234 |
+--------------------------+----+--------+
1 row in set (0.00 sec)

真正的删除数据,

这种语法不建议开发人员使用,可能造成数据不一致,一般由dba清除数据用

delete from b where id=1 ;

5、一些实战技巧

使用自增字段

我们仍然可以使用自增id,这要求在集群中的不同MySQL实例要设置不同的偏移,例如:

实例1:

auto-increment-increment = 2  
auto-increment-offset = 1  

实例2:

auto-increment-increment = 2  
auto-increment-offset = 2  

插入数据:

mysql> insert into c ( __version, name ) values ( mmversion(-1,0), '444') ;
Query OK, 1 row affected (0.03 sec)

mysql> select last_insert_id() ;
+------------------+
| last_insert_id() |
+------------------+
|                1 |
+------------------+
1 row in set (0.00 sec)

mysql> select * from c where id=1 ;
+---------------------+----+------+
| __version           | id | name |
+---------------------+----+------+
| 3234105709553524740 |  1 | 444  |
+---------------------+----+------+
1 row in set (0.00 sec)

利用事物逻辑实现自己想要的SET功能

MySQL原生的语法和功能都没有改变,所以我们可以利用事物来处理一些复杂事情。

start transaction; 
select * from a where id=1 and __version < new_version for update ; 
if ( has key ) 
  update a set xxxx = ffff, __version = new_version where id=1; 
else 
  insert into a ( id, xxxx , __version) values ( 1, 'ffff' ,new_version); 
end
commit transaction ;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容