## 鎖
[TOC=3,8]
>[danger] 鎖在并發編程中很重要,很多時候為了保證業務邏輯、數據正確,就必須要對程序的執行過程,或者資源(數據資源等多種資源)進行加鎖。
* * * * *
### 鎖的思考
既然鎖的本質就是保證過程的順序性(鎖過程,而不是靜態資源,其實初衷是要鎖靜態資源,但是由于一個操作里面往往需要鎖多個靜態資源,而這樣的操作又有很多種,這就導致鎖的情況很復雜,不可控,極易出現死鎖,并且數據庫性能也有問題,所以我們才另辟蹊徑轉而去鎖過程的,因為我們發現,如果我們給所有的過程排隊,資源競爭的問題就不存在了),而過程可以分類的,所以可以做一個寬鎖 窄鎖的設計 key jy:order jy:order:goods:3 寬鎖覆蓋域下面的所有窄鎖,獲取窄鎖時依次獲取所有的寬鎖(為了提高性能,先判斷鎖是否注冊,而不是直接獲取)
比如后臺批量生成會員卡號就可以用一個寬鎖(保證同一批次的卡卡id是連續的)
而購買商品就可以用一個窄鎖
不知道這個思想業界是不是已經有了
> 鎖的本質其實是排隊,不同的鎖排隊的空間和時間不同而已。
> 對于競態資源做上鎖處理防止并發問題,或者換一種思路,不讓其變成競態資源問題,比如消息隊列取模分開處理,但是對于賬戶資源處理的問題,就只能用鎖排隊處理了。
*****
#### php文件鎖實戰
實現單例一個操作,**保證同一時刻只有一個線程(最小粒度的執行實例 / 程序執行流的最小單元)在執行此操作** ,其他進程看見它正在執行,就自覺的走開。
```php
$fp = fopen(config('lock_path') . 'action/incrementGet.txt', 'w+');
// LOCK_NB 獲取鎖,非阻塞型,也就是一旦 獲取鎖失敗/加鎖失敗 則直接返回false,設置為LOCK_EX則會一直等待加鎖成功才返回()
// 這個用數據庫的行鎖也沒用,也是阻塞獲取鎖的,要的就是高效,發現有鎖,直接退出,如果檢測是否有鎖還要一直傻傻的阻塞等著那要你干什么
// 無奈,LOCK_NB好像用不了
// 原來要用這個姿勢:LOCK_EX | LOCK_NB
// http://www.php.net/manual/zh/function.flock.php 看來范例還是很有用的
if (flock($fp, LOCK_EX | LOCK_NB)) {
Db::startTrans();
$errMsg = '';
try {
} catch (\Exception $e) {
Db::rollback();
$errMsg = $e->getMessage() ?: '系統忙!';
$errCode = $e->getCode() ?: 1;
}
if ($errMsg == '') {
// 成功就將當前寫入,成為下一個點,否則的話下次繼續,這樣確保了只有成功才會“移動這個點”
fwrite($fp, $end_update_time);
} else {
trace($errMsg, 'error');
}
// 無論成功還是失敗,都要釋放鎖
flock($fp, LOCK_UN);
} else {
// 當前已在運行,直接退出
echo 'Be gone';
}
fclose($fp);
```
封裝成函數:
```php
// 實現的鎖,無阻塞,無等待
function lock($fp, $ok, $gone)
{
try {
if (is_string($fp)) {
$lockFile = $fp;
if (!file_exists($lockFile)) {
file_put_contents($lockFile, '');
}
if (!$fp = fopen($lockFile, 'r')) {
throw new \Exception("unable to open lock file!");
}
}
if (flock($fp, LOCK_EX | LOCK_NB)) {
// 獲得鎖
echo PHP_EOL . 'lock ok!' . PHP_EOL;
return $ok();
} else {
// 當前已在運行,直接退出
echo PHP_EOL . 'Be gone' . PHP_EOL;
return $gone();
}
} catch (\Exception $e) {
throw $e;
} finally {
// echo "string";
// 無論成功還是失敗,都要釋放鎖
flock($fp, LOCK_UN);
// 保證無論怎樣都會釋放文件資源句柄
// 如果 沒有釋放文件鎖 或者 沒有關閉文件句柄 也沒事,php腳本執行完畢也會自動釋放資源的,操作系統會回收的,所以不用擔心沒有關閉和釋放會一直鎖著獨占,除非此php腳本一直沒有結束
fclose($fp);
// This overrides the exception as if it were never thrown
// return "\nException erased";
}
}
```
sql 數據庫 樂觀鎖也是可以實現這樣無阻塞不等待的鎖的。
偽代碼:
```php
// 阻塞版就是用行鎖/悲觀鎖,非阻塞版用樂觀鎖
public function lock(k, ok, gone) {
res = select id from lock where k = k and lock = 0
if (res == 0) {
// 獲取鎖失敗,該k當前已被上鎖占用
gone();
return false;
}
res = update from lock where k = k and lock = 0 set lock = 1
if (res == 1) {
// 獲得鎖后的操作
ok();
// 釋放鎖
update from lock where k = k set lock = 0
return true;
} else {
// 獲取鎖失敗
gone();
return false;
}
}
```
### 用數據庫來做鎖合適嗎?
其實很多時候我們只是想鎖過程而已,現在都利用數據庫實現這個鎖了,這樣做不合適,這并不是數據庫應該解決的問題,應該改用文件鎖/redis鎖,來鎖過程
我們明明是需要鎖臨界過程,卻用數據庫來做鎖,簡單的還好,尤其是很復雜的sql,還將面臨很多引發死鎖的潛在問題,這顯然不合適,這不止是隔靴搔癢了,而是手癢撓背了。
*****
### 還有一個細節要注意:鎖的頒發者
使用文件鎖時,鎖的頒發者就是操作系統,只有一個操作系統,所以頒發唯一是可靠的。
使用數據庫鎖時,向數據庫申請鎖,鎖是數據庫頒發的,所以此時鎖都要以向這個數據庫申請為準,鎖的 申請/獲取 和 驗證/檢查 都要以這個數據庫為目標。(軟件都是封裝,數據庫底層也可能會向操作系統申請鎖。)
>[tip] php有個非線程安全的版本 [php版本nts和ts的區別。 - gltv587的博客 - CSDN博客](https://blog.csdn.net/gltv587/article/details/79054571)
*****
[Linux文件鎖flock - fengbohello - 博客園](https://www.cnblogs.com/fengbohello/p/4262949.html)
> flock鎖的釋放非常具有特色,即可調用LOCK\_UN參數來釋放文件鎖,也可以通過關閉fd的方式來釋放文件鎖(flock的第一個參數是fd),**意味著flock會隨著進程的關閉而被自動釋放掉。**
[fcntl 文件鎖和struct flock - 小 樓 一 夜 聽 春 雨 - 博客園](https://www.cnblogs.com/kex1n/p/13055518.html)
> fcntl是一個非常強大的函數,在這里我們可以使用它來**給文件的某一個部分上鎖**。
[PgSQL · 源碼分析 · PG中的無鎖算法和原子操作應用一則 · 數據庫內核月報 · 看云](http://www.hmoore.net/taobaomysql/monthly/213786)
> 這里的CPU的原子操作是不可中斷的一個或者一系列操作, **也就是不會被線程調度機制打斷的操作, 運行期間不會有任何的上下文切換。**
[分布式領域最重要的一篇論文,到底講了什么?](https://mp.weixin.qq.com/s/FZnJLPeTh-bV0amLO5CnoQ)
> 每個系統是一個小宇宙,宇宙之外還有更大的宇宙,個體獨立自治,個體之外還有更大的個體...
[基于Redis的分布式鎖到底安全嗎(上)?](https://mp.weixin.qq.com/s/JTsJCDuasgIJ0j95K8Ay8w)
[基于Redis的分布式鎖到底安全嗎(下)?](https://mp.weixin.qq.com/s/4CUe7OpM6y1kQRK8TOC_qQ)
[萬字長文說透分布式鎖](https://mp.weixin.qq.com/s/35aCS_5GqLyzZS3VobL6fg)
[5個步驟,教你瞬間明白線程和線程安全_CSDN資訊-CSDN博客](5個步驟,教你瞬間明白線程和線程安全_CSDN資訊-CSDN博客)
> 當多個線程訪問某個方法時,不管你通過怎樣的調用方式、或者說這些線程如何交替地執行,我們在主程序中不需要去做任何的同步,這個類的結果行為都是我們設想的正確行為,那么我們就可以說這個類是線程安全的。
[Chubby分布式鎖服務總結 - 知乎](https://zhuanlan.zhihu.com/p/64554506)
[如何正確使用redis分布式鎖](https://zhuanlan.zhihu.com/p/93947224)
> 由此, 我們可以看出單機redis鎖是不安全的, 當然, 這種不安全性的來源是分布式系統的內在特性--異步性.
[死鎖案例五](https://mp.weixin.qq.com/s/882oEbq2SyYfB5dEPBRvRQ)
[因 Redis 分布式鎖造成的 P0 級重大事故,整個項目組被扣了績效。。。](https://mp.weixin.qq.com/s/GaQ_jemfkUrF-PiPfpimtw)
[Google的鎖,才是分布式鎖?](https://mp.weixin.qq.com/s/R2FCLknar6bCvykJ1m7rJQ)
[面試難題:Redis 分布式鎖,真的完美無缺嗎?](https://mp.weixin.qq.com/s/aNJ_lqHF38WJ8ItHd8uUSw)
[就為了一個原子操作,其他CPU核心罷工了](https://mp.weixin.qq.com/s?__biz=MzAwNjkxNzgxNg==&mid=2247485772&idx=1&sn=0f89aa0d44ee2c1d0753e43588cca32d&source=41#wechat_redirect)
[還不懂分布式鎖?看看 Redisson 是如何實現分布式鎖的!](https://mp.weixin.qq.com/s/tTu7ho9By-JeWBWQKeQcKQ)
[從LONGADDER看更高效的無鎖實現](https://coolshell.cn/articles/11454.html)
> 可以看到,LongAdder確實用了很多心思減少并發量,并且,每一步都是在”沒有更好的辦法“的時候才會選擇更大開銷的操作,從而盡可能的用最最簡單的辦法去完成操作。追求簡單,但是絕對不粗暴。
[比AtomicLong更優秀的LongAdder確定不來了解一下嗎?](https://mp.weixin.qq.com/s/le-BfM6-mDftU2nGI1DmuA)
[解決分布式系統事務一致性的幾種方案對比,你有更好的嗎?](https://mp.weixin.qq.com/s/kzmTKKH-t6tpJ97fa6TYPg)
[一分鐘實現分布式鎖](https://mp.weixin.qq.com/s/d3cFp0DRw0JXkgk5ZLF7Hg)
[基于Redis的分布式鎖到底安全嗎(下)?](https://mp.weixin.qq.com/s/4CUe7OpM6y1kQRK8TOC_qQ)
[漫畫:什么是分布式鎖?](https://mp.weixin.qq.com/s/8fdBKAyHZrfHmSajXT_dnA)
[漫畫:如何用Zookeeper實現分布式鎖?](https://mp.weixin.qq.com/s/u8QDlrDj3Rl1YjY4TyKMCA)
[高并發環境下服務器該如何優化](https://mp.weixin.qq.com/s/VYf6vFzDcIzzCp8jHkqTSA)
> 這里說下無鎖編程,就是由內核完成這個鎖機制,主要是使用原子操作替代鎖來實現對共享資源的訪問保護,使用原子操作時,在進行實際的寫操作時,使用了lock指令,這樣就可以阻止其他任務寫這塊內存,避免出現數據競爭現象。原子操作速度比鎖快,一般要快一倍以上。
[JVM和Python解釋器的硬盤夜話](https://mp.weixin.qq.com/s/s9IykGHC-QY_smNprZgTmw)
> 找這篇文章,,提高鎖效率,原子鎖,系統內部fopen就是這樣的鎖
[鎖的升級打怪:通俗易懂講解偏向鎖、輕量級鎖和重量級鎖](https://mp.weixin.qq.com/s/ohloH-32u6iJeSsU8cdjxA)
[從共享單車學臨界區](https://mp.weixin.qq.com/s/qSiy9NewG1GzOQ1XriIofQ)
[Redisson是如何實現分布式鎖的?](https://mp.weixin.qq.com/s/XO9uJpAdo45kjJgJjIrx5w)
[還在為不懂AQS苦惱嗎?](https://mp.weixin.qq.com/s/chSjy7H4vW9X4G7oXyUYng)
[HotSpot虛擬機中具體的鎖實現](https://mp.weixin.qq.com/s/-lTjCC7J5z1YM1KwVsT8cQ)
> 我們可以用等紅綠燈作為例子。Java 線程的阻塞相當于熄火停車,而自旋狀態相當于怠速停車。如果紅燈的等待時間非常長,那么熄火停車相對省油一些;如果紅燈的等待時間非常短,比如說我們在 synchronized 代碼塊里只做了一個整型加法,那么在短時間內鎖肯定會被釋放出來,因此怠速停車更加合適。
[沒用過分布式鎖?那你該好好看看這篇文章。](https://mp.weixin.qq.com/s/TuAQHj2_VzXFGCvUfvTtiw)
樂觀鎖成立的 讀和寫是不可分割的,這在是在同一條sql語句中
[并發編程與鎖的底層原理](https://mp.weixin.qq.com/s/DX_GsBq31-cemADrqQKfqw)
[編程世界的那把鎖](https://mp.weixin.qq.com/s/hGlBSagmjv6B9jdIl6uY4w)
> ?我就在這里無限循環,拼命的搶, 除非我的時間片到了,被迫讓出CPU, 但是我不會阻塞, 還是就緒狀態,等待下一次的調度, 進入CPU繼續搶。
> 無限循環,也要聽從cpu分配時間片,只是不斷的去申請時間片而已,具體執行還是由操作系統的調度程序來決定
[synchronized與Lock 擂臺之戰](https://mp.weixin.qq.com/s/Ulcs16IuPtQtAVyv2oaFlw)
[從共享單車學臨界區](https://mp.weixin.qq.com/s/qSiy9NewG1GzOQ1XriIofQ)
[鎖的升級打怪:通俗易懂講解偏向鎖、輕量級鎖和重量級鎖](https://mp.weixin.qq.com/s/ohloH-32u6iJeSsU8cdjxA)
[沒用過分布式鎖?那你該好好看看這篇文章。](https://mp.weixin.qq.com/s/TuAQHj2_VzXFGCvUfvTtiw)
[如何實現分布式鎖?](https://mp.weixin.qq.com/s/_C6Dx1BNQxhJk4vT2ee62g)
[鎖的基本概念到 Redis 分布式鎖實現](https://mp.weixin.qq.com/s/9SJ7rbw1MW9Qod2OS_Kf2Q)
[共享資源那么多,如何用一把鎖保護多個資源?](https://mp.weixin.qq.com/s/ObaffbZe5fnbCku2Uq3EKw)
[鎖住余額,為何還會更新異常?](https://mp.weixin.qq.com/s/1-nttEL57F-h5XGE73zfYg)
[SpringBoot + Redisson實現分布式鎖](https://mp.weixin.qq.com/s/TXiT733pJr7TOPDRuimuvg)
[如何避免死鎖,我們有套路可循](https://mp.weixin.qq.com/s/MqVhyGcxVdIp8B_AkzzQMg)
[php多進程讀寫同一個文件鎖的問題及flock詳解 - CSDN博客](https://blog.csdn.net/zhang197093/article/details/52216081)
[php中并發讀寫文件沖突的解決方案(文件鎖應用示例) - coder狼 - 博客園](https://www.cnblogs.com/wellsoho/p/5166467.html)
[PHP文件鎖同步實例 - CSDN博客](https://blog.csdn.net/u014175572/article/details/53381049)
[PHP對文件進行加鎖、解鎖實例_php技巧_腳本之家](https://www.jb51.net/article/60280.htm)(LOCK_EX | LOCK_NB)
[PHP: flock - Manual](http://www.php.net/manual/zh/function.flock.php)
[搞懂“分布式鎖”,看這篇文章就對了](https://www.toutiao.com/a6611354991913337347/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1&api_ab_group=pyq_1×tamp=1539874485&app=news_article_lite&utm_source=weixin&iid=46408437753&utm_medium=toutiao_android&group_id=6611354991913337347)
> **互斥性:** 和我們本地鎖一樣互斥性是最基本,但是分布式鎖需要保證在不同節點的不同線程的互斥。
**可重入性:** 同一個節點上的同一個線程如果獲取了鎖之后那么也可以再次獲取這個鎖。
**鎖超時:** 和本地鎖一樣支持鎖超時,防止死鎖。
**高效,高可用:** 加鎖和解鎖需要高效,同時也需要保證高可用防止分布式鎖失效,可以增加降級。
**支持阻塞和非阻塞:** 和 ReentrantLock 一樣支持 lock 和 trylock 以及 tryLock(long timeOut)。
**支持公平鎖和非公平鎖(可選):** 公平鎖的意思是按照請求加鎖的順序獲得鎖,非公平鎖就相反是無序的。這個一般來說實現的比較少。
~~~
既然鎖的本質就是保證過程的順序性(鎖過程,而不是靜態資源,其實初衷是要鎖靜態資源,但是由于一個操作里面往往需要鎖多個靜態資源,而這樣的操作又有很多種,這就導致鎖的情況很復雜,不可控,極易出現死鎖,并且數據庫性能也有問題,所以我們才另辟蹊徑轉而去鎖過程的,因為我們發現,如果我們給所有的過程排隊,資源競爭的問題就不存在了),而過程可以分類的,所以可以做一個寬鎖 窄鎖的設計 key jy:order jy:order:goods:3 寬鎖覆蓋域下面的所有窄鎖,獲取窄鎖時依次獲取所有的寬鎖(為了提高性能,先判斷鎖是否注冊,而不是直接獲取)
比如后臺批量生成會員卡號就可以用一個寬鎖(保證同一批次的卡卡id是連續的)
而購買商品就可以用一個窄鎖
不知道這個思想業界是不是已經有了
~~~
* * * * *
**參考資料:**
- [php 鎖_百度搜索](https://www.baidu.com/s?wd=php+鎖&ie=UTF-8)
- [php并發加鎖示例_php實例_腳本之家](http://www.jb51.net/article/94878.htm)
- [PHP并發操作下的加鎖 -- 簡明現代魔法](http://www.nowamagic.net/php/php_LockInConcurrent.php)
- [PHP程序中的文件鎖、互斥鎖、讀寫鎖使用技巧解析_php技巧_腳本之家](http://www.jb51.net/article/81246.htm)
- [php并發加鎖 - CleverCode的博客 - 博客頻道 - CSDN.NET](http://blog.csdn.net/clevercode/article/details/52493568)
- [php網站的鎖機制 - 航天飛哥的博客 - 博客頻道 - CSDN.NET](http://blog.csdn.net/liu857279611/article/details/51587973)
- [并發下常見的加鎖及鎖的PHP具體實現 - xinqiyang - 博客園](http://www.cnblogs.com/scotoma/archive/2010/09/26/1836312.html)
- [非阻塞同步算法與CAS(Compare and Swap)無鎖算法 - Mainz - 博客園](http://www.cnblogs.com/Mainz/p/3546347.html)
- [Memcache的并發問題和利用CAS的解決方案 | 云博](http://cloudate.net/?p=306)
- [同步、異步 與 串行、并行的區別](http://blog.csdn.net/u010231453/article/details/53542672)
- [異常處理try-catch-finally](http://www.th7.cn/Program/php/201405/201159.shtml)
- [PHP的新特性finally | 風雪之隅](http://www.laruence.com/2012/08/16/2709.html)
- [同步鎖的三種實現與案例解析 - CSDN博客](http://blog.csdn.net/u013630349/article/details/78826305)
- [如何優雅地用Redis實現分布式鎖](http://mp.weixin.qq.com/s/EcrVp0dOpnc4FniaBlezcA)
- [分布式鎖之Redis實現(最終版)](https://www.toutiao.com/a6534781248928219651/?tt_from=weixin&utm_campaign=client_share×tamp=1523257620&app=news_article_lite&utm_source=weixin&iid=25315997380&utm_medium=toutiao_android&wxshare_count=1)
- [golang并發編程之互斥鎖、讀寫鎖詳解](https://mp.weixin.qq.com/s/RZ_YuGXe_R8S9Tvo2JT4TQ)
> 鎖是傳統的并發程序對共享資源進行訪問控制的主要手段。(鎖是對并發程序訪問共享資源進行訪問控制的主要手段)
* * * * *
**上github找找有沒有成熟的方案**
在github上找到幾個很棒的庫,看來遇到問題時,上github找一找總會有意想不到的收獲哈^_^(你遇到的問題總會有前輩早就遇到了,并且已經有了成熟的、經得起考驗的解決方案了)
- [Search · lock](https://github.com/search?l=PHP&o=desc&q=lock&s=stars&type=Repositories&utf8=?)
- [malkusch/lock](https://github.com/malkusch/lock) \=\> [php-lock/lock](https://github.com/php-lock/lock)(Lock library to provide serialized execution of PHP code.)
- [BeatSwitch/lock](https://github.com/BeatSwitch/lock)
- [sunli1223/phplock](https://github.com/sunli1223/phplock)
* * * * *
**還找到一個任務管理的工具:**
- [jobbyphp/jobby](https://github.com/jobbyphp/jobby)
* * * * *
### 同步/異步、阻塞/非阻塞、并行/并發:
設定A為調用方,B為被調用方。
**同步:** A調用B,在B返回結果前,A被掛起阻塞,不能繼續向下執行,直到B執行完畢并返回結果,A才繼續向下執行。此為同步調用。
**異步:** A調用B,在B返回結果前,A不會被掛起阻塞,而是可以繼續向下執行,在B執行完畢后會發送系統通知給A。此為異步調用。(至于這個異步的實現方式以及消息通知,不同系統有自己的實現)
**阻塞非阻塞:** 我們知道 進程/線程 在三態模型中有三種狀態(阻塞態、就緒態、運行態),阻塞/非阻塞就是阻塞態的一種形式了。
**并行:** 多核CPU下,多個進程在各自單獨的核心上運行,同一時刻可以有多個進程在時間上同時運行。
**并發:** 單核CPU下,多個進程運行,CPU一個時間片(同一時刻)只能運行其中的一個進程,每個進程快速進行上下文切換(搶占CPU的時間片),由于太快,對于我們看起來就像是同時運行多個進程一樣。
#### 串行/并行
**同步(sync),是串行,阻塞的**
**異步(Asynchronous ),是并行,非阻塞的**
> 這兩組詞往往都是成組出現的
>[tip] **不同場景下,“同步”這個詞所表示的意思可能是不同的,比如生活中“同步”往往表示;兩個事物的運行頻率,如:數據與XX保持同步;同步開展工作;步調保持同步一致。**
**注意:這里的理解已不再準確,請參閱:**[IO模型 · php筆記 · 看云](http://www.hmoore.net/xiak/php-node/786007)、[同步、異步 · php筆記 · 看云](http://www.hmoore.net/xiak/php-node/785187)
* * * * *
### 代碼演練
```php
public function test()
{
$mutex = new CASMutex(4);
$amount = 0;
// 這是怎么做到保證同步串行的啊(其實理解錯了,這種做不到同步鎖)
$mutex->synchronized(function () use ($mutex, $amount) {
$i = 3;
while ($i > 0) {
echo $i . ' - ' . date('Y-m-d H:i:s') . '<br />';
ob_flush();
flush();
$i--;
sleep(1);
}
$mutex->notify();
});
echo "我出來了<br />";
ob_flush();
flush();
}
```
理解錯了,這個 `CASMutex` 并不能直接幫我們做實現鎖,官方實例是使用`$memcached->cas()`做鎖的,所以上面的代碼并沒什么用,是我們自己理解錯了。
接下來我們使用文件鎖的實現:
```php
public function test2()
{
echo "go~<br />";
ob_flush();
flush();
echo "獲取或等待鎖……<br />";
ob_flush();
flush();
$mutex = new FlockMutex(fopen(__FILE__, "r"));
// 可以自己去看一下實現,當閉包執行完就會自動釋放鎖
$mutex->synchronized(function () {
$i = 3;
while ($i > 0) {
echo $i . ' - ' . date('Y-m-d H:i:s') . '<br />';
ob_flush();
flush();
$i--;
sleep(1);
}
});
echo "我出來了<br />";
ob_flush();
flush();
}
```
這樣就實現了我們想要的鎖定一個程序的執行過程,對執行過程加鎖,只允許一個進程執行,不能允許多個進行并行。以保證我們的業務邏輯正確。(生成二維碼ID必須連續,那就意味這,這個操作需要被鎖定獨占才能滿足要求)
* * * * *
補充:忘了說我這個需求是解決什么問題,是解決一個,多行插入二維碼表(有可能我一次要插入幾十萬條數據),但是需要保證ID是連續的(自增ID),所以就需要保證同一時刻只有一個進程能進行生成操作,類似于進程鎖一樣,要保證同時只有一個進程執行,這樣才能保證我的業務邏輯正確,程序完全按照我預期的結果執行,在并發的時候不出現BUG。
* * * * *
### 擴展
[swoole 中的鎖及其應用](https://mp.weixin.qq.com/s/BSGpBKxC_1vGttoHYlpwUg)
- [Python3之多進程](http://www.toutiao.com/a6439858194382962945/?tt_from=weixin&utm_campaign=client_share&app=news_article&utm_source=weixin&iid=11683717705&utm_medium=toutiao_android&wxshare_count=1)
[進程與線程的一個簡單解釋 - 阮一峰的網絡日志](http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html)
[Linux 守護進程的啟動方法 - 阮一峰的網絡日志](http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html)
[死磕 Java 并發 - 深入分析 synchronized 的實現原理](https://juejin.im/entry/589981fc1b69e60059a2156a)
[內存鎖與內存事務 - 劉小兵2014](https://my.oschina.net/digerl/blog/34001#tt_daymode=1)
> 鎖有更復雜的實現如更新鎖,意向鎖,樂觀鎖,悲觀鎖,輕量級鎖,偏向鎖,自旋鎖等等,都是針對不同的需求特征或事務等級定制的鎖。事實上,這里面的很多只是使用了鎖的名字,所以其實并不是鎖,因為它們沒有起到排它的作用如自旋鎖,輕量級鎖,偏向鎖等等其實都不是鎖。特別是樂觀鎖,卻是通過不鎖來達到鎖的效果。
> 另一方面,根據被封鎖的對象的大小,又可以被分為行鎖,表鎖,頁鎖,或其它范圍鎖,特別是對于內存數據,根據內存數據的結構與層次,鎖的種類其實無限多。**但是在真正的開發工作中只要找到合適粒度的對象就可以了,不必過于較真。**
> 這意味著如果要在項目中啟用OO的范式進行系統開發,則對程序員的要求其實非常高。**因為對上面所述這些鎖機制的深刻理解,不是一個初入者可以很容易搞明白的東西。大部分的程序員都必須在碰了很多釘子以后才可能真正學會一些東西。**
> 我其實認為程序員的知識重點是應該擺在建模上面。但是世界并不完美,我們卻要工作。這就要求我們不得不擁有一些應付這個不完美世界的能力。換一句話,如果世界已經是完美的,為什么我們還需要工作呢?我們還要繼續努力,因為世界還不完美(這個有點象悖論,,,因為它看上去好象是我們工作的目的是為了讓世界更完美,但是事實卻是世界從沒有變得“更”完美或永遠也不可能完美。。。歸根結底,我們并不是世界。我們也不存在。真正存在的從頭到尾只不過是一些雄激素而已。我們以為我們存在,只不過雄激素讓我們以為我們存在然后我們便可以成為我們)。
* * * * *
[2017上半年的精華文章全在這里了](http://mp.weixin.qq.com/s/FplDkMl5Z0nSk9Id_SjS7Q)
[編程世界的那把鎖](http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513653&idx=1&sn=e30c18c0c1780fb3ef0cdb858ee5201e&chksm=80d67af6b7a1f3e059466302c2c04c14d097c1a5de01cf986df84d4677299542f12b974dfde3&scene=21#wechat_redirect)
>[danger] ……你看,單線程的邏輯正確并不表示多線程并發運行時的邏輯也能正確。
[加鎖還是不加鎖,這是一個問題](http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513692&idx=1&sn=ef2416a4bb96d64db77e32d5b4c7967e&chksm=80d67a9fb7a1f3898e513cc1d9e96841610bb84aed2dc24cab2d403e74e317e3c447e45e7611&scene=21#wechat_redirect)
>[info] 你們之前的synchronized 叫做悲觀鎖, 元老院太悲觀了,總是怕你們把事情搞砸,你看現在樂觀一點兒, 不也玩的挺好嘛! 每個線程都不用阻塞,不用在那個無聊的鎖池里待著。 要知道,阻塞,激活是一筆不小的開銷啊。
* * * * *
鎖的本質是什么啊,核心是怎么做的,這個應該需要了解系統底層吧。
較真就是和自己過不去,人生苦短,最怕一生碌碌無為,還說平凡難能可貴。
* * * * *
### php真的是最好的編程語言嗎?
雖然說php是最好的編程語言,但是其實php相比java來說,確實在有些方面不是很成熟,比如php本身沒有提供鎖機制,也沒有很好的對多進程的支持和管理的方案,這些都是php的弱點。
正是php本身還存在一些問題,所以才會有一些如swoole之類的擴展的生存空間了。
我們來看一下php對自己的描述就知道了:
> PHP is a popular general-purpose scripting language that is especially suited to web development
> by: https://github.com/php
> Java was originally developed as an alternative to the C/C++ programming languages. It is now mainly used for building desktop, Android, and web server applications. Java is owned and licensed through Oracle.
> by: https://github.com/topics/java
所以任何一個東西有它的優點,就會有缺點,似乎沒有最好的,一攬子的完美方案,只能根據具體場景來看怎么選擇最合適的,最合適的就是最好的。
* * * * *
[\[求助\] PHP有沒有高效的鎖同步方案?](http://m.newsmth.net/article/PHP/94666)
[PHP的多進程互斥和鎖定機制討論_miceCMS開發團隊](http://blog.sina.cn/dpool/blog/s/blog_5ed1dcc70100d3qk.html?vt=4)
[請問搶購的時候為什么選擇用PHP文件鎖而不是MySQL鎖機制,PHP文件鎖與其對比有什么特殊優點嗎? - SegmentFault](https://segmentfault.com/q/1010000006782974)
[高并發下常見的緩存鎖機制的PHP實現 - PHPERZ中文資訊站](http://www.phperz.com/article/14/1112/8344.html)
[php 根據url自動生成縮略圖并處理高并發的問題解決方法 - PHPERZ中文資訊站](http://www.phperz.com/article/15/0123/5186.html)
> 任何時候都要考慮并發,只有考慮周到,才能設計出來高性能,穩定的系統。
[用memcached實現的php鎖機制 - 為程序員服務](http://ju.outofmemory.cn/entry/48924)
[網頁搜索_php鎖機制](https://so.m.sm.cn/s?q=php%E9%94%81%E6%9C%BA%E5%88%B6&by=relative&uc_param_str=dnntnwvepffrgibijbprsvdsme&dn=8747331402-4c93fc31&nt=99&nw=4G&ve=11.0.8.858&pf=145&fr=android&gi=bTkwBOl9HXrDhv3l6Di3lsqk6blEtdR9eWcYChOe27u8rio%3D&bi=800&jb=0&pr=UCMobile&sv=ucrelease1&ds=bTkwBBipMwOIa6gfb2LWSAGEXNqcc6h8lJ5chd5kXXjq7Q%3D%3D&me=AAQCzU1n1Bku3%2FKOD%2BdXI%2BXs&from=ucframe&uc_sm=1)
[網頁搜索_php沒有java的鎖](https://so.m.sm.cn/s?q=php%E6%B2%A1%E6%9C%89java%E7%9A%84%E9%94%81&uc_param_str=dnntnwvepffrgibijbprsvdsme&from=ucframe&uc_sm=1)
[網頁搜索_php監聽數據變更](https://so.m.sm.cn/s?uc_param_str=dnntnwvepffrgibijbprsvdsme&dn=8747331402-4c93fc31&nt=99&nw=4G&ve=11.0.8.858&pf=145&fr=android&gi=bTkwBOl9HXrDhv3l6Di3lsqk6blEtdR9eWcYChOe27u8rio%3D&bi=800&jb=0&pr=UCMobile&sv=ucrelease1&ds=bTkwBBipMwOIa6gfb2LWSAGEXNqcc6h8lJ5chd5kXXjq7Q%3D%3D&me=AAQCzU1n1Bku3%2FKOD%2BdXI%2BXs&from=ucframe&by=tuijian&by2=weini&sc=&q=php%E7%9B%91%E5%90%AC%E6%95%B0%E6%8D%AE%E5%8F%98%E6%9B%B4&uc_sm=1)
[php + mysql 這個下單流程怎么確保數據的完整性-云棲社區](https://m.aliyun.com/yunqi/ask/4094)
> 盜我在SF的問題啊,https://segmentfault.com/q/1010000002600161
[php中有沒有什么方法可以實現實時監聽數據庫中的某張..._慕課問答](http://www.imooc.com/qadetail/71289)
> 實時只有兩種,你要實時得到你要監聽的事物的狀態,要么事物發生變化時自己通知你,要么你定時輪詢查看它。
[我是一個線程(修訂版)](http://mp.weixin.qq.com/s/-BMCUuIWYE3O_oC3ZNNJRg)
[JAVA CAS原理深度分析 - CSDN博客](http://blog.csdn.net/hsuxu/article/details/9467651)
> 確保對內存的讀-改-寫操作原子執行。在Pentium及Pentium之前的處理器中,帶有lock前綴的指令在執行期間會鎖住總線,使得其他處理器暫時無法通過總線訪問內存。
>[danger] 可見,內部還是用的鎖,那怕是到cpu層面,也是用的鎖(CPU的鎖),不然對共享變量讀寫時怎么保證數據的正確性,雖然我們平時寫代碼不用考慮這個問題,編程語言對于我們來說總是安全的,但是計算機內部其實也做了安全考量和優化,以保證我們認為的那些再正常和普通的操作得以正確的執行,只不過我們忽略掉了操作系統/編程語言內部為此所做的努力而已。
[一篇文章看懂Java并發和線程安全](https://www.toutiao.com/a6513707798830776846/?tt_from=weixin&utm_campaign=client_share×tamp=1516737598&app=news_article&utm_source=weixin&iid=22069500288&utm_medium=toutiao_android&wxshare_count=1)
[基于Redis的分布式鎖到底安全嗎(下)?](https://mp.weixin.qq.com/s?__biz=MzA4NTg1MjM0Mg==&mid=2657261521&idx=1&sn=7bbb80c8fe4f9dff7cd6a8883cc8fc0a&chksm=84479e08b330171e89732ec1460258a85afe73299c263fcc7df3c77cbeac0573ad7211902649&scene=21#wechat_redirect)
> 一個進程持有鎖L,發起了請求R,但是請求失敗了。另一個進程獲得了鎖L并在請求R到達目的方之前執行了一些動作。如果后來請求R到達了,它就有可能在沒有鎖L保護的情況下進行操作,帶來數據不一致的潛在風險。
* * * * *
鎖的本質其實是對那些需要使用競態資源的操作進行排隊處理。
鎖的本質作用就是要**控制對競態資源的訪問(讀和寫)**,數據庫的行鎖,表鎖,文件鎖,內存鎖,總線鎖,任何鎖,只要能達到**控制對競態資源的訪問的目的**,那么本質就沒有什么不同。
比如開發中,需要鎖住用戶,如果數據庫不支持,那么用文件鎖也是可以的(用戶ID生成文件名的KEY),**只要所有實例都遵循相同的數據訪問規則,那么鎖的工作就是正常的。** 不過如果數據庫支持,那么直接鎖數據庫就更好了。
**所有實例都遵循相同的數據訪問規則:**
> <span style="color:red;">只有將所有線程都設計成遵守相同的數據訪問規則,互斥機制才能正常工作。</span> **操作系統并不會為我們做數據訪問的串行化。** 如果允許其中的某個線程在沒有得到鎖的情況下也可以訪問共享資源,那么即使其他的線程在使用共享資源前都申請鎖,也還是會出現數據不一致的問題。(UNIX環境高級編程第三版 P-380)
* * * * *
[伯克利推出世界最快的KVS數據庫Anna:秒殺Redis和Cassandra](http://mp.weixin.qq.com/s/3WmGpZkEuSz-ox_2CPCsqg)
> 針對同一個鍵值的并行更新操作會被串行化,它們需要同步機制來防止多個線程同時更新同一個鍵值。
[編程中的14種鎖,你知道幾個?](https://www.toutiao.com/a6534966157194035719/?tt_from=weixin&utm_campaign=client_share×tamp=1521551602&app=news_article_lite&utm_source=weixin&iid=25315997380&utm_medium=toutiao_android&wxshare_count=1)
[支付平臺的架構設計](https://mp.weixin.qq.com/s/QDqtnj02gfxdlyfTKOOnGQ)
> **鎖的本質其實是排隊,不同的鎖排隊的空間和時間不同而已。**
> **其實都是將并行強制變為串行的解決方案。**
[Java中如何提升鎖性能](https://mp.weixin.qq.com/s/xQnV9PC6vzMjal-ED-XNjw)
[Java并發編程之阻塞隊列](https://mp.weixin.qq.com/s/6XXpPykDtFfnAgnEXrZLKA)
[如何優雅地用Redis實現分布式鎖](https://mp.weixin.qq.com/s/EcrVp0dOpnc4FniaBlezcA)
>[danger] **鎖其實并不是要鎖數據本身,鎖的本質是要鎖操作的(對操作進行排隊),即控制操作的順序**,比如,針對同一條數據的多路操作,1. 消息發送操作;2. 庫存扣減操作,這兩路(類)操作要改的數據雖然在同一行,但其實它們并不是競爭關系,它們各自的目標數據是相互獨立的不同字段,所以并不算共享數據(競態資源),如果把鎖放在數據行上,那就太影響性能了,**其實這兩路操作互不影響,只是各自要防止同類操作的多進程并發而已,而不是防對方**,所以只要用針對各路操作的鎖就可以了,這才是鎖的本質意義。(通常說的狀態其實也是這個意思,可理解為操作的狀態,如:操作進行中,操作已完成/當前無操作)
~~~
然而這樣就能保證鎖一定會被釋放嗎?考慮這樣一種情況:代碼執行到doSomething()方法的時候,服務器宕機了,這個時候finally代碼塊就沒法被執行了,因此在這種情況下,該鎖不會被正常釋放,在上述案例中,可能會導致任務漏算。因此,這種方案的第一個問題是會出現鎖無法正常釋放的風險,解決這個問題的方法也很簡單,Redis設置key的時候可以指定一個過期時間,只要獲取鎖的時候設置一個合理的過期時間,那么即使服務器宕機了,也能保證鎖被正確釋放。
要考慮任何的極端情況,做好容錯,留后路,未雨綢繆,保證萬無一失。
仔細分析一下getLock方法,該方法并不是原子性的,當一個客戶端檢查到某個鎖不存在,并在執行setKey方法之前,別的客戶端可能也會檢查到該鎖不存在,并也會執行setKey方法,這樣一來,同一把鎖就有可能被不同的客戶端獲取到了。獲取鎖必須是原子操作
那么這種方案就沒有問題了嗎?很遺憾地說,這種方案也是有問題的。原因在于上述釋放鎖的操作不是原子性的
只要不是原子操作,就不得不仔細考慮其并發問題!
~~~
[漫畫:什么是分布式鎖?](https://mp.weixin.qq.com/s/8fdBKAyHZrfHmSajXT_dnA)
>[danger] **原子性的重要性,任何極端情況都要考慮**,無論什么極端情況都要保證數據可靠性,這是最重要的,否則其它的談都不用談。試問連數據可靠性都不能保證的系統誰敢用,過家家嗎,所以要重視這點,必須嚴謹。
>
> **原子性其實本質就是代碼,指令的原子性。** 一個操作可能有多行代碼,多個指令組成,這些代碼,指令的執行細節,都成功,都失敗,結果一致性,就顯得重要了,這和事務內的多條sql一樣,**嚴謹的軟件必須考慮原子性。時刻記住,你必須對你寫的每一條代碼負責**,你寫下它時有沒有考慮過它會不會執行失敗,和會不會被執行,**如果沒有被執行或者執行失敗你有應對措施沒有,還是不暇\[假\]思索,心安理得,一廂情愿的認為它一定會執行,一定會執行成功**,而不作任何考慮。**你的良心不會痛嗎?** 是什么給了你勇氣讓你如此自信,如果真是這樣,可以說你寫的軟件是弱雞,已經千瘡百孔了,毫無安全性可言。
>
> **這并不是鉆牛角尖,自找麻煩,也不是杞人憂天,俗話說小心使得萬年船,更何況代碼即科學,而科學必須嚴謹,必須一絲不茍不能怕麻煩,否則就不是科學了。** 這并不是說我們平常寫代碼/做事時要經常 [瞻前顧后、謹小慎微](https://www.zybang.com/question/7b973c6578ef148dd67e79126098b9d2.html),**而是對待科學要有對待科學應該有的嚴謹、務實的態度。**
>
> 代碼即科學,科學工作必須要**求真務實**,模棱兩可和不求甚解是不行的。
[漫畫:如何用Zookeeper實現分布式鎖?](https://mp.weixin.qq.com/s/u8QDlrDj3Rl1YjY4TyKMCA)
> php的運行模式和java不同,不是在一個JVM中運行的,每個進程彼此獨立,沒有統一管理的,所以php中要實現鎖,就相當于這個分布式環境中鎖的關系了。那個文件鎖,是php發出的系統調用,如果php進程掛了,操作系統應該會檢測到并自動釋放文件鎖吧。
>
> 系統并沒有直接提供鎖,鎖其實是一種業務的產物,都是通過某種約定,規則,或方式來實現鎖的,哪怕系統底層直接提供鎖,其實也是通過一些方式實現的,而我們無需關系怎么實現,只要知道可以達到鎖的目的就可以了。所以什么是鎖,鎖并不是指某個東西,門上一把鎖,組織你不能進,這個鐵東西就是鎖,指示燈顯示廁所里面有人不能進,這個有人不能進的規則就是鎖,約束了你不能進。所以鎖是寬泛的,它的意義就是指定某種規則,讓其它人遵守,以達到某種目的。
[我是一個線程(修訂版)](https://mp.weixin.qq.com/s/-BMCUuIWYE3O_oC3ZNNJRg)
> 鎖順序要保持一致才不會出現死鎖,也就是要遵循相同的規則。
[為什么分布式一定要有redis?](https://mp.weixin.qq.com/s/gEU8HtsQNPXY8bzkK-Qllg)
> 一剎那者為一念,二十念為一瞬,二十瞬為一彈指,二十彈指為一羅預,二十羅預為一須臾,一日一夜有三十須臾。
> 那么,經過周密的計算,一瞬間為0.36 秒,一剎那有 0.018 秒.一彈指長達 7.2 秒。
>[danger] **任何東西都要去深入思考,做到面面俱到,考慮每一種可能性,應對每一種突發情況,這樣才能保證是嚴謹的。** 避重就輕,拈輕怕重,避實就虛,怕麻煩,不肯深入問題的本質,這樣永遠都只會停留在表面,無法解決核心問題。
[【原創】分布式之延時任務方案解析 - 孤獨煙 - 博客園](https://www.cnblogs.com/rjzheng/p/8972725.html)
[【原創】分布式之消息隊列復習精講 - 孤獨煙 - 博客園](http://www.cnblogs.com/rjzheng/p/8994962.html)
[重入鎖是什么意思,鎖降級有什么好處-CSDN論壇](https://bbs.csdn.net/topics/390276124)
[[MySQL不為人知的主鍵與唯一索引約束](https://mp.weixin.qq.com/s/IE31GSDP0Ndjzc8kFj4Ukw)]()
[從單一架構到分布式交易架構,網易嚴選的成功實踐](https://mp.weixin.qq.com/s/nv3Ht7OqTYQw31QFDX3gNg)
>[danger] **沒有完美的架構設計,世上也沒有絕對的事情,沒有誰能保證絕對可靠、安全和高可用,但我們有補償和容錯(類似還有重試,確認等機制),也是能做到萬無一失的。**
* * * * *
[關于Java鎖機制面試官會怎么問](https://mp.weixin.qq.com/s/no6S3PUYoCjKLyzuYtOqSQ)
* * * * *
### 鎖的本質
**鎖是一種規定共享資源的訪問規則的強制手段。**
系統并沒有直接提供鎖,鎖其實是一種業務的產物,都是通過某種約定,規則,或方式來實現鎖的,哪怕系統底層直接提供鎖,其實也是通過一些方式實現的,而我們無需關系怎么實現,只要知道可以達到鎖的目的就可以了。所以什么是鎖,鎖并不是指某個東西,門上一把鎖,組織你不能進,這個鐵東西就是鎖,指示燈顯示廁所里面有人不能進,這個有人不能進的規則就是鎖,約束了你不能進。所以鎖是寬泛的,它的意義就是指定某種規則,讓其它人遵守,以達到某種目的。
[七大進程間通信和線程同步](https://mp.weixin.qq.com/s/VV_mTpuOYFIZRb94mwhr2Q)
* * * * *
### 從業務代碼來看鎖的應用案例
**鎖key命名:** 操作類型 [+ 競態資源ID(競態資源唯一標識)]
>[tip] (競態資源ID不是必須的,有時也會有直接所某項操作的情況,比如code項目發貨操作QrCode:add()和Order:delivery()操作)
>[danger] **確保:** 同一時刻,只能有一個進程 **“對此數據執行此種操作”** ,保證 **“對此數據執行的此種操作”** 是串行的,從而不會出現并發問題。 **(請仔細體會這句話的每一個字)**
```php
// 準備鎖文件
// lock:鎖這路操作,而不是直接鎖數據行!
// 鎖key命名:操作類型 [+ 競態資源ID(競態資源唯一標識)](競態資源ID不是必須的,有時也會有直接所某項操作的情況,比如code項目發貨操作QrCode:add()和Order:delivery()操作)
// **確保:同一時刻,只能有一個進程“對此數據執行此種操作”,保證“對此數據執行的此種操作”是串行的,從而不會出現并發問題。(仔細體會這句話的每一個字)**
$lock_key = 'intention_notice_' . $id;
$lockFile = config('lock_path') . $lock_key;
file_put_contents($lockFile, date('Y-m-d H:i:s'), LOCK_EX);
$mutex = new \malkusch\lock\mutex\FlockMutex(fopen($lockFile, "r")); // 鎖住
return $mutex->synchronized(function () use ($bankAccount, $amount) {
// 同步操作
// 這部分加鎖了的,為同步操作,能保證這部分代碼同一時刻只有一個進程在執行
$balance = $bankAccount->getBalance();
$balance -= $amount;
if ($balance < 0) {
throw new \DomainException("You have no credit.");
}
$bankAccount->setBalance($balance);
});
// 釋放鎖(PHP腳本執行完畢后結束進程,就會自動交出鎖,申請文件鎖其實是一次系統調用)
```
>[tip] 思考:執行系統調用的程序突然被 kill 了,那么系統會怎么回收資源呢,比如當執行的系統調用為一個文件鎖時,系統應該會自己回收這個鎖吧。在系統層面來說,文件鎖其實是一個被控制獨占訪問的系統資源,而獨占能力是由操作系統提供的。
#### 更多案例
```php
https://github.com/php-lock/lock
Mutex::synchronized() executes code exclusively. This method guarantees that the code is only executed by one process at once. Other processes have to wait until the mutex is available. The critical code may throw an exception, which would release the lock as well.
`Mutex::synchronized()` 會同步執行代碼。這將保證代碼同一時刻只能被一個進程所執行,而其他進程在此過程中則必須等待直到得到互斥鎖。關鍵代碼可能拋出異常,這將釋放鎖。
-----------
https://coding.net/u/xiasf/p/code/git/blob/master/application/admin/controller/QrCode.php
// 生成二維碼
public function add(Request $request)
{
// 使用文件鎖,使生產二維碼的操作不能夠并發執行,以保證起止連續,程序的邏輯才能正確
$mutex = new FlockMutex(fopen(__FILE__, "r"));
return $mutex->synchronized(function () use ($request) {
// 生成邏輯……
});
}
```
* * * * *
### 形象展示為什么會有并發問題

這就是并發問題,3操作對于進程2是不可見的,所以4這一刻還以為狀態還是2時的,但其實不是的,也就是持有了舊數據,既然持有舊數據,那么程序可能就會和預期不一致,這就是并發問題。
小明 可用
小紅 可用
小明 改為不可用
小紅 剛看了是可用的
**用鎖的話:**
小明 只能我一個人看,可用
小紅 有人再看,那我等他看完了我再看(什么都不做,就守在這等著他)
小明 改為不可用,走了
小紅 輪到我看了,也只準我一個人看,不可用
小紅 不可用啊,那我走了
#### 有鎖后的特性
數據不會被兩個人同時共享,每個人,每一刻持有的都是最新數據,不可能持有到舊數據。
將并行問題變成串行來解決,這也體現了鎖其實就是一種排隊,或者說是控制并發程序訪問共享資源的順序和規則。
* * * * *
### php與Java
java是老大哥,對很多東西都提供一攬子解決方案,并且是官方的。而php就不是這樣,很多時候并沒有官方的解決方案,比如沒有鎖的實現,每個進程相互獨立。這么來比喻,java五臟俱全,php靈活自由。如果你要用php寫出高級功能,需要具備深厚的計算機知識,而java很多都是開箱即用的。java是一套上古神兵(注意是一套哦),php是一把絕世好劍。
* * * * *
### 帶有“候補”功能的分布式鎖編排服務
> 鎖的本質是為了保證業務的并發安全性,即鎖是服務于業務的,所以我們并不是單純的 key 為真正的鎖,這也是為什么 goods:1 要等待 goods:1-user:2 釋放才能獲得鎖的原因,因為他們存在業務的競態關系,而不只是單純的看 key,這也正是鎖編排的**編排**意義的體現。
1. 請求鎖,沒請求到鎖就支持重試(支持重試次數和超時時間參數)
2. 請求鎖,沒有請求到鎖時,嘗試提交候補(候補也有限額的)
3. 鎖釋放時,優先給到候補列表中的等待者
4. 提前定義好編排規則,使用時一次性提前獲取業務過程中的全部所需鎖(如 goods:1-user:2),以避免出現死鎖
5. 鎖分析(超時鎖統計,耗時鎖分析,鎖使用情況統計等等,方便定位程序分析優化業務代碼)
6. 鎖告警(超時鎖,耗時鎖 發生告警通知)
----
### 鎖的公平性
輪詢爭奪的鎖表面上看起來是公平的,每個請求都有機會獲取到鎖,誰能獲取到鎖,往往看運氣(網絡到達、進程調度等因素),看起來很公平是嗎,但其實并不公平。
試想一下大家去窗口買票時都拼命往里面擠,誰力氣大,誰拳頭硬,誰就能能擠到前面去先買到票,仔細想一下這公平嗎,這算哪門子公平,弱小的人擠哭了都買不到票,擠不過別人,就算來得早也沒用,這是弱肉強食的叢林法則,不是文明社會的秩序公平。
所以我們日常使用的并不是公平的鎖,那么什么是公平的鎖?你應該問下那個弱小擠不進去買不到票的人:“文明排隊,先來后到!”
是啊,排隊,先來后到,這樣才公平嘛。
要獲取鎖,得先登記,等輪到你了自然會把鎖給你,不用爭,不用搶,不用打架,人人都能買到票。想要先買到票就別睡懶覺早點來排隊,早起的鳥兒有蟲吃,沒有比這更公平的了。
公平鎖除了比非公平鎖在道德上更公平外,還有性能優勢,由于公平鎖采用排隊方式,所以每個人只用關注自己前面一個人有沒有排到了就行,大家不用再像一群惡狼一樣盯著可憐的窗口,而那些在擠啊擠中摩擦產生的能量都被浪費掉了,最終只有一個人搶到票,其他人都精疲力竭,敗興而歸,這是極大的浪費,不符合勤儉節約的精神。
> 但非公平鎖也并不是一無是處,因為公平鎖的缺點也很明顯,它實現起來要比非公平鎖繁瑣不少,反而非公平鎖實現起來簡單粗暴,使用方便,更適合在某些簡單的場景下使用。
但肯定又有較真的人會跳出來說:“大家在登記時不也存在爭搶的情況嗎,不還是不公平”,這么說吧,取票叫號的窗口絕對沒有自助打飯的窗口熱鬧!想想那些打自助餐時在你前面磨磨唧唧選擇困難癥的人們吧,可不急死你了是嗎,哈哈哈。—— 我們不能完全做到公平,但我們可以去努力改變,努力讓世界變得更加公平!
https://github.com/HelloGitHub-Team/HelloZooKeeper/blob/main/content/2/content.md
[圖解:為什么非公平鎖的性能更高?](https://juejin.cn/post/6998317686836953124)
> 和我們的觀點相反
----
### 寫鎖的讀
查詢的操作會加讀鎖,讀鎖只與寫鎖沖突,所以多個請求并發同時來讀是可以的,但是如果某個時刻正在寫數據,其他的讀操作就會被阻塞,直到寫操作完成,寫鎖釋放后,讀鎖才能獲取到。
不讓讀“快照副本”是因為,這樣會導致理論上的爭議:某個時刻數據正在寫,寫操作還沒有完成,也就不確定數據最終的內容,此時有人來讀數據你期望讀到什么?
有人可能會說:“還沒寫完那你就把之前的數據先給我吧,我不在乎你正在寫的所謂的最新數據,你不要讓我等就行”。但較真的人會說:“我要讀的就是此時此刻的數據,而此時的數據還在寫,那我肯定要等一下的,等你寫完了再給我不就行了,你拿舊數據給我有什么意義!不要害朕!!!”
有的時候,對于一些重要的讀操作,也有給讀操作加寫鎖的情況,加了寫鎖的讀操作就不能和其他的請求同時讀/寫了,這么做是為了保證,某個時刻 只有這一個 讀操作 能接觸到數據(讀和寫)。這樣做雖然會降低系統吞吐,但通常只是在重要的時刻使用,并且是把數據讀到內存后就立馬釋放鎖了,后續對數據的操作在內存中進行操作,所以這個 寫鎖的讀 只有一瞬間而已,不會有性能問題。(至于到底什么要的重要操作,非這樣不可,暫時還沒想到這樣有什么特別的意義)
讀鎖底層是什么樣的,真的存在所謂的 同時 “讀”嗎?
https://github.com/HelloGitHub-Team/HelloZooKeeper/blob/main/content/4/content.md
----
### 現實世界的鎖
鎖并不是只有開發時需要用到,它并不是編程世界的專有,相反軟件開發中的一些概念其實都源于生活,是對生活中某些場景的描述。現實世界中也有很多鎖的例子,我們每天都在和它打交道,只是它已融入我們的生活,以至于忘記關注到它們的存在了。
鎖對應現實的例子:
1. 交通紅綠燈??
2. 門鎖
3. 取票叫號
如果沒有鎖會怎么樣,上廁所不鎖門你會很尷尬;紅綠燈罷工,交通會癱瘓,會堵車,會發生更多的車禍;...。
**鎖無處不在,因為資源是有限的,總是會發生負載**,馬路上總不止一輛車,飯店不止一個人要吃飯,...。所以 鎖即排隊,即秩序的守護者。
----
[Python Twisted介紹_技術筆記本-CSDN博客_twisted](https://blog.csdn.net/hanhuili/article/details/9389433)
> 在單線程同步模型中,任務按照順序執行。如果某個任務因為I/O而阻塞,其他所有的任務都必須等待,直到它完成之后它們才能依次執行。這種**明確的執行順序和串行化處理的行為**是很容易推斷得出的。**如果任務之間并沒有互相依賴的關系,但仍然需要互相等待的話這就使得程序不必要的降低了運行速度。**
> 通常我們寫的代碼,都是自上而下按順序執行的,即都是 “明確的執行順序和串行化處理的行為”。
----
last update:2018-8-6 17:18:37
- 開始
- 公益
- 更好的使用看云
- 推薦書單
- 優秀資源整理
- 技術文章寫作規范
- SublimeText - 編碼利器
- PSR-0/PSR-4命名標準
- php的多進程實驗分析
- 高級PHP
- 進程
- 信號
- 事件
- IO模型
- 同步、異步
- socket
- Swoole
- PHP擴展
- Composer
- easyswoole
- php多線程
- 守護程序
- 文件鎖
- s-socket
- aphp
- 隊列&并發
- 隊列
- 講個故事
- 如何最大效率的問題
- 訪問式的web服務(一)
- 訪問式的web服務(二)
- 請求
- 瀏覽器訪問阻塞問題
- Swoole
- 你必須理解的計算機核心概念 - 碼農翻身
- CPU阿甘 - 碼農翻身
- 異步通知,那我要怎么通知你啊?
- 實時操作系統
- 深入實時 Linux
- Redis 實現隊列
- redis與隊列
- 定時-時鐘-阻塞
- 計算機的生命
- 多進程/多線程
- 進程通信
- 拜占庭將軍問題深入探討
- JAVA CAS原理深度分析
- 隊列的思考
- 走進并發的世界
- 鎖
- 事務筆記
- 并發問題帶來的后果
- 為什么說樂觀鎖是安全的
- 內存鎖與內存事務 - 劉小兵2014
- 加鎖還是不加鎖,這是一個問題 - 碼農翻身
- 編程世界的那把鎖 - 碼農翻身
- 如何保證萬無一失
- 傳統事務與柔性事務
- 大白話搞懂什么是同步/異步/阻塞/非阻塞
- redis實現鎖
- 淺談mysql事務
- PHP異常
- php錯誤
- 文件加載
- 路由與偽靜態
- URL模式之分析
- 字符串處理
- 正則表達式
- 數組合并與+
- 文件上傳
- 常用驗證與過濾
- 記錄
- 趣圖
- foreach需要注意的問題
- Discuz!筆記
- 程序設計思維
- 抽象與具體
- 配置
- 關于如何學習的思考
- 編程思維
- 談編程
- 如何安全的修改對象
- 臨時
- 臨時筆記
- 透過問題看本質
- 程序后門
- 邊界檢查
- session
- 安全
- 王垠
- 第三方數據接口
- 驗證碼問題
- 還是少不了虛擬機
- 程序員如何談戀愛
- 程序員為什么要一直改BUG,為什么不能一次性把代碼寫好?
- 碎碎念
- 算法
- 實用代碼
- 相對私密與絕對私密
- 學習目標
- 隨記
- 編程小知識
- foo
- 落盤
- URL編碼的思考
- 字符編碼
- Elasticsearch
- TCP-IP協議
- 碎碎念2
- Grafana
- EFK、ELK
- RPC
- 依賴注入
- 科目一
- 開發筆記
- 經緯度格式轉換
- php時區問題
- 解決本地開發時調用遠程AIP跨域問題
- 后期靜態綁定
- 談tp的跳轉提示頁面
- 無限分類問題
- 生成微縮圖
- MVC名詞
- MVC架構
- 也許模塊不是唯一的答案
- 哈希算法
- 開發后臺
- 軟件設計架構
- mysql表字段設計
- 上傳表如何設計
- 二開心得
- awesomes-tables
- 安全的代碼部署
- 微信開發筆記
- 賬戶授權相關
- 小程序獲取是否關注其公眾號
- 支付相關
- 提交訂單
- 微信支付筆記
- 支付接口筆記
- 支付中心開發
- 下單與支付
- 支付流程設計
- 訂單與支付設計
- 敏感操作驗證
- 排序設計
- 代碼的運行環境
- 搜索關鍵字的顯示處理
- 接口異步更新ip信息
- 圖片處理
- 項目搭建
- 閱讀文檔的新方式
- mysql_insert_id并發問題思考
- 行鎖注意事項
- 細節注意
- 如何處理用戶的輸入
- 不可見的字符
- 抽獎
- 時間處理
- 應用開發實戰
- python 學習記錄
- Scrapy 教程
- Playwright 教程
- stealth.min.js
- Selenium 教程
- requests 教程
- pyautogui 教程
- Flask 教程
- PyInstaller 教程
- 蜘蛛
- python 文檔相似度驗證
- thinkphp5.0數據庫與模型的研究
- workerman進程管理
- workerman網絡分析
- java學習記錄
- docker
- 筆記
- kubernetes
- Kubernetes
- PaddlePaddle
- composer
- oneinstack
- 人工智能 AI
- 京東
- pc_detailpage_wareBusiness
- doc
- 電商網站設計
- iwebshop
- 商品規格分析
- 商品屬性分析
- tpshop
- 商品規格分析
- 商品屬性分析
- 電商表設計
- 設計記錄
- 優惠券
- 生成唯一訂單號
- 購物車技術
- 分類與類型
- 微信登錄與綁定
- 京東到家庫存系統架構設計
- crmeb
- 命名規范
- Nginx https配置
- 關于人工智能
- 從人的思考方式到二叉樹
- 架構
- 今日有感
- 文章保存
- 安全背后: 瀏覽器是如何校驗證書的
- 避不開的分布式事務
- devops自動化運維、部署、測試的最后一公里 —— ApiFox 云時代的接口管理工具
- 找到自己今生要做的事
- 自動化生活
- 開源與漿果
- Apifox: API 接口自動化測試指南