Sharding当前是Atlas的分布式分支, 是Atlas最近重点开发的功能. source code
rpm 包安装(推荐)
请到 https://github.com/Qihoo360/Atlas/releases/ 下载相应的rpm包(Sharding2.2.1)直接安装即可.
源码安装
sharding是以sharding分支发布的:
git clone https://github.com/Qihoo360/Atlas.git
git checkout sharding
git pull origin sharding
编译sharding分支的Atlas还需要安装lemon:
git clone https://github.com/winkyao/lemon
cd lemon
mkdir build && cd build && cmake ..
make
sudo make install
之后就可以编译了(glib jemalloc libffi libevent等依赖请自行安装):
./bootstrap.sh
make
sudo make install
Atlas sharding 简介
Sharding的基本思想就是把一个数据表中的数据切分成多个部分, 存放到不同的主机上去(切分的策略有多种), 从而缓解单台机器的性能跟容量的问题. sharding是一种水平切分, 适用于单表数据庞大的情景. 目前atlas支持静态的sharding方案, 暂时不支持数据的自动迁移以及数据组的动态加入.
Atlas以表为单位sharding, 同一个数据库内可以同时共有sharding的表和不sharding的表, 不sharding的表数据存在未sharding的数据库组中.
目前Atlas sharding支持insert, delete, select, update语句, 只支持不跨shard的事务. 所有的写操作如insert, delete, update只能一次命中一个组, 否则会报"ERROR 1105 (HY000):write operation is only allow to one dbgroup!"错误.
由于sharding取替了Atlas的分表功能, 所以在Sharding分支里面, Atlas单机分表的功能已经移除, 配置tables将不会再有效.
关于垂直切分与水平切分的区别与优缺点, 在这里就不详细解释了.
兼容性
Atlas支持非sharding跟sharding的表共存在同一个Atlas中, 2.2.1之前的配置可以直接运行. 之前的配置如
...
proxy-backend-addresses = 192.168.0.12:3306
proxy-read-only-backend-addresses = 192.168.0.13:3306,192.168.0.14:3306
...
这配置了一个master和两个slave, 这属于非sharding的组, 所有非sharding的表跟语句都会发往这个组内. 所以之前没有Sharding的Atlas的表可以无缝的在新版上使用, 注意: 非Sharding的组只能配置一个, 而sharding的组可以配置多个. 下面的配置, 配置了Sharding的组, 注意与上面的配置区分
...
[group-0]
proxy-backend-addresses=192.168.0.15:3306
proxy-read-only-backend-addresses=192.168.0.16:3306
[group-1]
proxy-backend-addresses=192.168.0.17:3306
proxy-read-only-backend-addresses=192.168.0.18:3306
...
Sharding 架构
Atlas是无状态的, 对于后端的多个组, 可以配置任意多个Atlas实例, 这一点与MongoDB的mongos类似.
Sharding数据库组
在Atlas中, 将一个组看做是数据存储的单位, 一个组由一台master, 零台或者多台slave组成(mysql主从同步需要由用户自己配置). 每个组之间的数据独立, 没有关系, 表的数据的各个部分存储在各个组中.
组内读写分离
与非sharding的方案一样, Atlas sharding也支持组内的读写分离, 也就是说Atlas在命中了某个组之后, 还是会对这个组内的master和slave执行读写分离(读发送到slave, 写发送到master).
Sharding 数据切分策略
shard key
每一个shard table都有一个shard key, 其可以是主键, 也可以是非主键, 但是这个列必须是一个整数. Atlas会利用这个shard key来判断应该把这条记录存放到哪一个数据库组中.
现在Atlas Shardingh支持两种类型的数据切分: Range方式和Hash方式.
Range 方式
如上图中, shard Key范围在0-1000的数据存放在DbGroup0中, 范围在1000-2000的数据存放在DbGroup1中, 2000-MaxInt 的数据存放在DbGroup2 中. 这些范围的大小不需要相同.比如id为shard key的话, sql: "select * from test where id = 1500;", Atlas会将此语句发往DbGroup1. 暂时Atlas的range是静态的, 不支持动态的增加范围.
优点:
对于range的sql查询如(where id > 100 or id < 1000), range方式的sharding可以精确的命中后端的数据组, 不需要将sql发到各个mysql去请求数据, 节约了网络传输的消耗.
缺点
如果shard key是递增的, 那么可能会在一段时间内的所有sql都命中到同一个数据组, 没有体现出sharding的优势, range不适用于这种场景.
适用场景
range适用于对范围查询有大量需求, 并且shard key相对离散插入的情景
hash 方式
目前Atlas使用取模的方式实现Hash, 也就是说Hash(id) = id % dbgroup_count, 如id = 10, id % 3 = 1, 所以会命中到DbGroup1中.
优缺点
hash跟range方式是恰好相反的, hash 可以应对数据递增的情景, 即使是在递增的情况下, sharding的数据也是均匀分布在各个数据组内的, 但是其缺点就是对于范围的查询通常都需要查询所有的dbgroup, 网络的消耗比较大.
适用场景
hash 适用于shard key顺序增长, 并对范围查询的需求比较小的情景
关于支持的语句
Atlas sharding只对sql语句提供有限的支持, 目前支持基本的Select, insert/replace, delete, update语句, 支持全部的Where语法(SQL-92标准), 不支持DDL(create drop alter)以及一些管理语句, DDL请直连MYSQL执行, 请只在Atlas上执行Select, insert, delete, update(CRUD)语句, 对于以下语句, 如果语句命中了多台dbgroup, Atlas均未做支持(如果语句只命中了一个dbgroup, 如select count(*) from test where id < 1000, 其中dbgroup0范围是0 - 1000, 那么这些特性都是支持的)
- Limit Offset(支持Limit)
- Order by
- Group by
- Join
- ON
- Count, Max, Min等函数请不要在Sharding的表上使用这些特性, 如果对这种特性有需求请不要让此表sharding.
注意:
子查询在Sharding中可能会返回不正确的结果, 也请不要使用子查询. 请把语句拆分成多句执行
对于写操作, 如果写操作命中了多个数据库组, 由于部分成功(某个组执行失败)需要回滚的问题, 暂时不支持写操作命中多个数据组的语句.请拆分成多个sql语句执行.
Atlas可能会在接下来的版本中对其中的一些特性中做出支持.
关于事务支持
事务在Atlas的非sharding的表是完全支持的, 但是对于sharding的表, Atlas只能提供部分的支持(不支持跨dbgroup的事务). Atlas只支持事务中涉及单个dbgroup的语句, 例如有两个dbgroup0, dbgroup1, 其切分方式是range, 规则是dbgroup0: 0 - 999, dbgroup1: 1000 - 2000,
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into sharding_test(id, name, age) values(1, 'test', 0);
Query OK, 1 row affected (0.00 sec)
mysql> insert into sharding_test(id, name, age) values(1500, 'test', 0);
ERROR 1179 (sqlst): Proxy Warning - sharding dbgroup is in trans, transaction will not work across multi dbgroup
mysql> /*master*/select * from sharding_test where id < 1000;
+----+------+------+----------+----------+
| id | name | age | birthday | nickname |
+----+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
+----+------+------+----------+----------+
1 row in set (0.00 sec)
mysql> /*master*/select * from sharding_test;
ERROR 1179 (sqlst): Proxy Warning - sharding dbgroup is in trans, transaction will not work across multi dbgroup
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> /*master*/select * from sharding_test;
+----+------+------+----------+----------+
| id | name | age | birthday | nickname |
+----+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
+----+------+------+----------+----------+
1 row in set (0.00 sec)
请注意第二条语句, 由于之前将insert与dbgroup0绑定了, 所以从此之后Atlas在此事务中只接受涉及dbgroup0的语句, 其他语句将会执行失败. "/master/select * from sharding_test;"
执行失败是因为, 这个语句会命中所有的dbgroup, 也是同理, 不支持这种语句. 在commit之后, sharding dbgroup不再处于事务状态, 就可以执行跨shard的操作了
换句话说, 如果是hash方式sharding的表, 基本上事务是无法支持的, 因为hash的表, 大部分操作都是会涉及多个dbgroup的.
增加节点
注意: 暂时只支持range方式的节点扩展, hash方式由于需要数据迁移, 暂时未做支持.
扩展节点在保证原来节点的范围不改变的情况下, 如已有dbgroup0为范围0 - 999, dbgroup1为范围 1000 - 1999, 这个时候可以增加范围>2000的节点. 如增加一个节点为2000 - 2999, 修改配置文件, 重启Atlas即可.
示例
假设我们有以下一个sharding的表, 建表语句如下:
DROP TABLE IF EXISTS `sharding_test`;
CREATE TABLE `sharding_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(50) COLLATE utf8_bin NOT NULL,
`age` int(11) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`nickname` char(50) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
)
有两个dbgroup(数据库组), 每个dbgroup有一个master, 一个slave, sharding_test使用range的方式, 以id作为shard key, 属于test数据库, dbgroup0属于范围0 - 999, dbgroup1 属于范围 1000 - 1999, 数据库用户为root, 密码为mysqltest
dbgroup0 有一主一从, 分别在localhost:3307 (master)和localhost:3308 (slave)端口dbgroup1 有一主一从, 分别为localhost:3309 (master)和localhost:3310 (slave)端口
配置文件如下
[mysql-proxy]
admin-username = user
admin-password = pwd
admin-lua-script = /usr/local/mysql-proxy/lib/mysql-proxy/lua/admin.lua
proxy-backend-addresses = 127.0.0.1:3306
daemon = true
keepalive = false
event-threads = 4
log-level = debug
log-path = /usr/local/mysql-proxy/log
sql-log = realtime
proxy-address = 0.0.0.0:1234
admin-address = 0.0.0.0:2345
charset = UTF8
wait-timeout = 3600
pwds = root:S4HJu78/H/6I/aYp2Xdb8Q==
[shardrule-0]
table = test.sharding_test
type = range
shard-key = id
groups = 0:0-999,1:1000-1999
[group-0]
# master
proxy-backend-addresses=127.0.0.1:3307
# slave
proxy-read-only-backend-addresses=127.0.0.1:3308
[group-1]
proxy-backend-addresses=127.0.0.1:3309
proxy-read-only-backend-addresses=127.0.0.1:3310
Atlas sharding部分新增配置项,包含两个部分:
- shardrule. 一个shardrule对应一个分表规则,不同的shardrule通过下划线后面的数字区分。例如shardrule-0, shardrule-1….。一个shardrule里面有以下几项:
[shardrule-0]
table = test.sharding_test #分表名,有数据库+表名组成
type = range #sharding类型:range 或 hash
shard-key = id #sharding 字段
groups = 0:0-999,1:1000-1999 #分片的group,如果是range类型的sharding,则groups的格式是:group_id:id范围。如果是hash类型的sharding,则groups的格式是:group_id。例如groups = 0, 1
- group. 一个group一般包含一主多从,由master(proxy-backend-addresses)和slave(proxy-read-only-backend-addresses)组成。group之间的区别也是通过通过下划线后面的数字区分。在本地启动好这些后端mysql实例(主从同步为了演示方便, 我们先不配置). 我们用Atlas插入几条数据:
$ mysql -h127.0.0.1 -P1234 -uroot -pmysqltest -c
mysql> use test;
Database changed
mysql> insert into sharding_test(id, name, age) values(1, 'test', 0);
Query OK, 1 row affected (0.00 sec)
mysql> insert into sharding_test(id, name, age) values(50, 'test', 0), (999, 'test', 0);
Query OK, 2 rows affected (0.00 sec)
以上几条数据都插入到了dbgroup0, 请注意第二条多值插入的语句, 因为50和999都命中了dbgroup0, 所以其执行成功, 但是如果执行以下的语句:
mysql> insert into sharding_test(id, name, age) values(100, 'test', 0), (1500, 'test', 0);
ERROR 1105 (HY000): Proxy Warning - write operation is only allow to one dbgroup!
在sharding的表中, 这是不允许的, 因为id为100命中了dbgroup0, 而id为1500 命中了dbgroup1, 由于分布式的多值插入可能导致部分成功, 需要回滚, 这个Atlas暂不支持. update, delete, replace同理.
我们再插几条数据到dbgroup1:
mysql> insert into sharding_test(id, name, age) values(1000, 'test', 0), (1999, 'test', 0);
Query OK, 2 rows affected (0.00 sec)
我们现在用直连mysql来检验一下:
$ mysql -uroot -pmysqltest -h 127.0.0.1 -P3307
mysql> use test;
Database changed
mysql> select * from sharding_test;
+-----+------+------+----------+----------+
| id | name | age | birthday | nickname |
+-----+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
| 50 | test | 0 | NULL | NULL |
| 999 | test | 0 | NULL | NULL |
+-----+------+------+----------+----------+
3 rows in set (0.00 sec)
mysql> exit
Bye
$ mysql -uroot -pmysqltest -h 127.0.0.1 -P3309
mysql> use test;
Database changed
mysql> select * from sharding_test;
+------+------+------+----------+----------+
| id | name | age | birthday | nickname |
+------+------+------+----------+----------+
| 1000 | test | 0 | NULL | NULL |
| 1999 | test | 0 | NULL | NULL |
+------+------+------+----------+----------+
2 rows in set (0.00 sec)
可以看到结果都正确的插入到了各个dbgroup的master中(注意现在由于没有配置主从, 所以在从库中都是没有数据的). 我们现在在Atlas中执行一下select:
$ mysql -h127.0.0.1 -P1234 -uroot -pmysqltest -c
mysql> use test;
Database changed
mysql> /*master*/ select * from sharding_test;
+------+------+------+----------+----------+
| id | name | age | birthday | nickname |
+------+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
| 50 | test | 0 | NULL | NULL |
| 999 | test | 0 | NULL | NULL |
| 1000 | test | 0 | NULL | NULL |
| 1999 | test | 0 | NULL | NULL |
+------+------+------+----------+----------+
5 rows in set (0.00 sec)
可以看到, 所有的数据都取出来了, 不管是dbgroup0或是dbgroup1的. (加上/master/
的目的是因为现在从库中没有数据, 将语句强制发往主库)
我们再来在Atlas中执行一些语句:
mysql> /*master*/select * from sharding_test where id >= 1000 and id <= 1999;
+------+------+------+----------+----------+
| id | name | age | birthday | nickname |
+------+------+------+----------+----------+
| 1000 | test | 0 | NULL | NULL |
| 1999 | test | 0 | NULL | NULL |
+------+------+------+----------+----------+
2 rows in set (0.00 sec)
mysql> /*master*/select * from sharding_test where id < 1999;
+------+------+------+----------+----------+
| id | name | age | birthday | nickname |
+------+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
| 50 | test | 0 | NULL | NULL |
| 999 | test | 0 | NULL | NULL |
| 1000 | test | 0 | NULL | NULL |
+------+------+------+----------+----------+
4 rows in set (0.00 sec)
mysql> /*master*/select * from sharding_test where id IN (1, 1999);
+------+------+------+----------+----------+
| id | name | age | birthday | nickname |
+------+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
| 1999 | test | 0 | NULL | NULL |
+------+------+------+----------+----------+
2 rows in set (0.00 sec)
mysql> /*master*/select * from sharding_test where id between 1 and 1999;
+------+------+------+----------+----------+
| id | name | age | birthday | nickname |
+------+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
| 50 | test | 0 | NULL | NULL |
| 999 | test | 0 | NULL | NULL |
| 1000 | test | 0 | NULL | NULL |
| 1999 | test | 0 | NULL | NULL |
+------+------+------+----------+----------+
5 rows in set (0.00 sec)
mysql> /*master*/select count(*) from sharding_test;
ERROR 1105 (HY000): Proxy Warning - Sharing Hit Multi Dbgroup Not Support SQL
mysql> /*master*/select count(*) from sharding_test where id <= 999;
+----------+
| count(*) |
+----------+
| 3 |
+----------+
1 row in set (0.00 sec)
mysql> /*master*/select * from sharding_test where id <= 999 limit 1;
+----+------+------+----------+----------+
| id | name | age | birthday | nickname |
+----+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
+----+------+------+----------+----------+
1 row in set (0.00 sec)
mysql> /*master*/select * from sharding_test limit 1;
+----+------+------+----------+----------+
| id | name | age | birthday | nickname |
+----+------+------+----------+----------+
| 1 | test | 0 | NULL | NULL |
+----+------+------+----------+----------+
1 row in set (0.00 sec)
mysql> /*master*/select * from sharding_test where id <= 999 order by id desc;
+-----+------+------+----------+----------+
| id | name | age | birthday | nickname |
+-----+------+------+----------+----------+
| 999 | test | 0 | NULL | NULL |
| 50 | test | 0 | NULL | NULL |
| 1 | test | 0 | NULL | NULL |
+-----+------+------+----------+----------+
3 rows in set (0.00 sec)