Skip to content

Latest commit

 

History

History
241 lines (218 loc) · 16.3 KB

Sharding-Mycat-Overview-Quickstart.md

File metadata and controls

241 lines (218 loc) · 16.3 KB

分库分表框架系列:


项目情况

MyCat基于阿里早期开源的分库分表组件Cobar改进而来,以下参考Cobar的架构与实践

  • Amoeba:阿里B2B开发的分库分表中间件,应该开始于2006年左右,2008年开源,2010年已经广泛运用于阿里B2B业务,目前项目停滞;
  • Cobar:Amoeba进化版,后端由JDBC Driver改为原生MySQL通信协议,2011年10月发布,2012年开源在code.alibabatech上,后来迁移到github,目前项目停滞;
  • Mycat:由leaderus等人基于Cobar改进发展而来,后端由BIO改为NIO,改掉一些重要bug,功能增强(增加对Order By、Group By、limit等聚合功能的支持)。当年社区比较活跃,目前活跃度一般;
  • 2012年阿里云将TDDL和Cobar结合开发DRDS;

Mycat架构

  • Mycat实现了MySQL协议,MySQL命令行客户端、任何开发语言都能像直接连MySQL一样连接Mycat,对客户端透明,支持所有开发语言;
  • Mycat解析SQL语句,根据SQL参数和分片规则进行路由,跨分片查询对结果集进行汇总、重排序、分页、聚合等,将分库分表、读写分离等数据存储伸缩方案与应用隔离;

Mycat分布式事务

只支持MySQL XA分布式事务,但MySQL 5.7之前的版本XA事务有问题(例如PREPARE命令没有写入binlog,发生故障主从切换时造成数据不一致),只能在5.7之后的版本上使用。

Mycat基于MySQL XA实现了TC、TM功能,主要处理方式如下:

  1. 客户端执行set xa = on命令开启XA事务:
    Mycat收到set xa = on命令后,在当前session中生成一个全局事务ID xaTxId,不做其它处理,参考SetHandler
    这表示当前会话在Mycat-Server上开启了XA事务,但此时Mycat还不会在后端MySQL上开启XA事务。
  2. 客户端执行DML SQL语句:
    Mycat-Server开启XA后,在每个dataNode上执行第1个更新语句(如果strictTxIsolation设为true,则是执行第一个查询语句)时,向MySQL发送XA START xaTxID语句,在MySQL节点上开启XA事务,事务状态为TX_STARTED_STATE,参考MySQLConnection.synAndDoExecute(...)

    Mycat有一个特性,尽量延迟在MySQL中开启XA事务。客户端开启XA后,执行查询操作不会触发Mycat向MySQL开启XA,只有在第一次执行更新操作时才开启。这样做的目的是尽量重用后端连接,但无法满足REPEADABLE READ,可以通过server.xml中的strictTxIsolation关闭这个特性。
    strictTxIsolationfalse时:

    strictTxIsolationtrue时(这种情况Mycat按下面图示占用后端连接,并且禁用读写分离强制读主库等,客户端可以用select ... for update加锁,实现REPEADABLE READ隔离级别):

  3. 客户端执行事务提交:
    • 如果只操作了单个MySQL节点,则无需TC协调,按顺序执行XA END xaTxIdXA PREPARE xaTxIdXA COMMIT xaTxId,参考NonBlockingSession.commit()CommitNodeHandler
    • 如果操作了多个MySQL节点,则在MultiNodeCoordinator中处理(入口方法executeBatchNodeCmd)。
      主要协调过程为:
      1. 向所有MySQL节点发送XA END xaTxIdXA PREPARE xaTxId命令。
      2. PREPARE全部成功,则向所有节点发送XA COMMIT xaTxId命令;如果有节点PREPARE失败,则回滚。
      3. 所有节点COMMIT成功,给客户端返回提交成功;如果有节点COMMIT失败,则重试,包括收到失败消息后立即重试、Mycat启动时根据协调日志的事务状态进行重试等。

Mycat实现TC功能时,将XA事务状态保存在内存和本地文件中,其结构外层为XA事务ID,内层为参与者RM信息和事务状态,用于XA过程发生异常时,可以根据TC日志回滚或重试。

Mycat事务管理方案和代码比较简单,不够严谨,例如:

  • 方案方面:TC日志存本地文件,Mycat多实例组建集群会有问题;
  • 代码方面:MultiNodeCoordinator.okResponse(...) #L259,收到节点PREPARE成功消息时,只更新内存TC日志,没有写文件,如果异常重启会造成TC日志丢失(这行代码注释掉,估计是写文件没有并发控制会造成问题);

演示方案说明

表结构及拆分方案

使用my-demo项目作为演示,运行项目查看演示效果。

表结构:

拆分情况:

  • 逻辑库db_user
    • usr_user: 会员表
      • 分片字段user_id
      • 分片规则(user_id % 32) => { 0-15: dn1, 16-31: dn2 }
      • 数据节点dn1, dn2
    • usr_user_account: 会员登录账号表
      • 分片字段account_hashaccount.hashCode()的绝对值)
      • 分片规则(account_hash % 2) => { 0: dn1, 1: dn2 }
      • 数据节点dn1, dn2
  • 逻辑库db_order
    • ord_order: 订单主表。order_id创建订单时由订单服务在应用代码中生成;
      • 分片字段order_id
      • 分片规则(order_id % 32) => { 0-7: dn1, 8-15: dn2, 16-23: dn3, 24-31: dn4 }
      • 数据节点dn1, dn2, dn3, dn4
    • ord_order_item:订单明细表,与ord_order以父子关系表进行分片(Mycat ER分片),分片情况同ord_order
    • ord_user_order: user_idorder_id的自定义索引表,用于查询会员订单列表
      • 分片字段user_id
      • 分片规则(user_id % 32) => { 0-7: dn1, 8-15: dn2, 16-23: dn3, 24-31: dn4 }
      • 数据节点dn1, dn2, dn3, dn4
物理部署

dn0~4可以分别部署在不同机器的MySQL实例上,演示项目方便起见只使用了一个MySQL实例,多实例部署只需修改配置文件中dataNodedataHost映射关系即可。


部署Mycat Server

Mycat版本1.6.7.3,Mycat配置文件参考docs/mycat-conf

配置

使用了Mycat数据库方式的全局序列,SQL脚本参考../docker/mysql/scripts/1-mydemo.sql

  • server.xml:配置服务器参数,配置逻辑用户名密码:
    <!-- 为Mycat逻辑库定义用户、密码 -->
    <user name="mydemo" defaultAccount="true">
      <property name="password">mydemo</property>
      <property name="schemas">db_user,db_order</property>
    </user>
  • schema.xml:配置dataHost、dataNode、逻辑schema:
    <mycat:schema xmlns:mycat="http://io.mycat/">
      <schema name="db_order" checkSQLschema="false" sqlMaxLimit="100">
          <table name="ord_order" primaryKey="order_id" dataNode="dn$1-4" rule="order-rule">
      	    <!-- 主键order_item_id使用Mycat全局序列,设置autoIncrement后可以像MySQL自增字段一样,
      		     insert时不指定这个列,且支持last_insert_id()函数,避免在SQL中使用next value for MYCATSEQ_XXX,导致druid报错 -->
      	    <childTable name="ord_order_item" primaryKey="order_item_id" autoIncrement="true" joinKey="order_id" parentKey="order_id" />
          </table>
          <table name="ord_user_order" primaryKey="id" autoIncrement="true" dataNode="dn$1-4" rule="user-order-rule" />
          <!-- Seata的回滚表,Seata要求回滚表在业务库中,因此必须添加到mycat逻辑库中 -->
          <table name="undo_log" dataNode="dn1" primaryKey="id" />
      </schema>
      <schema name="db_user" checkSQLschema="false" sqlMaxLimit="100">
      	<table name="usr_user" primaryKey="user_id" autoIncrement="true" dataNode="dn1, dn2" rule="user-rule" />
      	<table name="usr_user_account" primaryKey="account" dataNode="dn1, dn2" rule="user-account-rule" />
      	<!-- Seata的回滚表,Seata要求回滚表在业务库中,因此必须添加到mycat逻辑库中 -->
      	<table name="undo_log" dataNode="dn1" primaryKey="id" />
      </schema>
      <dataNode name="dn0" dataHost="db1" database="mydemo-dn0" />
      <dataNode name="dn1" dataHost="db1" database="mydemo-dn1" />
      <dataNode name="dn2" dataHost="db1" database="mydemo-dn2" />
      <dataNode name="dn3" dataHost="db2" database="mydemo-dn3" />
      <dataNode name="dn4" dataHost="db2" database="mydemo-dn4" />
      <dataHost name="db1" maxCon="5" minCon="5" balance="0" writeType="0" dbType="mysql" dbDriver="native">
      	<heartbeat>select user()</heartbeat>
      	<writeHost host="localhost" url="localhost:3306" user="root" password="1234" />
      </dataHost>
      <dataHost name="db2" maxCon="5" minCon="5" balance="0" writeType="0" dbType="mysql" dbDriver="native">
          <heartbeat>select user()</heartbeat>
          <writeHost host="127.0.0.1" url="127.0.0.1:3306" user="root" password="1234" />
      </dataHost>
    </mycat:schema>
  • rule.xml:配置分片规则:
    <mycat:rule xmlns:mycat="http://io.mycat/">
        <tableRule name="order-rule">
      	<rule>
      		<columns>order_id</columns>
      		<algorithm>order-func</algorithm>
      	</rule>
        </tableRule>
        <tableRule name="user-order-rule">
      	<rule>
      		<columns>user_id</columns>
      		<algorithm>user-order-func</algorithm>
      	</rule>
        </tableRule>
        <tableRule name="user-rule">
      	<rule>
      		<columns>user_id</columns>
      		<algorithm>user-func</algorithm>
      	</rule>
        </tableRule>
        <tableRule name="user-account-rule">
      	<rule>
      		<columns>account_hash</columns>
      		<algorithm>user-account-func</algorithm>
      	</rule>
        </tableRule>
        <function name="order-func" class="io.mycat.route.function.PartitionByPattern">
      	  <property name="patternValue">32</property> <!-- 对32求模 -->
      	  <!-- 求模结果按照文件配置的规则映射到分片 -->
      	  <property name="mapFile">order-partition.txt</property> 
        </function>
        <function name="user-order-func" class="io.mycat.route.function.PartitionByPattern">
      	  <property name="patternValue">32</property>
      	  <property name="mapFile">order-partition.txt</property> <!-- 与订单公用分片规则 -->
        </function>
        <function name="user-func" class="io.mycat.route.function.PartitionByPattern">
      	  <property name="patternValue">32</property> <!-- 对32求模 -->
      	  <property name="mapFile">user-partition.txt</property> <!-- 求模结果按照文件配置的规则映射到分片 -->
        </function>
        <function name="user-account-func" class="io.mycat.route.function.PartitionByMod">
      	  <property name="count">2</property> <!-- 数据分为2片 -->
        </function>
    </mycat:rule>
  • order-partition.txt,配置分片映射关系:
    0-7=0
    8-15=1
    16-23=2
    24-31=3
    
  • sequence_db_conf.properties,配置Mycat全局序列位于哪个dataNode:
    GLOBAL=dn0
    ORD_ORDER_ITEM=dn0
    ORD_USER_ORDER=dn0
    USR_USER=dn0
    
Windows环境部署

Windows环境下载Mycat-server-1.6.7.3-release-20190927161129-win.tar.gz

有2种运行方式:

  • 通过bin\startup_nowrap.bat在命令行直接运行。

    注意:需要在命令行进入bin目录后再执行startup_nowrap.bat命令,否则会将命令行所处当前目录作为Mycat主目录,导致无法找到lib等目录,classpath无法加载jar文件。

  • 通过bin\mycat.bat install注册为Windows服务(以管理员身份运行),开机自动启动。

    注意:

    1. 需要修改conf\wrapper.conf文件,将wrapper.java.command=java改为全路径,例如wrapper.java.command=E:\dev-tools\java\bin\java,否则服务启动时报无法找到java命令,启动失败;
    2. conf\wrapper.conf中配置Mycat JVM启动参数;
Mac/Linux环境部署
bin/mycat start      # 启动
bin/mycat stop       # 停止
bin/mycat console    # 前台运行
bin/mycat status     # 查看启动状态

Mycat管理

Mycat启动之后,8066为数据端口;9066为管理端口,不能操作数据,只能执行Mycat管理命令。连接Mycat使用server.xml文件中定义的用户名和密码。

Mac环境连接Mycat必须指定TCP协议,否则会直接连接mysql的3306端口而不是Mycat,没有任何错误信息:

mysql -h localhost -P 8066 -uroot -p --protocol=TCP
mysql -h localhost -P 9066 -uroot -p --protocol=TCP

管理端口登录,通过show @@help;查看Mycat提供的管理命令。


Mycat使用

my-demo项目中使用Mycat,只需要改为Mycat的JDBC连接参数,包括端口号、账号密码、数据库名称等,相关属性都定义在parent pom中了。

my-demo项目配置了maven profile来启用Mycat,为package.sh指定-mycat选项打包即可:package.sh -mycat


备注

  • Mycat 2.0在开发中,参考Mycat2
    从新特性来看,结果集缓存、自动集群管理、支持负载均衡等主要特性没有必要由Mycat管理,使用第三方即可。
  • 简单性能对比测试
    Mac book pro,单机测试,50并发线程,对相同的业务逻辑功能(使用手机号+密码注册会员)进行测试,TPS指被测试业务逻辑的每秒执行次数(包含select from usr_user_account + insert into usr_user + insert into usr_user_account):
    • Mycat + MyBatis,分片: TPS在2200上下波动;
    • MyBatis,不分片: TPS在2600上下波动;
    • 纯JDBC,不分片: TPS在3400上下波动;
      单机测试,Mycat server的CPU占用对测试结果有一定影响。
      从结果看中间加一层mycat后性能有一定下降,但幅度不大,不及MyBatis与原生JDBC之间的差异。