记一种数据库水平扩展的技巧
起因
這段時間我們游戲在持續推廣,每天數萬的玩家注冊,服務器壓力增加得很快。雖然我們服務器進程可以多開,redis用的是集群版,也可以水平擴展,但數據庫不是,一直是只有一個數據庫在支撐。即便大部分玩家數據都存放在redis中,只有redis中的數據過期后才會去請求數據庫進行查詢,但一樣頂不住人多,現在每天日活躍玩家將近20萬,同時在線接近2萬,數據庫壓力還是很大,所以擴展成多個數據庫支持是唯一選擇
思考
原來只有一個數據庫,現在要擴展多個數據庫,把新玩家哈希到不同的數據庫上,同時還要兼容原來的老玩家,依然能正確讀取,更新其舊數據,不能出錯
第一種方法
再買2個數據庫(當然也可以買多個),加上原來的數據庫,這樣一共有3個數據庫,新注冊的玩家根據playerID對3進行取余,這樣把新玩家的數據均勻地哈希到3個數據庫上,以做負載均衡。當讀取玩家數據時,按照playerID對3進行取余的算法,得到數據庫ID,先嘗試從這個數據庫里面讀取,如果讀取不到,說明是舊玩家,那么再到舊數據庫里面讀取。修改更麻煩一些,因為要先判斷在哪個數據庫上,然后再進行更新。
看似也沒什么問題,既能哈希式地存儲,也能哈希式地讀取
請仔細想一下,這種方法有什么問題嗎?
如果后面數據庫壓力越來越大,3個數據庫也不夠了怎么辦呢?只能再買幾個數據庫,比如5個,這樣一共有8個數據庫,進行新的哈希算法,新注冊的玩家根據playerID對8進行取余,把新玩家的數據均勻地哈希到8個數據庫上。讀取的時候就麻煩了,因為有三類不同的玩家了,先對8取余得到一個數據庫進行查詢,沒有的話再對3進行取余進行查詢,仍然沒有的話再到最舊的數據庫上進行查詢。讀取更新玩家數據的話就更痛苦了,要進行多次哈希算法庫判斷到底在哪個數據庫上,找到后才能進行更新,數據庫操作需要多次
看到了吧,每擴展一次數據庫,讀取玩家數據的時候就要多嘗試一次DB
想一想,讀取,更新玩家數據庫的地方有很多,每次都這么麻煩的判斷,累也累死了
(有個優化點,我們可以記錄下來分界點玩家ID,歸于哪個時間段生成的,在這個時間段內的玩家都是用一種算法,比如playerID在1~M之間的玩家直接到舊的數據庫上查詢,playerID在M~N之間的玩家直接對3進行取余后到對應的數據庫上進行操作,playerID在N之后的玩家對8進行取余后到對應的數據庫上進行操作。這個M,N可以提前在代碼里面定好,玩家注冊的時候也根據M,N進行不同的哈希算法。但是缺點也有,那就是M,N的預估不能出錯,而且沒法測試,一旦啟用了這個規則,玩家數據就會被自動歸類,出錯就完蛋了。而且再擴展一次數據庫的話要在所有選擇數據庫的地方修改一遍,吐血到死)
問題在哪里呢?
答案:擴展數據后,讀取更新玩家數據時的算法嚴重依賴生成數據時的算法,二者必須一樣也就是高耦合,因為有舊數據,只能多次哈希后判斷在不在當前數據庫上
所以我們要找到一個讀取更新數據時,不依賴于生成數據時算法的辦法,也就是下面方法2
第二種方法
再買N個數據庫,比如2個,加上原來的一個舊數據庫,一共3個。仍然采用對3取余的哈希辦法,把新玩家均衡的撒在3個數據庫上,但是同時記錄下新玩家所在的數據庫號,建議記錄在redis中,這樣最高效。讀取更新玩家數據時,先從redis中查到這個玩家所在的數據庫號,再到對應的數據庫上進行操作,如果redis中不存在,說明是舊玩家那就到舊數據庫中。由于redis的效率超高,每秒達到10萬級別,相對于數據庫的操作,這點兒耗時可以說忽略不計了
這個辦法有
優點
1.?查詢更新玩家數據時的算法永不需改變,只用先從redis中查找所在數據庫號,再到對應的數據庫操作即可,永遠只有一次數據庫操作。redis中查詢不到說明是當初沒有記,直接到舊數據庫查詢即可
2.?代碼改動比較小,只需新玩家記錄下數據庫ID號,讀取更新數據庫時根據redis的值選擇對應的數據庫連接即可
3.?以后再擴展數據庫的時候,只需更改玩家注冊時的哈希算法。讀取,更新時找數據庫的算法不需更新
缺點
1. 需要額外引入redis,且這些數據在redis中永久存在
2. 若redis出問題導致數據丟了,那這些玩家就找不到準確的歸屬數據庫了。不過現在云redis的集群模式下,數據安全是有保證的。如果你仍然不放心,也可以先到數據庫中保存一份玩家的歸屬數據庫ID,再加載到redis中做長期保存,這樣更保險,即使redis中丟了,再到數據庫中查詢導入即可。由于我們絕大多數都是查詢redis,數據庫里面只是用來做備份保險用,性能不用擔心
總結
以上是生活随笔為你收集整理的记一种数据库水平扩展的技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis会遇到的15个「坑」,你踩过几
- 下一篇: centos安装mysql8.0