利用 Sharding-JDBC 解決數(shù)據(jù)庫讀寫分離后,數(shù)據(jù)查詢延時(shí)問題

但是這種同步邏輯有一個比較嚴(yán)重的缺陷:數(shù)據(jù)延時(shí)問題。
我們可以想象一下這樣的場景:
當(dāng)一段程序在更新完數(shù)據(jù)后,需要立即查詢更新后的數(shù)據(jù),那么真的能查詢到更新后的數(shù)據(jù)嗎?
答案是:不一定!
這是因?yàn)橹鲝臄?shù)據(jù)同步時(shí)是異步操作,主從同步期間會存在數(shù)據(jù)延時(shí)問題,平常主庫寫數(shù)據(jù)量比較少的情況下,偶爾會遇到查詢不到數(shù)據(jù)的情況。但是隨著時(shí)間的推移,當(dāng)使用系統(tǒng)的用戶增多時(shí),會發(fā)現(xiàn)這種查詢不到數(shù)據(jù)的情況會變的越來越糟糕。


它使用客戶端直連數(shù)據(jù)庫,以 jar 包形式提供服務(wù),無需額外部署和依賴,可理解為增強(qiáng)版的 JDBC 驅(qū)動,完全兼容 JDBC 和各種 ORM 框架。
適用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
支持任何第三方的數(shù)據(jù)庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
支持任意實(shí)現(xiàn) JDBC 規(guī)范的數(shù)據(jù)庫,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 標(biāo)準(zhǔn)的數(shù)據(jù)庫。
讀寫分離特性
提供了一主多從的讀寫分離配置,可獨(dú)立使用,也可配合分庫分表使用。
同個調(diào)用線程,執(zhí)行多條語句,其中一旦發(fā)現(xiàn)有非讀操作,后續(xù)所有讀操作均從主庫讀取。
Spring命名空間。
基于Hint的強(qiáng)制主庫路由。
ShardingSphere-JDBC 官方提供 HintManager 分片鍵值管理器, 通過調(diào)用hintManager.setMasterRouteOnly() 強(qiáng)制路由到主庫查詢,這樣就解決了數(shù)據(jù)延時(shí)問題,無論什么時(shí)候都能夠從主庫 Master 查詢到最新數(shù)據(jù),而不用走從庫查詢。
HintManager hintManager = HintManager.getInstance() ;?hintManager.setMasterRouteOnly();
核心依賴
<dependency><groupId>io.shardingjdbcgroupId><artifactId>sharding-jdbc-coreartifactId><version>${sharding-jdbc.version}version>dependency>
數(shù)據(jù)庫配置
sharding:jdbc:data-sources:mvip:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://${ha.basedb.mvip.ip}:${ha.basedb.mvip.port}/unicom_portal?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=falseusername: ${ha.basedb.mvip.username}password: ${ha.basedb.mvip.password}svip:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://${ha.basedb.svip.ip}:${ha.basedb.svip.port}/unicom_portal?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=falseusername: ${ha.basedb.svip.username}password: ${ha.basedb.svip.password}master-slave-rule:name: ds_msmaster-data-source-name: mvipslave-data-source-names: svipload-balance-algorithm-type: round_robin
數(shù)據(jù)源初始化配置類? ?
(prefix = "sharding.jdbc")public class MasterSlaveConfig {private MapdataSources = new HashMap<>(); private MasterSlaveRuleConfiguration masterSlaveRule;}(DruidDataSource.class)(MasterSlaveConfig.class)({"sharding.jdbc.data-sources.mvip.url","sharding.jdbc.master-slave-rule.master-data-source-name"})static class ShardingDruid extends DruidConfig {private MasterSlaveConfig masterSlaveConfig;("masterSlaveDataSource")public DataSource dataSource() throws SQLException {masterSlaveConfig.getDataSources().forEach((k, v) -> configDruidParams(v));MapdataSourceMap = Maps.newHashMap(); dataSourceMap.putAll(masterSlaveConfig.getDataSources());DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, masterSlaveConfig.getMasterSlaveRule(), Maps.newHashMap());return dataSource;}public PlatformTransactionManager txManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}private void configDruidParams(DruidDataSource druidDataSource) {druidDataSource.setMaxActive(20);druidDataSource.setInitialSize(1);// 配置獲取連接等待超時(shí)的時(shí)間druidDataSource.setMaxWait(10000);druidDataSource.setMinIdle(1);// 配置間隔多久才進(jìn)行一次檢測,檢測需要關(guān)閉的空閑連接,單位是毫秒druidDataSource.setTimeBetweenEvictionRunsMillis(60000);// 配置一個連接在池中最小生存的時(shí)間,單位是毫秒 超過這個時(shí)間每次會回收默認(rèn)3個連接druidDataSource.setMinEvictableIdleTimeMillis(30000);// 線上配置的mysql斷開閑置連接時(shí)間為1小時(shí),數(shù)據(jù)源配置回收時(shí)間為3分鐘,以最后一次活躍時(shí)間開始算druidDataSource.setMaxEvictableIdleTimeMillis(180000);// 連接最大存活時(shí)間,默認(rèn)是-1(不限制物理連接時(shí)間),從創(chuàng)建連接開始計(jì)算,如果超過該時(shí)間,則會被清理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();}}}
強(qiáng)制路由到主庫查詢關(guān)鍵代碼:
public ArticleEntity getWithMasterDB(Long id, String wid) {HintManager hintManager = HintManager.getInstance() ;hintManager.setMasterRouteOnly();ArticleEntity article = baseMapper.queryObject(id, wid);}
通過強(qiáng)制路由到主庫查詢有個風(fēng)險(xiǎn),對于更新并實(shí)時(shí)查詢業(yè)務(wù)場景比較多,如果都切到主庫查詢,勢必會對主庫服務(wù)器性能造成影響,可能還會影響到主從數(shù)據(jù)同步,所以要根據(jù)實(shí)際業(yè)務(wù)場景評估采用這種方式帶來的系統(tǒng)性能問題。
另外,如果業(yè)務(wù)層面可以做妥協(xié)的話,盡量減少這種更新并實(shí)時(shí)查詢方式,一種思路是實(shí)時(shí)更新庫,利用 Java Future 特性異步查詢(例如更新后,睡眠1-2秒再查詢),偽代碼如下:
Callable c1 = new Callable(){@Overridepublic String call() throws Exception {ArticleEntity articleEntity = nulltry {?????????Thread.sleep(2000);articleEntity = articleService.get(id)???}?catch?(InterruptedException?e)?{e.printStackTrace();???}return articleEntity;}};FutureTaskf = new FutureTask (c1); new Thread(f).start();ArticleEntity article = f.get()

1.?人人都能看懂的 6 種限流實(shí)現(xiàn)方案!
3.?大型網(wǎng)站架構(gòu)演化發(fā)展歷程
8. 深入理解 MySQL:快速學(xué)會分析SQL執(zhí)行效率

掃碼二維碼關(guān)注我
·end·
—如果本文有幫助,請分享到朋友圈吧—
我們一起愉快的玩耍!

