如何在 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. 权限管理
    • 为主库和从库分别设置合适的用户权限。例如,从库用于读操作的用户不需要有写入权限,这样可以提高安全性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/69417.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

笔灵ai写作技术浅析(六):智能改写与续写

笔灵AI写作中的智能改写和续写技术是其核心功能之一,旨在帮助用户生成高质量、多样化的文本内容。 一、智能改写技术 1. 基本原理 智能改写的目标是在保持原文语义不变的前提下,对文本进行重新表述,生成语法正确、语义连贯且风格多样的新文本。其核心思想是通过语义理解和…

Rust语言的计算机基础

Rust语言的计算机基础 引言 在当今计算机科学的广阔领域中&#xff0c;编程语言是技术发展的基础。不同的编程语言应运而生&#xff0c;各自具有不同的特性和应用场景。Rust语言作为一种新兴的系统编程语言&#xff0c;凭借其卓越的性能和安全性&#xff0c;逐渐受到开发者的…

3.如何标注数据集

软件安装&#xff1a; Labelme&#xff0c;打开链接之后跳转如下&#xff1a; 这里往下翻&#xff0c;如下&#xff1a; 选择上图圈起来的exe进行下载就可以了&#xff0c;下载完成之后就可以双击直接打开了。如果通过github下载很慢的话可以直接选择通过网盘分享的文件&…

【分布式理论7】分布式调用之:服务间的(RPC)远程调用

文章目录 一、RPC 调用过程二、RPC 动态代理&#xff1a;屏蔽远程通讯细节1. 动态代理示例2. 如何将动态代理应用于 RPC 三、RPC序列化与协议编码1. RPC 序列化2. RPC 协议编码2.1. 协议编码的作用2.2. RPC 协议消息组成 四、RPC 网络传输1. 网络传输流程2. 关键优化点 一、RPC…

SSA-TCN麻雀算法优化时间卷积神经网络时间序列预测未来Matlab实现

SSA-TCN麻雀算法优化时间卷积神经网络时间序列预测未来Matlab实现 目录 SSA-TCN麻雀算法优化时间卷积神经网络时间序列预测未来Matlab实现预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SSA-TCN麻雀算法优化时间卷积神经网络时间序列预测未来&#xff08;优…

LVS + KeepAlived 配置HA集群的步骤

LVS KeepAlived 配置HA集群的步骤 &#xff08;一&#xff09;集群准备 准备vmvare linux虚拟主机4台&#xff0c;假设对外提供的VIP是192.168.174.110 主机IP备注LVS1192.168.174.101提供4层代理-主机LVS2192.168.174.102提供4层代理-备用Apache1192.168.174.201真实服务器…

智慧停车场解决方案(文末联系,领取整套资料,可做论文)

一、方案概述 本智慧停车场解决方案旨在通过硬件设备与软件系统的深度整合&#xff0c;实现停车场的智能化管理与服务&#xff0c;提升车主的停车体验&#xff0c;优化停车场运营效率。 二、硬件架构 硬件设备说明&#xff1a; 车牌识别摄像机&#xff1a;安装在停车场入口和…

DeepSeek开源多模态大模型Janus-Pro部署

DeepSeek多模态大模型部署 请自行根据电脑配置选择合适环境配置安装conda以及gitJanus 项目以及依赖安装运行cpu运行gpu运行 进入ui界面 请自行根据电脑配置选择合适 本人家用电脑为1060&#xff0c;因此部署的7B模型。配置高的可以考虑更大参数的模型。 环境配置 安装conda…

C#常用集合优缺点对比

先上结论&#xff1a; 在C#中&#xff0c;链表、一维数组、字典、List<T>和ArrayList是常见的数据集合类型&#xff0c;它们各有优缺点&#xff0c;适用于不同的场景。以下是它们的比较&#xff1a; 1. 一维数组 (T[]) 优点&#xff1a; 性能高&#xff1a;数组在内存中…

python-leetcode-删除有序数组中的重复项 II

80. 删除有序数组中的重复项 II - 力扣&#xff08;LeetCode&#xff09; class Solution:def removeDuplicates(self, nums: List[int]) -> int:if len(nums) < 2:return len(nums)j 2 # 允许最多两个相同的元素for i in range(2, len(nums)):if nums[i] ! nums[j - 2…

Render上后端部署Springboot + 前端Vue 问题及解决方案汇总

有一个 Vue 前端 和 Spring Boot 后端的动态网页游戏&#xff0c;当前在本地的 5173 端口和运行。你希望生成一个公开链接&#xff0c;让所有点击链接的人都能访问并玩这个游戏。由于游戏原本需要在本地执行 npm install 后才能启动&#xff0c;你现在想知道在部署时是选择 Ren…

力扣LeetCode: 80 删除有序数组中的重复项Ⅱ

题目&#xff1a; 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件…

redis之GEO 模块

文章目录 背景GeoHash 算法redis中的GeoHash 算法基本使用增加距离获取元素位置获取元素的 hash 值附近的元素 注意事项原理 背景 如果我们有需求需要存储地理坐标&#xff0c;为了满足高性能的矩形区域算法&#xff0c;数据表需要在经纬度坐标加上双向复合索引 (x, y)&#x…

51单片机俄罗斯方块清屏函数

/************************************************************************************************************** * 名称&#xff1a;LED_Clr * 功能&#xff1a;清屏 * 参数&#xff1a;NULL * 返回&#xff1a;NULL * 备注&#xff1a;temp数组为动态显示数据&#xff…

如何启用 Apache Rewrite 重写模块 ?

Apache 的 mod_rewrite 是最强大的 URL 操作模块之一。使用 mod_rewrite&#xff0c;您可以重定向和重写 url&#xff0c;这对于在您的网站上实现 seo 友好的 URL 结构特别有用。在本文中&#xff0c;我们将引导您了解如何在基于 Debian 和基于 RHEL 的系统上在 Apache 中启用 …

动手学图神经网络(9):利用图神经网络进行节点分类 WeightsBiases

利用图神经网络进行节点分类Weights&Biases 引言 在本篇博客中,将深入探讨如何使用图神经网络(GNNs)来完成节点分类任务。以 Cora 数据集为例,该数据集是一个引用网络,节点代表文档,推断每个文档的类别。同时,使用 Weights & Biases(W&B)来跟踪实验过程和…

“深入浅出”系列之C++:(19)C++14

C14的新拓展 自动类型推导&#xff08;auto&#xff09;的增强&#xff1a; C14在auto关键字的基础上进行了优化&#xff0c;使得类型推导更加智能。例如&#xff0c;可以使用auto来声明Lambda表达式的返回类型和参数类型&#xff0c;减少了繁琐的类型声明。 示例代码&#…

STM32单片机学习记录(2.9)

一、STM32 15.1 - FLASH闪存 1. FLASH简介 &#xff08;1&#xff09;STM32系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过闪存存储器接口&#xff08;外设&#xff09;可以对程序存储器和选项字节进行擦除和编程&#xff1b; &#xff08;2&#x…

尚硅谷课程【笔记】——大数据之Zookeeper【二】

课程视频&#xff1a;【尚硅谷Zookeeper教程】 四、Zookeeper实战 4.1分布式安装部署 1. 集群规划 在Hadoop102、Hadoop103和Hadoop104三个节点上部署Zookeeper 2. 解压安装 1&#xff09;解压Zookeeper.tar.gz到指定目录 tar -zxvf zookeeper-3.7.2.tar.gz -C /opt/mod…

<论文>DeepSeek-R1:通过强化学习激励大语言模型的推理能力(深度思考)

一、摘要 本文跟大家来一起阅读DeepSeek团队发表于2025年1月的一篇论文《DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning | Papers With Code》&#xff0c;新鲜的DeepSeek-R1推理模型&#xff0c;作者规模属实庞大。如果你正在使用Deep…