Java Microservices Distributed Split-Repository-Split-Table ShardingSphere – ShardingSphere-JDBC

Time:2024-4-23


preamble

Apache ShardingSphere is a distributed database ecosystem that transforms any database into a distributed database and enhances it with capabilities such as data sharding, elastic scaling, encryption, and more. Apache ShardingSphere is designed with the philosophy of Database Plus to build a standard and ecosystem for the upper layers of heterogeneous databases. It focuses on how to fully utilize the computational and storage power of databases, rather than implementing a completely new database. It takes a view of the upper layers of databases and focuses more on collaboration between them than on the database itself.

1、ShardingSphere-JDBC

ShardingSphere-JDBC is positioned as a lightweight Java framework with additional services provided at the JDBC layer of Java.

1.1 Application Scenarios

Apache ShardingSphere-JDBC can be configured in Java and YAML in two ways , developers can choose the appropriate configuration according to the scenario .
  • Database Read/Write Separation
  • Database Tables and Databases

1.2, Principle

  • Sharding-JDBC routing results are determined by the sharding field and sharding method, if the query condition has an id field the case is good, the query will fall to a specific sharding
  • If you query a field that is not sliced, you will query all the db’s or tables and let the result set be encapsulated to the client. Java Microservices Distributed Split-Repository-Split-Table ShardingSphere - ShardingSphere-JDBC

1.3. spring boot integration

1.3.1 Adding dependencies

<! -- Split Table Split Library Dependencies -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.1.1</version>
</dependency>

1.3.2, Additive placement

spring:
  main:
    # One entity class corresponds to multiple tables, overriding the
    allow-bean-definition-overriding: true
  shardingsphere:
    datasource:
      ds0:
        # Configure data source specifics, including connection pools, drivers, addresses, usernames and passwords
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/account?autoReconnect=true&allowMultiQueries=true
        password: root
        type: com.zaxxer.hikari.HikariDataSource
        username: root
      ds1:
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/account?autoReconnect=true&allowMultiQueries=true
        password: root
        type: com.zaxxer.hikari.HikariDataSource
        username: root
      # Configure the data source, give the data source a name
      names: ds0,ds1
    props:
      sql:
        show: true
    sharding:
      tables:
        user_info:
          # Specify the user_info table distribution, configure which database the table is in, and what the table names are.
          actual-data-nodes: ds0.user_info_${0..9}
          database-strategy:
            standard:
              preciseAlgorithmClassName: com.xxxx.store.account.config.PreciseDBShardingAlgorithm
              rangeAlgorithmClassName: com.xxxx.store.account.config.RangeDBShardingAlgorithm
              sharding-column: id
          table-strategy:
            standard:
              preciseAlgorithmClassName: com.xxxx.store.account.config.PreciseTablesShardingAlgorithm
              rangeAlgorithmClassName: com.xxxx.store.account.config.RangeTablesShardingAlgorithm
              sharding-column: id

1.3.3. Development of slicing algorithms

1.3.3.1, Precision library algorithm
/**
 * :: Precise pooling algorithms
 */
public class PreciseDBShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
    /**
     *
     * @param availableTargetNames Configure the list of all the
     * @param preciseShardingValue Fragment value
     * @return
     */
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {
        Long value = preciseShardingValue.getValue();
        //Suffix 0, 1
        String postfix = String.valueOf(value % 2);

        for (String availableTargetName : availableTargetNames) {
            if(availableTargetName.endsWith(postfix)){
                return availableTargetName;
            }
        }

        throw new UnsupportedOperationException();
    }

}
1.3.3.2. Range-splitting algorithm
/**
 * :: Range-splitting algorithm
 */
public class RangeDBShardingAlgorithm implements RangeShardingAlgorithm<Long> {


    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
        return collection;
    }
}
1.3.3.3, Precise table-splitting algorithm
/**
 * :: Precise tabulation algorithm
 */
public class PreciseTablesShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
    /**
     *
     * @param availableTargetNames Configure the list of all the
     * @param preciseShardingValue Fragment value
     * @return
     */
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {
        Long value = preciseShardingValue.getValue();
        // Suffix
        String postfix = String.valueOf(value % 10);

        for (String availableTargetName : availableTargetNames) {
            if(availableTargetName.endsWith(postfix)){
                return availableTargetName;
            }
        }

        throw new UnsupportedOperationException();
    }

}
1.3.3.4. Scope sub-tabulation algorithm
/**
 * :: Range-splitting table algorithm
 */
public class RangeTablesShardingAlgorithm implements RangeShardingAlgorithm<Long> {

    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {

        Collection<String> result = new ArrayList<>();
        Range<Long> valueRange = rangeShardingValue.getValueRange();
        Long start = valueRange.lowerEndpoint();
        Long end = valueRange.upperEndpoint();

        Long min = start % 10;
        Long max = end % 10;

        for (Long i = min; i < max +1; i++) {
            Long finalI = i;
            collection.forEach(e -> {
                if(e.endsWith(String.valueOf(finalI))){
                    result.add(e);
                }
            });
        }
        return result;
    }

}

1.3.4 Database table construction

DROP TABLE IF EXISTS `user_info_0`;
CREATE TABLE `user_info_0` (
  `id` bigint(20) NOT NULL,
  `account` varchar(255) DEFAULT NULL,
  `user_name` varchar(255) DEFAULT NULL,
  `pwd` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.3.5, Business applications

1.3.5.1 Defining entity classes
@Data
@TableName(value = "user_info")
public class UserInfo {
    /**
     * :: Primary key
     */
    private Long id;
    /**
     * :: Account number
     */
    private String account;
    /**
     * :: User name
     */
    private String userName;
    /**
     * Password
     */
    private String pwd;

}
1.3.5.2. Defining interfaces
public interface UserInfoService{
    /**
     * :: Preservation
     * @param userInfo
     * @return
     */
    public UserInfo saveUserInfo(UserInfo userInfo);

    public UserInfo getUserInfoById(Long id);

    public List<UserInfo> listUserInfo();
}
1.3.5.3. Implementation classes
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    @Override
    @Transactional
    public UserInfo saveUserInfo(UserInfo userInfo) {
        userInfo.setId(IdUtils.getId());
        this.save(userInfo);
        return userInfo;
    }

    @Override
    public UserInfo getUserInfoById(Long id) {

        return this.getById(id);
    }

    @Override
    public List<UserInfo> listUserInfo() {
        QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
        userInfoQueryWrapper.between("id",1623695688380448768L,1623695688380448769L);
        return this.list(userInfoQueryWrapper);
    }
}

1.3.6 Generating IDs – Snowflake Algorithm

package com.xxxx.tore.common.utils;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;

/**
 * :: Generation of various component IDs
 */
public class IdUtils {

    /**
     * :: Snowflake algorithm
     * @return
     */
    public static long getId(){
        Snowflake snowflake = IdUtil.getSnowflake(0, 0);
        long id = snowflake.nextId();
        return id;
    }
}

1.4, seata yielding sharding-jdbc consistent

https://github.com/seata/seata-samples/tree/master/springcloud-seata-sharding-jdbc-mybatis-plus-samples

1.4.1 Add dependencies in common

<! --seata dependencies -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2021.0.4.0</version>
</dependency>
<! -- sharding-jdbc integrates seata distributed transactions -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-transaction-base-seata-at</artifactId>
    <version>4.1.1</version>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2021.0.4.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>1.4.2</version>
</dependency>

1.4.2 Modification of the account-service service

@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {
    @Autowired
    private OrderService orderService;
    @Autowired
    private StorageService storageService;

    /**
     * :: Storage of commodity codes and their corresponding prices
     */
    private static Map<String,Integer> map = new HashMap<>();
    static {
        map.put("c001",3);
        map.put("c002",5);
        map.put("c003",10);
        map.put("c004",6);

    }
    @Override
    @Transactional
    @ShardingTransactionType(TransactionType.BASE)
    public void debit(OrderDTO orderDTO) {
        //Deduction of account balances
        int calculate = this.calculate(orderDTO.getCommodityCode(), orderDTO.getCount());

        AccountDTO accountDTO = new AccountDTO(orderDTO.getUserId(), calculate);

        QueryWrapper<Account> objectQueryWrapper = new QueryWrapper<>();
        objectQueryWrapper.eq("id",1);
        objectQueryWrapper.eq(accountDTO.getUserId() != null,"user_id",accountDTO.getUserId());

        Account account = this.getOne(objectQueryWrapper);
        account.setMoney(account.getMoney() - accountDTO.getMoney());
        this.saveOrUpdate(account);

        //Deductions from inventory
        this.storageService.deduct(new StorageDTO(null,orderDTO.getCommodityCode(),orderDTO.getCount()));
        // Generate orders
        this.orderService.create(orderDTO);      
    }

    /**
     * :: Calculation of the total price of goods purchased
     * @param commodityCode
     * @param orderCount
     * @return
     */
    private int calculate(String commodityCode, int orderCount){
        //Commodity prices
        Integer price = map.get(commodityCode) == null ? 0 : map.get(commodityCode);
        return price * orderCount;
    }
}
Note: The logic of the transfer order generation call is modified, Reduce Balance -> Reduce Inventory -> Generate Order. The call entry method note is added:@ShardingTransactionType(TransactionType.BASE)

1.4.3 Modifying the business-service service

@Service
public class BusinessServiceImpl implements BusinessService {
    @Autowired
    private OrderService orderService;
    @Autowired
    private StorageService storageService;
    @Autowired
    private AccountService accountService;

    @Override
    public void purchase(OrderDTO orderDTO) {
        //Deduct money from account
        accountService.debit(orderDTO);        
    }
}

1.4.4 Modifying the order-service service

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper,Order> implements OrderService {

    /**
     * :: Storage of commodity codes and their corresponding prices
     */
    private static Map<String,Integer> map = new HashMap<>();
    static {
        map.put("c001",3);
        map.put("c002",5);
        map.put("c003",10);
        map.put("c004",6);
    }
    @Override
    @Transactional
    @ShardingTransactionType(TransactionType.BASE)
    public Order create(String userId, String commodityCode, int orderCount) {
        int orderMoney = calculate(commodityCode, orderCount);

        Order order = new Order();
        order.setUserId(userId);
        order.setCommodityCode(commodityCode);
        order.setCount(orderCount);
        order.setMoney(orderMoney);
        
        //Save the order
        this.save(order);
        try {
            TimeUnit.SECONDS.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(true){
            throw new RuntimeException(" rollback test ");
        }
        return order;
    }

    /**
     * :: Calculation of the total price of goods purchased
     * @param commodityCode
     * @param orderCount
     * @return
     */
    private int calculate(String commodityCode, int orderCount){
        //Commodity prices
        Integer price = map.get(commodityCode) == null ? 0 : map.get(commodityCode);
        return price * orderCount;
    }
}

1.4.5 Configuration file reference

server:
  port: 8090

spring:
  main:
    # One entity class corresponds to multiple tables, overriding the
    allow-bean-definition-overriding: true
  shardingsphere:
    datasource:
      ds0:
        # Configure data source specifics, including connection pools, drivers, addresses, usernames and passwords
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/account?autoReconnect=true&allowMultiQueries=true
        password: root
        type: com.zaxxer.hikari.HikariDataSource
        username: root
      ds1:
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/account?autoReconnect=true&allowMultiQueries=true
        password: root
        type: com.zaxxer.hikari.HikariDataSource
        username: root
      # Configure the data source, give the data source a name
      names: ds0,ds1
    props:
      sql:
        show: true
    sharding:
      tables:
        account_tbl:
          actual-data-nodes: ds0.account_tbl_${0..1}
          database-strategy:
            standard:
              preciseAlgorithmClassName: com.xxxx.store.account.config.PreciseDBExtShardingAlgorithm
              #rangeAlgorithmClassName: com.xxxx.store.account.config.RangeDBShardingAlgorithm
              sharding-column: id
          table-strategy:
            standard:
              preciseAlgorithmClassName: com.xxxx.store.account.config.PreciseTablesExtShardingAlgorithm
              #rangeAlgorithmClassName: com.xxxx.store.account.config.RangeTablesShardingAlgorithm
              sharding-column: id

        user_info:
          # Specify the user_info table distribution, configure which database the table is in, and what the table names are.
          actual-data-nodes: ds0.user_info_${0..9}
          database-strategy:
            standard:
              preciseAlgorithmClassName: com.xxxx.store.account.config.PreciseDBShardingAlgorithm
              rangeAlgorithmClassName: com.xxxx.store.account.config.RangeDBShardingAlgorithm
              sharding-column: id
          table-strategy:
            standard:
              preciseAlgorithmClassName: com.xxxx.store.account.config.PreciseTablesShardingAlgorithm
              rangeAlgorithmClassName: com.xxxx.store.account.config.RangeTablesShardingAlgorithm
              sharding-column: id




  # Above is the sharding-jdbc configuration
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: 1ff3782d-b62d-402f-8bc4-ebcf40254d0a
  application:
    name: account-service # Name of the microservice
#  datasource:
#    username: root
#    password: root
#    url: jdbc:mysql://127.0.0.1:3306/account
#    driver-class-name: com.mysql.cj.jdbc.Driver

seata:
  enabled: true
  enable-auto-data-source-proxy: false
  application-id: account-service
  tx-service-group: default_tx_group
  service:
    vgroup-mapping:
      default_tx_group: default
    disable-global-transaction: false
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace: 1ff3782d-b62d-402f-8bc4-ebcf40254d0a
      group: SEATA_GROUP
      username: nacos
      password: nacos
  config:
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: 1ff3782d-b62d-402f-8bc4-ebcf40254d0a
      group: SEATA_GROUP
      username: nacos
      password: nacos

Recommended Today

[Unity] Download and Installation

Download the Unity Hub Installer Method 1 Go to the official website download page Unity Hub It is a must install software to use Unity, this software helps us to manage Unity software version and Unity project. Enter the URL in your browserunity.com/cn/download Go to the download page. Then clickDownload for Windows① maybeDownload other versions②, […]