想要做讀寫分離,送你一些小經(jīng)驗(yàn)
本文轉(zhuǎn)載自微信公眾號(hào)「猿天地」,作者尹吉?dú)g 。轉(zhuǎn)載本文請(qǐng)聯(lián)系猿天地公眾號(hào)。
讀寫分離是應(yīng)用中提升數(shù)據(jù)訪問性能最常見的一種技術(shù),當(dāng)用戶量越來越多,訪問量越來越大,單節(jié)點(diǎn)數(shù)據(jù)庫難免會(huì)遇到性能瓶頸。很多場(chǎng)景基本上都是讀多寫少,所以增加多個(gè)從節(jié)點(diǎn)來分擔(dān)主節(jié)點(diǎn)的壓力自然是水到渠成的事情。
在應(yīng)用接入讀寫分離后,難免會(huì)有一些我們意料之外的問題,這篇文章主要給大家介紹下一些經(jīng)常會(huì)遇到的問題,有其他的問題歡迎留言補(bǔ)充。
實(shí)現(xiàn)方式
對(duì)于讀寫分離的使用,主要分為兩種方式,客戶端方式和代理方式。
客戶端方式可以自己用 Spring 自帶的 AbstractRoutingDataSource 來實(shí)現(xiàn),也可以用開源的框架來實(shí)現(xiàn),比如 Sharding-JDBC。
代理方式需要編寫代理服務(wù)來對(duì)所有節(jié)點(diǎn)進(jìn)行管理,應(yīng)用不需要關(guān)注多個(gè)數(shù)據(jù)庫節(jié)點(diǎn)信息。可以自己實(shí)現(xiàn),也可以用開源的框架,也可以用商業(yè)的云服務(wù)。
數(shù)據(jù)延遲
談到數(shù)據(jù)延遲,你先得理解主從架構(gòu)的原理。對(duì)數(shù)據(jù)的增刪改操作在主庫上執(zhí)行,查詢?cè)趶膸焐蠄?zhí)行,當(dāng)數(shù)據(jù)剛插入到主庫,然后馬上去查詢的時(shí)候,很有可能數(shù)據(jù)還沒同步到從庫上,就會(huì)出現(xiàn)查詢不到的情況。
像我之前在某些網(wǎng)站發(fā)表文章,發(fā)表之后跳轉(zhuǎn)到列表頁面,發(fā)現(xiàn)沒有新發(fā)表的文章,重新刷新下頁面又有了,這一看這就是讀寫分離后的數(shù)據(jù)延遲導(dǎo)致的現(xiàn)象。
強(qiáng)制路由數(shù)據(jù)延遲要不要解決,一般取決于業(yè)務(wù)場(chǎng)景。對(duì)于實(shí)時(shí)性要求沒有那么高的業(yè)務(wù)場(chǎng)景,允許一定的延遲,對(duì)于實(shí)時(shí)性要求高的場(chǎng)景,唯一的方式就是直接從主庫進(jìn)行查詢,這樣才能及時(shí)讀到剛插入或者修改后最新的數(shù)據(jù)。
強(qiáng)制路由
就是一種解決方案,也就是將讀請(qǐng)求強(qiáng)制分發(fā)到主庫進(jìn)行查詢。大部分中間件都支持 Hint 語法/FORCE_MASTER/和/FORCE_SLAVE/。
以 Sharding-JDBC 舉例,框架提供了 HintManager 來強(qiáng)制路由,使用方式如下:
- HintManager hintManager = HintManager.getInstance();
- hintManager.setMasterRouteOnly();
為了方便使用,建議封裝一個(gè)注解,在需要實(shí)時(shí)查詢的業(yè)務(wù)方法上加上注解,通過切面進(jìn)行強(qiáng)制路由的設(shè)置。
注解使用:
- @MasterRoute
- @Override
- public UserBO getUser(Long id) {
- log.info("查詢用戶 [{}]", id);
- if (id == null) {
- throw new BizException(ResponseCode.PARAM_ERROR_CODE, "id不能為空");
- }
- UserDO userDO = userDao.getById(id);
- if (userDO == null) {
- throw new BizException(ResponseCode.NOT_FOUND_CODE);
- }
- return userBoConvert.convert(userDO);
- }
切面設(shè)置:
- @Aspect
- public class MasterRouteAspect {
- @Around("@annotation(masterRoute)")
- public Object aroundGetConnection(final ProceedingJoinPoint pjp, MasterRoute masterRoute) throws Throwable {
- HintManager hintManager = HintManager.getInstance();
- hintManager.setMasterRouteOnly();
- try {
- return pjp.proceed();
- } finally {
- hintManager.close();
- }
- }
- }
事務(wù)操作
在事務(wù)中的讀請(qǐng)求,走主庫還是從庫呢?對(duì)于這個(gè)問題,最簡(jiǎn)單的方式就是所有事務(wù)中的操作都走主庫,在事務(wù)中經(jīng)常會(huì)存在插入,然后再重新查詢的場(chǎng)景,此時(shí)事務(wù)沒提交,就算同步很快,從庫也是沒有數(shù)據(jù)的,所以只能走主庫。
但還有一些請(qǐng)求,只需要查詢從庫就行了,如果針對(duì)所有事務(wù)中的操作都強(qiáng)制路由,也不是很好。在 Sharding-JDBC 中的做法挺好的,對(duì)于同一線程且同一數(shù)據(jù)庫連接內(nèi),如有寫入操作,以后的讀操作均從主庫讀取,用于保證數(shù)據(jù)一致性。如果我們?cè)跀?shù)據(jù)寫入之前有查詢請(qǐng)求,還是走的從庫,減輕主庫壓力。
動(dòng)態(tài)強(qiáng)制路由
在功能開發(fā)的時(shí)候就決定了哪些接口要強(qiáng)制走主庫,這個(gè)時(shí)候我們會(huì)在代碼上進(jìn)行路由的控制,也就是前面講的自定義注解。如果有些是沒有加的,但是在線上運(yùn)行的時(shí)候發(fā)現(xiàn)還是要走主庫才可以,這個(gè)時(shí)候就需要改代碼重新發(fā)布了。
動(dòng)態(tài)強(qiáng)制路由可以結(jié)合配置中心來實(shí)現(xiàn),通過配置的方式來決定哪些接口要強(qiáng)制路由,然后在 Filter 中通過 HintManager 來設(shè)置,避免改代碼重啟。
也可以通過切面精確到業(yè)務(wù)方法級(jí)別的動(dòng)態(tài)路由配置。
流量分發(fā)
場(chǎng)景一:
假設(shè)你有一個(gè)主節(jié)點(diǎn),兩個(gè)從節(jié)點(diǎn),讀請(qǐng)求較多,兩個(gè)從節(jié)點(diǎn)壓力有點(diǎn)大。這個(gè)時(shí)候只能增加第三個(gè)從節(jié)點(diǎn)來分擔(dān)壓力。現(xiàn)象是主庫的壓力并不大,寫入較少,從成本來考慮,是否可以不增加第三個(gè)從節(jié)點(diǎn)呢?
場(chǎng)景二:
假設(shè)你有一個(gè) 8 核 64G 的主庫,8 核 64G 的從庫,4 核 32G 的從庫,從配置上來看,4 核 32G 的從庫處理能力肯定是要低于其他兩個(gè)的,這個(gè)時(shí)候如果我們沒有定制流量分發(fā)的比例,就會(huì)出現(xiàn)低配數(shù)據(jù)庫壓力過高而導(dǎo)致的問題。當(dāng)然這個(gè)也能避免使用不同規(guī)則的從庫。
上面的場(chǎng)景需要能夠?qū)φ?qǐng)求進(jìn)行管理,在 Sharding-JDBC 中提供了讀寫分離的路由算法,我們可以自定義算法來進(jìn)行流量的分發(fā)管理。
實(shí)現(xiàn)算法類:
- public class KittyMasterSlaveLoadBalanceAlgorithm implements MasterSlaveLoadBalanceAlgorithm {
- private RoundRobinMasterSlaveLoadBalanceAlgorithm roundRobin = new RoundRobinMasterSlaveLoadBalanceAlgorithm();
- @Override
- public String getDataSource(String name, String masterDataSourceName, List<String> slaveDataSourceNames) {
- String dataSource = roundRobin.getDataSource(name, masterDataSourceName, slaveDataSourceNames);
- // 控制邏輯,比如不同的從節(jié)點(diǎn)(配置不同)可以有不同的比例
- return dataSource;
- }
- @Override
- public String getType() {
- return "KITTY_ROUND_ROBIN";
- }
- @Override
- public Properties getProperties() {
- return roundRobin.getProperties();
- }
- @Override
- public void setProperties(Properties properties) {
- roundRobin.setProperties(properties);
- }
- }
基于 SPI 機(jī)制的配置:
- org.apache.shardingsphere.core.strategy.masterslave.RoundRobinMasterSlaveLoadBalanceAlgorithm
- org.apache.shardingsphere.core.strategy.masterslave.RandomMasterSlaveLoadBalanceAlgorithm
- com.cxytiandi.kitty.db.shardingjdbc.algorithm.KittyMasterSlaveLoadBalanceAlgorithm
讀寫分離的配置:
- spring.shardingsphere.masterslave.load-balance-algorithm-class-name=com.cxytiandi.kitty.db.shardingjdbc.algorithm.KittyMasterSlaveLoadBalanceAlgorithm
- spring.shardingsphere.masterslave.load-balance-algorithm-type=KITTY_ROUND_ROBIN
關(guān)于作者:尹吉?dú)g,簡(jiǎn)單的技術(shù)愛好者,《Spring Cloud 微服務(wù)-全棧技術(shù)與案例解析》, 《Spring Cloud 微服務(wù) 入門 實(shí)戰(zhàn)與進(jìn)階》作者, 公眾號(hào)猿天地發(fā)起人。