摘要:如果兩個(gè)事務(wù)同時(shí)修改同一個(gè)數(shù)據(jù),先的事務(wù)會成功,另一個(gè)會被拒絕,并重新開始運(yùn)行整個(gè)事務(wù)。
問題闡述
Mysql Galera集群是迄今OpenStack服務(wù)最流行的Mysql部署方案,它基于Mysql/InnoDB,我的OpenStack部署方式從原來的主從復(fù)制轉(zhuǎn)換到Galera的多主模式。
Galera雖然有很多好處,如任何時(shí)刻任何節(jié)點(diǎn)都可讀可寫,無復(fù)制延遲,同步復(fù)制,行級復(fù)制,但是Galera存在一個(gè)問題,也可以說是在實(shí)現(xiàn) 真正的多主可寫上的折衷權(quán)衡,也就是這個(gè)問題導(dǎo)致在代碼的數(shù)據(jù)庫層的操作需要棄用寫鎖,下面我說一下這個(gè)問題。
這個(gè)問題是Mysql Galera集群不支持跨節(jié)點(diǎn)對表加鎖,也就是當(dāng)OpenStack一個(gè)組件有兩個(gè)會話分布在兩個(gè)Mysql節(jié)點(diǎn)上同時(shí)寫入一條數(shù)據(jù),其中一個(gè)會話會遇到 死鎖的情況,也就是得到deadlock的錯(cuò)誤,并且該情況在高并發(fā)的時(shí)候發(fā)生概率很高,在社區(qū)Nova,Neutron該情況的報(bào)告有很多。
這個(gè)行為其實(shí)是Galera預(yù)期的結(jié)果,它是由樂觀鎖并發(fā)控制機(jī)制引起的,當(dāng)發(fā)生多個(gè)事務(wù)進(jìn)行寫操作的時(shí)候,樂觀鎖機(jī)制假設(shè)所有的修改都能 沒有沖突地完成。如果兩個(gè)事務(wù)同時(shí)修改同一個(gè)數(shù)據(jù),先commit的事務(wù)會成功,另一個(gè)會被拒絕,并重新開始運(yùn)行整個(gè)事務(wù)。 在事務(wù)發(fā)生的起始節(jié)點(diǎn),它可以獲取到所有它需要的鎖,但是它不知道其他節(jié)點(diǎn)的情況,所以它采用樂觀鎖機(jī)制把事務(wù)(在Galera中叫writes et)廣播到所有其他節(jié)點(diǎn)上,看在其他節(jié)點(diǎn)上是否能提交成功。這個(gè)writeset會在每個(gè)節(jié)點(diǎn)上進(jìn)行驗(yàn)證測試,來決定該writeset是否被接受, 如果檢驗(yàn)失敗,這個(gè)writeset就會被拋棄,然后最開始的事務(wù)也會被回滾;如果檢驗(yàn)成功,事務(wù)就被提交,writeset也被應(yīng)用到其他節(jié)點(diǎn)上。 這個(gè)過程如下圖所示:
在Python的SQLAlchemy庫中,有一個(gè)“with_lockmode("update")”語句,這個(gè)代表SQL語句中的“SELECT ... FOR UPDATE”,在我參與過的計(jì)費(fèi)項(xiàng)目和社區(qū)的一些項(xiàng)目的代碼中有大量的該結(jié)構(gòu),由于寫鎖不能在集群中同步,所以這個(gè)語句在Mysql集群中就沒有得到它應(yīng)有的效果,也就是在語義上有問題,但是最后Galera會通過報(bào)deadlock錯(cuò)誤,只讓一個(gè)commit成功,來保證Mysql集群的ACID性。
一些解決方法
把請求發(fā)往一個(gè)節(jié)點(diǎn),這個(gè)在HAProxy中就可以配置,只設(shè)定一個(gè)節(jié)點(diǎn)為master,其余節(jié)點(diǎn)為backup,HAProxy會在master失效的時(shí)候 自動(dòng)切換到某一個(gè)backup上,這個(gè)也
是很多解決方案目前使用的方法,HAProxy配置如下:
server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check backup server xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx:3306 check backup
對OpenStack的所有Mysql操作做讀寫分離,寫操作只在master節(jié)點(diǎn)上,讀操作在所有節(jié)點(diǎn)上做負(fù)載均衡。OpenStack沒有原生支持,但 是有一個(gè)開源軟件可以使用,maxscale。
終極解決方法上面的解決方法只是一些workaround,目前情況下最終極的解決方法是使用lock-free的方法來對數(shù)據(jù)庫進(jìn)行操作,也就是無鎖的方式,這就 需要對代碼進(jìn)行修改,現(xiàn)在Nova,Neutron,Gnocchi等項(xiàng)目已經(jīng)對其進(jìn)行了修改。
首先得有一個(gè)retry機(jī)制,也就是讓操作執(zhí)行在一個(gè)循環(huán)中,一旦捕獲到deadlock的error就將操作重新進(jìn)行,這個(gè)在OpenStack的oslo.db中已 經(jīng)提供了相應(yīng)的方法叫wrap_db_retry,是一個(gè)Python裝飾器,使用方法如下:
from oslo_db import api as oslo_db_api @oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True, retry_on_request=True) def db_operations(): ...
然后在這個(gè)循環(huán)之中我們使用叫做"Compare And Swap(CAS)"的無鎖方法來完成update操作,CAS是最先在CPU中使用的,CAS說白了就是先比較,再修改,在進(jìn)行UPDATE操作之前,我們先SELEC T出來一些數(shù)據(jù),我們叫做期望數(shù)據(jù),在UPDATE的時(shí)候要去比對這些期望數(shù)據(jù),如果期望數(shù)據(jù)有變化,說明有另一個(gè)會話對該行進(jìn)行了修改, 那么我們就不能繼續(xù)進(jìn)行修改操作了,只能報(bào)錯(cuò),然后retry;如果沒變化,我們就可以將修改操作執(zhí)行下去。該行為體現(xiàn)在SQL語句中就是在 UPDATE的時(shí)候加上WHERE語句,如"UPDATE ... WHERE ..."。
給出一個(gè)計(jì)費(fèi)項(xiàng)目中修改用戶等級的DB操作源碼:
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True, retry_on_request=True) def change_account_level(self, context, user_id, level, project_id=None): session = get_session() with session.begin(): # 在會話剛開始的時(shí)候,需要先SELECT出來該account的數(shù)據(jù),也就是期望數(shù)據(jù) account = session.query(sa_models.Account). filter_by(user_id=user_id). one()] # 在執(zhí)行UPDATE操作的時(shí)候需要比對期望數(shù)據(jù),user_id和level,如果它們變化了,那么rows_update就會被賦值為0 ,就會走入retry的邏輯 params = {"level": level} rows_update = session.query(sa_models.Account). filter_by(user_id=user_id). filter_by(level=account.level). update(params, synchronize_session="evaluate") # 修改失敗,報(bào)出RetryRequest的錯(cuò)誤,使上面的裝飾器抓獲該錯(cuò)誤,然后重新運(yùn)行邏輯 if not rows_update: LOG.debug("The row was updated in a concurrent transaction, " "we will fetch another one") raise db_exc.RetryRequest(exception.AccountLevelUpdateFailed()) return self._row_to_db_account_model(account)數(shù)據(jù)的一致性問題
該問題在OpenStack郵件列表中有說過,雖然Galera是生成同步的,也就是寫入數(shù)據(jù)同步到整個(gè)集群非常快,用時(shí)非常短,但既然是分布式系 統(tǒng),本質(zhì)上還是需要一些時(shí)間的,尤其是在負(fù)載很大的時(shí)候,同步不及時(shí)會很嚴(yán)重。
所以Galera只是虛擬同步,不是直接同步,也就是會存在一些gap時(shí)間段,無法讀到寫入的數(shù)據(jù),Galera提供了一個(gè)配置項(xiàng),叫做wsrep_sync_ wait,它的默認(rèn)值是0,如果賦值為1,就能夠保證讀寫的一致性,但是會帶來延遲問題。
Appendixunderstanding reservations concurrency locking in nova
investigating replication latency in percona xtradb cluster
understanding multi node writing conflict metrics in percona xtradb cluster and galera
an introduction to lock-free programming
mysql multi master replication with galera
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/38657.html
摘要:鎖共享鎖與排他鎖它們都是標(biāo)準(zhǔn)的行級鎖。防止任何其他事務(wù)變動(dòng)的行。間隙鎖是性能和并發(fā)性之間權(quán)衡的一種折衷,用于某些特定的事務(wù)隔離級別,如級別級別,我司為了減少死鎖,關(guān)閉了鎖,使用級別。 本文首發(fā)于 vivo 互聯(lián)網(wǎng)技術(shù)微信公眾號 https://mp.weixin.qq.com/s/JF...作者:張碩 本文對 MySQL 數(shù)據(jù)庫中有關(guān)鎖、事務(wù)及并發(fā)控制的知識及其原理做了系統(tǒng)化的介紹和總...
閱讀 2377·2021-11-15 11:37
閱讀 2635·2021-09-23 11:21
閱讀 2962·2021-09-07 10:11
閱讀 3173·2019-08-30 15:53
閱讀 2831·2019-08-29 15:13
閱讀 1614·2019-08-26 13:57
閱讀 1109·2019-08-26 12:23
閱讀 2446·2019-08-26 11:51