首页 > 其他分享 >使用 Sharding Jdbc 实现读写分离

使用 Sharding Jdbc 实现读写分离

时间:2023-05-02 12:12:53浏览次数:46  
标签:主库 Jdbc jobs 读写 Employee Sharding import com

上一篇博客介绍了 MySQL 的主从复制的搭建,为实现读写分离创造了条件。对于一个网站来说,80% 来源于读操作,绝大多数情况下的网站宕机,都是由于过多的读操作导致的,因此在实际的生产环境中,经常会搭建一主多从的架构,主库只负责写操作,多个从库用来负责读操作,对于少量需要实时获取信息的读操作,可以从主库进行读取。

本篇博客将使用 Sharding Jdbc 在主从复制的基础上和已经开发好的项目上,只需要进行配置,不需要写任何代码就可以实现读写分离。只有当需要强制从主库读取数据时,才需要写极少量的代码指定从主库读取。在本篇博客的最后会提供源代码下载。


一、Sharding Jdbc 简单介绍

Sharding Jdbc 是轻量级的 Java 框架,在 Java 的 Jdbc 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 Jdbc 驱动,完全兼容 Jdbc 和各种 ORM 框架。可以在程序中轻松的实现读写分离。

Sharding Jdbc 具有以下几个特点:

  • 适用于任何基于 Jdbc 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring Jdbc Template 或直接使用 Jdbc。

  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。

  • 支持任意实现 Jdbc 规范的数据库。支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。

当然 Sharding Jdbc 也可以实现物理上的分库分表,由于物理上的分库分表自身也存在很多缺点,主要体现在人工处理问题时比较麻烦,如测试环境出现 bug 或生产环境出现问题时,需要人工排查问题时很麻烦,所以 Sharding Jdbc 逐渐很少在分库分表上发挥作用了,现在大多数使用分布式数据库在逻辑上使用分区表,物理上还是一张表,如 OceanBase 数据库。


二、搭建工程

搭建一个 SpringBoot 工程,具体结构如下:

image

pom 文件中使用的 SpringBoot 版本和引入的 jar 包依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/>
    </parent>
    <groupId>com.jobs</groupId>
    <artifactId>springboot_rw_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <!--sharding jdbc引入-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

从上面可以发现,如果想使用 sharding jdbc 只需要引入 sharding-jdbc-spring-boot-starter 即可,这里引入的是最新的 4.1.1 版本,需要注意的是:如果使用 durid 连接池的话,请使用 durid 包,不要使用 druid-spring-boot-starter 依赖包。

本篇博客做的是 Demo ,使用上篇博客搭建的 Mysql 一主一从结构作为演示,application.yml 配置如下:

server:
  port: 8888
spring:
  shardingsphere:
    datasource:
      # 数据源名称,以英文逗号分隔,需要跟下面的每个数据源配置对应上
      names: master,slave
      # 主库连接信息
      master:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.216.128:3306/rw_demo?characterEncoding=utf-8
        username: root
        password: root
      # 从库连接信息
      slave:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.216.129:3306/rw_demo?characterEncoding=utf-8
        username: root
        password: root
    masterslave:
      # 从库负载均衡算法,可选值为:round_robin 和 random
      load-balance-algorithm-type: round_robin
      # 最终的数据源名称(可以随便指定)
      name: ds
      # 主库数据源名称
      master-data-source-name: master
      # 从库数据源名称列表,多个逗号分隔
      slave-data-source-names: slave
    props:
      sql:
        #开启SQL显示,默认false
        show: true
mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

配置文件的内容很简单,注释也很详细,应该很容易看懂。最主要就是把主库和从库的数据库连接信息配置好即可。

默认情况下 sharding jdbc 所有的写操作都是在主库上执行,所有的读操作都是在从库上执行。

对于从库的选择,有两种负载均衡算法:轮询和随机。

到此为止,Sharding Jdbc 的读写分离,就已经全部搞定了,下面要编写的业务代码,跟 Sharding Jdbc 没有什么关系了,只有想要强制从主库读取数据时,才需要编写一点点代码。下面就让我们快速把用于测试效果的业务代码堆起来吧。


三、代码细节展示

创建了一个实体类 Employee 具体细节如下:

package com.jobs.entity;

import lombok.Data;
import java.io.Serializable;

@Data
public class Employee implements Serializable {

    private Long id;

    private String name;

    private Integer age;
}

由于使用 Mybatis Plus 框架,因此对于 Mapper 和 Service 等都是自动生成的,具体如下:

package com.jobs.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jobs.entity.Employee;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {}
package com.jobs.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.jobs.entity.Employee;

public interface EmployeeService extends IService<Employee> {}
package com.jobs.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jobs.entity.Employee;
import com.jobs.mapper.EmployeeMapper;
import com.jobs.service.EmployeeService;
import org.springframework.stereotype.Service;

@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> 
        implements EmployeeService {}

下面就是用于对外提供接口测试的 EmployeeController 代码:

package com.jobs.controller;

import com.jobs.entity.Employee;
import com.jobs.service.EmployeeService;
import org.apache.shardingsphere.api.hint.HintManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RequestMapping("/emp")
@RestController
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    //主库添加
    @PostMapping
    public Employee addEmployee(@RequestBody Employee employee) {
        employeeService.save(employee);
        return employee;
    }

    //从库读取
    @GetMapping("/{id}")
    public Employee getEmployee(@PathVariable("id") Long id) {
        Employee employee = employeeService.getById(id);
        return employee;
    }

    //如果必须要及时拿到最新结果的话,可以强制从主库读取
    @GetMapping("/list")
    public List<Employee> getList() {
        HintManager.clear();
        //HintManager 实现了 AutoCloseable 接口,因此使用 try 可以自动释放资源
        try (HintManager hintManager = HintManager.getInstance()) {
            hintManager.setMasterRouteOnly();
            List<Employee> list = employeeService.list();
            return list;
        }
    }
}

四、验证效果

我们使用 Postman 工具来请求结果,在 IDEA 的控制台中,查看日志结果来验证读写分离的效果。


(1)调用添加 Employee 的接口

image

控制台打印的日志如下,由于原始控制台的信息换行过多,因此我复制到文本文件中进行展示:

image

从 Actual SQL:master 可以看出,添加操作是访问主库进行操作的。


(2)调用通过 id 获取 Employee 的接口

image

控制台打印的结果如下:

image

从 Actual SQL:slave 可以看出,读取数据是访问从库进行操作的。


(3)调用获取员工列表接口,代码中强制从主库中获取数据

由于主从复制需要一定的时间,尽管时间很短暂,为了能够实时获取数据,我们希望强制从主库获取数据,此时只需要添加一下代码,通过 HintManager 指定从主库获取即可。需要注意的是一定要提前调用 HintManager.clear() 方法。

HintManager.clear();
//HintManager 实现了 AutoCloseable 接口,因此使用 try 可以自动释放资源
try (HintManager hintManager = HintManager.getInstance()) {
    hintManager.setMasterRouteOnly();
    //自己需要编写的操作主库的业务代码...
}

使用 Postman 调用获取员工列表接口如下:

image

控制台打印的结果如下:

image

从上图中的 Actual SQL:master 可以发现,我们已经实现了从主库强制进行读取的操作。


本篇博客的源代码下载地址:https://files.cnblogs.com/files/blogs/699532/springboot_rw_demo.zip

标签:主库,Jdbc,jobs,读写,Employee,Sharding,import,com
From: https://www.cnblogs.com/studyjobs/p/17367516.html

相关文章

  • 学习jdbc时遇到的问题
    jar包问题问题描述:java.sql.SQLException:Unabletoloadauthenticationplugin'caching_sha2_password”如果是上述的问题,可能就是jar包的问题我的Mysql是8.0.26的,而我所用的Java包时MySQL5的Java包,这时只要把jar包更改为MySQL8的jar包即可解决问题成功使用......
  • JDBC
    1.JDBC是什么JavaDataBaseConnectivity(Java语言连接数据库)2.JDBC的本质是什么?JDBC是SUN公司制定的一套接口(interface)java.sql.*;(这个软件包下有很多接口。)接口都有调用者和实现者。面向接口调用、面向接口写实现类,这都属于面向接口编程。2.1为什么要面......
  • C++文件读写常用操作整理
    C++对于文件的操作需要包含<fstream>头文件文件类型分为两种:文本文件-文件以文件的ASCII码的形式存储在计算机中二进制文件-文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们操作文件的三大类:ofstream:写操作ifstream:读操作fstream:读写操作一、文......
  • 在Android中实现文件读写
    android实现下载文件/***从网上下载*@paramurl下载路径*@paramoutputFile创建本地保存流的文件*@return*@return下载失败返回1(比如没有网络等情况)下载成功返回0*/publicstaticintdownloadFile(Stri......
  • JAVA的Jdbc连接Access数据库
      Eclipse加入Access_JDBC30.jar:   程序如下:importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.Statement;publicclassconn{publicstaticStringconnct(){try{......
  • kafka 不支持读写分离的原因
    前段时间在看kafka相关内容,发现kafka“所有的”读写流量都在主partition上,从partition只负责备份数据。那么为什么kafka从partition不跟其他中间件一样承接读流量?读写分离的初衷读写分离的初衷我觉得是利用读流量&写流量不同的特性做针对性的优化,而这两种流量......
  • odbc和jdbc的区别与联系
    一、定义 ODBC (OpeDatabaseConnectivity),开放数据库互联,是微软公司开发和定义的一套数据库访问标准,用户也可以直接将sql语句送给ODBC。通过使用ODBC,应用程序能够使用相同的源代码和各种各样的数据库进行交互。下面是ODBC应用系统的体系结构。一个基于ODBC的应用程序,对数据......
  • 项目启动时数据库报错:com.mysql.cj.jdbc.exceptions.CommunicationsException: Commun
    项目启动时报错:com.mysql.cj.jdbc.exceptions.CommunicationsException:Communicationslinkfailure解决方法(转载):https://blog.csdn.net/lvoelife/article/details/129284611?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTR......
  • python 读写mdb
    Python中可以使用pyodbc模块连接MicrosoftAccess数据库(.mdb格式)。首先需要先安装pyodbc模块和MicrosoftAccess驱动程序,可以使用pip安装pyodbc:```pipinstallpyodbc```然后需要下载安装MicrosoftAccess驱动程序,下载链接:https://www.microsoft.com/zh-cn/download/details......
  • python 读写sqlite3 读写内存中的数据库
    Python中,可以使用标准库sqlite3来读写SQLite数据库。下面是一个示例代码,展示如何连接到SQLite数据库,创建表格,插入数据,查询数据和关闭数据库:importsqlite3#连接到数据库conn=sqlite3.connect('example.db')#创建一个表格conn.execute('''CREATETABLEIFNOTE......