您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何運用LIST和RANGE與HASH分區(qū)解決熱點數(shù)據(jù)的分散,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
熱點數(shù)據(jù)通俗的講是指被高頻使用到的數(shù)據(jù),比如某些熱點事件,由于網(wǎng)絡的發(fā)酵效應,短時間能夠達到幾十萬甚至上百萬的并發(fā)量,針對這類場景,我們需要分析系統(tǒng)瓶頸所在,以及應對的技術(shù)實現(xiàn)
大并發(fā)架構(gòu)演進
1、圖1和圖2的區(qū)別是中間會有一層web緩存服務器,該服務它可以由nginx+lua+redis進行設計完成,緩存層的熱點數(shù)據(jù)分散,將會在后續(xù)的‘高并發(fā)度’章節(jié)做介紹。
2、熱點數(shù)據(jù)肯定能在web層的緩存服務器被攔截住,防止把大量的請求打到應用服務器,但是對于非熱點的數(shù)據(jù)穿透緩存后會請求至DB,這部分數(shù)據(jù)每秒幾千的QPS對DB造成的壓力也是非常大的,這個時候我們需要一定的方案,保證請求的時效性,就是如何降低DB層面的IO次數(shù)
場景分類
熱點數(shù)據(jù)并發(fā)分為讀和寫兩種場景,日常高并發(fā)遇到的大多數(shù)都是讀場景,無論是采用何種的架構(gòu)設計,都需要在緩存層和DB層面做熱點數(shù)據(jù)的分散,本章著重介紹后者
原理分析
大家都知道對熱點數(shù)據(jù)分散后,系統(tǒng)的性能會有顯著提升,是什么原理導致的,接下來我們探討一下db存儲的一些關(guān)鍵知識,上面兩個是大家經(jīng)常用到的兩種mysql存儲引擎,尤其是后者,基本上筆者在工作中遇到的絕大多數(shù)的表都定義成了innodb引擎,兩者的差異在哪里?使用場景的區(qū)別在什么地方?
1、讀數(shù)據(jù)
myisam:與innodb一樣都采用BTREE實現(xiàn),myisam是非聚集索引,索引文件和數(shù)據(jù)文件分離,它對讀的效率非常好,為什么呢,是因為它的存儲結(jié)構(gòu)決定的,數(shù)據(jù)順序存儲,樹的葉子節(jié)點指向的是文件物理地址,所以查詢起來效率較高
innodb:它是通過聚集索引實現(xiàn),按照主鍵聚集,所以innodb引擎必須要擁有一個唯一標識這列數(shù)據(jù)的標識,對于聚集索引它的葉子節(jié)點存放的是數(shù)據(jù),對于innodb的輔助索引它的葉子節(jié)點是主鍵的值,所以查詢的時候增加了二次查找,為了避免這種情況,可以直接使用聚集索引去查,但是現(xiàn)實情況是大多數(shù)的業(yè)務場景我們依然需要借助于輔助索引
2、寫數(shù)據(jù)
myisam:不支持事物,且寫優(yōu)先級高于讀優(yōu)先級,多線程讀可并發(fā),讀和插入通過優(yōu)化參數(shù)可并發(fā),讀和更新不可并發(fā),鎖的級別是表級別鎖
innodb:支持事物,可以實現(xiàn)讀寫并發(fā),行級別鎖,寫性能優(yōu)于myisam的引擎
3、數(shù)據(jù)頁
是innodb數(shù)據(jù)存儲的一個基本單位,可以通過優(yōu)化innodb_page_size參數(shù)進行修改,默認是16K,根據(jù)上面提到的聚集索引的原理,索引的大小、單條數(shù)據(jù)的大小決定了該數(shù)據(jù)頁所能包含的記錄條數(shù),包含的數(shù)據(jù)記錄越多,需要做翻頁的幾率就越小,進行IO的次數(shù)相對就會減小
垂直分表
垂直分表是對列做拆解,可以根據(jù)業(yè)務功能或者冷熱去拆解,比如對用戶表根據(jù)使用冷熱場景進行拆解的示意圖如下:
垂直分表的意義是在于將熱表進一步拆分,降低數(shù)據(jù)表的因為單行長度過大,導致的多頁查詢,引起的IO過多問題
水平分表
水平分表是對行做拆解,拆完以后單張表的數(shù)據(jù)量會更小
比如對5000萬的數(shù)據(jù)量,做水平分表,原來單表5000萬的數(shù)據(jù)記錄,拆分為10張表以后,每張表則為500萬記錄
每個索引頁的大小固定默認16K,所以在單頁大小固定的情況下,單表記錄越多,索引頁的頁數(shù)越多,查詢期間分頁的概率和頻次就會增加
水平分表就是解決這個問題,分表的實現(xiàn)方式:分區(qū)、分表/分庫,具體參考下面的介紹。
最佳實踐
1、冷熱數(shù)據(jù)分離
以文章內(nèi)容系統(tǒng)為例:標題、作者、分類、創(chuàng)建時間、點贊數(shù)、回復數(shù)、最近回復時間
1.1、冷數(shù)據(jù):可以理解成偏靜態(tài)的數(shù)據(jù),會頻繁的被讀取,但是幾乎或者很少被改變,這類數(shù)據(jù)對讀的性能要求較高,數(shù)據(jù)存儲可以使用myisam引擎
1.2、熱數(shù)據(jù):數(shù)據(jù)內(nèi)容被頻繁改變,這類數(shù)據(jù)對并發(fā)讀寫要求較高,我們可以使用innodb引擎存儲
根據(jù)具體的使用場景使用不同的存儲引擎,以達到性能的相對最優(yōu),將文章內(nèi)容系統(tǒng)的表結(jié)構(gòu)進行冷熱拆分,拆分后的表結(jié)構(gòu)如下:
1.3、拆分前后性能比對
插入100000條數(shù)據(jù),對拆分前后的文章表做查詢,性能比對的趨勢如下,同樣都是模擬50個并發(fā),一共2500次請求,每個線程50個請求,采用ID隨機,這樣更貼近真實的查詢場景,很明顯拆分后的效果更勝一籌:
拆分后單表測試:
mysqlslap -h227.0.0.1 -uroot -P3306 -p --concurrency=50 --iterations=1 --engine=myisam --number-of-queries=2500 --query='select * from cms_blog_static where id=RAND()*1000000' --create-schema=test
未拆分測試:
mysqlslap -h227.0.0.1 -uroot -P3306 -p --concurrency=50 --iterations=1 --engine=innodb --number-of-queries=2500 --query='select * from cms_blog where id=RAND()*1000000' --create-schema=test
2、減少單行數(shù)據(jù)大小
對于拆分以后的數(shù)據(jù)表,我們能否進一步降低單行數(shù)據(jù)的大小呢,總結(jié)起來常用的方法如下:
2.1、設置合理的字段長度
大家都知道不同的字段類型占用的存儲空間不同,如下圖:
類型 | 長度(字節(jié)) | 定長/非定長 | |
TINYINT | 1 | 定長 | |
SMALLINT | 2 | 定長 | |
MEDIUMINT | 3 | 定長 | |
INT | 4 | 定長 | |
BIGINT | 8 | 定長 | |
FLOAT(m) | 4字節(jié)(m<=24)、8字節(jié)(m>=24 and m<=53) | 非定長 | |
FLOAT | 4 | 定長 | |
DOUBLE | 8 | 定長 | |
DOUBLE PRECISION | 8 | 定長 | |
DECIMAL(m,d) | m字節(jié)(m>d)、d+2字節(jié)(m<d) | 非定長 | |
NUMBER(m,d) | m字節(jié)(m>d)、d+2字節(jié)(m<d) | 非定長 | |
DATE | 3 | 定長 | |
DATETIME | 8 | 定長 | |
TIMESTAMP | 4 | 定長 | |
TIME | 3 | 定長 | |
YEAR | 1 | 定長 | |
CHAR(m) | m | 非定長 | |
VARCHAR(m) | l字節(jié),l就是實際存儲字節(jié)(l<=m) | 非定長 | |
BLOB, TEXT | l+2字節(jié),l就是實際存儲字節(jié) | 非定長 | |
LONGBLOB, LONGTEXT | l+4字節(jié),l就是實際存儲字節(jié) | 非定長 |
我們在實際使用中,需要根據(jù)實際的需要選擇合理的類型,能有效的減小單行數(shù)據(jù)的大小,比如,user_status,一般我們定義成tinyint(1)即可,沒必要定義成int,白白多占用3個字節(jié)
2.2、設置合理的索引長度
2.2.1、對于需要建索引的字段,如果字段占用的空間越大,對于索引來說,建立索引的長度就越長,索引頁大小不變的情況下,數(shù)據(jù)條數(shù)就越少,查詢需要做IO的次數(shù)就越頻繁
2.2.2、對于某些索引字段,如果我們可以通過前綴字段能達到很好的區(qū)分度,則可以控制創(chuàng)建索引的長度,目的是索引頁的含的數(shù)據(jù)行數(shù)更多,減少IO,方式如下:
//如下,我們根據(jù)字段1和字段2,指定的組合索引的長度 alter table table_name add index index_name (field1(length2),field2(length3))
2.2.3、索引的選擇性
索引本身是由開銷的,首先是存儲資源,然后插入和更新帶來的對B+Tree樹的維護,數(shù)據(jù)更新帶來的性能下降,所以對于我們的原則是:索引該不該建,以及用什么字段建
數(shù)據(jù)量少-則不建,區(qū)分度或者選擇性不高-則不建,數(shù)據(jù)量少大家很容易理解,小于1W條數(shù)據(jù)全表掃描也能接收,選擇性或者區(qū)分度是指,數(shù)據(jù)的分散程度,比如某個用戶表,有一個性別字段,數(shù)據(jù)量越大它的索引選擇性越差,計算公式如下:
//返回值范圍(0,1],該值越大,索引選擇性越高 select distinct(col)/count(*) from table_name
同理,對于需要控制索引長度的字段,計算選擇性如下:
select distinct(left(col,n))/count(*) from table_name
2.2.4、性能比較
通過對100w的數(shù)據(jù),對數(shù)據(jù)行大小做優(yōu)化,前后性能比對結(jié)果如下:
優(yōu)化前后的表結(jié)構(gòu)定義如下:
--優(yōu)化前 CREATE TABLE `cms_blog` ( `id` bigint(20) NOT NULL auto_increment, `title` varchar(60) NOT NULL, `creator` varchar(20) NOT NULL, `blog_type` tinyint(1) not NULL, `reply_praise` int(10) UNSIGNED, `reply_count` int(10) UNSIGNED, `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=innodb DEFAULT CHARSET=utf8; --創(chuàng)建索引 alter table cms_blog add index idx_reply_count (reply_count); --優(yōu)化后reply_praise和reply_count CREATE TABLE `cms_blog_v2` ( `id` bigint(20) NOT NULL auto_increment, `title` varchar(60) NOT NULL, `creator` varchar(20) NOT NULL, `blog_type` tinyint(1) not NULL, `reply_praise` MEDIUMINT(10) UNSIGNED, `reply_count` MEDIUMINT(10) UNSIGNED, `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=innodb DEFAULT CHARSET=utf8; --創(chuàng)建索引 alter table cms_blog_v2 add index idx_reply_count (reply_count);
壓測腳本如下:
--壓測腳本,舊表 mysqlslap -h227.0.0.1 -uroot -P3306 -p --concurrency=50 --iterations=1 --engine=innodb --number-of-queries=2500 --query='select * from cms_blog where reply_count>999990' --create-schema=test --壓測腳本,新表 mysqlslap -h227.0.0.1 -uroot -P3306 -p --concurrency=50 --iterations=1 --engine=innodb --number-of-queries=2500 --query='select * from cms_blog_v2 where reply_count>999990' --create-schema=test
性能表現(xiàn),舊表和新表壓測表現(xiàn)如下:
新表優(yōu)化后性能明顯有提升:
2.3、主鍵的選擇
盡量使用保持單調(diào)性的自增主鍵,避免使用uuid、hash方式、業(yè)務自定義主鍵,減少索引重建對性能的影響
3、分散數(shù)據(jù)頁查詢
3.1、數(shù)據(jù)分區(qū)
數(shù)據(jù)分區(qū)可以有效的提升查詢的性能,充分利用不同分區(qū)所關(guān)聯(lián)的IO存儲,在邏輯上是屬于同一張表,物理上可以分散到不同的磁盤存儲,缺點是跨分區(qū)查詢的性能稍差,所以互聯(lián)網(wǎng)公司在實際當中很少用到數(shù)據(jù)分區(qū),一般建議物理分表的方式實現(xiàn)
CREATE TABLE table_name ( id INT AUTO_INCREMENT, customer_surname VARCHAR(30), store_id INT, salesperson_id INT, order_date DATE, note VARCHAR(500), INDEX idx (id) ) ENGINE = INNODB PARTITION BY LIST(store_id) ( PARTITION p1 VALUES IN (1, 3, 4, 17) INDEX DIRECTORY = '/var/path3' DATA DIRECTORY = '/var/path2';
3.1.1、分區(qū)方式
3.1.1.1、RANGE partitioning
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT NOT NULL, store_id INT NOT NULL ) PARTITION BY RANGE (store_id) ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11), PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21) );
3.1.1.2、LIST Partitioning
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY LIST(store_id) ( PARTITION pNorth VALUES IN (3,5,6,9,17), PARTITION pEast VALUES IN (1,2,10,11,19,20), PARTITION pWest VALUES IN (4,12,13,14,18), PARTITION pCentral VALUES IN (7,8,15,16) );
3.1.1.3、COLUMNS Partitioning
--range columns CREATE TABLE rc1 ( a INT, b INT ) PARTITION BY RANGE COLUMNS(a, b) ( PARTITION p0 VALUES LESS THAN (5, 12), PARTITION p3 VALUES LESS THAN (MAXVALUE, MAXVALUE) ); --list columns CREATE TABLE customers_1 ( first_name VARCHAR(25), last_name VARCHAR(25), street_1 VARCHAR(30), street_2 VARCHAR(30), city VARCHAR(15), renewal DATE ) PARTITION BY LIST COLUMNS(city) ( PARTITION pRegion_1 VALUES IN('Oskarshamn', 'H?gsby', 'M?nster?s'), PARTITION pRegion_2 VALUES IN('Vimmerby', 'Hultsfred', 'V?stervik'), PARTITION pRegion_3 VALUES IN('N?ssj?', 'Eksj?', 'Vetlanda'), PARTITION pRegion_4 VALUES IN('Uppvidinge', 'Alvesta', 'V?xjo') );
3.1.1.4、HASH Partitioning
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY HASH(store_id) PARTITIONS 4;
3.1.1.5、KEY Partitioning
CREATE TABLE tk ( col1 INT NOT NULL, col2 CHAR(5), col3 DATE ) PARTITION BY LINEAR KEY (col1) PARTITIONS 3;
3.2、數(shù)據(jù)分表/分庫
數(shù)據(jù)分表解決的問題,提升單表的并發(fā)能力,文件分布在不同的表文件,對IO性能進一步提升,另外對讀寫鎖影響的數(shù)據(jù)量變少,插入數(shù)據(jù)需要做索引重建的數(shù)據(jù)減少,insert或update性能會更好
3.2.1、分表和分庫方式
3.2.1.1:哈希取模方式,hash(關(guān)鍵字)%N
3.2.1.2:按照時間,如按照年或者月分表
3.2.1.3、按照業(yè)務,以訂單業(yè)務為例,平臺訂單、三方訂單
3.2.2、分庫分表中間件
整體來說分為在客戶端實現(xiàn),和代理端實現(xiàn),比如:cobar、sharding-jdbc、mycat等,具體使用可以自行檢索
上述內(nèi)容就是如何運用LIST和RANGE與HASH分區(qū)解決熱點數(shù)據(jù)的分散,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。