目录
本文概览:介绍了如何通过heisenberg部署一个分库分表的服务。
1 背景
现在使用heisenberg进行分库分表。这里采用规则是按照user_id的后两位来进行分库分表,如user_id为xxx23,分库分表之后为,数据库为split_2;对应的表为customer_2_1,2表示分库index,1表示表的index。
2 数据库准备
1、建立10个库
split_0-slipt_9
2、 创建测试表
如对于split_1库中表是split1_0~split_1_9。对于创建表的sql为
1 2 3 4 5 |
CREATE TABLE `customer_1_0` ( `user_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键', `name` varchar(32) NOT NULL DEFAULT '' COMMENT 'varchar类型', PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='分库分表的demo' |
3 部署
3.1 代码
1、git clone的代码
https://github.com/brucexx/heisenberg
2、打包
1 |
mvn clean package |
3、拷贝target下面的heisenberg-server.zip文件到某一个目录下面,进行解压到/Users/test/local下面,此时就可以在/Users/test/local/heisenberg-server-1.0.6/可以看到:bin、conf、lib和logs 四个文件夹。
3.2 server.xml
在这个heisenberg服务的信息,可以直接使用默认的文件就可以
- 配置<system>,指定配置heisenberg服务的端口号
- 配置<user>,指定heisenberg的用户名、密码、schemas。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
?xml version="1.0" encoding="UTF-8"?> <heisenberg:server xmlns:heisenberg="https://github.com/brucexx/heisenberg"> <!-- 系统参数定义,服务端口、管理端口,处理器个数、线程池等。 --> <system> <!-- 设置mysqlServer连接NIO --> <property name="isBackNIO">false</property> <property name="serverPort">8176</property> <property name="managerPort">8177</property> <property name="initExecutor">16</property> <property name="timerExecutor">4</property> <property name="managerExecutor">4</property> <property name="processors">16</property> <property name="processorHandler">32</property> <property name="processorExecutor">32</property> <property name="clusterHeartbeatUser">_HEARTBEAT_USER_</property> <property name="clusterHeartbeatPass">_HEARTBEAT_PASS_</property> <property name="dataNodeHeartbeatPeriod">15000</property> <property name="isBackNIO">false</property> </system> <!-- 用户访问定义,用户名、密码、schema等信息。 --> <user name="root"> <property name="password">st0078</property> <property name="schemas">wms_shard</property> </user> </heisenberg:server> |
3.2 schemal配置
1、配置文件配置
- 配置<table>信息
- 配置数据库URL、用户名、密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?xml version="1.0" encoding="UTF-8"?> <heisenberg:schema xmlns:heisenberg="https://github.com/brucexx/heisenberg"> <schema name="wms_shard"> <table name="customer" dataNode="wmsDN$0-9" rule="userIDRule" /> </schema> <dataNode name="wmsDN"> <property name="dataSource"> <dataSourceRef>wmsDS$0-9</dataSourceRef> </property> <property name="poolSize">20</property> <property name="coreSize">2</property> <property name="heartbeatSQL">select user()</property> </dataNode> <!-- 配置数据的信息:URL、用户名和密码 --> <dataSource name="wmsDS" type="mysql"> <property name="location"> <location>localhost:3306/split_$0-9</location> </property> <property name="user">root</property> <property name="password">53061208</property> </dataSource> </heisenberg:schema> |
2、dataNode和dataSource的关系
可以理解dataNodel为一个数据库连接池,dataSource是要连接的数据库信息。
3.3 配置分表规则
1、配置文件
- 配置<dbRuleList>。分库规则取user_id的倒数第二位
- 配置<tbRuleList>。分表规则取user_id的倒数第一位
- 配置<tbPrefix>。库和表的映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?xml version="1.0" encoding="UTF-8"?> <rule> <tableRule name="userIDRule" forceHit="true" unless="SELECT"> <columns>USER_ID</columns> <dbRuleList> <dbRule><![CDATA[ #set($sub_str=$USER_ID%100+"") $!stringUtil.substring($stringUtil.alignRights($sub_str,2,"0"),0,1) ]]> </dbRule> </dbRuleList> <tbRuleList> <tbRule><![CDATA[ #set($sub_str=$USER_ID%100+"") #set($sub_str=$stringUtil.alignRights($sub_str,2,"0")) #set($db_flag=$stringUtil.substring($sub_str,0,1)) #set($tb_flag=$stringUtil.substring($sub_str,1)) #set($prefix="_"+$db_flag+"_"+$tb_flag)## $!prefix ]]> </tbRule> </tbRuleList> <!-- 0-9 10个表,每个表属于哪个结点 Map<Integer,Set<String>> --> <tbPrefix> <![CDATA[ def map = [:]; for (int i=0; i<10; i++) { def list = []; for (int j=0; j<10; j++) { list.add("_"+i+"_"+j); } map.put(i,list); }; return map; ]]> </tbPrefix> </tableRule> </rule> |
3.3 启动和执行sql
1、执行bin目录下面的脚本
(1)启动
1 |
sh startup.sh -c /Users/q/local/heisenberg-server-1.0.6/conf/ |
通过jps可以查看到服务已启动
55744 HeisenbergStartup
55786 Jps
54858 RemoteMavenServer
(2) 关闭
1 |
sh shutdown.sh -c /Users/test/local/heisenberg-server-1.0.6/conf/ |
(3) 重新启动
1 |
sh restart.sh -c /Users/test/local/heisenberg-server-1.0.6/conf/ |
2、连接数据库wms_shard
1 |
mysql -uroot -pst0078 -P8176 -h127.0.0.1 -Dwms_shard |
3、执行sql
(1)插入一条数据
1 |
insert into customer(user_id,name) values(2232,'ts') |
(2)为了验证,我们可以通过mysql客户端登录到数据库来查看在split_3的cutsomer_3_2中是否有数据
4 常用功能
4.1 只分表不分库-按日分表
对于一个数据库中某一个表进行按日分表。此时只涉及分表,不需要分库。
1、schema.xml
因为现在只有一个库,所以如下设置
- 在<table>指定datanode的时候不使用”wmsDN$0-9″,而直接写成”wmsDN”
- <dataNode>的name不使用”wmsDN$0-9″,而直接写成”wmsDN”
- <dataSource>中location中不使用split$0-9,而是写成split。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?xml version="1.0" encoding="UTF-8"?> <heisenberg:schema xmlns:heisenberg="https://github.com/brucexx/heisenberg"> <schema name="wms_shard"> <table name="customer" dataNode="wmsDN" rule="userIDRule" /> </schema> <dataNode name="wmsDN"> <property name="dataSource"> <dataSourceRef>wmsDS</dataSourceRef> </property> <property name="poolSize">20</property> <property name="coreSize">2</property> <property name="heartbeatSQL">select user()</property> </dataNode> <!-- 配置数据的信息:URL、用户名和密码 --> <dataSource name="wmsDS" type="mysql"> <property name="location"> <location>localhost:3306/split</location> </property> <property name="user">root</property> <property name="password">53061208</property> </dataSource> </heisenberg:schema> |
2、rule.mxl
假设队列按照batch_no分表,batch_no字段格式为xxx_yyyyMMdd,如xxxx_20161123。分表规则如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?xml version="1.0" encoding="UTF-8"?> <rule> <tableRule name="rule1" unless=""> <columns>BATCH_NO</columns> <!-- 分库规则直接返回0 --> <dbRuleList> <dbRule><![CDATA[0]]> </dbRule> </dbRuleList> <tbRuleList> <tbRule><![CDATA[ #set($tb_flag=$stringUtil.right($BATCH_NO,8)) #set($prefix="_"+$tb_flag)## $!prefix]]> </tbRule> </tbRuleList> <tbPrefix> <![CDATA[ def map = [:]; def list = []; for(int year=2016;year<=2017;year++){ for (int i=1; i<=12; i++) { if(year==2016&i<11){ continue; } def month=i<10?"0"+i:i; for(int j=1;j<=31;j++){ def day=j<10?"0"+j:j; list.add("_"+year+""+month+""+day); }; }; }; map.put(0,list); return map; ]]> </tbPrefix> </tableRule> </rule> |
4.2 同一个数据源存在分表和不分表两种类型
假设在一数据库中detail是按日分表,log表是不分表的,只需要修改schemal.xml就可以了:
- <schemal>中添加一个dataNode属性就可以了
- <table>只需要指定customer就可以了,不需要指定detail了
1 2 3 |
<schema name="wms_shard" dataNode="wmsDN"> <table name="customer" dataNode="wmsDN$0-9" rule="userIDRule" /> </schema> |
4.3 分布式部署台机器
在我们公司是通过bns来配置。类似一个zookeeper的功能,可以把一个请求分发到不同的heisenberg服务上。
4.4 查看每一个表执行时间
因为这个日志级别是debug,后续的改进不知道是否可以提升为info级别。所以我们修改log4j.xml如下日志级别为debug就可以了
1 2 3 4 |
<logger name="com.baidu.hsb" additivity="false"> <level value="debug" /> <appender-ref ref="COMMON" /> </logger> |
执行如下命令
1 |
grep customer_6_5 *.log |
返回结果为:
common.log:2017-01-19 00:34:47,597 DEBUG executor.MultiNodeTask [wmsDN[6]][select * from customer_6_5]exetime:16ms pre:585
5 编码规范
5.1 规范1 需要定义Dao
有了mapper,为什么还需要定义Dao?
因为分库分表时,需要在查询条件必须包含分库分表的字段,如果直接使用mapper,很容易忽略,如不能直接使用mapper中
1 |
int updateByPrimaryKeySelective(AssetAccounts record); |
我们应该在dao中封装一个如下方法,然后更新操作都通过这个方法进行,
1 2 3 4 5 6 7 8 9 |
/** * 更新持仓信息 * 只需要在accounts设置需要更新的内容 * * @param userId 用户id * @param itemId 产品id * @param accounts */ void updateAssetSelective(Long userId, Long itemId, AssetAccounts accounts); |
其中,userId和itemId是分库分表的规则依赖的字段,而且这两个字段可以组合成一个唯一键。
6 参考
1、作者官方博客 http://brucexx.iteye.com/blog/2041284
7 后续
还是感觉网上的这个资料太少了。到时候还是去看看mycat吧,官方博客:http://mycat.io/
附:生成分库分表的sql
生成分库分表的sql的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
public class SqlGenerator { private static final String UNDERSCORE = "_"; private static final String DOT = "."; public static void generateSql(String databasePrefix,String tableNamePrefix,String tableContent,String tableEnd,String dstPath){ int limit = 10; List<String> tables = Lists.newArrayList(); for (int i = 0; i < limit; i++) { for (int j = 0; j < limit; j++) { for (int k = 0; k < limit; k++) { String fullTable = databasePrefix + i + j + DOT + tableNamePrefix + UNDERSCORE + i + j + UNDERSCORE + k; StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("CREATE TABLE ").append(fullTable).append("(\n") .append(tableContent).append("\n") .append(tableEnd).append("\n"); sqlBuilder.append("\n"); tables.add(sqlBuilder.toString()); } } } try { FileUtils.writeLines(new File(dstPath),tables , false); } catch (Exception e) { } } public static void main(String[] args) { String database = "wms_db_"; String tableName = "t_user_total_trans_log"; String tableContent ="`F_id` bigint(21) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',\n" + "`F_trans_id` varchar(32) COLLATE gbk_bin NOT NULL COMMENT '内部交易订单号',\n" + "`F_user_id` bigint(20) NOT NULL COMMENT '用户userId',\n" + "`F_operation_type` tinyint(3) unsigned NOT NULL COMMENT '操作类型,1-add,2-subtract',\n" + "`F_create_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间',\n" + "`F_modify_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '修改时间',\n" + "`F_enabled` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '逻辑删除标志',\n" + "\n" + " PRIMARY KEY (`F_id`),\n" + " KEY `idx_transid_operation` (`F_trans_id`,`F_operation_type`)"; String tableEnd = ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=gbk COLLATE=gbk_bin COMMENT='用户购买产品的订单累加记录';"; String dstPath = "/Users/HeartThinkDo/t_user_total_trans_log.sql"; SqlGenerator.generateSql(database,tableName,tableContent,tableEnd,dstPath); } } |