利用 Sharding-JDBC 解决数据库读写分离后,数据查询延时问题

news/2024/5/18 21:49:17 标签: 数据库, mysql, java, jdbc, redis

点击上方蓝色“架构荟萃”关注我们,输入1024,你懂的

一般熟知 Mysql 数据库的朋友知道,当表的数据量达到千万级时,SQL 查询会逐渐变的缓慢起来,往往会成为一个系统的瓶颈所在。为了提升程序的性能,除了在表字段建立索引(如主键索引、唯一索引、普通索引等)、优化程序代码以及 SQL 语句等常规手段外,利用数据库主从读写分离(Master/Slave)架构,是一个不错的选择。但是在这种分离架构中普遍存在一个共性问题:数据读写一致性问题。

数据读写一致性问题

主从库同步逻辑

主库 Master 负责“写”,会把数据库的 BinLog 日志记录通过 I/O 线程异步操作同步到从库(负责“读”),这样每当业务系统发送 select 语句时,会直接路由到从库去查询数据,而不是主库。

但是这种同步逻辑有一个比较严重的缺陷:数据延时问题

我们可以想象一下这样的场景:

当一段程序在更新完数据后,需要立即查询更新后的数据,那么真的能查询到更新后的数据吗?

答案是:不一定!

这是因为主从数据同步时是异步操作,主从同步期间会存在数据延时问题,平常主库写数据量比较少的情况下,偶尔会遇到查询不到数据的情况。但是随着时间的推移,当使用系统的用户增多时,会发现这种查询不到数据的情况会变的越来越糟糕。

Sharding-JDBC

想必大家并不陌生,Sharding-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。

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

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

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

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

读写分离特性

  • 提供了一主多从的读写分离配置,可独立使用,也可配合分库分表使用。

  • 同个调用线程,执行多条语句,其中一旦发现有非读操作,后续所有读操作均从主库读取。

  • Spring命名空间。

  • 基于Hint的强制主库路由。

ShardingSphere-JDBC 官方提供 HintManager 分片键值管理器, 通过调用hintManager.setMasterRouteOnly() 强制路由到主库查询,这样就解决了数据延时问题,无论什么时候都能够从主库 Master 查询到最新数据,而不用走从库查询。

 HintManager hintManager = HintManager.getInstance() ;
 hintManager.setMasterRouteOnly();

实际案例

核心依赖

<dependency>
   <groupId>io.shardingjdbc</groupId>
   <artifactId>sharding-jdbc-core</artifactId>
   <version>${sharding-jdbc.version}</version>
</dependency>

数据库配置

sharding:
 jdbc:
   data-sources:
     mvip:
       type: com.alibaba.druid.pool.DruidDataSource
       driver-class-name: com.mysql.jdbc.Driver
       url: jdbc:mysql://${ha.basedb.mvip.ip}:${ha.basedb.mvip.port}/unicom_portal?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
       username: ${ha.basedb.mvip.username}
       password: ${ha.basedb.mvip.password}
     svip:
       type: com.alibaba.druid.pool.DruidDataSource
       driver-class-name: com.mysql.jdbc.Driver
       url: jdbc:mysql://${ha.basedb.svip.ip}:${ha.basedb.svip.port}/unicom_portal?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
       username: ${ha.basedb.svip.username}
       password: ${ha.basedb.svip.password}
   master-slave-rule:
     name: ds_ms
     master-data-source-name: mvip
     slave-data-source-names: svip
     load-balance-algorithm-type: round_robin

数据源初始化配置类   

java">@Data
@ConfigurationProperties(prefix = "sharding.jdbc")
public class MasterSlaveConfig {
    private Map<String, DruidDataSource> dataSources = new HashMap<>();
   private MasterSlaveRuleConfiguration masterSlaveRule;
}
@ConditionalOnClass(DruidDataSource.class)
   @EnableConfigurationProperties(MasterSlaveConfig.class)
   @ConditionalOnProperty({
           "sharding.jdbc.data-sources.mvip.url",
           "sharding.jdbc.master-slave-rule.master-data-source-name"
})
static class ShardingDruid extends DruidConfig {
       @Autowired
       private MasterSlaveConfig masterSlaveConfig;
       @Bean("masterSlaveDataSource")
       public DataSource dataSource() throws SQLException {
           masterSlaveConfig.getDataSources().forEach((k, v) -> configDruidParams(v));
           Map<String, DataSource> dataSourceMap = Maps.newHashMap();
           dataSourceMap.putAll(masterSlaveConfig.getDataSources());
           DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, masterSlaveConfig.getMasterSlaveRule(), Maps.newHashMap());
           return dataSource;
       }
       @Bean
       public PlatformTransactionManager txManager(DataSource dataSource) {
           return new DataSourceTransactionManager(dataSource);
       }
       private void configDruidParams(DruidDataSource druidDataSource) {
           druidDataSource.setMaxActive(20);
           druidDataSource.setInitialSize(1);
           // 配置获取连接等待超时的时间
           druidDataSource.setMaxWait(10000);
           druidDataSource.setMinIdle(1);
           // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
           druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
           // 配置一个连接在池中最小生存的时间,单位是毫秒 超过这个时间每次会回收默认3个连接
           druidDataSource.setMinEvictableIdleTimeMillis(30000);
           // 线上配置的mysql断开闲置连接时间为1小时,数据源配置回收时间为3分钟,以最后一次活跃时间开始算
           druidDataSource.setMaxEvictableIdleTimeMillis(180000);
           // 连接最大存活时间,默认是-1(不限制物理连接时间),从创建连接开始计算,如果超过该时间,则会被清理
           druidDataSource.setPhyTimeoutMillis(15000);
           druidDataSource.setValidationQuery("select 1");
           druidDataSource.setTestWhileIdle(true);
           druidDataSource.setTestOnBorrow(false);
           druidDataSource.setTestOnReturn(false);
           druidDataSource.setPoolPreparedStatements(true);
           druidDataSource.setMaxOpenPreparedStatements(20);
           druidDataSource.setUseGlobalDataSourceStat(true);
           druidDataSource.setKeepAlive(true);
           druidDataSource.setRemoveAbandoned(true);
           druidDataSource.setRemoveAbandonedTimeout(180);
           try {
               druidDataSource.setFilters("stat,slf4j");
               List filterList = new ArrayList<>();
               filterList.add(wallFilter());
               druidDataSource.setProxyFilters(filterList);
           } catch (SQLException e) {
               e.printStackTrace();
           }
       }
   }

强制路由到主库查询关键代码:

public ArticleEntity getWithMasterDB(Long id, String wid) {
  HintManager hintManager = HintManager.getInstance() ;
  hintManager.setMasterRouteOnly();
  ArticleEntity article = baseMapper.queryObject(id, wid);
}

通过强制路由到主库查询有个风险,对于更新并实时查询业务场景比较多,如果都切到主库查询,势必会对主库服务器性能造成影响,可能还会影响到主从数据同步,所以要根据实际业务场景评估采用这种方式带来的系统性能问题。

另外,如果业务层面可以做妥协的话,尽量减少这种更新并实时查询方式,一种思路是实时更新库,利用 Java Future 特性异步查询(例如更新后,睡眠1-2秒再查询),伪代码如下:

Callable c1 = new Callable(){  @Override
  public String call() throws Exception {
    ArticleEntity articleEntity = null
    try {
         Thread.sleep(2000);
         articleEntity = articleService.get(id)
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
    return articleEntity;
  }
};
FutureTask<ArticleEntity> f = new FutureTask<ArticleEntity>(c1);
new Thread(f).start();
ArticleEntity article = f.get()

推荐阅读

  • 记一次 base64 图片存储引发后端查询接口性能断崖式下降问题全程解析

  • 月薪8K,java初级程序员需要掌握的一些面试经验

  • 基于分布式文件系统 FastDFS,利用 Zuul 网关实现滑块验证登录

  • jsp的10年是谁让它如此落幕?

  • 一文读懂阿里大中台、小前台战略

 

擅长编筐 Java,熟悉微服务&分布式架构、Golang 、SpringCloud&SpringBoot、工作流。头条付费专栏《Spring Cloud Alibaba微服务实战》 、《Go语言Web开发从入门到项目实战》                 

后台回复 1024 免费领取微服务、SpringCloud&SpringBoot,微信小程序、Java面试等视频资料,扫描上图微信二维码(WooolaDuang)进微信群


http://www.niftyadmin.cn/n/1240835.html

相关文章

解决前端跨域问题-搭建反向代理服务器

(:/67f6172bf05d4353bcdb3b8dbdcc5865)] 开发中最常见的跨域问题 跨域问题的出现 “同源策略” &#xff1a;同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源&#xff08;即指在同一个域&#xff09;就是两个页面具有相同的协议&#xff08;prot…

SpringCloud 配置中心服务端配置解析流程分析

点击上方蓝色“架构荟萃”关注我们&#xff0c;输入1024&#xff0c;你懂的 环境准备 启动 Eureka 启动 ConfigServer 启动 aiportal-wsm-service 微服务 Gitlab 配置文件&#xff1a;aiportal-wsm-service、globa、,dbconfig 等yml文件 配置解析入口 在启动 aiportal-wsm-s…

中望CAD的引线标注格式怎么改_大神总结的CAD设计五个段位 快来看看你在哪个阶段...

看到段位&#xff0c;大家可能第一时间会想到时下风靡的各种游戏。但事实上&#xff0c;尽管没有绝对数值&#xff0c;大多数行业都会有其隐性的段位&#xff0c;能让人初步判断自己所处的阶段&#xff0c;我们CAD设计也不例外。今天&#xff0c;不妨分享一位大神总结的CAD设计…

datetime 比较_Python常用标准库之datetime模块

一、概述之前我们整理了time模块的日常用法&#xff0c;处理时间数据的过程中还有一个比较常用的模块就是datetime模块&#xff0c;其应用更为广泛&#xff0c;用于处理日期和时间的类。对于基本的时间表示方法、时间戳、UTC等概念请参考之前的文章&#xff1a;Python常用标准库…

使用 Eureka 简单实现服务健康监控日志分析

1 背景 当我们用 K8s Docker 容器化部署基于 SpringCloud 微服务时&#xff0c;根据实际业务需要&#xff0c;可能会对某些服务采取多节点实例部署&#xff0c;这样可以实现服务的负载均衡及高可用架构。但我们有时为了监控服务的稳定性&#xff0c;除了 K8s 平台提供的控制台…

python 字符串排序_如何在一场面试中展现你对Python的coding能力?

如果你已经通过了招聘人员的电话面试&#xff0c;那么下面正是该展现你代码能力的时候了。无论是练习&#xff0c;作业&#xff0c;还是现场白板面试&#xff0c;这都是你证明自己的代码技巧的时刻。我们知道面试官常常会出一些题让你来解决&#xff0c;作为一名程序员&#xf…

用 MHA 做 MySQL 读写分离,频繁爆发线上生产事故后,泪奔分享 Druid 连接池参数优化实战...

点击上方蓝色“架构荟萃”关注我们&#xff0c;输入1024&#xff0c;你懂的前言最近利用 MHA 做好 Mysql 读写分离后&#xff0c;时不时有用户反馈后台发布文章时&#xff0c;报程序“通用异常"&#xff0c;经问题排查&#xff0c;里面涉及应用JDBC连接池参数及Mysql参数调…

高性能网关设计实践

前言之前的高性能短链设计一文颇受大家好评&#xff0c;共被转载 「47」 次&#xff0c;受宠若惊&#xff0c;在此感谢大家的认可&#xff01;在文末简单提了一下 OpenResty&#xff0c;一些读者比较感兴趣&#xff0c;刚好我们接入层网关也是用的 OpenResty&#xff0c;所以希…