成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

數(shù)據(jù)庫(kù)寫(xiě)操作棄用“SELECT ... FOR UPDATE”解決方案

Imfan / 1017人閱讀

摘要:如果兩個(gè)事務(wù)同時(shí)修改同一個(gè)數(shù)據(jù),先的事務(wù)會(huì)成功,另一個(gè)會(huì)被拒絕,并重新開(kāi)始運(yùn)行整個(gè)事務(wù)。

問(wèn)題闡述

Mysql Galera集群是迄今OpenStack服務(wù)最流行的Mysql部署方案,它基于Mysql/InnoDB,我的OpenStack部署方式從原來(lái)的主從復(fù)制轉(zhuǎn)換到Galera的多主模式。

Galera雖然有很多好處,如任何時(shí)刻任何節(jié)點(diǎn)都可讀可寫(xiě),無(wú)復(fù)制延遲,同步復(fù)制,行級(jí)復(fù)制,但是Galera存在一個(gè)問(wèn)題,也可以說(shuō)是在實(shí)現(xiàn) 真正的多主可寫(xiě)上的折衷權(quán)衡,也就是這個(gè)問(wèn)題導(dǎo)致在代碼的數(shù)據(jù)庫(kù)層的操作需要棄用寫(xiě)鎖,下面我說(shuō)一下這個(gè)問(wèn)題。

這個(gè)問(wèn)題是Mysql Galera集群不支持跨節(jié)點(diǎn)對(duì)表加鎖,也就是當(dāng)OpenStack一個(gè)組件有兩個(gè)會(huì)話分布在兩個(gè)Mysql節(jié)點(diǎn)上同時(shí)寫(xiě)入一條數(shù)據(jù),其中一個(gè)會(huì)話會(huì)遇到 死鎖的情況,也就是得到deadlock的錯(cuò)誤,并且該情況在高并發(fā)的時(shí)候發(fā)生概率很高,在社區(qū)Nova,Neutron該情況的報(bào)告有很多。

這個(gè)行為其實(shí)是Galera預(yù)期的結(jié)果,它是由樂(lè)觀鎖并發(fā)控制機(jī)制引起的,當(dāng)發(fā)生多個(gè)事務(wù)進(jìn)行寫(xiě)操作的時(shí)候,樂(lè)觀鎖機(jī)制假設(shè)所有的修改都能 沒(méi)有沖突地完成。如果兩個(gè)事務(wù)同時(shí)修改同一個(gè)數(shù)據(jù),先commit的事務(wù)會(huì)成功,另一個(gè)會(huì)被拒絕,并重新開(kāi)始運(yùn)行整個(gè)事務(wù)。 在事務(wù)發(fā)生的起始節(jié)點(diǎn),它可以獲取到所有它需要的鎖,但是它不知道其他節(jié)點(diǎn)的情況,所以它采用樂(lè)觀鎖機(jī)制把事務(wù)(在Galera中叫writes et)廣播到所有其他節(jié)點(diǎn)上,看在其他節(jié)點(diǎn)上是否能提交成功。這個(gè)writeset會(huì)在每個(gè)節(jié)點(diǎn)上進(jìn)行驗(yàn)證測(cè)試,來(lái)決定該writeset是否被接受, 如果檢驗(yàn)失敗,這個(gè)writeset就會(huì)被拋棄,然后最開(kāi)始的事務(wù)也會(huì)被回滾;如果檢驗(yàn)成功,事務(wù)就被提交,writeset也被應(yīng)用到其他節(jié)點(diǎn)上。 這個(gè)過(guò)程如下圖所示:

在Python的SQLAlchemy庫(kù)中,有一個(gè)“with_lockmode("update")”語(yǔ)句,這個(gè)代表SQL語(yǔ)句中的“SELECT ... FOR UPDATE”,在我參與過(guò)的計(jì)費(fèi)項(xiàng)目和社區(qū)的一些項(xiàng)目的代碼中有大量的該結(jié)構(gòu),由于寫(xiě)鎖不能在集群中同步,所以這個(gè)語(yǔ)句在Mysql集群中就沒(méi)有得到它應(yīng)有的效果,也就是在語(yǔ)義上有問(wèn)題,但是最后Galera會(huì)通過(guò)報(bào)deadlock錯(cuò)誤,只讓一個(gè)commit成功,來(lái)保證Mysql集群的ACID性。

一些解決方法

把請(qǐng)求發(fā)往一個(gè)節(jié)點(diǎn),這個(gè)在HAProxy中就可以配置,只設(shè)定一個(gè)節(jié)點(diǎn)為master,其余節(jié)點(diǎn)為backup,HAProxy會(huì)在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

對(duì)OpenStack的所有Mysql操作做讀寫(xiě)分離,寫(xiě)操作只在master節(jié)點(diǎn)上,讀操作在所有節(jié)點(diǎn)上做負(fù)載均衡。OpenStack沒(méi)有原生支持,但 是有一個(gè)開(kāi)源軟件可以使用,maxscale。

終極解決方法

上面的解決方法只是一些workaround,目前情況下最終極的解決方法是使用lock-free的方法來(lái)對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,也就是無(wú)鎖的方式,這就 需要對(duì)代碼進(jìn)行修改,現(xiàn)在Nova,Neutron,Gnocchi等項(xiàng)目已經(jīng)對(duì)其進(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)"的無(wú)鎖方法來(lái)完成update操作,CAS是最先在CPU中使用的,CAS說(shuō)白了就是先比較,再修改,在進(jìn)行UPDATE操作之前,我們先SELEC T出來(lái)一些數(shù)據(jù),我們叫做期望數(shù)據(jù),在UPDATE的時(shí)候要去比對(duì)這些期望數(shù)據(jù),如果期望數(shù)據(jù)有變化,說(shuō)明有另一個(gè)會(huì)話對(duì)該行進(jìn)行了修改, 那么我們就不能繼續(xù)進(jìn)行修改操作了,只能報(bào)錯(cuò),然后retry;如果沒(méi)變化,我們就可以將修改操作執(zhí)行下去。該行為體現(xiàn)在SQL語(yǔ)句中就是在 UPDATE的時(shí)候加上WHERE語(yǔ)句,如"UPDATE ... WHERE ..."。

給出一個(gè)計(jì)費(fèi)項(xiàng)目中修改用戶等級(jí)的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():
# 在會(huì)話剛開(kāi)始的時(shí)候,需要先SELECT出來(lái)該account的數(shù)據(jù),也就是期望數(shù)據(jù) account = session.query(sa_models.Account).
        filter_by(user_id=user_id).
        one()]
# 在執(zhí)行UPDATE操作的時(shí)候需要比對(duì)期望數(shù)據(jù),user_id和level,如果它們變化了,那么rows_update就會(huì)被賦值為0 ,就會(huì)走入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ù)的一致性問(wèn)題

該問(wèn)題在OpenStack郵件列表中有說(shuō)過(guò),雖然Galera是生成同步的,也就是寫(xiě)入數(shù)據(jù)同步到整個(gè)集群非常快,用時(shí)非常短,但既然是分布式系 統(tǒng),本質(zhì)上還是需要一些時(shí)間的,尤其是在負(fù)載很大的時(shí)候,同步不及時(shí)會(huì)很?chē)?yán)重。

所以Galera只是虛擬同步,不是直接同步,也就是會(huì)存在一些gap時(shí)間段,無(wú)法讀到寫(xiě)入的數(shù)據(jù),Galera提供了一個(gè)配置項(xiàng),叫做wsrep_sync_ wait,它的默認(rèn)值是0,如果賦值為1,就能夠保證讀寫(xiě)的一致性,但是會(huì)帶來(lái)延遲問(wèn)題。

Appendix

understanding 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)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/38657.html

相關(guān)文章

  • 深入理解 MySQL ——鎖、事務(wù)與并發(fā)控制

    摘要:鎖共享鎖與排他鎖它們都是標(biāo)準(zhǔn)的行級(jí)鎖。防止任何其他事務(wù)變動(dòng)的行。間隙鎖是性能和并發(fā)性之間權(quán)衡的一種折衷,用于某些特定的事務(wù)隔離級(jí)別,如級(jí)別級(jí)別,我司為了減少死鎖,關(guān)閉了鎖,使用級(jí)別。 本文首發(fā)于 vivo 互聯(lián)網(wǎng)技術(shù)微信公眾號(hào) https://mp.weixin.qq.com/s/JF...作者:張碩 本文對(duì) MySQL 數(shù)據(jù)庫(kù)中有關(guān)鎖、事務(wù)及并發(fā)控制的知識(shí)及其原理做了系統(tǒng)化的介紹和總...

    Dr_Noooo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<