分庫(kù)分表,讀寫分離后,數(shù)據(jù)庫(kù)中間件扮演了一個(gè)怎樣的角色?
分庫(kù)分表,讀寫分離會(huì)帶來(lái)哪些問(wèn)題?
前面一篇文章圖解分布式系統(tǒng)架構(gòu)(看推薦閱讀)大概講了一下分庫(kù)分表,以及讀寫分離出現(xiàn)的場(chǎng)景,分庫(kù)分表為了解決高并發(fā)和海量數(shù)據(jù)的問(wèn)題。
分庫(kù)后會(huì)出現(xiàn)新的問(wèn)題
1、跨庫(kù)join問(wèn)題
如有2個(gè)庫(kù),訂單庫(kù),用戶庫(kù),要查詢買了某件商品的所有用戶信息
2、事務(wù)問(wèn)題
用戶下訂單的時(shí)候需要扣減商品庫(kù)存,如果訂單數(shù)據(jù)和商品數(shù)據(jù)在一個(gè)數(shù)據(jù)庫(kù)中,我們可以使用事務(wù)來(lái)保證扣減商品庫(kù)存和生成訂單的操作要么都成功要么都失敗,但分庫(kù)后就無(wú)法使用數(shù)據(jù)庫(kù)事務(wù)了,這時(shí)就要用到分布式事務(wù)了
分表后也會(huì)出現(xiàn)新的問(wèn)題
1、join操作
水平分表后,數(shù)據(jù)分散在多個(gè)表中,如果需要與其他表進(jìn)行join查詢,需要在業(yè)務(wù)代碼或數(shù)據(jù)庫(kù)中間件中進(jìn)行多次join查詢,然后將結(jié)果合并
2、count()操作
業(yè)務(wù)代碼或者數(shù)據(jù)庫(kù)中間件對(duì)每個(gè)表進(jìn)行count(*)操作,然后將結(jié)果相加?;蛘咝陆ㄒ粡埍?,假如表名為“記錄數(shù)表”,包含table_name和row_count兩個(gè)字段,每次插入或刪除子表數(shù)據(jù)成功后,都更新“記錄數(shù)表”
3、order by操作
水平分表后,數(shù)據(jù)分散到多個(gè)字表中,排序操作無(wú)法再數(shù)據(jù)庫(kù)中完成,只能由業(yè)務(wù)代碼或數(shù)據(jù)庫(kù)中間件分別查詢每個(gè)子表中的數(shù)據(jù),然后匯總進(jìn)行排序
而高并發(fā)這個(gè)階段,肯定是需要做讀寫分離的,啥意思?因?yàn)閷?shí)際上大部分的互聯(lián)網(wǎng)公司,一些網(wǎng)站,或者是 app,其實(shí)都是讀多寫少。所以針對(duì)這個(gè)情況,就是寫一個(gè)主庫(kù),但是主庫(kù)掛多個(gè)從庫(kù),然后從多個(gè)從庫(kù)來(lái)讀,那不就可以支撐更高的讀并發(fā)壓力了嗎?
那么如何實(shí)現(xiàn) MySQL 的讀寫分離?
其實(shí)很簡(jiǎn)單,就是基于主從復(fù)制架構(gòu),簡(jiǎn)單來(lái)說(shuō),就搞一個(gè)主庫(kù),掛多個(gè)從庫(kù),然后我們就單單只是寫主庫(kù),然后從庫(kù)讀取bin log進(jìn)行重放,這樣主庫(kù)和從庫(kù)數(shù)據(jù)就一樣,只不過(guò)并發(fā)量比較高時(shí),會(huì)有主從同步延時(shí)問(wèn)題
放個(gè)圖理解一下MySQL主從復(fù)制的原理,這塊面試經(jīng)常被問(wèn)到
總的來(lái)說(shuō),MySQL復(fù)制有三個(gè)步驟
1、在主庫(kù)上把數(shù)據(jù)更改記錄到二進(jìn)制日志中(Binary Log)中(這些記錄被稱為二進(jìn)制日志事件)
2、備庫(kù)將主庫(kù)上的日志復(fù)制到自己的中繼日志(Relay Log)中
3、備庫(kù)讀取中繼日志中的事件,將其重放到備庫(kù)數(shù)據(jù)之上
現(xiàn)在理論知識(shí)都有了,就剩怎么實(shí)現(xiàn)了?本來(lái)就是為了實(shí)現(xiàn)一個(gè)功能,現(xiàn)在好了,單寫讀寫分離,跨庫(kù)join,分布式事務(wù),排序操作等就夠你忙的了。
這時(shí)候你就應(yīng)該想起數(shù)據(jù)庫(kù)中間件了,它能幫你進(jìn)行上述操作,把你從復(fù)雜的數(shù)據(jù)處理中解放出來(lái),專注于開(kāi)發(fā)業(yè)務(wù)代碼。
數(shù)據(jù)庫(kù)中間件能幫你做什么?
目前國(guó)內(nèi)用的最多的中間件就是sharding-jdbc,mycat,別的用的很少,不再介紹
而數(shù)據(jù)庫(kù)中間件針對(duì)數(shù)據(jù)源管理,目前主要有兩種思路
1、客戶端模式,在每個(gè)應(yīng)用程序模塊中配置管理自己需要的一個(gè)(或者多個(gè))數(shù)據(jù)源,直接訪問(wèn)各個(gè)數(shù)據(jù)庫(kù),在模塊內(nèi)完成數(shù)據(jù)的整合,sharding-jdbc的實(shí)現(xiàn)方式
2、通過(guò)中間代理層來(lái)統(tǒng)一管理所有的數(shù)據(jù)源,后端數(shù)據(jù)庫(kù)集群對(duì)前端應(yīng)用程序透明,mycat的實(shí)現(xiàn)方式
放兩張圖就能理解區(qū)別了
一般的建議是小公司用sharding-jdbc,大公司用mycat,因?yàn)榫S護(hù)一套mycat集群也需要人力,物力。鑒于篇幅限制,本文就介紹一下mycat的基本使用
以一個(gè)最形象的例子,讓你明白mycat到底幫你做了什么?
先介紹一下什么是分片?簡(jiǎn)單來(lái)說(shuō),就是通過(guò)某種特定的條件,將我們存放在同一個(gè)數(shù)據(jù)庫(kù)中的數(shù)據(jù),分散存放到多個(gè)數(shù)據(jù)庫(kù)上面,以達(dá)到分散單臺(tái)設(shè)備負(fù)載的效果
如上圖所表示,數(shù)據(jù)被分到多個(gè)分片數(shù)據(jù)庫(kù)后,應(yīng)用如果需要讀取數(shù)據(jù),就要需要處理多個(gè)數(shù)據(jù)源的數(shù)據(jù)。如果沒(méi)有數(shù)據(jù)庫(kù)中間件,那么應(yīng)用將直接面對(duì)分片集群,數(shù)據(jù)源切換、事務(wù)處理、數(shù)據(jù)聚合都需要應(yīng)用直接處理,原本該是專注于業(yè)務(wù)的應(yīng)用,將會(huì)花大量的工作來(lái)處理分片后的問(wèn)題,最重要的是每個(gè)應(yīng)用處理將是完全的重復(fù)造輪子。
所以有了數(shù)據(jù)庫(kù)中間件,應(yīng)用只需要集中與業(yè)務(wù)處理,大量的通用的數(shù)據(jù)聚合,事務(wù),數(shù)據(jù)源切換都由中間件來(lái)處理。
那么數(shù)據(jù)庫(kù)中間件是怎么做到的呢?
綠色的部分為mycat的邏輯節(jié)點(diǎn),藍(lán)色的部分為物理節(jié)點(diǎn)(即數(shù)據(jù)庫(kù)的部署地址)
schema:邏輯庫(kù)
通常對(duì)實(shí)際應(yīng)用來(lái)說(shuō),并不需要知道中間件的存在,業(yè)務(wù)開(kāi)發(fā)人員只需要知道
數(shù)據(jù)庫(kù)的概念,所以數(shù)據(jù)庫(kù)中間件可以被看做是一個(gè)或多個(gè)數(shù)據(jù)庫(kù)集群構(gòu)成的邏輯庫(kù)
table:邏輯表
既然有邏輯庫(kù),那么就會(huì)有邏輯表,分布式數(shù)據(jù)庫(kù)中,對(duì)應(yīng)用來(lái)說(shuō),讀寫數(shù)據(jù)的表就是邏輯表。邏輯表,可以是數(shù)據(jù)切分后,分布在一個(gè)或多個(gè)分片庫(kù)中,也可以不做數(shù)據(jù)切分,不分片,只有一個(gè)表構(gòu)成
datanode:分片節(jié)點(diǎn)
數(shù)據(jù)切分后,一個(gè)大表被分到不同的分片數(shù)據(jù)庫(kù)上面,每個(gè)表分片所在的數(shù)據(jù)庫(kù)就是分片節(jié)點(diǎn)
datahost:節(jié)點(diǎn)主機(jī)(上圖藍(lán)色節(jié)點(diǎn))
數(shù)據(jù)切分后,每個(gè)分片節(jié)點(diǎn)(dataNode)不一定都會(huì)獨(dú)占一臺(tái)機(jī)器,同一機(jī)器上面可以有多個(gè)分片數(shù)據(jù)庫(kù),這樣一個(gè)或多個(gè)分片節(jié)點(diǎn)(dataNode)所在的機(jī)器就是節(jié)點(diǎn)主機(jī)(dataHost),為了規(guī)避單節(jié)點(diǎn)主機(jī)并發(fā)數(shù)限制,盡量將讀寫壓力高的分片節(jié)點(diǎn)(dataNode)均衡的放在不同的節(jié)點(diǎn)主機(jī)(dataHost)。
rule:分片規(guī)則
前面講了數(shù)據(jù)切分,一個(gè)大表被分成若干個(gè)分片表,就需要一定的規(guī)則,這樣按照某種業(yè)務(wù)規(guī)則把數(shù)據(jù)分到某個(gè)分片的規(guī)則就是分片規(guī)則,數(shù)據(jù)切分選擇合適的分片規(guī)則非常重要,將極大的避免后續(xù)數(shù)據(jù)處理的難度。
實(shí)戰(zhàn)Mycat
為了快速熟悉各種配置,一般直接從git上下載代碼,本地用idea打開(kāi)啟動(dòng),方便練習(xí)一波,小編演示本文就是用的這種方法
mycat的配置其實(shí)是蠻簡(jiǎn)單的,最主要的是熟悉各配置文件的規(guī)則。如用戶名,密碼,分片規(guī)則,都是在配置文件中定義的
關(guān)于配置文件,conf目錄下主要以下三個(gè)需要熟悉,要是本地測(cè)試用idea打開(kāi)在resources目錄下
小編演示一個(gè)最簡(jiǎn)單的映射配置,找一個(gè)數(shù)據(jù)庫(kù)服務(wù)器,建立3個(gè)庫(kù),db1,db2,db3,把id為0-500 0000的數(shù)據(jù)放在db1,id為500 0001到1000 0000的數(shù)據(jù)放在db2,以此類推
server.xml是Mycat服務(wù)器參數(shù)調(diào)整和用戶授權(quán)的配置文件(省略了一些配置,后面2個(gè)配置文件一樣)
- <mycat:server xmlns:mycat="http://io.mycat/">
- <user name="root" defaultAccount="true">
- <property name="password">123456</property>
- <property name="schemas">TESTDB</property>
- </user>
- </mycat:server>
schema.xml是邏輯庫(kù),邏輯表定義以及分片定義的配置文件
- <mycat:schema xmlns:mycat="http://io.mycat/">
- <!--邏輯庫(kù)名-->
- <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
- <!--rule的值和rule.xml的實(shí)現(xiàn)對(duì)應(yīng)-->
- <table name="tb_test" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
- </schema>
- <!--dataHost可以配置不同主機(jī)上的數(shù)據(jù)庫(kù),這里為了演示就配置了一個(gè)主機(jī)上的不同數(shù)據(jù)庫(kù)-->
- <dataNode name="dn1" dataHost="localhost1" database="db1" />
- <dataNode name="dn2" dataHost="localhost1" database="db2" />
- <dataNode name="dn3" dataHost="localhost1" database="db3" />
- <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
- writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
- <!--心跳語(yǔ)句-->
- <heartbeat>select user()</heartbeat>
- <!--這里我本地mycat配了一個(gè)遠(yuǎn)程mysql-->
- <writeHost host="hostM1" url="遠(yuǎn)程mysql的ip地址:3306" user="root"
- password="2014">
- </writeHost>
- </dataHost>
- </mycat:schema>
rule.xml是分片規(guī)則的配置文件
- <mycat:rule xmlns:mycat="http://io.mycat/">
- <tableRule name="auto-sharding-long">
- <rule>
- <!--根據(jù)哪個(gè)列進(jìn)行分片-->
- <columns>id</columns>
- <!--分片規(guī)則,連續(xù)分片-->
- <algorithm>rang-long</algorithm>
- </rule>
- </tableRule>
- <function name="rang-long"
- <!--分片規(guī)則的實(shí)現(xiàn)類-->
- class="io.mycat.route.function.AutoPartitionByLong">
- <!--分片規(guī)則配置文件-->
- <property name="mapFile">autopartition-long.txt</property>
- </function>
- </mycat:rule>
autopartition-long.txt詳細(xì)的分片策略
- # range start-end ,data node index
- # K=1000,M=10000.
- 00-500M=0
- 500M-1000M=1
- 1000M-1500M=2
這個(gè)配置的意思是,id在0到500w放在***個(gè)分片,以此類推
小編這里用Navicat(數(shù)據(jù)庫(kù)連接工具)連接到本地的mycat
主機(jī):localhost
端口:8066
用戶名:root(server.xml中配置好的用戶名密碼)
密碼:123456
看到有一個(gè)TestDB庫(kù),在這個(gè)庫(kù)里面執(zhí)行建表語(yǔ)句
- CREATE TABLE `tb_test` (
- `id` int(11) NOT NULL,
- `name` varchar(255) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
然后到對(duì)應(yīng)的物理數(shù)據(jù)庫(kù)db1,db2,db3上看,3個(gè)庫(kù)都有了這個(gè)表。
在邏輯數(shù)據(jù)庫(kù)中插入如下三條數(shù)據(jù)
- insert into tb_test (id, name) values (1, "1");
- insert into tb_test (id, name) values (5000001, "5000001");
- insert into tb_test (id, name) values (10000001, "10000001");
可以看到id為1的數(shù)據(jù)插入到物理數(shù)據(jù)庫(kù)中的db1,id為5000001的數(shù)據(jù)插入到db2,id為10000001的數(shù)據(jù)插入到db3
在邏輯數(shù)據(jù)庫(kù)中執(zhí)行如下語(yǔ)句又能拿到這3條記錄
- select id, name from tb_test
執(zhí)行如下語(yǔ)句,可以看到mycat從三個(gè)數(shù)據(jù)庫(kù)中取了記錄,LIMIT 100是因?yàn)閟chema.xml中配置了sqlMaxLimit=“100”
- explain select id, name from tb_test
有了mycat以后,我們的數(shù)據(jù)庫(kù)地址配置成mycat即可,它幫我們做了很多,其他各種分片規(guī)則,讀寫分離等的配置就不再演示,理解整個(gè)框架的大概運(yùn)行流程就行
***再分享一個(gè)知識(shí)點(diǎn),mycat1.5 開(kāi)始會(huì)支持本地 xml 啟動(dòng),以及從 zookeeper 加載配置轉(zhuǎn)為本地 xml 的兩種方式,即原來(lái)分享的zookeeper可以用作配置中心