你們團(tuán)隊(duì)使用SpringMVC+Spring+JPA框架枉证,快速開發(fā)了一個(gè)NB的系統(tǒng),上線后客戶訂單跟雪花一樣紛沓而來。
慢慢地历筝,你的心情開始變差,因?yàn)榭蛻艉彤a(chǎn)品的抱怨越來越頻繁廊谓,抱怨的最多的一個(gè)問題就是:系統(tǒng)越來越慢了梳猪。
1?常規(guī)優(yōu)化
你組織團(tuán)隊(duì),進(jìn)行了一系列的優(yōu)化蒸痹。
1.1?數(shù)據(jù)表索引優(yōu)化
經(jīng)過初步分析春弥,發(fā)現(xiàn)瓶頸在數(shù)據(jù)庫。WEB服務(wù)器的CPU閑來無事叠荠,但數(shù)據(jù)庫服務(wù)器的CPU使用率高居不下匿沛。
于是,請(qǐng)來架構(gòu)組的DBA同事榛鼎,監(jiān)控?cái)?shù)據(jù)庫的訪問逃呼,整理出那些耗時(shí)的SQL,并且進(jìn)行SQL查詢分析者娱。根據(jù)分析結(jié)果抡笼,對(duì)數(shù)據(jù)表索引進(jìn)行重新整理。同時(shí)也對(duì)數(shù)據(jù)庫本身的參數(shù)設(shè)置進(jìn)行了優(yōu)化黄鳍。
優(yōu)化后推姻,頁面速度明顯提升,客戶抱怨減少框沟,又過了一段時(shí)間的安逸日子藏古。
1.2?多點(diǎn)部署+負(fù)載均衡
慢慢的,訪問速度又不行了忍燥,這次是WEB服務(wù)器壓力很大拧晕,數(shù)據(jù)庫服務(wù)器相對(duì)空閑。經(jīng)過分析灾前,發(fā)現(xiàn)是系統(tǒng)并發(fā)用戶數(shù)太多防症,單WEB服務(wù)器不能夠支持如此眾多的并發(fā)請(qǐng)求。
于是,請(qǐng)架構(gòu)協(xié)助進(jìn)行WEB多點(diǎn)部署蔫敲,前端使用nginx做負(fù)載分發(fā)饲嗽。這時(shí)候必須要解決的一個(gè)問題就是用戶會(huì)話保持的問題。這可以有幾種不同解決方案:
1奈嘿、nginx實(shí)現(xiàn)sticky分發(fā)
因?yàn)閚ginx缺省沒有sticky機(jī)制貌虾,可以使用ip_hash方式來代替。
2裙犹、配置Tomcat實(shí)現(xiàn)Session復(fù)制
3尽狠、代碼使用SpringSession,利用redis實(shí)現(xiàn)session復(fù)制叶圃。
具體做法就不一一介紹了袄膏。其中使用SpringSession的方法,可以參考我的文章《集群環(huán)境CAS的問題及解決方案》掺冠。
2?試用當(dāng)當(dāng)?shù)腟harding JDBC框架
多點(diǎn)部署之后沉馆,系統(tǒng)又運(yùn)行了一段時(shí)間,期間增加了更多的WEB節(jié)點(diǎn)德崭,基本能應(yīng)對(duì)客戶需求斥黑。慢慢的,增加WEB服務(wù)器也不能解決問題了眉厨,因?yàn)橄到y(tǒng)瓶頸又回到了數(shù)據(jù)庫服務(wù)器锌奴。SQL執(zhí)行時(shí)間越來越長,而且無法優(yōu)化憾股。原因也很簡單鹿蜀,數(shù)據(jù)量太大。
單表數(shù)據(jù)已經(jīng)超過幾千萬行荔燎,通過數(shù)據(jù)庫的優(yōu)化已經(jīng)不能滿足速度的要求耻姥。分庫分表提到了日程上,必須解決有咨。
因?yàn)槭褂昧薐PA琐簇,如果分庫分表需要對(duì)數(shù)據(jù)訪問層做較大的改動(dòng),工作量太大座享,修改的風(fēng)險(xiǎn)也太高婉商。恰好看到當(dāng)當(dāng)開源了其Sharding-JDBC組件,摘抄一段介紹:
https://github.com/dangdangdotcom/sharding-jdbc
Sharding-JDBC直接封裝JDBC API渣叛,可以理解為增強(qiáng)版的JDBC驅(qū)動(dòng)丈秩,舊代碼遷移成本幾乎為零:
可適用于任何基于java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC淳衙。
可基于任何第三方的數(shù)據(jù)庫連接池蘑秽,如:DBCP, C3P0, BoneCP, Druid等饺著。
理論上可支持任意實(shí)現(xiàn)JDBC規(guī)范的數(shù)據(jù)庫。雖然目前僅支持MySQL肠牲,但已有支持Oracle幼衰,SQLServer,DB2等數(shù)據(jù)庫的計(jì)劃缀雳。
它支持JPA渡嚣,可以在幾乎不修改代碼的情況下完成分庫分表的實(shí)現(xiàn)。因此肥印,選擇這個(gè)框架做一次分庫分表的嘗試识椰。
先做一個(gè)最簡單的試用,不做分庫深碱,僅做分表腹鹉。選擇數(shù)據(jù)表operate_history,這個(gè)數(shù)據(jù)表記錄所有的操作歷史莹痢,是整個(gè)系統(tǒng)中數(shù)據(jù)量最大的一個(gè)數(shù)據(jù)表种蘸。
希望將這個(gè)表拆分為四個(gè)數(shù)據(jù)表,分別是 operate_history_0operate_history_1 operate_history_2 operate_history_3竞膳。數(shù)據(jù)能夠分配保存到四個(gè)數(shù)據(jù)表中,降低單表的數(shù)據(jù)量诫硕。同時(shí)坦辟,為了盡量減少跨表的查詢操作,決定使用字段 entity_key為分表依據(jù)章办,這樣同一個(gè)entity對(duì)象的所有操作锉走,將會(huì)記錄在同一個(gè)數(shù)據(jù)表中。拆分后的數(shù)據(jù)表結(jié)構(gòu)為:
3 實(shí)現(xiàn)過程
以下是針對(duì)JPA項(xiàng)目的修改過程藕届。其他項(xiàng)目請(qǐng)參考官方網(wǎng)站的文檔挪蹭。
3.1?修改pom.xml增加dependency
需要添加兩個(gè)jar,sharding-jdbc-core和sharding-jdbc-config-spring休偶。
? com.dangdang
?sharding-jdbc-core
? 1.3.0
? com.dangdang
? sharding-jdbc-config-spring
? 1.3.0
3.2?修改Spring中Database部分的配置
原Database配置
?
?
?
?
修改后的配置
?
?
?
?
?sharding-columns="entity_key"
?algorithm-class="cn.codestory.sharding.SingleKeyTableShardingAlgorithm"/>
?
?
???actual-tables="operate_history_0,operate_history_1,operate_history_2,operate_history_3"
???table-strategy="historyTableStrategy" />
3.3?編寫類SingleKeyTableShardingAlgorithm
這個(gè)類用來根據(jù)entity_key值確定使用的分表名梁厉。參考sharding提供的示例代碼進(jìn)行修改。核心代碼如下
publicCollection doInSharding(
???????? CollectionavailableTargetNames,
???????? ShardingValueshardingValue) {
?int targetCount = availableTargetNames.size();
?Collection result = newLinkedHashSet<>(targetCount);
?Collection values =shardingValue.getValues();
?for (Long value : values) {
? for (String tableNames :availableTargetNames) {
?? if (tableNames.endsWith(value % targetCount+ "")) {
??? result.add(tableNames);
?? }
? }
?}
?return result;
}
這是一個(gè)簡單的實(shí)現(xiàn)踏兜,對(duì)entity_key進(jìn)行求模词顾,用余數(shù)確定數(shù)據(jù)表名。
3.4?修改主鍵生成方法
因?yàn)閿?shù)據(jù)分表保存碱妆,不能使用identify方式生成數(shù)據(jù)表主鍵肉盹。如果主鍵是String類型,可以考慮使用uuid生成方法疹尾,但它查詢效率會(huì)相對(duì)比較低上忍。
如果使用long型主鍵骤肛,可以使用其他方式,一定要確保各個(gè)子表中的主鍵不重復(fù)窍蓝。
3.5?歷史數(shù)據(jù)的處理
根據(jù)數(shù)據(jù)分表的規(guī)則萌衬,需要對(duì)原有數(shù)據(jù)包的數(shù)據(jù)進(jìn)行遷移,分別移動(dòng)到四個(gè)數(shù)據(jù)表中它抱。如果不做這一步秕豫,或者數(shù)據(jù)遷移到了錯(cuò)誤的數(shù)據(jù)表,后續(xù)將會(huì)查詢不到這些數(shù)據(jù)观蓄。
至此混移,對(duì)項(xiàng)目的修改基本完成,重新啟動(dòng)項(xiàng)目并增加operate_history數(shù)據(jù)侮穿,就會(huì)看到新添加的數(shù)據(jù)歌径,已經(jīng)根據(jù)我們的分表規(guī)則,插入到了某一個(gè)數(shù)據(jù)表中亲茅。查詢的時(shí)候回铛,能夠同時(shí)查詢到多個(gè)實(shí)際數(shù)據(jù)表中的數(shù)據(jù)。
4?數(shù)據(jù)分表規(guī)則的一些考慮
前面的例子克锣,演示的是根據(jù)entity_key進(jìn)行分表茵肃,也可以使用其他字段如主鍵進(jìn)行分表。以下是我想到的一些分表規(guī)則:
根據(jù)主鍵進(jìn)行分配
這種方式能夠?qū)崿F(xiàn)最平均的分配方法袭祟,每生成一條新數(shù)據(jù)验残,會(huì)依次保存到下一個(gè)數(shù)據(jù)表中。
根據(jù)用戶ID進(jìn)行分配
這種方式能夠確保同一個(gè)用戶的所有數(shù)據(jù)保存在同一個(gè)數(shù)據(jù)表中巾乳。如果經(jīng)常按用戶id查詢數(shù)據(jù)您没,這是比較經(jīng)濟(jì)的一種做法。
根據(jù)某一個(gè)外鍵的值進(jìn)行分配
前面的例子采用的就是這種方法胆绊,因?yàn)檫@個(gè)數(shù)據(jù)可能會(huì)經(jīng)常根據(jù)這個(gè)外鍵進(jìn)行查詢氨鹏。
根據(jù)時(shí)間進(jìn)行分配
適用于一些經(jīng)常按時(shí)間段進(jìn)行查詢的數(shù)據(jù),將一個(gè)時(shí)間段內(nèi)的數(shù)據(jù)保存在同一個(gè)數(shù)據(jù)表中压状。比如訂單系統(tǒng)仆抵,缺省查詢一個(gè)月之內(nèi)的數(shù)據(jù)。