SpringBoot 玩轉讀寫分離
摘要: 基於SpringBoot與Sharding-JDBC實現讀寫分離
環境概覽
框架版本號Spring Boot1.5.12.RELEASESharding-JDBC2.0.3MyBatis-Plus2.2.0前言介紹
Sharding-JDBC是噹噹網的一個開源項目,只需引入jar即可輕鬆實現讀寫分離與分庫分表。與MyCat不同的是,Sharding-JDBC致力於提供輕量級的服務框架,無需額外部署,底層是對JDBC進行增強,兼容各種連接池和ORM框架。不僅如此還提供分散式事務及分散式治理功能,即將出世的3.X版本可能會提供更加全面的功能。有興趣的小夥伴們,可以去了解下,這裡提供官方文檔和GitHub地址。
讀寫分離
引自Sharding-JDBC官方文檔
面對日益增加的系統訪問量,資料庫的吞吐量面臨著巨大瓶頸。 對於同一時間有大量並發讀操作和較少寫操作類型的應用系統來說,將單一的資料庫拆分為主庫和從庫,主庫負責處理事務性的增刪改操作,從庫負責處理查詢操作,能夠有效的避免由數據更新導致的行鎖,使得整個系統的查詢性能得到極大的改善。 通過一主多從的配置方式,可以將查詢請求均勻的分散到多個數據副本,能夠進一步的提升系統的處理能力。 使用多主多從的方式,不但能夠提升系統的吞吐量,還能夠提升系統的可用性,可以達到在任何一個資料庫宕機,甚至磁碟物理損壞的情況下仍然不影響系統的正常運行。
雖然讀寫分離可以提升系統的吞吐量和可用性,但同時也帶來了數據不一致的問題,這包括多個主庫之間的數據一致性,以及主庫與從庫之間的數據一致性的問題。並且,讀寫分離也帶來了與數據分片同樣的問題,它同樣會使得應用開發和運維人員對資料庫的操作和運維變得更加複雜。透明化讀寫分離所帶來的影響,讓使用方盡量像使用一個資料庫一樣使用主從資料庫,是讀寫分離中間件的主要功能。
讀寫分離,簡單來說,就是將DML交給主資料庫去執行,將更新結果同步至各個從資料庫保持主從數據一致,DQL分發給從資料庫去查詢,從資料庫只提供讀取查詢操作。讀寫分離特別適用於讀多寫少的場景下,通過分散讀寫到不同的資料庫實例上來提高性能,緩解單機資料庫的壓力。
這裡解釋一下什麼是DML和DQL?SQL語言四大分類:DQL、DML、DDL、DCL。
- DQL(Data QueryLanguage):數據查詢語言,比如select查詢語句
- DML(Data Manipulation Language):數據操縱語言,比如insert、delete、update更新語句
- DDL():數據定義語言,比如create/drop/alter等語句
- DCL():數據控制語言,比如grant/rollback/commit等語句
實現步驟
實現步驟非常簡單,僅需兩步,即可在代碼上實現讀寫分離功能,感覺非常帶勁。
1.引入jar包
<dependency>
<groupId>io.shardingjdbc</groupId>
<artifactId>sharding-jdbc-core-spring-boot-starter</artifactId>
<version>2.0.3</version></dependency>
2.配置讀寫分離
sharding: jdbc:
# 配置真實數據源 datasource: names: ds_master_0,ds_slave_0_1,ds_slave_0_2 # 配置主庫 ds_master_0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver jdbc-url: jdbc:mysql://ip:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true username: username password: password maxPoolSize: 20
# 配置第一個從庫 ds_slave_0_1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver jdbc-url: jdbc:mysql://ip:3307/test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true username: username password: password maxPoolSize: 20
# 配置第二個從庫 ds_slave_0_2: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver jdbc-url: jdbc:mysql://ip:3308/test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true username: username password: password maxPoolSize: 20
# 配置讀寫分離 config: masterslave:
# 配置從庫選擇策略,提供輪詢與隨機,這裡選擇用輪詢 load-balance-algorithm-type: round_robin name: ds_m_1_s_2 master-data-source-name: ds_master_0 slave-data-source-names: ds_slave_0_1,ds_slave_0_2 sharding: props:
# 開啟SQL顯示,默認值: false,注意:僅配置讀寫分離時不會列印日誌!!! sql: show: true
準備測試
在測試開始之前,我們先明確一點,由於只配置了讀寫分離,即使上文中配置了sql.show=true也不會有日誌列印出來(如果配置了分庫/分表就不會有這種情況),那麼我們怎麼知道資料庫操作到底是走的主庫還是主庫呢?怎麼知道如果走從庫有沒有遵循輪詢演算法走的具體是哪個從庫呢?
帶著上述的疑問,追溯源碼進入MasterSlaveDataSource這個類中(友情提示:IDEA連續按兩次shift在彈框中輸入MasterSlaveDataSource即可查看該類),主要關注其中的getDataSource()方法。下面貼出關鍵源碼。
/**
* Get data source from master-slave data source.
*
* @param sqlType SQL type
* @return data source from master-slave data source
*/
public NamedDataSource getDataSource(final SQLType sqlType) { if (isMasterRoute(sqlType)) {
DML_FLAG.set(true); return new NamedDataSource(masterSlaveRule.getMasterDataSourceName(), masterSlaveRule.getMasterDataSource());
}
String selectedSourceName = masterSlaveRule.getStrategy().getDataSource(masterSlaveRule.getName(),
masterSlaveRule.getMasterDataSourceName(), new ArrayList<>(masterSlaveRule.getSlaveDataSourceMap().keySet()));
DataSource selectedSource = selectedSourceName.equals(masterSlaveRule.getMasterDataSourceName())
? masterSlaveRule.getMasterDataSource() : masterSlaveRule.getSlaveDataSourceMap().get(selectedSourceName);
Preconditions.checkNotNull(selectedSource, ""); return new NamedDataSource(selectedSourceName, selectedSource);
} private boolean isMasterRoute(final SQLType sqlType) { return SQLType.DQL != sqlType || DML_FLAG.get() || HintManagerHolder.isMasterRouteOnly();
}
isMasterRoute() 方法判斷當前操作是否應該路由到主庫數據源,如果SQL類型是DML則返回true
getDataSource() 方法根據SQL類型返回一個數據源。如果SQL類型是DQL則通過配置的演算法返回一個從庫數據源,如果SQL類型是DML則返回主庫數據源。
那麼了解了以上兩個方法後,通過打斷點DEBUG的方式,我們可以很容易的得知,執行SQL時到底走的是哪個庫。
開始測試
這邊我準備了兩個測試介面,一個用於測試讀操作,一個用於測試寫操作。
@RestController@RequestMapping("/users")public class UserController { @Autowired
private IUserService userService; /**
* 查詢用戶列表
* @return
*/
@GetMapping
public List<User> getUser() { return userService.selectList(null);
} /**
* 創建/修改用戶信息
* @param user
* @return
*/
@PostMapping
public User saveUser(@RequestBody User user) { return userService.insertOrUpdate(user) ? userService.selectById(user.getId()) : null;
}
}
發起GET請求/users介面,期望通過輪詢演算法去從庫中查詢獲取數據
第一次,通過上圖我們可以很容易發現SQL類型是DQL,走的是ds_slave_0_1從資料庫,且策略是輪詢策略
第二次,我們可以發現走的是ds_slave_0_2從資料庫,讀操作和輪詢演算法都沒毛病
發起POST請求/users介面,期望從主庫中創建或修改用戶數據。
可見,寫操作時,走的是ds_master_0主資料庫。當userService.insertOrUpdate(user)執行成功返回true後,接著再執行userService.selectById(user.getId())時,又會走到ds_slave_0_1從庫讀取數據。寫操作也沒毛病,以上我們的測試階段就大功告成了。
輪詢策略
有興趣的小夥伴可以看下輪詢策略的源碼,非常的簡單。這裡貼出輪詢策略主要源碼
/**
* Round-robin slave database load-balance algorithm.
*
* @author zhangliang
*/public final class RoundRobinMasterSlaveLoadBalanceAlgorithm implements MasterSlaveLoadBalanceAlgorithm {
private static final ConcurrentHashMap<String, AtomicInteger> COUNT_MAP = new ConcurrentHashMap<>();
@Override
public String getDataSource(final String name, final String masterDataSourceName, final List<String> slaveDataSourceNames) {
AtomicInteger count = COUNT_MAP.containsKey(name) ? COUNT_MAP.get(name) : new AtomicInteger(0);
COUNT_MAP.putIfAbsent(name, count); count.compareAndSet(slaveDataSourceNames.size(), 0); return slaveDataSourceNames.get(count.getAndIncrement() % slaveDataSourceNames.size());
}
}
其內部通過並發容器ConcurrentHashMap與AtomicInteger的CAS保障高並發下計數線程安全,使用無所的方式比加鎖更加高效。
靈活性
Sharding-JDBC使用簡單,容易上手且十分靈活,不僅可以使用默認策略,還可以使用自定義的策略。可以說是對Java開發者十分的友好,通過寫Java代碼的方式就可以實現更加深度的定製化路由規則。這裡如果想要自定義輪詢策略可以使用如下配置來自定義的輪詢策略。
sharding: jdbc: config: masterslave: load-balance-algorithm-class-name:
注意點
在玩轉讀寫分離時,遇到如下幾個需要注意的地方
- Sharding-JDBC目前僅支持一主多從的結構
- Sharding-JDBC沒有提供主從同步的實現,該功能需要自己額外搭建,可參照《基於Docker搭建MySQL主從複製》簡易搭建測試使用
- 主庫和從庫的數據同步延遲導致的數據不一致問題需要自己去解決
- Sharding-JDBC雖然提供了列印SQL日誌的開關,但是如果僅配置了讀寫分離好像是沒有用的
- 文中配置使用的是HikariCP連接池,使用其他連接池時,需要將jdbc-url配置名該為url,否則可能會拋異常
※SSH 連接、遠程上傳下載文件
※tensorflow隨筆-tf.nn.conv2d卷積運算
TAG:程序員小新人學習 |