您好,欢迎来到星星旅游。
搜索
您的当前位置:首页Sharding-JDBC自动实现MySQL读写分离的示例代码

Sharding-JDBC自动实现MySQL读写分离的示例代码

来源:星星旅游
Sharding-JDBC⾃动实现MySQL读写分离的⽰例代码

⽬录

⼀、ShardingSphere和Sharding-JDBC概述

1.1、ShardingSphere简介 1.2、Sharding-JDBC简介1.3、Sharding-JDBC作⽤1.4、ShardingSphere规划线路图1.5、ShardingSphere三种产品的区别⼆、数据库中间件

2.1、数据库中间件简介2.2、Sharding-JDBC和MyCat区别三、Sharding-JDBC+MyBatisPlus实现读写分离

3.0、项⽬代码结构和建表SQL语句3.1、引⼊Maven依赖3.2、yml⽂件配置3.3、UserEntity实体类3.4、UserMapper接⼝3.5、UserService类3.6、UserController类3.7、项⽬启动测试四、HikariCP连接池使⽤遇到的两个Bug

五、读写分离架构,经常出现的读延迟的问题如何解决? 参考链接:

前⾔:博客我⽤AOP+AbstractRoutingDataSource实现了MySQL读写分离,⾃⼰写代码实现判断该使⽤哪个数据源挺⿇烦的。Sharding-JDBC 是 Apache 旗下的 ShardingSphere 中的⼀款轻量级产品,引⼊ jar 即可完成读写分离的需求,可以理解为增强版的 JDBC,现在被使⽤的较多。使⽤Sharding-JDBC配置MySQL读写分离,优点在于数据源完全有Sharding-JDBC托管,写操作⾃动执⾏master库,读操作⾃动执⾏slave库。不需要程序员在程序中关注这个实现,⽐你⾃⼰去配多数据源简单多了。

⼀、ShardingSphere和Sharding-JDBC概述

1.1、ShardingSphere简介

在介绍Sharding-JDBC之前,有必要先介绍下Sharding-JDBC的⼤家族ShardingSphere。在介绍ShardingSphere之后,相信⼤家会对ShardingSphere的整体架构以及Sharding-JDBC扮演的⾓⾊会有更深的了解。

ShardingSphere是后来规划的,最开始是只有 Sharding-JDBC ⼀款产品,基于客户端形式的分库分表。后⾯发展变成了现在的Apache ShardingSphere(Incubator) ,它是⼀套开源的分布式数据库中间件解决⽅案组成的⽣态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(规划中)这3款相互独⽴,却⼜能够混合部署配合使⽤的产品组成。它们均提供标准化的数据分⽚、分布式事务和数据库治理功能,可适⽤于如Java同构、异构语⾔、容器、云原⽣等各种多样化的应⽤场景。

ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利⽤关系型数据库的计算和存储能⼒,⽽并⾮实现⼀个全新的关系型数据库。 它与NoSQL和NewSQL是并存⽽⾮互斥的关系。NoSQL和NewSQL作为新技术探索的前沿,放眼未来,拥抱变化,是⾮常值得推荐的。反之,也可以⽤另⼀种思路看待问题,放眼未来,关注不变的东西,进⽽抓住事物本质。 关系型数据库当今依然占有巨⼤市场,是各个公司核⼼业务的基⽯,未来也难于撼动,我们⽬前阶段更加关注在原有基础上的增量,⽽⾮。

1.2、Sharding-JDBC简介

定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使⽤客户端直连数据库,以jar包形式提供服务,⽆需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

适⽤于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使⽤JDBC。基于任何第三⽅的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。

⽀持任意实现JDBC规范的数据库。⽬前⽀持MySQL,Oracle,SQLServer和PostgreSQL。

1.3、Sharding-JDBC作⽤

1.4、ShardingSphere规划线路图

1.5、ShardingSphere三种产品的区别

⼆、数据库中间件

透明化读写分离所带来的影响,让使⽤⽅尽量像使⽤⼀个数据库⼀样使⽤主从数据库,是读写分离数据库中间件的主要功能。

2.1、数据库中间件简介

数据库中间件可以简化对读写分离以及分库分表的操作,并隐藏底层实现细节,可以像操作单库单表那样操作多库多表,主流的设计⽅案主要有两种:

服务端代理:需要独⽴部署⼀个代理服务,该代理服务后⾯管理多个数据库实例,在应⽤中通过⼀个数据源与该代理服务器建⽴连接,由该代理去操作底层数据库,并返回相应结果。优点是⽀持多语⾔,对业务透明,缺点是实现复杂,实现难度⼤,同时代理需要确保⾃⾝⾼可⽤

客户端代理:在连接池或数据库驱动上进⾏⼀层封装,内部与不同的数据库建⽴连接,并对SQL进⾏必要的操作,⽐如读写分离选择⾛主库还是从库,分库分表select后如何聚合结果。优点是实现简单,天然去中⼼化,缺点是⽀持语⾔较少,版本升级困难⼀些常见的数据库中间件如下:

Cobar:阿⾥开源的关系型数据库分布式服务中间件,已停更DRDS:脱胎于Cobar,全称分布式关系型数据库服务MyCat:开源数据库中间件,⽬前更新了MyCat2版本

Atlas:Qihoo 360公司Web平台部基础架构团队开发维护的⼀个基于MySQL协议的数据中间层项⽬,同时还有⼀个NoSQL的版本,叫Pikatddl:阿⾥巴巴⾃主研发的分布式数据库服务

Sharding-JDBC:ShardingShpere的⼀个⼦产品,⼀个轻量级Java框架

2.2、Sharding-JDBC和MyCat区别

1)mycat是⼀个中间件的第三⽅应⽤,sharding-jdbc是⼀个jar包2)使⽤mycat时不需要改代码,⽽使⽤sharding-jdbc时需要修改代码Mycat(proxy中间件层):

Sharding-jdbc(TDDL为代表的应⽤层):

可以看出sharding-jdbc作为⼀个组件集成在应⽤内,⽽mycat则作为⼀个独⽴的应⽤需要单独部署。从架构上看sharding-jdbc更符合分布式架构的设计,直连数据库,没有中间应⽤,理论性能是最⾼的(实际性能需要结合具体的代码实现,理论性能可以理解为上限,通过不断优化代码实现,逐渐接近理论性能)。同时缺点也很明显,由于作为组件存在,需要集成在应⽤内,意味着作为使⽤⽅,必须要集成到代码⾥,使得开发成本相对较⾼;另⼀⽅⾯,由于需要集成在应⽤内,使得需要针对不同语⾔(java、C、PHP……)有不同的实现(事实上sharding-jdbc⽬前只⽀持Java),这样组件本⾝的维护成本也会很⾼。最终将应⽤场景限定在由Java开发的应⽤这⼀种场景下。

Sharding-JDBC较于MyCat,我认为最⼤的优势是:sharding-jdbc是轻量级的第三⽅⼯具,直连数据库,没有中间应⽤,我们只需要在项⽬中引⽤指定的jar包即可,然后根据项⽬的业务需要配置分库分表或者读写分离的规则和⽅式。

三、Sharding-JDBC+MyBatisPlus实现读写分离

3.0、项⽬代码结构和建表SQL语句

(1) 项⽬代码结构

(2) 建表SQL语句

DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (

`user_id` int(11) NOT NULL AUTO_INCREMENT, `account` varchar(45) NOT NULL, `nickname` varchar(18) NOT NULL, `password` varchar(45) NOT NULL,

`headimage_url` varchar(45) DEFAULT NULL, `introduce` varchar(45) DEFAULT NULL, PRIMARY KEY (`user_id`),

UNIQUE KEY `account_UNIQUE` (`account`), UNIQUE KEY `nickname_UNIQUE` (`nickname`)

) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8;

3.1、引⼊Maven依赖

org.apache.shardingsphere sharding-jdbc-core 4.1.1

com.baomidou

mybatis-plus-boot-starter 3.4.2

mysql

mysql-connector-java

org.springframework.boot spring-boot-starter-web

org.projectlombok lombok 1.18.4 provided

3.2、yml⽂件配置

Spring Boot 2.x中,对数据源的选择也紧跟潮流,默认采⽤了⽬前性能最佳的

spring:

shardingsphere: datasource:

names: master,slave # 数据源名字 master:

type: com.zaxxer.hikari.HikariDataSource # 连接池 driver-class-name: com.mysql.cj.jdbc.Driver

jdbc-url: jdbc:mysql://xxxxxx:3306/test?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=utf-8 #主库地址 username: root password: xxxxxx hikari:

maximum-pool-size: 20 #最⼤连接数量 minimum-idle: 10 #最⼩空闲连接数

max-lifetime: 0 #最⼤⽣命周期,0不过期。不等于0且⼩于30秒,会被重置为默认值30分钟.设置应该⽐mysql设置的超时时间短 idle-timeout: 30000 #空闲连接超时时长,默认值600000(10分钟) connection-timeout: 60000 #连接超时时长 data-source-properties: prepStmtCacheSize: 250

prepStmtCacheSqlLimit: 2048 cachePrepStmts: true useServerPrepStmts: true slave:

type: com.zaxxer.hikari.HikariDataSource # 连接池 driver-class-name: com.mysql.cj.jdbc.Driver

jdbc-url: jdbc:mysql://xxxxxx:3306/test?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=utf-8 #从库地址 username: root

password: xxxxxx hikari:

maximum-pool-size: 20 minimum-idle: 10 max-lifetime: 0 idle-timeout: 30000

connection-timeout: 60000 data-source-properties: prepStmtCacheSize: 250

prepStmtCacheSqlLimit: 2048 cachePrepStmts: true useServerPrepStmts: true masterslave:

load-balance-algorithm-type: round_robin # 负载均衡算法,⽤于配置从库负载均衡算法类型,可选值:ROUND_ROBIN(轮询),RANDOM(随机) name: ms # 最终的数据源名称

master-data-source-name: master # 主库数据源名称

slave-data-source-names: slave # 从库数据源名称列表,多个逗号分隔 props: sql:

show: true # 在执⾏SQL时,会打印SQL,并显⽰执⾏库的名称,默认false

3.3、UserEntity实体类

package com.hs.sharingjdbc.entity;

import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import lombok.Data;

@Data

//定义表名:当数据库名与实体类名不⼀致或不符合驼峰命名时,需要在此注解指定表名@TableName(value = \"user\")public class UserEntity {

//指定主键⾃增策略:value与数据库主键列名⼀致,若实体类属性名与表主键列名⼀致可省略value @TableId(type = IdType.AUTO) private Integer user_id; private String account; private String nickname; private String password;

private String headimage_url; private String introduce;}

3.4、UserMapper接⼝

编写的接⼝需要继承 BaseMapper接⼝,该接⼝源码定义了⼀些通⽤的操作数据库⽅法, 单表⼤部分 CRUD 操作都能直接搞定,相⽐原⽣的MyBatis,效率提⾼了很多

package com.hs.sharingjdbc.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.hs.sharingjdbc.entity.UserEntity;

public interface UserMapper extends BaseMapper {

// int insert(T entity);//

// int deleteById(Serializable id);//

// int deleteByMap(@Param(\"cm\") Map columnMap);//

// int delete(@Param(\"ew\") Wrapper queryWrapper);//

// int deleteBatchIds(@Param(\"coll\") Collection idList);//

// int updateById(@Param(\"et\") T entity);//

// int update(@Param(\"et\") T entity, @Param(\"ew\") Wrapper updateWrapper);//

// T selectById(Serializable id);//

// List selectBatchIds(@Param(\"coll\") Collection idList);//

// List selectByMap(@Param(\"cm\") Map columnMap);//

// T selectOne(@Param(\"ew\") Wrapper queryWrapper);//

// Integer selectCount(@Param(\"ew\") Wrapper queryWrapper);//

// List selectList(@Param(\"ew\") Wrapper queryWrapper);//

// List> selectMaps(@Param(\"ew\") Wrapper queryWrapper);//

// List selectObjs(@Param(\"ew\") Wrapper queryWrapper);//

// > E selectPage(E page, @Param(\"ew\") Wrapper queryWrapper);//

// >> E selectMapsPage(E page, @Param(\"ew\") Wrapper queryWrapper); }

主类添加@MapperScan(\"com.hs.sharingjdbc.mapper\"),扫描所有Mapper接⼝

package com.hs.sharingjdbc;

import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication()

@MapperScan(\"com.hs.sharingjdbc.mapper\")public class SharingJdbcApplication {

public static void main(String[] args) {

SpringApplication.run(SharingJdbcApplication.class, args); }}

3.5、UserService类

package com.hs.sharingjdbc.service;

import com.hs.sharingjdbc.entity.UserEntity;import com.hs.sharingjdbc.mapper.UserMapper;

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;

@Service

public class UserService {

@Autowired

UserMapper userMapper;

public List findAll() {

//selectList() ⽅法的参数为 mybatis-plus 内置的条件封装器 Wrapper,这⾥不填写表⽰⽆任何条件,全量查询 List userEntities = userMapper.selectList(null);

return userEntities; }

public int insertUser(UserEntity user) {

int i = userMapper.insert(user); return i; }}

3.6、UserController类

package com.hs.sharingjdbc.controller;

import com.hs.sharingjdbc.entity.UserEntity;import com.hs.sharingjdbc.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;

@RestController

public class UserController{

@Autowired

UserService userService;

@RequestMapping(\"/listUser\") public List listUser() {

List users = userService.findAll(); return users; }

@RequestMapping(\"/insertUser\") public void insertUser() {

UserEntity userEntity = new UserEntity(); userEntity.setAccount(\"22222\"); userEntity.setNickname(\"hshshs\"); userEntity.setPassword(\"123\"); userService.insertUser(userEntity); }}

3.7、项⽬启动测试

这样读写分离就算是可以了。

四、HikariCP连接池使⽤遇到的两个Bug

4.1、SpringBoot2关于HikariPool-1 - Failed to validate connection com.mysql.cj.jdbc.ConnectionImp

上⾯在使⽤springboot2.x 时遇到了⼀个很奇怪的问题,在程序运⾏起来之后,长时间的不进⾏数据库操作就会出现这样的错误,后⾯跟着这样的叙述, Connection is not available, requesttimed out after XXXms. Possibly consider using a shorter maxLifetime value.

为什么会存在不可⽤的连接呢?maxLifetime可以控制连接的⽣命周期,我们来以前看看maxLifetime参数。

我引⼀下chrome上⾯的中⽂翻译:

此属性控制数据库连接池中连接的最⼤⽣存期。使⽤中的连接永远不会停⽌,只有关闭连接后,连接才会被移除。在逐个连接的基础上,应⽤较⼩的负衰减以避免池中的质量消灭。 我们强烈建议设置此值,它应该⽐任何数据库或基础结构施加的连接时间短⼏秒钟。 值0表⽰没有最⼤寿命(⽆限寿命),当然要遵守该idleTimeout设置。 默认值:1800000(30分钟)分析是HikariCP连接池对连接管理的问题,因此想⽅设法进⾏SpringBoot2.0 HikariCP连接池配置

hikari:

maximum-pool-size: 20 #最⼤连接数量 minimum-idle: 10 #最⼩空闲连接数

max-lifetime: 0 #最⼤⽣命周期,0不过期。不等于0且⼩于30秒,会被重置为默认值30分钟.设置应该⽐mysql设置的超时时间短 idle-timeout: 30000 #空闲连接超时时长,默认值600000(10分钟) connection-timeout: 60000 #连接超时时长 data-source-properties: prepStmtCacheSize: 250

prepStmtCacheSqlLimit: 2048 cachePrepStmts: true useServerPrepStmts: true

spring.datasource.hikari.minimum-idle: 最⼩空闲连接,默认值10,⼩于0或⼤于maximum-pool-size,都会重置为maximum-pool-sizespring.datasource.hikari.maximum-pool-size: 最⼤连接数,⼩于等于0会被重置为默认值10;⼤于零⼩于1会被重置为minimum-idle的值

spring.datasource.hikari.idle-timeout: 空闲连接超时时间,默认值600000(10分钟),⼤于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且⼩于10秒,会被重置为10秒。spring.datasource.hikari.max-lifetime: 连接最⼤存活时间,不等于0且⼩于30秒,会被重置为默认值30分钟.设置应该⽐mysql设置的超时时间短spring.datasource.hikari.connection-timeout: 连接超时时间:毫秒,⼩于250毫秒,否则被重置为默认值30秒

4.2、com.zaxxer.hikari.pool.HikariPool : datasource -Thread starvation or clock leap detected (housekeeper delta=4m32s295ms949µs223ns).分析:WARN警告级别,看起来不是什么错误,但是连接数据库就是连不上

英译汉:数据源-检测到线程饥饿或时钟跳动

⼈话:要么是检测到等待连接的时间过长,造成进饥饿;要么是检测到时钟跳动,反正最后是关闭了数据库连接。

其实,这⾥根本就没有报错,只是⼀个警告。是上游代码出了问题,长时间不调⽤Service层进⾏存储,然后Hikari数据源就关掉了⾃⼰;当有新的调⽤时,会启⽤新的数据源。修改默认的连接池配置如下:

datasource:

type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://${ip}:3306/${数据库}?useSSL=false&characterEncoding=UTF-8 username: ${username} password: ${password} hikari:

auto-commit: true #空闲连接超时时长 idle-timeout: 60000 #连接超时时长

connection-timeout: 60000 #最⼤⽣命周期,0不过期 max-lifetime: 0 #最⼩空闲连接数 minimum-idle: 10 #最⼤连接数量

maximum-pool-size: 20

五、读写分离架构,经常出现的读延迟的问题如何解决?

刚插⼊⼀条数据,然后马上就要去读取,这个时候有可能会读取不到?归根到底是因为主节点写⼊完之后数据是要复制给从节点的,读不到的原因是复制的时间⽐较长,也就是说数据还没复制到从节点,你就已经去从节点读取了,肯定读不到。mysql5.7 的主从复制是多线程了,意味着速度会变快,但是不⼀定能保证百分百马上读取到,这个问题我们可以有两种⽅式解决:(1)业务层⾯妥协,是否操作完之后马上要进⾏读取

(2)对于操作完马上要读出来的,且业务上不能妥协的,我们可以对于这类的读取直接⾛主库

当然Sharding-JDBC也是考虑到这个问题的存在,所以给我们提供了⼀个功能,可以让⽤户在使 ⽤的时候指定要不要⾛主库进⾏读取。在读取前使⽤下⾯的⽅式进⾏设置就可以了:

public List findAllUser() {

// 强制路由主库

HintManager.getInstance().setMasterRouteOnly();

return this.list(); }

参考链接:

到此这篇关于Sharding-JDBC⾃动实现MySQL读写分离的⽂章就介绍到这了,更多相关Sharding-JDBC MySQL读写分离内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- stra.cn 版权所有 赣ICP备2024042791号-4

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务