ShardingSphere-JDBC+SpringBoot+Mybatis读写分离(一)

1,205 阅读5分钟

示例源码地址gitee.com/xiaoyangzha…

依赖版本

<!--  Spring Boot          -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
    <relativePath/>
</parent>
<!--    ORM框架     -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
<!--  数据源类型:以下二选一       -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<!--  或者       -->
<!--    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
-->

<!--  ShardingSphere-JDBC       -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.2.1</version>
</dependency>

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.33</version>
</dependency>

ShardingSphere-JDBC有单机模式集群模式

单机模式的话,上面的依赖就可以。

集群模式

由于我们要在生产环境使用,ShardingSphere官方推荐生产环境使用集群模式。

目前的版本(5.2.1)的集群模式支持4种类型的元数据仓库

  • Zookeeper(官方推荐)
  • Nacos
  • Etcd
  • Consul

Zookeeper是老牌分布式服务协同中间件了(请自行百度使用教程),老大不推荐使用,而且考虑到配置中心(目前用的Apollo)和注册中心(目前用的Eureka)以后会合并到Nacos,所以这里的元数据仓库我选择的Nacos(官方推荐的是Zookeeper)。

那么这里就需要再添加两个依赖

<dependency>
    <!--  nacos作为集群的元数据仓库       -->
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-cluster-mode-repository-nacos</artifactId>
    <version>5.2.1</version>
    <!--  这里排除掉nacos-client是考虑到版本冲突问题       -->
    <exclusions>
        <exclusion>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--  naocs配置中心       -->
<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>nacos-config-spring-boot-starter</artifactId>
    <version>0.2.12</version>
</dependency>

MySQL集群搭建

juejin.cn/post/720353…

配置

5.2.1版本的ShardingSphere支持4种方式的配置:(5.3.0开始只支持下面的前两种)

  • YAML
  • JAVA编码
  • Spring Boot Starter(properties)
  • Spring命名空间

由于我们之前使用的Apollo就是properties格式的,所以这里我继续采用了第三种(Spring Boot Starter)配置方式。

本地配置

如果前边的数据源类型依赖使用了druid的starter,需要在application.properties中排除druid数据源的自动配置 spring.autoconfigure.exclude=com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure ,不然启动会报错

application.properties配置Nacos配置中心属性

nacos.config.remote-first=true
# 配置组
nacos.config.group=DEFAULT_GROUP
# 配置文件类型
nacos.config.type=properties
nacos.config.max-retry=5
nacos.config.config-retry-time=6000
nacos.config.config-long-poll-timeout=60000
#默认地址
nacos.config.server-addr=http://127.0.0.1:8848
nacos.config.auto-refresh=true
nacos.config.bootstrap.enable=true
nacos.config.bootstrap.log-enable=true
nacos.config.enable-remote-sync-config=true
# dataId(见名知意即可,没有特殊要求)
nacos.config.data-id=db-dev.properties
nacos.config.password=nacos
nacos.config.username=nacos
# 命名空间
nacos.config.namespace=sharding-demo

management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=*

application.properties配置ShardingSphere元数据仓库属性

#启用ShardingSphere
spring.shardingsphere.enabled=true

#运行模式 https://shardingsphere.apache.org/document/5.2.1/cn/user-manual/shardingsphere-jdbc/spring-boot-starter/mode/
spring.shardingsphere.mode.type=Cluster

#元数据仓库类型 https://shardingsphere.apache.org/document/5.2.1/cn/user-manual/common-config/builtin-algorithm/metadata-repository/
spring.shardingsphere.mode.repository.type=Nacos

#以下两个属性是针对集群模式的配置
spring.shardingsphere.mode.repository.props.namespace=sharding-demo
spring.shardingsphere.mode.repository.props.server-lists=http://127.0.0.1:8848

Nacos配置(配置中心&元数据持久化仓库)

首先根据Nacos官网快速开始,我们先启动Nacos-Server(可以本地服务启动,也可以Docker启动),启动成功后访问localhost:8848/nacos(默认端口是8848),跳转到登录界面,默认的用户名和密码都是nacos,登录成功后看到

image.png

根据Nacos官网的概念,

命名空间(namespace): 用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

配置集(dataId):Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。

配置分组(group):Nacos 中的一组配置集,是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和 MQ_topic 配置。

所以根据自己需要创建新的命名空间

image.png

然后在某个命名空间(sharding-demo)下创建配置文件,配置文件名可以随意取,组可以使用默认的DEFAULT_GROUP

image.png

# 数据源名称
spring.shardingsphere.datasource.names=write-ds,read-ds-0,read-ds-1
# 数据源配置
spring.shardingsphere.datasource.write-ds.url=jdbc:mysql://127.0.0.1:3310/sharding_test?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.write-ds.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.write-ds.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.write-ds.dataSourceClassName=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.write-ds.username=root
spring.shardingsphere.datasource.write-ds.password=root

spring.shardingsphere.datasource.read-ds-0.url=jdbc:mysql://127.0.0.1:3311/sharding_test?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.read-ds-0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.read-ds-0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.read-ds-0.dataSourceClassName=com.alibaba.druid.pool.DruidDataSource
#前文创建的授权账户
spring.shardingsphere.datasource.read-ds-0.username=slave
spring.shardingsphere.datasource.read-ds-0.password=123456

spring.shardingsphere.datasource.read-ds-1.url=jdbc:mysql://127.0.0.1:3312/sharding_test?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.read-ds-1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.read-ds-1.dataSourceClassName=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.read-ds-1.driver-class-name=com.mysql.jdbc.Driver
#前文创建的授权账户
spring.shardingsphere.datasource.read-ds-1.username=slave
spring.shardingsphere.datasource.read-ds-1.password=123456

##读写分离 5.2.x及以上
spring.shardingsphere.database.name=readwrite_ds
#写库
spring.shardingsphere.rules.readwrite-splitting.data-sources.readwrite_ds.static-strategy.write-data-source-name=write-ds
#读库
spring.shardingsphere.rules.readwrite-splitting.data-sources.readwrite_ds.static-strategy.read-data-source-names=read-ds-0,read-ds-1
# 负载均衡策略
spring.shardingsphere.rules.readwrite-splitting.data-sources.readwrite_ds.load-balancer-name=round_robin
spring.shardingsphere.rules.readwrite-splitting.load-balancers.round_robin.type=ROUND_ROBIN

# 开启打印sql,方便查看具体访问的哪个数据源
spring.shardingsphere.props.sql-show=true

应用开启Nacos配置

在应用启动类(带有@SpringBootApplication注解的类)上添加@EnableNacosConfig注解即可。

测试读写分离

按照上文配置后,启动应用。 如果启动失败,查看下Nacos的日志nacos.log和start.out

应用启动成功后,在nacos控制台可以看到ShardingSphere持久化的元数据:

image.png 点击右侧的详情进入,看到如下元数据信息: image.png

测试写操作

可以看到写操作访问的写库write-ds image.png

无事务注解

@Override
public void addUser(AddUserReq req) {
    UserDO userDO = userMapper.selectById(1L);
    UserDO entity = new UserDO();
    entity.setName(req.getName());
    entity.setBirthday(req.getBirthday());
    userMapper.insert(entity);
}

image.png

事务注解

image.png

测试读操作

可以看到读操作访问的读库read-ds-0。如果要测试负载均衡策略,可以多请求几次,可以从日志里查看负载均衡是否生效 image.png

常见问题

元数据存在,应用启动失败

有时候会遇到Nacos的服务列表里已经存在了元数据,但是仍然启动失败,提示ShardingSphereDataSource注入失败的消息,

image.png 原因之一(也可能是其他原因,我没遇到)是第一次持久化元数据时,会把IP作为实例标志的一部分 image.png 同样的,元数据持久化以后,服务再次启动时去取元数据也会把IP作为key的一部分,如果两次的IP不一致,当然取不到元数据啦,所以就导致ShardingSphereDataSource实例化失败

Nacos服务实例删除失败

如果在服务列表里的服务名是PERSISTENT,这时候想删除时会出现如下提示 image.png 这和Nacos的实现机制有关,我们可以通过Nacos提供的开放API来注销实例

import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.remote.http.NamingHttpClientManager;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;

import java.util.List;
import java.util.Properties;

public class NacosClient {
    public static void main(String[] args)
            throws Exception {
        Properties properties = new Properties();
        properties.setProperty("serverAddr", "127.0.0.1:8848");
        properties.setProperty("namespace", "sharding-demo");

        NamingService naming = NamingFactory.createNamingService(properties);
        List<Instance> allInstances = naming.getAllInstances("PERSISTENT");
        NacosRestTemplate nacosRestTemplate = NamingHttpClientManager.getInstance().getNacosRestTemplate();
        Header header = Header.newInstance().addParam("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
        for (Instance instance : allInstances) {
            Query query = new Query();
            query.addParam("serviceName", instance.getServiceName());
            query.addParam("ip", instance.getIp());
            query.addParam("port", instance.getPort());
            query.addParam("namespaceId", "sharding-demo");
            query.addParam("ephemeral", false);
            query.addParam("healthy", false);
            HttpRestResult<Object> result = nacosRestTemplate.delete("http://127.0.0.1:8848/nacos/v1/ns/instance", header, query, String.class);
            System.out.println(result.getData() + ":" + result.getMessage());
        }
    }

}

或者通过curl的方式请求

com.alibaba.nacos.consistency.exception.ConsistencyException: The Raft Group xxx did not find the Leader node

从报错信息我们大致可以看出,和Raft以及一致性、Leader选举有关,具体的解决方法是:

将Nacos安装目录下的data目录下的protocol目录删除,然后重启Nacos即可。

image.png