当前位置: 首页 > article >正文

如何在 Java 应用中实现数据库的主从复制(读写分离)?请简要描述架构和关键代码实现?

在Java应用中实现数据库主从复制(读写分离)

一、架构描述

(一)整体架构

  1. 主库(Master)
    • 负责处理所有的写操作(INSERT、UPDATE、DELETE等)。它是数据的源头,所有的数据变更首先发生在主库。
    • 主库会将写操作产生的日志(例如MySQL中的binlog)发送给从库。
  2. 从库(Slave)
    • 从库会接收主库发送过来的日志,并根据这些日志来重放操作,从而保持与主库的数据一致性。
    • 从库主要用于处理读操作,分担主库的读负载。
  3. Java应用
    • Java应用需要根据操作的类型(读或写)来决定连接主库还是从库。通常会使用数据库连接池来管理连接,并且有专门的逻辑来判断何时连接主库,何时连接从库。

(二)数据流向

  1. 当有写请求时,例如向数据库插入一条新记录,Java应用会将请求发送到主库。
  2. 主库执行写操作后,将操作记录到日志中,并将日志发送给从库。
  3. 从库接收到日志后,按照日志中的操作顺序在本地执行相同的操作,使自己的数据与主库保持一致。
  4. 当有读请求时,Java应用会将请求发送到从库(可以是多个从库中的一个,通过负载均衡策略),从库返回查询结果。

二、关键代码实现

(一)使用数据库连接池(以Druid为例)

  1. 配置主库连接池
    • 首先需要在项目的配置文件(例如application.propertiesapplication.yml)中配置主库的连接信息。
    • 在Java代码中创建主库的DataSource
import com.alibaba.druid.pool.DruidDataSource;public class MasterDataSourceConfig {public static DruidDataSource createMasterDataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl("jdbc:mysql://master_host:3306/mydb?useSSL=false&serverTimezone = UTC");dataSource.setUsername("root");dataSource.setPassword("password");dataSource.setInitialSize(5);dataSource.setMaxActive(20);// 可以根据需要设置其他参数,如最小空闲连接数等return dataSource;}
}
  1. 配置从库连接池
    • 同样,在配置文件中配置从库的连接信息(如果有主从多个从库,可以配置多个从库连接池)。
    • 创建从库的DataSource
public class SlaveDataSourceConfig {public static DruidDataSource createSlaveDataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl("jdbc:mysql://slave_host:3306/mydb?useSSL=false&serverTimezone = UTC");dataSource.setUsername("root");dataSource.setPassword("password");dataSource.setInitialSize(5);dataSource.setMaxActive(20);return dataSource;}
}

(二)读写分离逻辑实现

  1. 创建一个数据访问层(DAO)的代理类
    • 这个代理类将根据操作类型决定连接主库还是从库。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;public class ReadWriteSplitProxy implements InvocationHandler {private DruidDataSource masterDataSource;private DruidDataSource slaveDataSource;public ReadWriteSplitProxy(DruidDataSource masterDataSource, DruidDataSource slaveDataSource) {this.masterDataSource = masterDataSource;this.slaveDataSource = slaveDataSource;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (isWriteOperation(method)) {return executeOnMaster(method, args);} else {return executeOnSlave(method, args);}}private boolean isWriteOperation(Method method) {// 简单判断方法名是否以insert、update、delete开头来判断是否为写操作String methodName = method.getName().toLowerCase();return methodName.startsWith("insert") || methodName.startsWith("update") || methodName.startsWith("delete");}private Object executeOnMaster(Method method, Object[] args) throws SQLException {try (Connection conn = masterDataSource.getConnection()) {return method.invoke(conn, args);}}private Object executeOnSlave(Method method, Object[] args) throws SQLException {try (Connection conn = slaveDataSource.getConnection()) {return method.invoke(conn, args);}}public static Object newProxyInstance(DruidDataSource masterDataSource, DruidDataSource slaveDataSource, Class<?> clazz) {ReadWriteSplitProxy handler = new ReadWriteSplitProxy(masterDataSource, slaveDataSource);return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);}
}
  1. 使用代理类进行数据库操作
    • 在业务逻辑层中,使用代理类来代替真实的数据库连接进行操作。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class UserDao {private UserDao proxy;public UserDao(DruidDataSource masterDataSource, DruidDataSource slaveDataSource) {this.proxy = (UserDao) ReadWriteSplitProxy.newProxyInstance(masterDataSource, slaveDataSource, UserDao.class);}public void insertUser(String name, int age) throws SQLException {String sql = "INSERT INTO users (name, age) VALUES (?,?)";try (Connection conn = proxy.getConnection();PreparedStatement ps = conn.prepareStatement(sql)) {ps.setString(1, name);ps.setInt(2, age);ps.executeUpdate();}}public User getUserById(int id) throws SQLException {String sql = "SELECT * FROM users WHERE id =?";try (Connection conn = proxy.getConnection();PreparedStatement ps = conn.prepareStatement(sql)) {ps.setInt(1, id);ResultSet rs = ps.executeQuery();if (rs.next()) {User user = new User();user.setId(rs.getInt("id"));user.setName(rs.getString("name"));user.setAge(rs.getInt("age"));return user;}}return null;}
}class User {private int id;private String name;private int age;// 省略getter和setter方法
}

三、日常开发中的合理化使用建议

(一)连接池配置方面

  1. 合理设置连接池大小
    • 主库连接池的大小要根据写操作的并发量和数据库服务器的处理能力来设置。如果写操作非常频繁且数据库性能有限,可以适当增大连接池的最大连接数。
    • 从库连接池的大小要考虑读操作的并发量以及从库的数量。如果有多个从库并且读操作负载较高,可以适当增大每个从库连接池的大小。
  2. 连接有效性检查
    • 配置连接池定期检查连接的有效性。例如,Druid连接池可以通过设置testWhileIdletimeBetweenEvictionRunsMillis等参数来确保获取到的连接是可用的。

(二)故障处理方面

  1. 主库故障
    • 当主库发生故障时,需要有相应的故障转移机制。可以将其中一个从库提升为新的主库,然后更新Java应用中的主库连接配置。
    • 在故障转移过程中,要确保数据的一致性,可能需要暂停部分写操作,等待主库恢复或者新的主库完全准备好。
  2. 从库故障
    • 如果某个从库发生故障,可以暂时将其从可用从库列表中移除。如果有多个从库,可以调整读操作的负载均衡策略,将原本分配给故障从库的读请求分配到其他正常的从库上。

(三)数据一致性方面

  1. 延迟处理
    • 由于主从复制存在一定的延迟,在一些对数据实时性要求较高的场景下,要谨慎处理读操作。例如,在写入主库后立即读取从库可能会得到旧的数据。可以采用重试机制或者等待一定的时间后再从从库读取。
  2. 监控复制状态
    • 定期监控主从复制的状态,包括复制延迟、是否有复制错误等。可以通过数据库自带的工具(如MySQL的SHOW SLAVE STATUS命令)或者在Java应用中编写监控逻辑来实现。

四、实际开发过程中需要注意的点

(一)事务管理

  1. 跨库事务
    • 如果一个业务操作涉及到主库和从库的操作(虽然这种情况较少,但在一些复杂业务场景下可能存在),要考虑跨库事务的管理。可以使用分布式事务框架(如Seata)来确保数据的一致性。
  2. 本地事务
    • 在只涉及主库或者只涉及从库的操作中,要正确使用本地事务。例如,在主库上进行写操作时,要确保事务的原子性、隔离性、一致性和持久性(ACID)。

(二)SQL兼容性

  1. 不同数据库版本差异
    • 主从库可能使用不同版本的数据库,在编写SQL语句时要考虑版本的兼容性。例如,某些函数在不同版本的MySQL中的行为可能不同。
  2. 特定数据库特性
    • 避免使用只在某个数据库版本或者特定数据库中有而其他数据库不支持的特性。如果必须使用,要考虑如何在不同的数据库环境中进行适配。

(三)安全方面

  1. 连接安全
    • 确保主库和从库的连接都是安全的,使用SSL加密连接或者配置合适的防火墙规则来限制对数据库端口的访问。
  2. 权限管理
    • 为主库和从库分别设置合适的用户权限。例如,从库用于读操作的用户不需要有写入权限,这样可以提高安全性。

相关文章:

如何在 Java 应用中实现数据库的主从复制(读写分离)?请简要描述架构和关键代码实现?

在Java应用中实现数据库主从复制&#xff08;读写分离&#xff09; 一、架构描述 &#xff08;一&#xff09;整体架构 主库&#xff08;Master&#xff09; 负责处理所有的写操作&#xff08;INSERT、UPDATE、DELETE等&#xff09;。它是数据的源头&#xff0c;所有的数据变…...

【pytest】获取所有用例名称并存于数据库

数据库操作包&#xff0c;引用前面创建的py文件&#xff0c;【sqlite】python操作sqlite3&#xff08;含测试&#xff09; #!/usr/bin/env python # -*- coding: utf-8 -*- # Time : 2025-02-11 8:45 # Author : duxiaowei # File : get_filename.py # Software: 这个文…...

【论文笔记】Are Self-Attentions Effective for Time Series Forecasting? (NeurIPS 2024)

官方代码https://github.com/dongbeank/CATS Abstract 时间序列预测在多领域极为关键&#xff0c;Transformer 虽推进了该领域发展&#xff0c;但有效性尚存争议&#xff0c;有研究表明简单线性模型有时表现更优。本文聚焦于自注意力机制在时间序列预测中的作用&#xff0c;提…...

maven导入spring框架

在eclipse导入maven项目&#xff0c; 在pom.xml文件中加入以下内容 junit junit 3.8.1 test org.springframework spring-core ${org.springframework.version} org.springframework spring-beans ${org.springframework.version} org.springframework spring-context ${org.s…...

AUTOGPT:基于GPT模型开发的实验性开源应用程序; 目标设定与分解 ;;自主思考与决策 ;;信息交互与执行

目录 AUTOGPT是一款基于GPT模型开发的实验性开源应用程序目标设定与分解自主思考与决策信息交互与执行AUTOGPT是一款基于GPT模型开发的实验性开源应用程序 目标设定与分解 自主思考与决策 信息交互与执行 AUTOGPT是一款基于GPT模型开发的实验性开源应用程序,它能让大语言模…...

瑞芯微开发板/主板Android调试串口配置为普通串口方法 深圳触觉智能科技分享

本文介绍瑞芯微开发板/主板Android调试串口配置为普通串口方法&#xff0c;不同板型找到对应文件修改&#xff0c;修改的方法相通。触觉智能RK3562开发板演示&#xff0c;搭载4核A53处理器&#xff0c;主频高达2.0GHz&#xff1b;内置独立1Tops算力NPU&#xff0c;可应用于物联…...

Redis 数据类型 Hash 哈希

在 Redis 中&#xff0c;哈希类型是指值本⾝⼜是⼀个键值对结构&#xff0c;形如 key "key"&#xff0c;value { { field1, value1 }, ..., {fieldN, valueN } }&#xff0c;Redis String 和 Hash 类型⼆者的关系可以⽤下图来表⽰。 Hash 数据类型的特点 键值对集合…...

IntelliJ IDEA 2024.1.4版无Tomcat配置

IntelliJ IDEA 2024.1.4 (Ultimate Edition) 安装完成后&#xff0c;调试项目发现找不到Tomcat服务&#xff1a; 按照常规操作添加&#xff0c;发现服务插件中没有Tomcat。。。 解决方法 1、找到IDE设置窗口 2、点击Plugins按钮&#xff0c;进入插件窗口&#xff0c;搜索T…...

连锁收银系统的核心架构与技术选型

在连锁门店的日常运营里&#xff0c;连锁收银系统扮演着极为重要的角色&#xff0c;它不仅承担着交易结算的基础任务&#xff0c;还关联着库存管理、会员服务、数据分析等多个关键环节。一套设计精良的核心架构与合理的技术选型&#xff0c;是保障收银系统高效、稳定运行的基础…...

CSS 小技巧 —— CSS 实现 Tooltip 功能-鼠标 hover 之后出现弹层

CSS 小技巧 —— CSS 实现 Tooltip 功能-鼠标 hover 之后出现弹层 1. 两个元素实现 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>纯 CSS 实现 Tooltip 功能-鼠标 hover 之后出现弹层</titl…...

19.4.2 -19.4.4 新增、修改、删除数据

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 需要北风数据库的请留言自己的信箱。 19.4.2 新增数据 数据库数据的新增、修改和删除不同于查询&#xff0c;查询需要返回一个DbD…...

【PL/SQL】常用操作复习20250212

目录标题 1.基本语法结构二级目录三级目录 1.基本语法结构2。变量声明和使用3. SELECT 查询4.插入 insert5.更新UPDATE6.删除(DELETE) 7游标 cursor例子1&#xff1a;基本游标使用例子2&#xff1a;使用FOR循环的简化写法实际应用例子&#xff1a;给高工资员工增加奖金 8 IF 条…...

C++自研游戏引擎-碰撞检测组件-八叉树AABB检测算法实现

八叉树碰撞检测是一种在三维空间中高效处理物体碰撞检测的算法&#xff0c;其原理可以类比为一个管理三维空间物体的智能系统。这个示例包含两个部分&#xff1a;八叉树部分用于宏观检测&#xff0c;AABB用于微观检测。AABB可以更换为均值或节点检测来提高检测精度。 八叉树的…...

haproxy详解笔记

一、概述 HAProxy&#xff08;High Availability Proxy&#xff09;是一款开源的高性能 TCP/HTTP 负载均衡器和代理服务器&#xff0c;用于将大量并发连接分发到多个服务器上&#xff0c;从而提高系统的可用性和负载能力。它支持多种负载均衡算法&#xff0c;能够根据服务器的…...

windows 通过docker 安装mysql

参考&#xff1a;Docker安装并使用Mysql&#xff08;可用详细&#xff09;_docker 安装mysql-CSDN博客 1. 拉取镜像&#xff1a;docker pull mysql:5.7 2. 查看镜像&#xff1a;docker image 3. 创建mysql 容器实例&#xff0c;并将data 目录挂载到本地d盘上 docker run --n…...

【STM32】通过L496的HAL库Flash建立FatFS文件系统(CubeMX自动配置R0.12C版本)

【STM32】通过L496的HAL库Flash建立FatFS文件系统&#xff08;CubeMX自动配置R0.12C版本&#xff09; 文章目录 FlashFlash地址写Flash地址读 FatFS文件系统配置FatFS移植驱动函数时间戳函数 文件操作函数工作区缓存文件挂载和格式化测试文件读写测试其他文件操作函数 测试附录…...

QTreeView笔记

1.定义TreeModel类 我们需要继承自QAbstractItemModel&#xff0c;让我们来看看它有哪些接口。 QAbstractItemModel类中定义如下&#xff1a; Q_INVOKABLE virtual QModelIndex index(int row, int column, const QModelIndex &parent QModelIndex()) const 0;Q_INVOK…...

传感器篇(一)——深度相机

目录 一 概要 二 原理 三 对比 四 产品 五 结论 一 概要 深度相机是一种能够获取物体深度信息的设备&#xff0c;相较于普通相机只能记录物体的二维图像信息&#xff0c;深度相机可以感知物体与相机之间的距离&#xff0c;从而提供三维空间信息。在你正在阅读的报告中提到…...

Qt 控件整理 —— 按钮类

一、PushButton 1. 介绍 在Qt中最常见的就是按钮&#xff0c;它的继承关系如下&#xff1a; 2. 常用属性 3. 例子 我们之前写过一个例子&#xff0c;根据上下左右的按钮去操控一个按钮&#xff0c;当时只是做了一些比较粗糙的去演示信号和槽是这么连接的&#xff0c;这次我们…...

校园网绕过认证上网很简单

校园网绕过认证就是不用通过校园WiFi的WEB页面登录&#xff0c;这个WEB登录页面就是认证页面. 所谓绕过认证&#xff0c;就是不通过校园WiFi WEB登录页面直接上网&#xff0c;校园WiFi没有密码&#xff0c;直接就能连接上&#xff0c;我们连上这个WiFi的时候&#xff0c;它会给…...

蓝桥杯篇---温度传感器 DS18B20

文章目录 前言DS18B201. DS18B20 引脚说明2. 单总线通信协议3. DS18B20 操作流程初始化写操作读操作 4. 示例代码5. 代码说明6. 注意事项总结 前言 以上就是今天要讲的内容&#xff0c;本文简单介绍了IAP15F2K61S2中温度传感器模块DS18B20的使用。 DS18B20 DS18B20 是一款数字…...

WPS或word接入智能AI

DeepSeek接入WPS 配置WPS &#xff08;1&#xff09;下载 OfficeAl助手插件: 插件下载地址:https://www.office-ai.cn/。 安装插件后&#xff0c;打开WPS&#xff0c;菜单栏会新增"OfficeAl助手”选项卡。 如果没有出现&#xff0c; 左上找到文件菜单 -> 选项 ,在…...

vue3:template中v-for循环遍历这个centrerTopdata,我希望自循环前面三个就可以了怎么写?

问&#xff1a; template中v-for循环遍历这个centrerTopdata&#xff0c;我希望自循环前面三个就可以了怎么写&#xff1f; 回答&#xff1a; 问&#xff1a; <div v-for"(item, index) in centrerTopdata.slice(0, 3)" :key"index"> div cl…...

Java练习(20)

ps:练习来自力扣 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 class Solution {pu…...

MySQL | MySQL安装教程

MySQL | MySQL安装教程(压缩包&#xff08;ZIP&#xff09;安装-详细版) &#x1fa84;个人博客&#xff1a;https://vite.xingji.fun MySQL概述 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB公司开发&#xff0c;MySQL AB公司被Sun公司收购&#xff0c;Sun公…...

.NET 9.0 的 Blazor Web App 项目,进度条 <progress> 组件使用注意事项

一、执行过程中&#xff0c;要刷新 进度条 的显示&#xff0c;需要 延时、释放&#xff0c;否则进度条不 实时 更新&#xff0c;最后一下到 100% // 延时&#xff0c;释放给前端&#xff1a;【必须】&#xff0c;否则进度条不 实时 更新&#xff0c;最后一下到 100await Task.D…...

李超线段树 树链剖分 学习笔记

今天学习了李超线段树。 [洛谷 P4097] 【模板】李超线段树 & [HEOI2013] Segment 刚开始学李超线段树&#xff0c;觉得挺简单的。其实它跟吉司机线段树有点像&#xff0c;只是维护的东西要少一些&#xff0c;并且代码更好写。 对于每个节点&#xff0c;考虑维护在它中点处的…...

【SpringBoot3.x+】slf4j-log4j12依赖引入打印日志报错的两种解决方法

最开始引入了1.7.5版本的slf4j-log4j依赖包&#xff0c;但是控制台不报错也不显示日志 在https://mvnrepository.com/找到最新的2.0.16版本之后出现报错&#xff1a; 进入提示的slf4j网站中可以找到从2.0.0版本开始&#xff0c;slf4j-log4j已经被slf4j-reload4j取代&#xff1…...

安装 Ollama 需要哪些步骤?(windows+mac+linux+二进制+Docker)

安装 Ollama 的步骤根据操作系统不同会有所差异,以下是针对不同操作系统的详细安装指南: Windows 系统 下载安装包:访问 Ollama 官方下载页面,下载适用于 Windows 的安装程序 OllamaSetup.exe。运行安装程序:双击下载的安装包,按照提示完成安装。默认安装路径为 C:\User…...

算法学习笔记之贪心算法

导引&#xff08;硕鼠的交易&#xff09; 硕鼠准备了M磅猫粮与看守仓库的猫交易奶酪。 仓库有N个房间&#xff0c;第i个房间有 J[i] 磅奶酪并需要 F[i] 磅猫粮交换&#xff0c;硕鼠可以按比例来交换&#xff0c;不必交换所有的奶酪 计算硕鼠最多能得到多少磅奶酪。 输入M和…...