MySQL分庫分表詳情
一、業(yè)務(wù)場景介紹
假設(shè)目前有一個電商系統(tǒng)使用的是MySQL
,要設(shè)計大數(shù)據(jù)量存儲、高并發(fā)、高性能可擴展的方案,數(shù)據(jù)庫中有用戶表。用戶會非常多,并且要實現(xiàn)高擴展性,你會怎么去設(shè)計? OK咱們先看傳統(tǒng)的分庫分表方式
當然還有些小伙伴知道按照省份/地區(qū)或一定的業(yè)務(wù)關(guān)系進行數(shù)據(jù)庫拆分
OK,問題來了,如何保證合理的讓數(shù)據(jù)存儲在不同的庫不同的表里呢?讓庫減少并發(fā)壓力?應(yīng)該怎么去制定分庫分表的規(guī)則?不用急,這不就來了
二、水平分庫分表方法
1.RANGE
第一種方法們可以指定一個數(shù)據(jù)范圍來進行分表,例如從1~1000000,1000001-2000000,使用一百萬一張表的方式,如下圖所示
在這里插入圖片描述 當然這種方法需要維護表的ID,特別是分布式環(huán)境下,這種分布式ID,在不使用第三方分表工具的情況下,建議使用Redis
,Redis
的incr
操作可以輕松的維護分布式的表ID。
RANGE方法優(yōu)點: 擴容簡單,提前建好庫、表就好
RANGE方法缺點: 大部分讀和寫都訪會問新的數(shù)據(jù),有IO瓶頸,這樣子造成新庫壓力過大,不建議采用。
2.HASH取模
針對上述RANGE
方式分表有IO瓶頸的問題,咱們可以采用根據(jù)用戶ID HASG
取模的方式進行分庫分表,如圖所示:
這樣就可以將數(shù)據(jù)分散在不同的庫、表中,避免了IO瓶頸的問題。
HASH取模方法優(yōu)點: 能保證數(shù)據(jù)較均勻的分散落在不同的庫、表中,減輕了數(shù)據(jù)庫壓力
HASH取模方法缺點: 擴容麻煩、遷移數(shù)據(jù)時每次都需要重新計算hash值分配到不同的庫和表
3.一致性HASH
通過HASH取模也不是最完美的辦法,那什么才是呢?
使用一致性HASH算法能完美的解決問題
普通HASH算法:
普通哈希算法將任意長度的二進制值映射為較短的固定長度的二進制值,這個小的二進制值稱為哈希值。哈希值是一段數(shù)據(jù)唯一且極其緊湊的數(shù)值表示形式。
普通的hash
算法在分布式應(yīng)用中的不足:在分布式的存儲系統(tǒng)中,要將數(shù)據(jù)存儲到具體的節(jié)點上,如果我們采用普通的hash
算法進行路由,將數(shù)據(jù)映射到具體的節(jié)點上,如key%n
,key
是數(shù)據(jù)的key
,n是機器節(jié)點數(shù),如果有一個機器加入或退出集群,則所有的數(shù)據(jù)映射都無效了,如果是持久化存儲則要做數(shù)據(jù)遷移,如果是分布式緩存,則其他緩存就失效了。
一致性HASH算法: 按照常用的hash
算法來將對應(yīng)的key哈希到一個具有2^32次方個節(jié)點的空間中,即0~ (2^32)-1的數(shù)字空間中?,F(xiàn)在我們可以將這些數(shù)字頭尾相連,想象成一個閉合的環(huán)形,如下圖所示。
這個圓環(huán)首尾相連,那么假設(shè)現(xiàn)在有三個數(shù)據(jù)庫服務(wù)器節(jié)點node1
、node2
、node3
三個節(jié)點,每個節(jié)點負責自己這部分的用戶數(shù)據(jù)存儲,假設(shè)有用戶user1、user2、user3,我們可以對服務(wù)器節(jié)點進行HASH運算,假設(shè)HASH計算后,user1落在node1
上,user2
落在node2
上,user3落在user3上
OK,現(xiàn)在咱們假設(shè)node3節(jié)點失效了
user3
將會落到node1上,而之前的node1和node2數(shù)據(jù)不會改變,再假設(shè)新增了節(jié)點node4
你會發(fā)現(xiàn)user3會落到node4上,你會發(fā)現(xiàn),通過對節(jié)點的添加和刪除的分析,一致性哈希算法在保持了單調(diào)性的同時,還是數(shù)據(jù)的遷移達到了最小,這樣的算法對分布式集群來說是非常合適的,避免了大量數(shù)據(jù)遷移,減小了服務(wù)器的的壓力。
當然還有一個問題還需要解決,那就是平衡性。從圖我們可以看出,當服務(wù)器節(jié)點比較少的時候,會出現(xiàn)一個問題,就是此時必然造成大量數(shù)據(jù)集中到一個節(jié)點上面,極少數(shù)數(shù)據(jù)集中到另外的節(jié)點上面。
為了解決這種數(shù)據(jù)傾斜問題,一致性哈希算法引入了虛擬節(jié)點機制,即對每一個服務(wù)節(jié)點計算多個哈希,每個計算結(jié)果位置都放置一個節(jié)點,稱為虛擬節(jié)點。具體做法可以先確定每個物理節(jié)點關(guān)聯(lián)的虛擬節(jié)點數(shù)量,然后在ip或者主機名后面增加編號。例如上面的情況,可以為每臺服務(wù)器計算三個虛擬節(jié)點,于是可以分別計算 “node 1-1
”、“node 1-2
”、“node 1-3
”、“node 2-1
”、“node 2-2
”、“node 2-3
”、“node 3-1
”、“node 3-2
”、“node 3-3
”的哈希值,這樣形成九個虛擬節(jié)點
例如user1定位到node 1-1
、node 1-2
、node 1-3
上其實都是定位到node1
這個節(jié)點上,這樣能夠解決服務(wù)節(jié)點少時數(shù)據(jù)傾斜的問題,當然這個虛擬節(jié)點的個數(shù)不是說固定三個或者至多、至少三個,這里只是一個例子,具體虛擬節(jié)點的多少,需要根據(jù)實際的業(yè)務(wù)情況而定。
一致性HASH方法優(yōu)點: 通過虛擬節(jié)點方式能保證數(shù)據(jù)較均勻的分散落在不同的庫、表中,并且新增、刪除節(jié)點不影響其他節(jié)點的數(shù)據(jù),高可用、容災(zāi)性強。
一致性取模方法缺點: 嗯,比起以上兩種,可以認為沒有。
三、單元測試
OK,不廢話,接下來上單元測試,假設(shè)有三個節(jié)點,每個節(jié)點有三個虛擬節(jié)點的情況
package com.hyh.core.test; import com.hyh.utils.common.StringUtils; import org.junit.Test; import java.util.LinkedList; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; /** * 一致性HASH TEST * * @Author heyuhua * @create 2021/1/31 19:50 */ public class ConsistentHashTest { //待添加入Hash環(huán)的服務(wù)器列表 private static String[] servers = {"192.168.5.1", "192.168.5.2", "192.168.5.3"}; //真實結(jié)點列表,考慮到服務(wù)器上線、下線的場景,即添加、刪除的場景會比較頻繁,這里使用LinkedList會更好 private static List<String> realNodes = new LinkedList<>(); //虛擬節(jié)點,key表示虛擬節(jié)點的hash值,value表示虛擬節(jié)點的名稱 private static SortedMap<Integer, String> virtualNodes = new TreeMap<>(); //一個真實結(jié)點對應(yīng)3個虛擬節(jié)點 private static final int VIRTUAL_NODES = 3; /** * 測試有虛擬節(jié)點的一致性HASH */ @Test public void testConsistentHash() { initNodes(); String[] users = {"user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9"}; for (int i = 0; i < users.length; i++) System.out.println("[" + users[i] + "]的hash值為" + getHash(users[i]) + ", 被路由到結(jié)點[" + getServer(users[i]) + "]"); } /** * 先把原始的服務(wù)器添加到真實結(jié)點列表中 */ public void initNodes() { for (int i = 0; i < servers.length; i++) realNodes.add(servers[i]); for (String str : realNodes) { for (int i = 0; i < VIRTUAL_NODES; i++) { String virtualNodeName = str + "-虛擬節(jié)點" + String.valueOf(i); int hash = getHash(virtualNodeName); System.out.println("虛擬節(jié)點[" + virtualNodeName + "]被添加, hash值為" + hash); virtualNodes.put(hash, virtualNodeName); } } System.out.println(); } //使用FNV1_32_HASH算法計算服務(wù)器的Hash值,這里不使用重寫hashCode的方法,最終效果沒區(qū)別 private static int getHash(String str) { final int p = 16777619; int hash = (int) 2166136261L; for (int i = 0; i < str.length(); i++) hash = (hash ^ str.charAt(i)) * p; hash += hash << 13; hash ^= hash >> 7; hash += hash << 3; hash ^= hash >> 17; hash += hash << 5; // 如果算出來的值為負數(shù)則取其絕對值 if (hash < 0) hash = Math.abs(hash); return hash; } //得到應(yīng)當路由到的結(jié)點 private static String getServer(String key) { //得到該key的hash值 int hash = getHash(key); // 得到大于該Hash值的所有Map SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash); String virtualNode; if (subMap.isEmpty()) { //如果沒有比該key的hash值大的,則從第一個node開始 Integer i = virtualNodes.firstKey(); //返回對應(yīng)的服務(wù)器 virtualNode = virtualNodes.get(i); } else { //第一個Key就是順時針過去離node最近的那個結(jié)點 Integer i = subMap.firstKey(); //返回對應(yīng)的服務(wù)器 virtualNode = subMap.get(i); } //virtualNode虛擬節(jié)點名稱要截取一下 if (StringUtils.isNotBlank(virtualNode)) { return virtualNode.substring(0, virtualNode.indexOf("-")); } return null; } }
這里模擬9個用戶對象hash后被路由的情況,看下結(jié)果
總結(jié):
分庫分表在分布式微服務(wù)架構(gòu)環(huán)境下建議強烈使用一致性HASH
算法來做,當然分布式環(huán)境下也會產(chǎn)生業(yè)務(wù)數(shù)據(jù)數(shù)據(jù)一致性、分布式事務(wù)問題,下期咱們再來探討數(shù)據(jù)一致性、分布式事務(wù)的解決方案
到此這篇關(guān)于MySQL
分庫分表詳情的文章就介紹到這了,更多相關(guān)MySQL
分庫分表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
什么是分表和分區(qū) MySql數(shù)據(jù)庫分區(qū)和分表方法
這篇文章主要為大家詳細介紹了MySql數(shù)據(jù)庫分區(qū)和分表方法,告訴大家什么是分表和分區(qū),mysql分表和分區(qū)有什么聯(lián)系,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02windows2008 64位系統(tǒng)下MySQL 5.7綠色版的安裝教程
這篇文章主要給大家分享了在windows2008 64位系統(tǒng)下MySQL 5.7綠色版的安裝教程,文中將安裝步驟介紹的非常詳細,相信會對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面來一起看看吧。2017-05-05MySQL 5.6下table_open_cache參數(shù)優(yōu)化合理配置詳解
這篇文章主要介紹了MySQL 5.6下table_open_cache參數(shù)合理配置詳解,需要的朋友可以參考下2018-03-03mysql?sum(if())和count(if())的用法說明
這篇文章主要介紹了mysql?sum(if())和count(if())的用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01虛擬機linux端mysql數(shù)據(jù)庫無法遠程訪問的解決辦法
最近在項目搭建過程中遇到一問題,有關(guān)虛擬機linux端mysql數(shù)據(jù)庫無法遠程訪問,通過查閱相關(guān)數(shù)據(jù)庫資料問題解決,下面把具體的解決辦法分享給大家,有需要的朋友可以參考下2015-08-08MySQL查詢數(shù)據(jù)庫所有表名以及表結(jié)構(gòu)其注釋(小白專用)
查詢數(shù)據(jù)庫所有表的表名、備注,其實也是比較常見的操作,這篇文章主要給大家介紹了關(guān)于MySQL查詢數(shù)據(jù)庫所有表名以及表結(jié)構(gòu)其注釋的相關(guān)資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2024-08-08