您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何運用LIST和RANGE與HASH分區解決熱點數據的分散,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
熱點數據通俗的講是指被高頻使用到的數據,比如某些熱點事件,由于網絡的發酵效應,短時間能夠達到幾十萬甚至上百萬的并發量,針對這類場景,我們需要分析系統瓶頸所在,以及應對的技術實現
大并發架構演進
1、圖1和圖2的區別是中間會有一層web緩存服務器,該服務它可以由nginx+lua+redis進行設計完成,緩存層的熱點數據分散,將會在后續的‘高并發度’章節做介紹。
2、熱點數據肯定能在web層的緩存服務器被攔截住,防止把大量的請求打到應用服務器,但是對于非熱點的數據穿透緩存后會請求至DB,這部分數據每秒幾千的QPS對DB造成的壓力也是非常大的,這個時候我們需要一定的方案,保證請求的時效性,就是如何降低DB層面的IO次數
場景分類
熱點數據并發分為讀和寫兩種場景,日常高并發遇到的大多數都是讀場景,無論是采用何種的架構設計,都需要在緩存層和DB層面做熱點數據的分散,本章著重介紹后者
原理分析
大家都知道對熱點數據分散后,系統的性能會有顯著提升,是什么原理導致的,接下來我們探討一下db存儲的一些關鍵知識,上面兩個是大家經常用到的兩種mysql存儲引擎,尤其是后者,基本上筆者在工作中遇到的絕大多數的表都定義成了innodb引擎,兩者的差異在哪里?使用場景的區別在什么地方?
1、讀數據
myisam:與innodb一樣都采用BTREE實現,myisam是非聚集索引,索引文件和數據文件分離,它對讀的效率非常好,為什么呢,是因為它的存儲結構決定的,數據順序存儲,樹的葉子節點指向的是文件物理地址,所以查詢起來效率較高
innodb:它是通過聚集索引實現,按照主鍵聚集,所以innodb引擎必須要擁有一個唯一標識這列數據的標識,對于聚集索引它的葉子節點存放的是數據,對于innodb的輔助索引它的葉子節點是主鍵的值,所以查詢的時候增加了二次查找,為了避免這種情況,可以直接使用聚集索引去查,但是現實情況是大多數的業務場景我們依然需要借助于輔助索引
2、寫數據
myisam:不支持事物,且寫優先級高于讀優先級,多線程讀可并發,讀和插入通過優化參數可并發,讀和更新不可并發,鎖的級別是表級別鎖
innodb:支持事物,可以實現讀寫并發,行級別鎖,寫性能優于myisam的引擎
3、數據頁
是innodb數據存儲的一個基本單位,可以通過優化innodb_page_size參數進行修改,默認是16K,根據上面提到的聚集索引的原理,索引的大小、單條數據的大小決定了該數據頁所能包含的記錄條數,包含的數據記錄越多,需要做翻頁的幾率就越小,進行IO的次數相對就會減小
垂直分表
垂直分表是對列做拆解,可以根據業務功能或者冷熱去拆解,比如對用戶表根據使用冷熱場景進行拆解的示意圖如下:
垂直分表的意義是在于將熱表進一步拆分,降低數據表的因為單行長度過大,導致的多頁查詢,引起的IO過多問題
水平分表
水平分表是對行做拆解,拆完以后單張表的數據量會更小
比如對5000萬的數據量,做水平分表,原來單表5000萬的數據記錄,拆分為10張表以后,每張表則為500萬記錄
每個索引頁的大小固定默認16K,所以在單頁大小固定的情況下,單表記錄越多,索引頁的頁數越多,查詢期間分頁的概率和頻次就會增加
水平分表就是解決這個問題,分表的實現方式:分區、分表/分庫,具體參考下面的介紹。
最佳實踐
1、冷熱數據分離
以文章內容系統為例:標題、作者、分類、創建時間、點贊數、回復數、最近回復時間
1.1、冷數據:可以理解成偏靜態的數據,會頻繁的被讀取,但是幾乎或者很少被改變,這類數據對讀的性能要求較高,數據存儲可以使用myisam引擎
1.2、熱數據:數據內容被頻繁改變,這類數據對并發讀寫要求較高,我們可以使用innodb引擎存儲
根據具體的使用場景使用不同的存儲引擎,以達到性能的相對最優,將文章內容系統的表結構進行冷熱拆分,拆分后的表結構如下:
1.3、拆分前后性能比對
插入100000條數據,對拆分前后的文章表做查詢,性能比對的趨勢如下,同樣都是模擬50個并發,一共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、減少單行數據大小
對于拆分以后的數據表,我們能否進一步降低單行數據的大小呢,總結起來常用的方法如下:
2.1、設置合理的字段長度
大家都知道不同的字段類型占用的存儲空間不同,如下圖:
類型 | 長度(字節) | 定長/非定長 | |
TINYINT | 1 | 定長 | |
SMALLINT | 2 | 定長 | |
MEDIUMINT | 3 | 定長 | |
INT | 4 | 定長 | |
BIGINT | 8 | 定長 | |
FLOAT(m) | 4字節(m<=24)、8字節(m>=24 and m<=53) | 非定長 | |
FLOAT | 4 | 定長 | |
DOUBLE | 8 | 定長 | |
DOUBLE PRECISION | 8 | 定長 | |
DECIMAL(m,d) | m字節(m>d)、d+2字節(m<d) | 非定長 | |
NUMBER(m,d) | m字節(m>d)、d+2字節(m<d) | 非定長 | |
DATE | 3 | 定長 | |
DATETIME | 8 | 定長 | |
TIMESTAMP | 4 | 定長 | |
TIME | 3 | 定長 | |
YEAR | 1 | 定長 | |
CHAR(m) | m | 非定長 | |
VARCHAR(m) | l字節,l就是實際存儲字節(l<=m) | 非定長 | |
BLOB, TEXT | l+2字節,l就是實際存儲字節 | 非定長 | |
LONGBLOB, LONGTEXT | l+4字節,l就是實際存儲字節 | 非定長 |
我們在實際使用中,需要根據實際的需要選擇合理的類型,能有效的減小單行數據的大小,比如,user_status,一般我們定義成tinyint(1)即可,沒必要定義成int,白白多占用3個字節
2.2、設置合理的索引長度
2.2.1、對于需要建索引的字段,如果字段占用的空間越大,對于索引來說,建立索引的長度就越長,索引頁大小不變的情況下,數據條數就越少,查詢需要做IO的次數就越頻繁
2.2.2、對于某些索引字段,如果我們可以通過前綴字段能達到很好的區分度,則可以控制創建索引的長度,目的是索引頁的含的數據行數更多,減少IO,方式如下:
//如下,我們根據字段1和字段2,指定的組合索引的長度 alter table table_name add index index_name (field1(length2),field2(length3))
2.2.3、索引的選擇性
索引本身是由開銷的,首先是存儲資源,然后插入和更新帶來的對B+Tree樹的維護,數據更新帶來的性能下降,所以對于我們的原則是:索引該不該建,以及用什么字段建
數據量少-則不建,區分度或者選擇性不高-則不建,數據量少大家很容易理解,小于1W條數據全表掃描也能接收,選擇性或者區分度是指,數據的分散程度,比如某個用戶表,有一個性別字段,數據量越大它的索引選擇性越差,計算公式如下:
//返回值范圍(0,1],該值越大,索引選擇性越高 select distinct(col)/count(*) from table_name
同理,對于需要控制索引長度的字段,計算選擇性如下:
select distinct(left(col,n))/count(*) from table_name
2.2.4、性能比較
通過對100w的數據,對數據行大小做優化,前后性能比對結果如下:
優化前后的表結構定義如下:
--優化前 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; --創建索引 alter table cms_blog add index idx_reply_count (reply_count); --優化后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; --創建索引 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
性能表現,舊表和新表壓測表現如下:
新表優化后性能明顯有提升:
2.3、主鍵的選擇
盡量使用保持單調性的自增主鍵,避免使用uuid、hash方式、業務自定義主鍵,減少索引重建對性能的影響
3、分散數據頁查詢
3.1、數據分區
數據分區可以有效的提升查詢的性能,充分利用不同分區所關聯的IO存儲,在邏輯上是屬于同一張表,物理上可以分散到不同的磁盤存儲,缺點是跨分區查詢的性能稍差,所以互聯網公司在實際當中很少用到數據分區,一般建議物理分表的方式實現
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、分區方式
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、數據分表/分庫
數據分表解決的問題,提升單表的并發能力,文件分布在不同的表文件,對IO性能進一步提升,另外對讀寫鎖影響的數據量變少,插入數據需要做索引重建的數據減少,insert或update性能會更好
3.2.1、分表和分庫方式
3.2.1.1:哈希取模方式,hash(關鍵字)%N
3.2.1.2:按照時間,如按照年或者月分表
3.2.1.3、按照業務,以訂單業務為例,平臺訂單、三方訂單
3.2.2、分庫分表中間件
整體來說分為在客戶端實現,和代理端實現,比如:cobar、sharding-jdbc、mycat等,具體使用可以自行檢索
上述內容就是如何運用LIST和RANGE與HASH分區解決熱點數據的分散,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。