Hablando sobre el combate real de la subbase de datos MySQL y la subtabla en la reconstrucción del orden

1. Antecedentes

Después de publicar el artículo anterior sobre el camino hacia la reconstrucción de pedidos , muchos amigos quieren saber cómo realizar la subbase de datos y la subtabla. Luego, este artículo presenta específicamente el combate real de la subbase de datos y la subtabla.

2. Metas

  1. Este documento logrará los siguientes objetivos:

* Número de subtablas: 256 Número de subbases de datos: 4

* Use la ID de usuario (user_id) como la clave de fragmentación de la base de datos

* Por último, creación de pedidos de prueba, actualización, eliminación, consulta de número de pedido único, operación de lista de consulta según user_id.

Diagrama de arquitectura:

La estructura de la tabla es la siguiente:

CREATE TABLE `order_XXX` (
  `order_id` bigint(20) unsigned NOT NULL,
  `user_id` int(11) DEFAULT '0' COMMENT '订单id',
  `status` int(11) DEFAULT '0' COMMENT '订单状态',
  `booking_date` datetime DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`order_id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_bdate` (`booking_date`),
  KEY `idx_ctime` (`create_time`),
  KEY `idx_utime` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Nota: 000<= XXX <= 255, este artículo se centra en la práctica de la subbase de datos y la subtabla, solo se reservan campos representativos y se pueden mejorar otros escenarios sobre esta base.

Diseño de identificación único a nivel mundial

Requisitos: 1. Globalmente único 2: Aproximadamente ordenado 3: Descifrado reversible del número de almacén

  • 1 bit + diferencia horaria de 39 bits + número de máquina de 8 bits + número de usuario de 8 bits (número de biblioteca) + secuencia de incremento automático de 8 bits

Componentes del número de pedido texto reservado diferencia horaria en milisegundos Número de máquinas Número de usuario (número de mesa) secuencia de incremento automático
Bytes ocupados (unidad: bit) 1 39 8 8 8

QPS máximo de una sola máquina: 256000 Vida útil: 17 años

Para obtener detalles sobre las reglas de generación de números de pedido, consulte Discusión sobre las mejores prácticas de generadores de ID únicos distribuidos

3. Preparación ambiental

  1. Información básica

artículo Versión Observación
SpringBoot 2.1.10.LIBERAR
mango 1.6.16 Dirección wiki: https://github.com/jfaster/mango
HikariCP 3.2.0
mysql 5.7 Prueba usando la ventana acoplable para construir con un clic

  1. Preparación del entorno de la base de datos

    Recordatorio: use docker-compose para crear rápidamente un clúster de base de datos de 4 maestros y 4 esclavos para lograr una implementación local rápida con un solo clic (para conocer el método de implementación, consulte el artículo de mi cuenta oficial: Hablando sobre la dockerización local de aplicaciones de uso común software en el trabajo, ¿no es bueno implementarlo con un clic? ? ), el entorno de producción generalmente lo configuran los estudiantes de DBA.

    Para una implementación específica, vaya a ver: https://gitee.com/bytearch_admin/docker-app/tree/main/mysql-cluster

  2. Crear base de datos e importar subtabla

* Cree bases de datos por separado en la instancia maestra de mysql 

172.30.1.21(pedido_db_1), 172.30.1.22(pedido_db_2),

172.30.1.23(pedido_db_3), 172.30.1.24(pedido_db_4)

* Importe el comando SQL de creación de tablas en secuencia como

mysql -uroot -pbytearch -h172.30.1.21 order_db_1<fast-cloud-mysql-sharding/doc/sql/order_db_1.sql;
mysql -uroot -pbytearch -h172.30.1.22 order_db_2<fast-cloud-mysql-sharding/doc/sql/order_db_2.sql;
mysql -uroot -pbytearch -h172.30.1.23 order_db_3<fast-cloud-mysql-sharding/doc/sql/order_db_3.sql;
mysql -uroot -pbytearch -h172.30.1.24 order_db_4<fast-cloud-mysql-sharding/doc/sql/order_db_4.sql;  

   

4. Configuración y práctica

  1. archivo pom

                <!-- mango 分库分表中间件 --> 
                <dependency>
                    <groupId>org.jfaster</groupId>
                    <artifactId>mango-spring-boot-starter</artifactId>
                    <version>2.0.1</version>
                </dependency>
             
                 <!-- 分布式ID生成器 -->
                <dependency>
                    <groupId>com.bytearch</groupId>
                    <artifactId>fast-cloud-id-generator</artifactId>
                    <version>${version}</version>
                </dependency>
    
                <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>6.0.6</version>
                </dependency>
    
  2. configuración constante

    package com.bytearch.fast.cloud.mysql.sharding.common;
    
    /**
     * 分库分表策略常用常量
     */
    public class ShardingStrategyConstant {
        /**
         * database 逻辑名称 ,真实库名为 order_db_XXX
         */
        public static final String LOGIC_ORDER_DATABASE_NAME = "order_db";
        /**
         * 分表数 256,一旦确定不可更改
         */
        public static final int SHARDING_TABLE_NUM = 256;
    
        /**
         * 分库数, 不建议更改, 可以更改,但是需要DBA迁移数据
         */
        public static final int SHARDING_DATABASE_NODE_NUM = 4;
    }
    
  3. configuración yml

    4 configuraciones de bases de datos maestras y 4 esclavas, solo la contraseña de usuario root se usa de forma predeterminada para las pruebas aquí, y el usuario root no se recomienda para entornos de producción.

    mango:
      scan-package: com.bytearch.fast.cloud.mysql.sharding.dao
      datasources:
        - name: order_db_1
          master:
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://172.30.1.21:3306/order_db_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
            user-name: root
            password: bytearch
            maximum-pool-size: 10
            connection-timeout: 3000
          slaves:
            - driver-class-name: com.mysql.cj.jdbc.Driver
              jdbc-url: jdbc:mysql://172.30.1.31:3306/order_db_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
              user-name: root
              password: bytearch
              maximum-pool-size: 10
              connection-timeout: 3000
        - name: order_db_2
          master:
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://172.30.1.22:3306/order_db_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
            user-name: root
            password: bytearch
            maximum-pool-size: 10
            connection-timeout: 3000
          slaves:
            - driver-class-name: com.mysql.cj.jdbc.Driver
              jdbc-url: jdbc:mysql://172.30.1.32:3306/order_db_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
              user-name: root
              password: bytearch
              maximum-pool-size: 10
              connection-timeout: 3000
        - name: order_db_3
          master:
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://172.30.1.23:3306/order_db_3?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
            user-name: root
            password: bytearch
            maximum-pool-size: 10
            connection-timeout: 3000
          slaves:
            - driver-class-name: com.mysql.cj.jdbc.Driver
              jdbc-url: jdbc:mysql://172.30.1.33:3306/order_db_3?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
              user-name: root
              password: bytearch
              maximum-pool-size: 10
              connection-timeout: 3000
        - name: order_db_4
          master:
            driver-class-name: com.mysql.cj.jdbc.Driver
            jdbc-url: jdbc:mysql://172.30.1.24:3306/order_db_4?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
            user-name: root
            password: bytearch
            maximum-pool-size: 10
            connection-timeout: 3000
          slaves:
            - driver-class-name: com.mysql.cj.jdbc.Driver
              jdbc-url: jdbc:mysql://172.30.1.34:3306/order_db_4?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
              user-name: root
              password: bytearch
              maximum-pool-size: 10
              connection-timeout: 300
    
  4. Estrategia de subtabla de subbase de datos

    1) De acuerdo con order_id, la estrategia de la subtabla de la subbase de datos shardKey

    package com.bytearch.fast.cloud.mysql.sharding.strategy;
    
    import com.bytearch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant;
    import com.bytearch.id.generator.IdEntity;
    import com.bytearch.id.generator.SeqIdUtil;
    import org.jfaster.mango.sharding.ShardingStrategy;
    
    /**
     * 订单号分库分表策略
     */
    public class OrderIdShardingStrategy implements ShardingStrategy<Long, Long> {
        @Override
        public String getDataSourceFactoryName(Long orderId) {
            if (orderId == null || orderId < 0L) {
                throw new IllegalArgumentException("order_id is invalid!");
            }
            IdEntity idEntity = SeqIdUtil.decodeId(orderId);
            if (idEntity.getExtraId() >= ShardingStrategyConstant.SHARDING_TABLE_NUM) {
                throw new IllegalArgumentException("sharding table Num is invalid, tableNum:" + idEntity.getExtraId());
            }
            //1. 计算步长
            int step = ShardingStrategyConstant.SHARDING_TABLE_NUM / ShardingStrategyConstant.SHARDING_DATABASE_NODE_NUM;
            //2. 计算出库编号
            long dbNo = Math.floorDiv(idEntity.getExtraId(), step) + 1;
            //3. 返回数据源名
            return String.format("%s_%s", ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME, dbNo);
        }
    
        @Override
        public String getTargetTable(String logicTableName, Long orderId) {
            if (orderId == null || orderId < 0L) {
                throw new IllegalArgumentException("order_id is invalid!");
            }
            IdEntity idEntity = SeqIdUtil.decodeId(orderId);
            if (idEntity.getExtraId() >= ShardingStrategyConstant.SHARDING_TABLE_NUM) {
                throw new IllegalArgumentException("sharding table Num is invalid, tableNum:" + idEntity.getExtraId());
            }
            // 基于约定,真实表名为 logicTableName_XXX, XXX不足三位补0
            return String.format("%s_%03d", logicTableName, idEntity.getExtraId());
        }
    }
    

    2) De acuerdo con el ID de usuario, la estrategia de la subtabla de la subbase de datos shardKey

    package com.bytearch.fast.cloud.mysql.sharding.strategy;
    
    import com.bytearch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant;
    import org.jfaster.mango.sharding.ShardingStrategy;
    
    /**
     * 指定分片KEY 分库分表策略
     */
    public class UserIdShardingStrategy implements ShardingStrategy<Integer, Integer> {
    
        @Override
        public String getDataSourceFactoryName(Integer userId) {
            //1. 计算步长 即单库放得表数量
            int step = ShardingStrategyConstant.SHARDING_TABLE_NUM / ShardingStrategyConstant.SHARDING_DATABASE_NODE_NUM;
            //2. 计算出库编号
            long dbNo = Math.floorDiv(userId % ShardingStrategyConstant.SHARDING_TABLE_NUM, step) + 1;
            //3. 返回数据源名
            return String.format("%s_%s", ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME, dbNo);
        }
    
        @Override
        public String getTargetTable(String logicTableName, Integer userId) {
            // 基于约定,真实表名为 logicTableName_XXX, XXX不足三位补0
            return String.format("%s_%03d", logicTableName, userId % ShardingStrategyConstant.SHARDING_TABLE_NUM);
        }
    }
    
  5. Escritura de capa Dao

    1). Ordenar partición por Id Dao

    package com.bytearch.fast.cloud.mysql.sharding.dao;
    
    import com.bytearch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant;
    import com.bytearch.fast.cloud.mysql.sharding.pojo.entity.OrderEntity;
    import com.bytearch.fast.cloud.mysql.sharding.strategy.OrderIdShardingStrategy;
    import org.jfaster.mango.annotation.*;
    
    @DB(name = ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME, table = "order")
    @Sharding(shardingStrategy = OrderIdShardingStrategy.class)
    public interface OrderPartitionByIdDao {
    
        @SQL("INSERT INTO #table (order_id, user_id, status, booking_date, create_time, update_time) VALUES" +
                "(:orderId,:userId,:status,:bookingDate,:createTime,:updateTime)"
        )
        int insertOrder(@TableShardingBy("orderId") @DatabaseShardingBy("orderId") OrderEntity orderEntity);
    
        @SQL("UPDATE #table set update_time = now()" +
                "#if(:bookingDate != null),booking_date = :bookingDate #end " +
                "#if (:status != null), status = :status #end" +
                "WHERE order_id = :orderId"
        )
        int updateOrderByOrderId(@TableShardingBy("orderId") @DatabaseShardingBy("orderId") OrderEntity orderEntity);
    
    
        @SQL("SELECT * FROM #table WHERE order_id = :1")
        OrderEntity getOrderById(@TableShardingBy @DatabaseShardingBy Long orderId);
    
        @SQL("SELECT * FROM #table WHERE order_id = :1")
        @UseMaster
        OrderEntity getOrderByIdFromMaster(@TableShardingBy @DatabaseShardingBy Long orderId);
    
  6. prueba de unidad

    @SpringBootTest(classes = {Application.class})
    @RunWith(SpringJUnit4ClassRunner.class)
    public class ShardingTest {
        @Autowired
        OrderPartitionByIdDao orderPartitionByIdDao;
    
        @Autowired
        OrderPartitionByUserIdDao orderPartitionByUserIdDao;
    
        @Test
        public void testCreateOrderRandom() {
            for (int i = 0; i < 20; i++) {
                int userId = ThreadLocalRandom.current().nextInt(1000,1000000);
                OrderEntity orderEntity = new OrderEntity();
                orderEntity.setOrderId(SeqIdUtil.nextId(userId % ShardingStrategyConstant.SHARDING_TABLE_NUM));
                orderEntity.setStatus(1);
                orderEntity.setUserId(userId);
                orderEntity.setCreateTime(new Date());
                orderEntity.setUpdateTime(new Date());
                orderEntity.setBookingDate(new Date());
                int ret = orderPartitionByIdDao.insertOrder(orderEntity);
                Assert.assertEquals(1, ret);
            }
        }
    
        @Test
        public void testOrderAll() {
            //insert
            int userId = ThreadLocalRandom.current().nextInt(1000,1000000);
            OrderEntity orderEntity = new OrderEntity();
            orderEntity.setOrderId(SeqIdUtil.nextId(userId % ShardingStrategyConstant.SHARDING_TABLE_NUM));
            orderEntity.setStatus(1);
            orderEntity.setUserId(userId);
            orderEntity.setCreateTime(new Date());
            orderEntity.setUpdateTime(new Date());
            orderEntity.setBookingDate(new Date());
            int i = orderPartitionByIdDao.insertOrder(orderEntity);
            Assert.assertEquals(1, i);
    
            //get from master
            OrderEntity orderInfo = orderPartitionByIdDao.getOrderByIdFromMaster(orderEntity.getOrderId());
            Assert.assertNotNull(orderInfo);
            Assert.assertEquals(orderInfo.getOrderId(), orderEntity.getOrderId());
    
            //get from slave
            OrderEntity slaveOrderInfo = orderPartitionByIdDao.getOrderById(orderEntity.getOrderId());
            Assert.assertNotNull(slaveOrderInfo);
            //update
            OrderEntity updateEntity = new OrderEntity();
            updateEntity.setOrderId(orderInfo.getOrderId());
            updateEntity.setStatus(2);
            updateEntity.setUpdateTime(new Date());
            int affectRows = orderPartitionByIdDao.updateOrderByOrderId(updateEntity);
            Assert.assertTrue( affectRows > 0);
        }
    
        @Test
        public void testGetListByUserId() {
            int userId = ThreadLocalRandom.current().nextInt(1000,1000000);
            for (int i = 0; i < 5; i++) {
                OrderEntity orderEntity = new OrderEntity();
                orderEntity.setOrderId(SeqIdUtil.nextId(userId % ShardingStrategyConstant.SHARDING_TABLE_NUM));
                orderEntity.setStatus(1);
                orderEntity.setUserId(userId);
                orderEntity.setCreateTime(new Date());
                orderEntity.setUpdateTime(new Date());
                orderEntity.setBookingDate(new Date());
                orderPartitionByIdDao.insertOrder(orderEntity);
            }
            try {
                //防止主从延迟引起的校验错误
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            List<OrderEntity> orderListByUserId = orderPartitionByUserIdDao.getOrderListByUserId(userId);
            Assert.assertNotNull(orderListByUserId);
            Assert.assertTrue(orderListByUserId.size() == 5);
        }
    }
    

ya terminaste:

El código fuente anterior se ha abierto en: https://gitee.com/bytearch_admin/fast-cloud/tree/master/fast-cloud-mysql-sharding Bienvenido a Me gusta y recopilar.

V. Resumen

Este artículo presenta principalmente la versión de Java que utiliza el marco Mango para implementar el combate de subtablas y subbases de datos de Mysql. El middleware de subbases de datos y tablas también se puede usar de manera similar a ShardingJDBC, o desarrollarse por sí mismo.

La cantidad de subbases de datos y subtablas anteriores es solo para referencia de demostración. La cantidad de subtablas y subbases de datos en el trabajo real se calcula de acuerdo con la tasa de crecimiento de datos comerciales reales de la empresa, el pico de QPS, la configuración física de la máquina y otros factores

Supongo que te gusta

Origin blog.csdn.net/weixin_38130500/article/details/120148195
Recomendado
Clasificación