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

如何在 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;所有的数据变…...

【css】width:100%;padding:20px;造成超出100%宽度的解决办法 - box-sizing的使用方法 - CSS布局

问题 修改效果 解决方法 .xx {width: 100%;padding: 0 20px;box-sizing: border-box; } 默认box-sizing: content-box下&#xff0c; width 内容的宽度 height 内容的高度 宽度和高度的计算值都不包含内容的边框&#xff08;border&#xff09;和内边距&#xff08;padding&…...

【TI C2000】F28002x的系统延时、GPIO配置及SCI(UART)串口发送、接收

【TI C2000】F28002x的系统延时、GPIO配置及SCI&#xff08;UART&#xff09;串口发送、接收 文章目录 系统延时GPIO配置GPIO输出SCI配置SCI发送、接收测试附录&#xff1a;F28002x开发板上手、环境配置、烧录及TMS320F280025C模板工程建立F28002x叙述烧录SDK库文件说明工程建…...

【PyQt】信号与槽机制

PyQt信号与槽机制详解 &#x1f680; 一、信号与槽类型 &#x1f50c; 1. 内置信号 &#x1f4e1; # 按钮点击信号 &#x1f5b1;️ QPushButton.clicked# 文本输入变化信号 ⌨️ QLineEdit.textChanged# 窗口关闭信号 &#x1f6aa; QWidget.closeEvent2. 自定义信号 ✨ c…...

STM32 是什么?同类产品有哪些

STM32 是什么&#xff1f; STM32 是由意法半导体&#xff08;STMicroelectronics&#xff09;推出的基于 ARM Cortex-M 内核 的 32 位微控制器&#xff08;MCU&#xff09;系列。它专为高性能、低功耗的嵌入式应用设计&#xff0c;广泛应用于以下领域&#xff1a; 工业控制&am…...

20250213编译飞凌的OK3588-C_Linux5.10.209+Qt5.15.10_用户资料_R1

20250213编译飞凌的OK3588-C_Linux5.10.209Qt5.15.10_用户资料_R1 2025/2/13 11:43 缘起&#xff1a;飞凌发布了高版本内核的适配OK3588-C的Buildroot的SDK&#xff1a;OK3588-C_Linux5.10.209Qt5.15.10_用户资料_R1。 但是编译异常了。 于是按照百度升级libc6&#xff0c;可以…...

【DeepSeek】DeepSeek R1 本地windows部署(Ollama+Docker+OpenWebUI)

1、背景&#xff1a; 2025年1月&#xff0c;DeepSeek 正式发布 DeepSeek-R1 推理大模型。DeepSeek-R1 因其成本价格低廉&#xff0c;性能卓越&#xff0c;在 AI 行业引起了广泛关注。DeepSeek 提供了多种使用方式&#xff0c;满足不同用户的需求和场景。本地部署在数据安全、性…...

AI知识库 - Cherry Studio

1 引言&#xff1a; 最近 DeepSeek 很火啊&#xff0c;想必大家都知道&#xff0c;DeepSeek 这个开源的模型出来后&#xff0c;因其高质量能力和R1 的思维链引发了大家本地部署的热潮。我也不例外&#xff0c;本地部署了一个 14B 的模型&#xff0c;然后把&#xff0c;感觉傻傻…...

【ubuntu24.04】 强制重启导致大模型的磁盘挂载出错

挂载NTFS文件系统出错 各种模型放在了这个机械硬盘上&#xff0c;虽然速度慢&#xff0c;但是好在容量大。大模型在工作&#xff0c;但是程序看起来有问题&#xff0c;导致系统卡死了&#xff0c;然后我重启了&#xff0c;然后报错&#xff1a;wrong fs type bad option &…...

OpenLayer创建第一个基础地图实例

OpenLayers创建第一个基础地图实例 OpenLayers 是一个开源的 JavaScript 库&#xff0c;用于在网页上显示交互式地图。它支持多种地图源&#xff0c;包括 OpenStreetMap、Google Maps、Bing Maps 等。本文将介绍如何使用 OpenLayers 创建一个基础地图实例。 1. 准备工作 在开…...

Git命令摘录

使用 Git 升级软件通常是指通过 Git 仓库获取软件的最新版本或更新代码。以下是详细的步骤和方法&#xff1a; 1. 克隆软件仓库 如果这是你第一次获取软件代码&#xff0c;可以使用 git clone 命令将远程仓库克隆到本地。 git clone <仓库地址> 例如&#xff1a; git cl…...

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…...

实现Tree 树形控件的鼠标拖拽功能

1.element中的el-tree实现可拖拽节点 通过 draggable 属性可让节点变为可拖拽 <el-tree :data"data" node-key"id" default-expand-all node-drag-start"handleDragStart" node-drag-enter"handleDragEnter" node-drag-leave"…...

同为科技智能PDU助力Deepseek人工智能和数据交互的快速发展

1 2025开年&#xff0c;人工智能领域迎来了一场前所未有的变革。Deepseek成为代表“东方力量”的开年王炸&#xff0c;不仅在国内掀起了技术热潮&#xff0c;并且在全球范围内引起了高度关注。Deepseek以颠覆性技术突破和现象级应用场景席卷全球&#xff0c;这不仅重塑了产业格…...

硬件学习笔记--42 电磁兼容试验-6 传导差模电流干扰试验介绍

目录 电磁兼容试验-传导差模电流试验 1.试验目的 2.试验方法 3.判定依据及意义 电磁兼容试验-传导差模电流干扰试验 驻留时间是在规定频率下影响量施加的持续时间。被试设备&#xff08;EUT&#xff09;在经受扫频频带的电磁影响量或电磁干扰的情况下&#xff0c;在每个步进…...

基于 Filebeat 的日志收集

在现代分布式系统中&#xff0c;日志数据作为关键的监控与故障排查依据&#xff0c;越来越受到重视。本文将深入探讨 Filebeat 的技术原理、配置方法及在 ELK&#xff08;Elasticsearch、Logstash、Kibana&#xff09;生态系统中的应用&#xff0c;帮助开发者构建高效、稳定的日…...

Next.js 15【实用教程】2025最新版

官网 https://nextjs.org/docs/app/getting-started Next.js 简介 Next.js 由 Vercel 开发和维护&#xff0c;旨在解决单页应用&#xff08;SPA&#xff09;和多页应用&#xff08;MPA&#xff09;在性能和 SEO 上的不足。 核心特性 服务端渲染&#xff08;SSR&#xff09;--…...

vue学习10

1.GPT和Copilot Copilot Tab接受 删除键&#xff0c;不接受 ctrlenter更多方案 更适合的是修改方向 const submitForm async () > {//等待校验结果await formRef.value.validate()//提交修改await userUpdateInfoService(form.value)//通知user模块&#xff0c;进行数据更…...

redis 缓存击穿问题与解决方案

前言1. 什么是缓存击穿?2. 如何解决缓存击穿?怎么做?方案1: 定时刷新方案2: 自动续期方案3: 定时续期 如何选? 前言 当我们使用redis做缓存的时候,查询流程一般是先查询redis,如果redis未命中,再查询MySQL,将MySQL查询的数据同步到redis(回源),最后返回数据 流程图 为什…...

【Vue3 入门到实战】16. Vue3 非兼容性改变

目录 1. 全局 API 的变化 2. 模板指令的变化 2.1 组件v-model用法 2.2 template v-for用法 2.3 v-if 和v-for 优先级变化 2.4 v-bind"object" 顺序敏感 2.5 v-on:event.native 被移除 3. 组件的变化 3.1 功能组件只能使用普通函数创建 3.2 SFC弃用功能属性…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

云原生周刊:k0s 成为 CNCF 沙箱项目

开源项目推荐 HAMi HAMi&#xff08;原名 k8s‑vGPU‑scheduler&#xff09;是一款 CNCF Sandbox 级别的开源 K8s 中间件&#xff0c;通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度&#xff0c;为容器提供统一接口&#xff0c;实现细粒度资源配额…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...

Spring Boot + MyBatis 集成支付宝支付流程

Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例&#xff08;电脑网站支付&#xff09; 1. 添加依赖 <!…...

前端开发者常用网站

Can I use网站&#xff1a;一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use&#xff1a;Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站&#xff1a;MDN JavaScript权威网站&#xff1a;JavaScript | MDN...