分庫分表、主從、讀寫分離

天天學習一點點 編程PDF電子書、視頻教程免費下載: http://www.shitanlife.com/code

 

 

1. 漫談

在進入正題以前,我想先隨意談談對架構的拓展週期的想法(僅我的觀點)。首先,我認爲初期規劃不應太複雜或者龐大,不管項目的中長期可能會發展地如何如何,前期都應該以靈活爲優先,像分庫分表等操做不該該在開始的時候就考慮進去。其次,我認爲需求變動是很是正常的,這點在我等開發的圈子裏吐槽的最多,其中天然有 「領導們」 在業務方面欠缺總體考慮的因素,但咱們也不應侷限在一個觀點內,市場中變則通,不變則死,前期更是如此,所以在前幾版的架構中咱們必需要考慮較高的可擴展性。最後,當項目通過幾輪市場的洗禮和迭代開發,核心業務趨於穩定了,此時咱們再結合中長期的規劃給系統來一次重構,細緻地去劃分領域邊界,該解耦的解耦,該拆分的拆分。java

2. 分庫分表

2.1 概述

當數據庫達到必定規模後(好比說大幾千萬以上),切分是必需要考慮的。通常來講咱們首先要進行垂直切分,即按業務分割,好比說用戶相關、訂單相關、統計相關等等均可以單獨成庫。圖片來源 →node

但僅僅如此這是徹底不夠的,垂直切分雖然剝離了必定的數據,但每一個業務仍是那個數量級,所以咱們還得采起水平切分進一步分散數據,這也是本節論述的重點。
mysql

分庫分表的優勢相信上述兩圖都一目瞭然了,一個是專庫專用,業務更集中,另外一個是提高數據庫服務的負載能力。But there are always two sides to a coin。 今後之後你要接受你的系統複雜度將提高一個檔次,迭代、遷移、運維等都再也不容易。sql

2.2 切分策略

垂直切分在實現上就是一個多數據源的問題,沒啥好講的。如下 Demo 爲水平切分,基於 Sharding-JDBC 中間件,我只作邏輯上的陳述,有關其更詳細的信息和配置請移步 「官方文檔」。docker

首先,咱們得在配置文件中定義分片策略,application.yml:數據庫

server:  port: 8001 mybatis:  config-location: classpath:mybatis/mybatis-config.xml  mapper-locations: classpath:mybatis/mappers/*.xml sharding:  jdbc:  datasource:  names: youclk_0,youclk_1  youclk_0:  type: org.apache.commons.dbcp.BasicDataSource  driver-class-name: com.mysql.jdbc.Driver  url: jdbc:mysql://mysql:3306/youclk_0?useSSL=false  username: root  password: youclk  youclk_1:  type: org.apache.commons.dbcp.BasicDataSource  driver-class-name: com.mysql.jdbc.Driver  url: jdbc:mysql://mysql:3306/youclk_1?useSSL=false  username: root  password: youclk  config:  sharding:  default-database-strategy:  inline:  sharding-column: number  algorithm-expression: youclk_${number % 2}  tables:  user:  actual-data-nodes: youclk_${0..1}.user

具體每一個參數的含義在官方文檔有詳細解釋,其實看名稱也能理解個大概了,我定義將 number 爲偶數的數據存入 youclk_0,奇數存入 youclk_1。express

User:apache

@Data public class User { private String id; private Integer number; private Date createTime; }

UserRepository:編程

@Mapper public interface UserRepository { void insert(User user); }

UserMapper.xml:安全

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.youclk.data.repository.UserRepository"> <resultMap id="BaseResultMap" type="com.youclk.data.entity.User"> <id column="id" property="id" jdbcType="CHAR"/> <result column="number" property="number" jdbcType="INTEGER"/> <result column="createTime" property="create_time" jdbcType="DATE"/> </resultMap> <sql id="Base_Column_List"> id, number, createTime </sql> <insert id="insert"> INSERT INTO user ( id, number ) VALUES ( uuid(), #{number,jdbcType=INTEGER} ) </insert> </mapper>

UserService:

@Service public class UserService { @Resource private UserRepository userRepository; public void insert() { for (int i = 0; i < 10; i++) { User user = new User(); user.setNumber(i); userRepository.insert(user); } } }

Result:

以上作了一個簡單的循環插入,能夠看到數據已經按策略分庫存儲,結果符合咱們的預期。

分庫以後在查詢方面要比以前更加謹慎,既然按策略去切了,那最好就是按策略去查,不然...好比我水平切分了 100個庫,若不按策略去查詢 LIMIT 100000, 10 這麼一組數據,那最後掃描的數量級別是 100 * (100000 + 10), 這是比較恐怖的,雖然 Sharding-JDBC 作了一些優化,好比他不是一次性去查詢到內存中,而是採用流式處理 + 歸併排序的方式,但仍然比較耗資源,能避免仍是儘可能去避免吧。

2.3 分佈式事務

在任何系統中事務都是頂要緊的事情,面對已分庫的系統更是如此,保證誇庫事務的安全歷來不容易。分佈式事務的場景有兩種,一個是在分佈式服務中,這個後續有機會再探討,本節重點關注誇庫事務。

Sharding-JDBC 自動包含了弱XA事務支持,即可以保證邏輯上的事務安全,但因網絡或硬件致使的異常沒法回滾,實現上與通常事務無異:

@Test @Transactional public void insertTest() { userService.insert(); int error = Integer.parseInt("I want error"); userService.insert(); }

能夠看到誇庫事務已回滾,除此以外 Sharding-JDBC 還提供了最大努力送達型柔性事務(將執行過程記錄到日誌中,失敗重試,成功後刪除,若最終仍是失敗則保留事務日誌,供人工干預),雖然安全性更高,但沒法保證時效,限制也不少,這裏留個待續吧,後續有空再深刻探討(主要是比較晚了,想早點寫完休息😁)。

3. 主從與讀寫分離

3.1 概述

爲何要作主從?咱們先來探討如下這幾個場景:

  • 咱們知道每臺數據庫服務器有他的最大鏈接數和 IOPS,如有一天他沒法再知足咱們的業務需求,那相比於在單臺服務器上去作性能堆疊,是是否橫向去擴展幾臺 Slave 去分擔 Master 的壓力更加合理。
  • 若是服務對數據庫的需求是 IO 密集型的,那可能會常常遇到行鎖等待等問題,若要魚與熊掌兼得,讀寫分離是不是更好的選擇。
  • 若是咱們的系統須要作不少報表,或者統計和數據分析,這些業務每每至關地耗費資源但又不是很重要,那針對此,咱們是否應該開幾臺 Slave,讓他們去小黑屋裏慢慢執行,別來影響我處理核心業務的效率。

我大體能想到這麼幾點,歡迎各位繼續留言補充。

3.2 主從部署

我以 MySQL 爲例,通常部署架構爲一臺 Master 和 n 臺 Slave,Master 的主責爲寫,並將數據同步至 Slave,Slave 主要提供查詢功能。

爲了測試方便,我直接使用 Docker 來部署,首先建立主從的配置文件,master.cnf:

[mysqld] server_id = 1 character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default-storage-engine=INNODB #Optimize omit sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES log-bin = /var/lib/mysql/binlog log_bin_trust_function_creators=1 binlog_format = ROW expire_logs_days = 99 sync_binlog = 0 slow-query-log=1 slow-query-log-file=/var/log/mysql/slow-queries.log long_query_time = 3 log-queries-not-using-indexes

slave.cnf:

[mysqld] server_id = 2 character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default-storage-engine=INNODB #Optimize omit sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES log-bin = /var/lib/mysql/binlog log_bin_trust_function_creators=1 binlog_format = ROW expire_logs_days = 99 sync_binlog = 0 relay_log=slave-relay-bin log-slave-updates=1 slave-skip-errors=all slow-query-log=1 slow-query-log-file=/var/log/mysql/slow-queries.log long_query_time = 3

而後進行 compose 編排,加入 warm 集羣,docker-compose.yml:

version: '3.5' services: mysql-master: image: mysql ports: - 3301:3306 networks: - proxy - youclk volumes: - /Users/Jermey/Documents/data/db/cluster/master/mysql:/var/lib/mysql - /Users/Jermey/Documents/data/db/cluster/master/conf.d:/etc/mysql/conf.d environment: MYSQL_ROOT_PASSWORD: youclk mysql-slave: image: mysql ports: - 3302:3306 networks: - proxy - youclk volumes: - /Users/Jermey/Documents/data/db/cluster/slave/mysql:/var/lib/mysql - /Users/Jermey/Documents/data/db/cluster/slave/conf.d:/etc/mysql/conf.d environment: MYSQL_ROOT_PASSWORD: youclk networks: proxy: external: true youclk: external: true

再次感激 Docker, 從編排配置文件到最後啓動服務整個過程不到一分鐘:

接下來就是配置主從關係:

docker exec -it cluster_mysql-master mysql -p CREATE USER 'reader'@'%' IDENTIFIED BY 'youclk'; GRANT REPLICATION SLAVE ON *.* TO 'reader'@'%'; show master status\G
docker exec -it cluster_mysql-slave mysql -p CHANGE MASTER TO \ MASTER_HOST='mysql-master',\ MASTER_PORT=3306,\ MASTER_USER='reader',\ MASTER_PASSWORD='youclk',\ MASTER_LOG_FILE='binlog.000004',\ MASTER_LOG_POS=154; start slave; show slave status\G

Test:

上圖中左邊連的是 Master, 右邊爲 Slave, 我在 Master 中執行 create database youclk_0; 能夠看到 Slave 中也生成了 youclk_0,至此主從配置測試完成。

3.3 讀寫分離

基於 Sharding-JDBC 的讀寫分離實現很是簡單,改一下配置文件,其他幾乎是無感知的,application.yml:

server:  port: 8001 mybatis:  config-location: classpath:mybatis/mybatis-config.xml  mapper-locations: classpath:mybatis/mappers/*.xml sharding:  jdbc:  datasource:  names: ds_master,ds_slave  ds_master:  type: org.apache.commons.dbcp.BasicDataSource  driver-class-name: com.mysql.jdbc.Driver  url: jdbc:mysql://mysql:3301/youclk_0?useSSL=false  username: root  password: youclk  ds_slave:  type: org.apache.commons.dbcp.BasicDataSource  driver-class-name: com.mysql.jdbc.Driver  url: jdbc:mysql://mysql:3302/youclk_0?useSSL=false  username: root  password: youclk  config:  masterslave:  load-balance-algorithm-type: round_robin  name: ds_ms  master-data-source-name: ds_master  slave-data-source-names: ds_slave  sharding:  props: sql.show: true

Test:

@Test public void selectAndInsertTest() { userService.selectAll(); userService.insert(); }

Result:

跟蹤 MySQL 的日誌能夠發現主從庫分別執行了插入與查詢,實現了讀寫分離。

相關文章
相關標籤/搜索