大型的網(wǎng)站開發(fā)crm系統(tǒng)網(wǎng)站
道生一,一生二,二生三,三生萬物
我旁邊的一位老哥跟我說,你知道分布式是是用來干什么的嘛?一句話給我干懵了,我能隱含知道,大概是用來做分壓處理的,并增加系統(tǒng)穩(wěn)定性的。但是具體如何,我卻道不出個1,2,3?,F(xiàn)在就將這些做一個詳細的總結(jié)。至少以后碰到面試官可以說上個123。
那么就正式進入正題:
文章目錄
- 道生一,一生二,二生三,三生萬物
- 分布式概念
- 分布式鎖
- 分布式鎖實現(xiàn)的方案
- 數(shù)據(jù)庫實現(xiàn)(Mysql)
- 方式1:樂觀鎖
- 方式2:讀寫鎖的實現(xiàn)
- 第一步:鏈接數(shù)據(jù)庫
- 第二部:建立數(shù)據(jù)庫表
- 第三步:讀方法(讀操作)
- 讀加鎖操作
- 讀解鎖操作
- 第四步:寫方法(寫操作)
- 寫加鎖
- 寫解鎖
- redis實現(xiàn)
分布式概念
分布式是一種,將一個大問題拆分成多個小問題,并分別由多個節(jié)點協(xié)同完成的計算機解決方案
。
既然是解決問題,那么是解決什么呢?
分布式的目的:是為了解決單個機器無法滿足性能要求的問題
至于為什么單個機器無法滿足,其實回頭細想,現(xiàn)代的計算數(shù)據(jù)都是突出一個字,
大
,這個大字就意味著處理數(shù)據(jù)的運算速度也得大,但是因為現(xiàn)代材料局限,一個主機的運算的能力有限,然而數(shù)據(jù)是成指數(shù)倍的增長,為了解決這個問題就是增加主機的數(shù)量,但是這就出現(xiàn)了另一個問題:->:如何將這些主機有機結(jié)合起來
?分布式就是解決這個問題的。其實很多公司其實都不需要分布式,但是,時代進步的。不管現(xiàn)在是不是要用,但是未來的事情誰又說的清?
這個是網(wǎng)圖,但是我認為這個是可以將現(xiàn)在大部分的分布式知識做一個較為全面的總結(jié)的
分布式這種框架的誕生,伴隨著許多的問題,包括硬件,也包括軟件,比如:
- 數(shù)據(jù)一致性
- 數(shù)據(jù)亂序
- 數(shù)據(jù)丟失
- 分庫分表的擴容
- …
問題可以說是非常多。
但是大部分是可以用技術手段去避免很多的問題。而分布式加鎖就是一個較為有效的手段。不過就是犧牲了一部分的時效性,但是對于人類來說,這點時間的損耗,就是一個眨眼的功夫。希望日后會出現(xiàn)更多手段,去解決這方面的問題。
接下來就是對于分布式鎖介紹
分布式鎖
分布式鎖的應用場景,這些既是應用場景也是問題所在:
- 處理緩存擊穿
- 處理緩存雪崩
- 重試處理
- 冪等性
- 數(shù)據(jù)不一致的問題
- …
基本上很多分布式應用上的問題基本可以用分布式鎖處理
那么什么算是一個合格的分布式鎖呢?眾多巨人的文章中無不透露以下這幾點:
互斥性
:這個是鎖的基本功能,即:同一時刻只允許一個客戶端持有鎖避免死鎖
:獲取到鎖的客戶端出現(xiàn)問題了,沒有辦法解鎖,所以要避免死鎖。讓系統(tǒng)繼續(xù)運行下去(有些也叫安全性)容錯
:既然是服務器,那么提供鎖的系統(tǒng)也是有可能崩潰的,所以要保證這一點。
這些屬性,將會貫穿這篇文章。
分布式鎖實現(xiàn)的方案
這些我會舉出大體的實現(xiàn)思路,并不會全部去實現(xiàn)。
🌝每種實現(xiàn)類型中都有不同實現(xiàn)方式 ,比如mysql有悲觀鎖和樂觀鎖,讀寫鎖這些方式的實現(xiàn)方式
常見的實現(xiàn)方案有:
- 數(shù)據(jù)庫實現(xiàn)(我這里用的是mysql)
- redis實現(xiàn)
- zookeeper實現(xiàn)
- Redlock 算法實現(xiàn)
這里都會說到,但是對于實現(xiàn)來說,我目前就是只說MySQL的實現(xiàn),redis的實現(xiàn)
語言對于程序員來說只是說某種工具而言,真正重要的是邏輯(算法)
和數(shù)據(jù)結(jié)構
,這個才是一個程序員安生立命的本錢。
所以這篇文章我會用go語言實現(xiàn),其他語言的版本這里就不多說了。但是我會將常見的語言包說出,方便友友們能夠快速查到相關的資料。
數(shù)據(jù)庫實現(xiàn)(Mysql)
這里我用MySQL去實現(xiàn):
技術:golang
,gorm
,數(shù)據(jù)結(jié)構
方式1:樂觀鎖
實現(xiàn)方式:通過對數(shù)據(jù)表添加一個字段 Version
實現(xiàn)(數(shù)據(jù)版本(Version
)記錄機制實現(xiàn))
主要邏輯:為數(shù)據(jù)庫表增加一個數(shù)字類型的 version
字段來實現(xiàn)。當讀取數(shù)據(jù)時,將version
字段的值一同讀出,數(shù)據(jù)每更新一次,對此version
值加一
如果對于跟新操作,需要先判斷當前version與數(shù)據(jù)庫中的version版本號是否對應,對應的上就允許跟新,諾是不相同,就會導致沖突,此時就更新失敗。
是不是很簡單?是的確實很簡單。畫個圖解釋一下
那么悲觀鎖如何實現(xiàn)我相信大家肯定也就明白了。但是這里我就淺淺提一點:
在sql
語句后添加for update
邏輯實現(xiàn):
- 通過添加線程做輪詢等待,然后搶鎖
- 添加過期時間
- 更新版本號
接下來重點是讀寫鎖的實現(xiàn)
方式2:讀寫鎖的實現(xiàn)
而這兩種鎖的實現(xiàn),需要滿足一下幾個特點:
- 執(zhí)行操作的環(huán)境是分布式的(當然單機不是不能用)
- 讀操作,不做限制,里面資源是共享的??梢灾С侄鄠€線程或者協(xié)程對資源的讀取的操作。
- 寫操作,是互斥的,也就是說明一個時刻只允許一個協(xié)程或者進程對資源進行訪問。
- 讀操作,寫操作兩者是互斥的。不能同時存在
ps:相當于對于讀寫這兩個操作來說,都有自己的申請鎖和解鎖的方法,讀模式共享,寫模式互斥
讀寫鎖的有點在于:
- 分布式讀寫鎖是比分布式鎖粒度更小的鎖
- 對于業(yè)務場景更加靈活
所以綜上的出:讀寫鎖的狀態(tài)有:讀加鎖狀態(tài)、寫加鎖狀態(tài)、無鎖狀態(tài)
當前鎖狀態(tài) | 讀鎖請求 | 寫鎖請求 |
---|---|---|
無鎖狀態(tài) | 可以 | 可以 |
讀鎖狀態(tài) | 可以 | 不可以 |
寫鎖狀態(tài) | 不可以 | 不可以 |
第一步:鏈接數(shù)據(jù)庫
每個程序員的鏈接手法各不相同,所以這里就不獻丑了。只要連上數(shù)據(jù)庫就好。然后將客戶端暴露出來。
第二部:建立數(shù)據(jù)庫表
要包含一下這幾個字段。
var (statusUnLock = "Unlock"statusReadLock = "ReadLock"statusWirteLock = "WirteLock"
)type RWLock struct {
//表示某條數(shù)據(jù)加鎖的狀態(tài),只能是讀鎖、寫鎖、無鎖狀態(tài)中的一種,默認狀態(tài)為無鎖狀態(tài)LockStatus string `gorm:"default:'Unlock'"` //ReadLockCount 字段則記錄當前并發(fā)訪問的 goroutine(可以理解成線程) 數(shù)量ReadLockCount uint32 `gorm:"default:0"` LockReason string //記錄當前加鎖的原因
}// Stock 存儲鎖
type Stock struct {gorm.ModelRWLockCount int64
}func (Stock) TableName() string {return "stock"
}
這三個是狀態(tài)值,分別代表:無鎖,讀鎖,寫鎖
statusUnLock = “Unlock”
statusReadLock = “ReadLock”
statusWirteLock = “WirteLock”
gorm.Model
中包含了:一下字段:
//主鍵idID uint `gorm:"primarykey"`//創(chuàng)建時間CreatedAt time.Time//更新時間UpdatedAt time.Time//刪除時間,軟刪除DeletedAt DeletedAt `gorm:"index"`
這里我們通過的方式是,建立一個鎖表來管理整個鎖。相應的字段的功能我這里就不做贅述,在代碼中已經(jīng)有了。
第三步:讀方法(讀操作)
讀加鎖操作
// ReadLock 讀鎖
func (s Stock) ReadLock(ctx context.Context, db *gormX.DataBD, lockReason string) error {fields := map[string]interface{}{"lock_status": statusReadLock,"read_lock_count": gorm.Expr("read_lock_count + ?", 1),"lock_reason": lockReason,}//將所屬的id鎖的寫狀態(tài)改為讀狀態(tài)result := db.DB(ctx).Model(&Stock{}).Where("id=? AND lock_status=?", s.ID, statusWirteLock).Updates(fields)if result.Error != nil {return result.Error}if result.RowsAffected == 0 {return errors.New("重入鎖失敗,受影響的行數(shù)為 0")}return nil
}
我將對這里的代碼做一個解釋:
context.Context
:上下文,用來管理請求
db *gormX.DataBD
: 用來處理mysql連接的
lockReason
:對每個鎖進行備注
這里的讀鎖就是做一個統(tǒng)計,統(tǒng)計有多少個線程,是讀鎖狀態(tài)
result := db.DB(ctx).Model(&Stock{}).Where("id=? AND lock_status=?", s.ID, statusWirteLock).Updates(fields)
:
這個整個sql的翻譯,{參數(shù)}
update stock
setlock_status={statusReadLock},read_lock_count =read_lock_count +1 ,//這個不是參數(shù)lock_reason={lockReason} where id={s.ID} and statusWirteLoc={statusWirteLock}
這里為什么是update呢?因為在這里gorm
中,update
沒有的數(shù)據(jù)的話,會變成insert
插入數(shù)據(jù)。其他語言在做的時候一定要注意。
讀解鎖操作
// UnReadLock 讀解鎖
func (s Stock) UnReadLock(ctx context.Context, db *gormX.DataBD, UnLockReason string) error {fields := map[string]interface{}{"read_lock_count": gorm.Expr("if(read_lock_count>0),read_lock_count-1,0"),"lock_status": gorm.Expr("if(lock_status < 1),?,?", statusUnLock, statusReadLock),"lock_reason": UnLockReason,}result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusReadLock).UpdateColumns(fields)if result.Error != nil {return result.Error}if result.RowsAffected == 0 {return errors.New("解讀鎖失敗,受影響的行數(shù)為 0")}return nil
}
這里將讀操作做完的業(yè)務進行釋放后,在表中做統(tǒng)計減少的操作。
我將對這里的代碼做一個解釋:
result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusReadLock).UpdateColumns(fields)
update stock
setlock_status=(if(read_lock_count>0),read_lock_count-1,0),read_lock_count =(if(lock_status < 1),{statusUnLock},{statusReadLock}),lock_reason={lockReason} where id={s.ID} and statusWirteLoc={statusReadLock}
第四步:寫方法(寫操作)
寫加鎖
// WriteLock 寫鎖
func (s Stock) WriteLock(ctx context.Context, db *gormX.DataBD, lockReason string) error {fields := map[string]interface{}{"read_lock_count": 0,"lock_status": statusWirteLock,"lock_reason": lockReason,}result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusUnLock).Updates(fields)if result.Error != nil {return result.Error}if result.RowsAffected == 0 {return errors.New("寫入鎖失敗,受影響的行數(shù)為 0")}return nil
}
這里不對線程進行統(tǒng)計,因為這是互斥的。并將鎖寫入狀態(tài)
我將對這里的代碼做一個解釋:
result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusUnLock).Updates(fields)
update stock
setlock_status={statusWirteLock},read_lock_count =0 ,//這個不是參數(shù)lock_reason={lockReason} where id={s.ID} and statusWirteLoc={statusWirteLock}
這里為什么是update
呢?因為在這里gorm
中,update
沒有的數(shù)據(jù)的話,會變成insert
插入數(shù)據(jù)。其他語言在做的時候一定要注意。
寫解鎖
// UnWriteLock 寫解鎖
func (s Stock) UnWriteLock(ctx context.Context, db gormX.DataBD, UnLockReason string) error {fields := map[string]interface{}{"read_lock_count": 0,"lock_status": statusUnLock,"lock_reason": UnLockReason,}result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusWirteLock).Updates(fields)if result.Error != nil {return result.Error}if result.RowsAffected == 0 {return errors.New("解寫鎖失敗,受影響的行數(shù)為 0")}return nil
}
這里不對線程進行統(tǒng)計,因為這是互斥的。并修改其狀態(tài)
我將對這里的代碼做一個解釋:
result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusWirteLock).Updates(fields)
update stock
setlock_status={statusUnLock},read_lock_count =0 ,//這個不是參數(shù)lock_reason={lockReason} where id={s.ID} and statusWirteLoc={statusWirteLock}
redis實現(xiàn)
今天太晚了,先不寫,明天補上:連接
續(xù):今天補上了:請看這篇文章