示例源码地址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集群搭建
配置
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,登录成功后看到
根据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 配置。
所以根据自己需要创建新的命名空间
然后在某个命名空间(sharding-demo
)下创建配置文件,配置文件名可以随意取,组可以使用默认的DEFAULT_GROUP
# 数据源名称
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持久化的元数据:
点击右侧的
详情
进入,看到如下元数据信息:
测试写操作
可以看到写操作访问的写库write-ds
无事务注解
@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);
}
事务注解
测试读操作
可以看到读操作访问的读库read-ds-0
。如果要测试负载均衡策略,可以多请求几次,可以从日志里查看负载均衡是否生效
常见问题
元数据存在,应用启动失败
有时候会遇到Nacos的服务列表里已经存在了元数据,但是仍然启动失败,提示ShardingSphereDataSource
注入失败的消息,
原因之一(也可能是其他原因,我没遇到)是第一次持久化元数据时,会把IP作为实例标志的一部分
同样的,元数据持久化以后,服务再次启动时去取元数据也会把IP作为key的一部分,如果两次的IP不一致,当然取不到元数据啦,所以就导致
ShardingSphereDataSource
实例化失败
Nacos服务实例删除失败
如果在服务列表里的服务名是PERSISTENT
,这时候想删除时会出现如下提示
这和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即可。