前面一篇文章說到,當遇到數據存儲層的高併發的時候,會首先想到讀寫分離,同時高併發有可能意味着數據量大,大量的查詢或更新操做集中在一張大表中,鎖的頻繁使用,會致使訪問速度的降低,並且數據量可能超過了單機的容量,因此咱們想到了分庫分表。
可是在分庫分表以前,我仍是想多說幾句,除非使用那些透明的分庫分表方案,不然分庫分表是一個大工程。 因此在分庫分表前,我建議儘量先升級數據庫的硬件,SSD/NVMe硬盤 + 大容量內存基本能夠知足一個小型互聯網公司大部分的應用, 對於中型互聯網公司須要使用到分庫分表的場景也不會太多,用戶,訂單,交易可能會用到的。可是即便是這些場景,如今也有了不一樣的解決方案,在之前,大部分的應用仍是基於web的,若是一些操做須要用到用戶校驗,就要進行登陸,對數據庫操做比較頻繁,隨着移動互聯網的興起以及Nosql的出現,現在大部分的應用都遷移到了App端,基本都是經過accessToken+NoSql來進行用戶校驗的,你們能夠想一想你有多久沒有進行過登陸操做了。對於訂單交易,經過冷熱數據分離,也能夠解決大部分場景。
數據庫切分分爲水平切分,垂直切分, 垂直切分通常在拆分系統的時候使用,這裏再也不贅述,下面主要說數據的水平切分。
水平切分方式
1. 只分表:在一個數據庫下面,分紅10張表,表名 user_0 ,user_1, user_2.....
數據集中在一臺服務器上,當單機性能瓶頸的時候,後續擴展困難。
2. 只分庫,分紅10個庫,每一個庫一張表, 表名都是同樣的。 db0-user db1-user db2-user......
出現跨庫,沒有辦法使用事務。不過在分佈式系統中,通常不會使用事務,保證數據最終一致性便可
3. 分表分庫,10X10這種方式。 先分10個庫, 每一個庫10張表。
肯定了表切分的方式,接下來就是要根據必定的規則把數據落入到指定表中,這裏介紹兩種形式,
取模:即找到某個字段,這個字段經過取模後的值儘量平均,根據字段取模的方式決定數據落在哪張表,好比,咱們訂單根據用戶Id來決定落在哪張表。
日期:這種方法在最開始的時候,並無決定要分多少張表,只是指定日期的數據落到指定的表中,好比用戶註冊,在2017-5-30日註冊,則能夠將數據落入user_201705,這張表中。
這兩種分表方法各有優劣,按照取模分,每張表的數據能夠儘量的平均,可是若是後面表擴展則比較麻煩,按照日期來分,雖然能夠解決表擴展問題,可是數據有可能不平均,好比在5月,舉辦了促銷,結果那個月註冊的人不少。
當數據開始寫數據庫,Id就是要考慮的,這裏有幾個解決方案:
1. Id自增,步進=分表數。好比分了3張表,每張表的起始Id不同,而且自增的幅度=表數量,這樣Id就是1,4,7 2,5,8 3,6,9 ,可是若是之後分表數增長,Id的步進還要變。
2. 單獨的表,用於記錄自增,能夠單獨一張表,裏面有一列,記錄的是當前的id, 能夠獲取下一個Id, 只是這樣每次都要訪問這張表,會有性能瓶頸。
3. Guid
4. snowflake算法
關於Id有幾個注意的地方,若是你的Id有可能出如今頁面上, 好比訂單Id, 用戶Id ,儘可能不要使用自增的,由於別人能夠根據這些Id,看出你如今的用戶規模,訂單量。經過在兩天中相同時間下單,能夠看出你的交易量,若是權限沒有控制好,經過遍歷能夠查出全部的信息。
咱們這裏以用戶表爲例,看把用戶信息寫入數據庫的一些方法,以及這些方法的一些問題。
當一個新用戶註冊,並無Id,能夠經過分佈式Id獲取到一個Id ,並對這個Id取模,獲得具體的表,而後將Id連同用戶信息寫入表。 後面若是要根據Id獲得用戶信息,只要對Id採用相同的方法,便可查到對應的信息。
可是上面的方法沒有覆蓋到一個場景,用戶登陸都是經過用戶名登陸的,怎麼查呢,這個時候,通常的方法就是並行查全部的表,好一點的方法就是,在註冊的時候,額外創建一個映射,是username->表名的映射,當用戶登陸的時候,能夠根據這個映射找到對應的表名,而後根據表名,再插到具體的用戶,這個映射能夠放到數據庫中,也能夠放到redis/mongodb中。
上面的方法能夠解決問題,可是須要引入映射,若是不想要映射, 這個時候能夠採用複合Id , 咱們使用用戶名進行取模,算出具體的表索引,而後經過分佈式Id+表索引做爲新的Id(NewId)插入到表中,這樣咱們對用戶名進行計算能夠獲得表索引,經過對NewId進行計算也能夠獲得表索引(NewId包含了分佈式Id和表索引,經過分離,能夠獲得表索引)。
咱們上面沒有考慮到擴展,好比如今咱們有10張表,過段時間,發現10張表的存儲也快滿了, 這個時候就要再擴展10張表,變成20張表,那取模算法也要改,這個時候再對之前的NewId進行計算可能就得不到準備的表了,因此在NewId中,咱們還要包含一個算法版本號。
當全部的操做都在一個數據庫的時候, 能夠很方便的使用事務,好比Java下spring的聲明式事務,可是當出現跨庫操做,事務的使用就不怎麼方便了,對於互聯網公司,大多數系統都是分佈式的,會選擇柔性事物。
隨着業務的發展,數據量增多,咱們須要再次對錶進行擴展,擴展方式以下:
1. 須要遷移數據
1. 首次擴展:假如咱們要開始分庫分表,原來有一個庫一張表,如今要擴展爲10個,能夠新建9個slave庫,等9個slave庫數據都和主庫都同步了,而後修改路由算法。
2. 擴容後再擴容:好比如今3個表,要改成5個表,採用Hash算法,當數據擴容,則總體數據都要作遷移。 新增和刪除都要作。 技巧:選擇2個倍數,能夠只作新表新增和刪除,這個可能須要用到雙寫。
3. 劃分樹形組,好比,以前一個數據庫,不須要Hash,如今新增9個,同步數據,完成後,修改路由算法(321%10),再刪除數據,若是10個數據庫不夠,則再次擴容,原來的10個庫,每一個庫各自再擴展9個庫(可根據實際須要擴展),同步數據,方法和上面第一種相似,而後經過 321%10 321/10%10, 算出具體的數據庫。若是不夠,能夠用一樣的方式擴容。
2. 不遷移數據
1. 根據時間定義,好比一個月一張表。
2. 定義mapping關係,能夠放入到分佈式緩存中,寫入的時候,寫入緩存,讀取的時候讀取緩存, 若是緩存失效,全量讀取。
3. 增量和Hash同時使用,定義策略,1-1000W一張組,1000-3000W一張組,組內,根據Hash避免熱點數據。 新數據都會到新的數據庫中。這裏注意,這個組不要和機器綁定,好比1-1000W,在DB0機器,1000-3000W在DB1機器,雖然數據Hash, 熱點數據不會在同一個表中,可是仍是在通一臺機器。 要考慮以下的方式,熱點數據仍是會分佈在老機器上。
4. 生成的ID具備自描述性。 ID+DBIndex,好比Id爲321,Hash命中到第一張表,變爲32101,最後兩位表示具體的數據庫索引,老的數據庫也會寫數據,若是咱們的表增多,則修改hash算法,只命中到新表。
目前分庫分表的解決方案主要集中在這幾層:
1. dao層,在dao層根據指定的分表鍵決定要操做那個數據庫。
2. ORM層
2. JDBC層,比較有名的是sharding-jdbc.
3. 代理層,好比mycat.
分庫分表是一個大工程, 如何分, ID怎麼取, 事物怎麼作都是要考慮 的。另外你們不要用操做單表的思惟去操做多表,有些人一接觸分表就去考慮gourp by 怎麼作, join怎麼作。 從我接觸的一些分表來看,若是牽扯到分表,操做都比較簡單,基本在執行sql前都已經肯定了要操做哪張表。 固然若是真的遇到要join的操做或者沒有辦法肯定數據在哪一個表中,像sharding-jdbc,mycat這種透明分表分庫的都會幫你處理。