shardingsphere之sharding-proxy分库分表学习笔记

shardingsphere之sharding-proxy分库分表学习笔记

  • 引言
  • 重要提示
  • sharding-proxy简介
    • 概念
    • 特点
    • 使用情况
  • 从零开始整合sharding-proxy
    • 整合前的思考
    • 场景模拟
    • 搭建项目
      • 1. 建库建表
      • 2.下载sharding-proxy二进制压缩包
      • 3、解压zip包
      • 4、下载mysql驱动包
      • 5、编写yml文件
      • 6.编写config-sharding.yaml配置文件
      • 7.编写logback.xml日志记录文件
      • 8.启动sharding-proxy
      • 9.客户端连接
      • 10.基础CRUD代码生成
    • 项目运行
    • 分布式事务
    • 弹性伸缩
    • 配置zookeeper
  • 结语
  • 相关链接

引言

随着业务数据量的变大,单库单表已经不能满足需求了。当单表数据量超过五百万行,查询性能急剧下降。分库分表迫在眉睫,寻找一个简单实用的解决方案相信是很多小伙伴的想法。
我在看了好多的博客之后遇到了开源数据库中间件mycat和shardingsphere(前身是sharding-jdbc),经过一番比较之后,我选了京东开源的shardingsphere作为我的解决方案。
写这篇文章的目的有两个,一来是帮助刚入门学习shardingsphere的童鞋快速上手,减少时间成本,先看下怎么用再去看官方文档可以达到事半功倍的效果;二来是记录自己在学习过程中遇到的问题,方便以后在项目中的使用。

重要提示

(1)版本信息:

  • 操作系统:windows 7
  • JDK1.8
  • mybatis-plus-generator 3.1.0
  • mybatis-plus-boot-starter 3.1.0
  • druid-spring-boot-starter 1.1.18
  • MYSQL 5.7.10

(2)用于演示的代码和重要的参考链接已经放到文章的末尾,有需要的童鞋可直接下载查看。其中
sharding_proxy文件夹存放代码,sharding-proxy-server存放proxy服务端文件。

sharding-proxy简介

概念

太多的理论知识我就不赘述了,麻烦自己到官网去看。
shardingsphere之sharding-proxy分库分表学习笔记_第1张图片
sharding-jdbc 和 sharding-proxy对比

shardingsphere之sharding-proxy分库分表学习笔记_第2张图片

特点

配置好之后可作为独立的数据源使用,一个逻辑数据库代理着几个真实数据库,可以用客户端软件比如Navicat Premium 直接去连接和操作。很好的帮助我们处理分库分表的问题,基本不需要对现有的业务代码修改,减少时间成本。

使用情况

目前生产环境已使用的公司
在这里插入图片描述

从零开始整合sharding-proxy

(1)其实大部分的工作都是在编写conf目录下的yml文件,配置好之后应用还是跟以前的方式一样连接和操作,基本不需要对代码进行修改。
(2)逻辑数据源可以配置多个,每个yml文件代表一个逻辑数据源,可手动创建yml文件和编写规则实现多个逻辑数据源的使用
(3)本文演示的是单个逻辑数据源的配置和使用。

整合前的思考

首先你要对业务需要用到的表有一个清晰的认识。哪些表不需要拆分,哪些表需要拆分,表跟表之间是否存在关联。通过阅读官网和我的理解,我觉得主要分为这几种表:

  • 单库单表

这种表数据量不大,小于十万这样,而且跟其他表没有关联。这样的表不需要拆分,放在一个默认库中即可。比如:配置表,地区编码表。

  • 广播表

这种表数据量不大,没有必要拆分;但是跟其他表有关联关系。在每个库都保存一个完整表,当读取数据的时候随机路由到任一库,当写入数据时每个库下的表都写入。

  • 逻辑表

数据量较大需要拆分的表。比如说订单数据根据主键尾数拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名为t_order。

  • 绑定表

按我的理解就是父子表,常见的就是订单表和订单详情表,通过订单id关联。这种类型的表数据量大也是需要拆分的。

场景模拟

为了加深对sharding-proxy的理解,我在这里模拟了一个场景,基本涵盖了常见的情况,顺便把实现步骤和使用过程的问题也提一提。
在这里插入图片描述

搭建项目

1. 建库建表

按照前面表的关系图,我们可以划分一个默认库(存放单库单表和广播表)和三个库(存放逻辑表和广播表);如下所示。sql文件放在git地址的sql目录下。

data_source
	--area
	--config
	--factory
	--warehouse
	
data_source0
	--code_relate0
	--code_relate1
	--customer0
	--customer1
	--factory
	--indent_detail0
	--indent_detail1
	--indent0
	--indent1
	--task_upload0
	--task_upload1
	--task0
	--task1
	--warehouse
	
data_source1
	--code_relate0
	--code_relate1
	--customer0
	--customer1
	--factory
	--indent_detail0
	--indent_detail1
	--indent0
	--indent1
	--task_upload0
	--task_upload1
	--task0
	--task1
	--warehouse

data_source2
	--code_relate0
	--code_relate1
	--customer0
	--customer1
	--factory
	--indent_detail0
	--indent_detail1
	--indent0
	--indent1
	--task_upload0
	--task_upload1
	--task0
	--task1
	--warehouse

2.下载sharding-proxy二进制压缩包

压缩包下载地址
shardingsphere之sharding-proxy分库分表学习笔记_第3张图片

3、解压zip包

注意事项:

(1)解压工具最好选择winrar,不然解压出来的lib文件夹下的jar包由于命名太长被截断,后面将会导致文件找不到,系统启动失败异常。

(2)解压后的文件的路径下最好不要有中文,否则也会导致启动失败。

4、下载mysql驱动包

MySQL Connector/J,下载之后放到lib文件夹下。

5、编写yml文件

(1)首先要编写的是server.yaml,配置全局信息

##治理中心
# orchestration:
#   name: orchestration_ds
#   overwrite: true
#   registry:
#     type: zookeeper
#     serverLists: localhost:2181
#     namespace: orchestration

#权限配置
authentication:
  users:
    root:               #用户名
      password: root	#密码
    sharding:
      password: sharding 
      authorizedSchemas: sharding_db	#只能访问的逻辑数据库

#Proxy属性
props:
  max.connections.size.per.query: 1
  acceptor.size: 16  #用于设置接收客户端请求的工作线程个数,默认为CPU核数*2
  executor.size: 16  # Infinite by default.
  proxy.frontend.flush.threshold: 128  # The default value is 128.
    # LOCAL: Proxy will run with LOCAL transaction.
    # XA: Proxy will run with XA transaction.
    # BASE: Proxy will run with B.A.S.E transaction.
  proxy.transaction.type: LOCAL 	#默认为LOCAL事务
  proxy.opentracing.enabled: false     #是否开启链路追踪功能,默认为不开启。
  query.with.cipher.column: true
  sql.show: true				#SQL打印
  check.table.metadata.enabled: true			#是否在启动时检查分表元数据一致性,默认值: false
# proxy.frontend.flush.threshold: 				# 对于单个大查询,每多少个网络包返回一次

注意事项:

(1)当你把注释的示例内容复制粘贴,然后用快捷键去掉‘#’注释符的时候,注意父子层的距离不要改变,比如下方父子层之间是两个空格。

props:
  max.connections.size.per.query: 1

(2) 在编写的时候不要用‘Tab’键拉开距离,应该使用空格键,否则启动的时候会报错。

小插曲

当你编写yml文件的时候,里面都会有这句话,一开始我以为还要在哪里引用这些要用的文件,后面才发现直接去掉注释编写就好,文件都是自动引用的。

If you want to configure orchestration, authorization and proxy properties, please refer to this file.

6.编写config-sharding.yaml配置文件

其实编写这个文件和sharding-jdbc的yml配置文件基本相同,区别就是对于组合单词jdbc用的是驼峰,proxy用的是“-”,如果之前玩过sharding-jdbc的话可以直接把配置文件复制过来,对应修改即可。

schemaName: sharding_db		#逻辑数据库名配置

dataSources:      #真实数据源配置
  db:
    url: jdbc:mysql://127.0.0.1:3306/data_source?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
  db0:
    url: jdbc:mysql://127.0.0.1:3306/data_source0?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
  db1:
    url: jdbc:mysql://127.0.0.1:3306/data_source1?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
  db2:
    url: jdbc:mysql://127.0.0.1:3306/data_source2?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50


shardingRule:      ##分库分表规则
  defaultDataSourceName: db    #默认数据源,放置不需要分片的表和广播表
  broadcastTables: 
    - factory
    - warehouse
  bindingTables: 
    - indent,indent_detail
    - task_upload,code_relate       ##绑定表配置
  defaultDatabaseStrategy:    #默认的分库规则,如果逻辑表没单独配置则使用这个
    inline:
      shardingColumn: customer_id    #默认按照customer_id分库
      algorithmExpression: db$->{customer_id % 3}
  tables:       #逻辑表配置
    config:               ###单库单表,使用UUID作为主键
      actualDataNodes: db.config
      keyGenerator:
        column: code
        type: UUID
    customer: 
      actualDataNodes: db$->{0..2}.customer$->{0..1}  #具体的数据节点
      tableStrategy:     ##分表策略
        inline:
          shardingColumn: customer_name     #根据hash值取模确定落在哪张表
          algorithmExpression: customer$->{Math.abs(customer_name.hashCode() % 2)}
      keyGenerator:    #配置主键生成策略,默认使用SNOWFLAKE
        column: customer_id
        type: SNOWFLAKE
        props:
          worker:
            id: 20200422
    indent:
      actualDataNodes: db$->{0..2}.indent$->{0..1}
      tableStrategy:
        inline: 
          shardingColumn: indent_id
          algorithmExpression: indent$->{indent_id % 2}
      keyGenerator:
        column: indent_id
        type: SNOWFLAKE 
    indent_detail:
      actualDataNodes: db$->{0..2}.indent_detail$->{0..1}
      tableStrategy:
        inline:
          shardingColumn: indent_id
          algorithmExpression: indent_detail$->{indent_id % 2}
      keyGenerator:
        column: detail_id
        type: SNOWFLAKE
    task:
      actualDataNodes: db$->{0..2}.task$->{0..1}  #具体的数据节点   
      databaseStrategy:   #分库规则 
        inline:
          shardingColumn: task_id
          algorithmExpression: db$->{task_id % 3}         
      tableStrategy:
        inline:
          shardingColumn: task_id
          algorithmExpression: task$->{task_id % 2}        
    task_upload:
      actualDataNodes: db$->{0..2}.task_upload$->{0..1}  #具体的数据节点   
      databaseStrategy:   #分库规则 
        inline:
          shardingColumn: task_id
          algorithmExpression: db$->{task_id % 3}         
      tableStrategy:
        inline:
          shardingColumn: stack_code
          algorithmExpression: task_upload$->{Math.abs(stack_code.hashCode() % 2)}        
      keyGenerator:
        column: upload_id
        type: SNOWFLAKE      
    code_relate:
      actualDataNodes: db$->{0..2}.code_relate$->{0..1}  #具体的数据节点   
      databaseStrategy:   #分库规则 
        inline:
          shardingColumn: task_id
          algorithmExpression: db$->{task_id % 3}         
      tableStrategy:
        inline:
          shardingColumn: stack_code
          algorithmExpression: code_relate$->{Math.abs(stack_code.hashCode() % 2)}        
      keyGenerator:
        column: relate_id
        type: SNOWFLAKE 

注意事项:

(1)数组的写法

  • 如果是广播表应该这么写
 broadcastTables: 
    - factory
    - warehouse
  • 如果是绑定表应该这么写
bindingTables: 
    - indent,indent_detail
    - task_upload,code_relate       ##绑定表配置

(2)分片键分为分库键和分表键。

(3)主键生成默认使用SNOWFLAKE算法,使用UUID主键的话需要配置。

(4)如果分片键的值为long型,分片规则为分片字段取模即可;如果是String型,分片规则为分片字段的哈希值取模再求绝对值,因为哈希值取模之后也许会出现负数。

(5)逻辑表和绑定表配置建议,尽可能的让同一类型的数据落在同一个库中。比如用户的信息和他产生的订单以及订单详情,可以通过consumer_id作为分库键,indent_id作为分表键存放,这样如果查询命中分片键的话可以提高查询效率(少查了不必要的表)。

(6)绑定表建表的时候,子表最好增加分库键字段便于新增数据时确定落到哪个库中。比如用户表、订单表和订单详情表,consumer_id作为分库键,订单表需要有这个字段,订单详情表也需要这个字段,否则订单详情新增数据的时候会在每个库都新增数据,很明显是不合理的情况。

7.编写logback.xml日志记录文件

日志路径默认在启动方式start.bat下创建个logs文件夹,所以只要填写相对路径就好。下面设置的是按级别按天输出日志,基本符合常规需求。

<?xml version="1.0"?>

>
  
    <!-- 控制台输出 -->
    
        
            
            [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %highlight([%-5level] %logger{50} - %msg%n)
            UTF-8
        
    
   
    <!-- 系统正常日志文件 -->
    
        
        
            INFO
            ACCEPT
            DENY
        
        
            
            ../logs/%d{yyyy-MM-dd}/info.%i.log
            
            
                
                
                    10MB
                
        
          
            true
      
        
            
            [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] %logger{50} - %msg%n
            UTF-8
        
    
    
     <!-- 系统警告日志文件,记录用户输入不符合规则的数据 -->
    
        
        
            WARN
            ACCEPT
            DENY
        
        
            
            ../logs/%d{yyyy-MM-dd}/warn.%i.log
                
                    10MB
                
        
          
            true
      
        
            
            [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] %logger{50} - %msg%n
            UTF-8
        
    
    
    <!-- 系统错误日志文件 -->
    
        
        
            ERROR
            ACCEPT
            DENY
        
        
            
            ../logs/%d{yyyy-MM-dd}/error.%i.log
                
                
                    10MB
                
        
          
            true
      
        
            
            [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] [%-5level] %logger{50} - %msg%n
            UTF-8
        
    
    
  
     >
        -ref ref="SYSTEM_ERROR"/>
    >
    
     <!-- 默认纪录级别 -->
    
        
        
        
         
    
  
> 

8.启动sharding-proxy

在cmd命令窗口中cd到bin目录下,然后直接运行start.bat,默认是3307端口;当然你也可以指定端口,在后面加上参数即可,比如start.bat 3309即可指定连接端口为3309。然后观察cmd窗口,如果没报出异常的话表示启动成功。
shardingsphere之sharding-proxy分库分表学习笔记_第4张图片

9.客户端连接

这里推荐使用Navicat Premium 11 去连接,使用Navicat Premium 12的话你会发现很多奇怪的问题,下面是官网的建议和成功连接的图。至此,proxy服务端搭建好了,接下来是编写代码。

shardingsphere之sharding-proxy分库分表学习笔记_第5张图片
shardingsphere之sharding-proxy分库分表学习笔记_第6张图片

10.基础CRUD代码生成

通过示例代码的工具类连接generator库可以快速生成基础的CRUD代码

src/test/java/com/project/generator/MybatisGenerator.java

注意事项

(1)实体主键类型的选择

  • 如果主键是long型的话,可以这么配置,个人建议选择type = IdType.ID_WORKER这样更直白明了。否则插入数据时会报错。
/**
     * id
     */
    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;

或者

  /**
     * id
     */
    @TableId(value = "id", type = IdType.NONE)
    private Long id;

使用type= ID.AUTO的异常信息

Caused by: java.sql.SQLException: Field ‘id’ doesn’t have a default value
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3978)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3914)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2495)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1903)
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2124)
at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:1801)
… 89 common frames omitted

  • 如果主键是String型的话,可以这么配置。因为默认的是SNOWFLAKE生成,否则会插入一个long型的主键值导致报错。
   /**
     * 编号
     */
    @TableId(value = "code", type = IdType.AUTO)
    private String code;

或者

  /**
     * 编号
     */
    @TableId(value = "code", type = IdType.UUID)
    private String code;

项目运行

测试代码已经写到里面了,通过发起请求和观察cmd窗口或者日志文件你会发现逻辑SQL和真实SQL,从而发现他的查询规则:

1、如果表没配置规则,那么直接到默认库去访问

2、如果访问的是广播表,那么读的时候是随机路由到一个库,写的时候是全部库都写数据。

3、逻辑表查询,查询字段命中了分库键,那么路由到指定库下的所有表查询;命中了分表键,到所有库下指定表查询。如果都没命中,那么将发生笛卡尔积,进行全路由所有的库和表都查询一遍,效率不高。所以合理的配置分片规则是很重要的。

分布式事务

由于这里只配置了一个逻辑数据源(包含多个真实数据源),不大清楚算不算分布式事务。不过写法跟原来是一样的,加上注解即可。运行下面的代码发生异常,数据将不会写进数据库,表示事务控制成功。

 /**
     * 测试事务管理
     * 
     * @date 2020年5月3日
     * @author huangjg
     */
    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/save")
    public ResponseData<?> save() {
        customerService.saveBatch(list);
        int i = 5 / 0;
        return ResponseData.out(CodeEnum.SUCCESS, null);
    }

弹性伸缩

这个还没开始研究,估计在4.1.x版本会有这个功能。

配置zookeeper

目前我将zookeeper跑起来的时候不懂如何跟项目对接起来,如果有成功的同学麻烦将方法告知下。

结语

官网的文档比较详细和社区都是很活跃的,这些可以减少我们的学习成本,快速用于项目。如果在学习的过程中遇到问题可以多看看官方文档或者直接到github上面提issues,官方人员会很快给予答复的。

相关链接

演示代码地址

shardingsphere官网地址

shardingsphere github地址

你可能感兴趣的:(shardingsphere)