最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

【淺嘗高并發(fā)編程】接私活差點(diǎn)翻車

2023-03-03 17:10 作者:兩年半的java練習(xí)生  | 我要投稿


真愛粉別尬黑

前言

作為一名本本分分的練習(xí)時長兩年半的Java練習(xí)生,一直深耕在業(yè)務(wù)邏輯里,對并發(fā)編程的了解僅僅停留在八股文里。一次偶然的機(jī)會,接到一個私活,核心邏輯是寫一個?定時訪問api把數(shù)據(jù)持久化到數(shù)據(jù)庫的小服務(wù)。


期間遇到了很多坑還挺有意思,做出來很簡單,做得好還是挺難的,這里跟大家分享一下。


maven引入外部jar包部署

項(xiàng)目背景是某家廠商要對接第三方支付公司的open api拿到每日商品銷售量與銷售額,第三方支付公司就是嘩啦啦,這里吐槽下嘩啦啦做的開放文檔寫的是真撈。。。

首先要把嘩啦啦這邊提供的jar包引入到我們的服務(wù)里,本地開發(fā)直接引入即可可以用maven的一條命令直接把本地的jar包打到本地倉庫里。

但是這樣部署服務(wù)的時候就會發(fā)現(xiàn)打不出jar包來,項(xiàng)目能跑,但是到關(guān)鍵的調(diào)用sdk的時候就報(bào)ClassNofFoundException錯誤。

需要在pom里配置好引入外部jar包的插件才行,這里算是一個小坑。

上線程池

客戶大約有150個門店,這樣阻塞請求下來僅僅是請求api的耗時就將近半小時,還不算入庫的時間。


這時候根據(jù)熟讀并背誦的八股文只是,要充分利用cpu的能力無腦選擇線程池,于是起了一個核心線程20個的線程池去并發(fā)請求api數(shù)據(jù)入庫。部署之后發(fā)現(xiàn)總有這么幾條錯誤日志。

果然背八股文救不了中國,根據(jù)報(bào)錯判斷出入庫時死鎖了。。。。


追查死鎖

因?yàn)槲以诜?wù)里用了mybatis-plus的orm框架,而且數(shù)據(jù)是一些訂單數(shù)據(jù)存在一些狀態(tài)變更,然后服務(wù)要同步的數(shù)據(jù)可能會有更改之前舊數(shù)據(jù)的場景,所以就用了Mybatis里的SaveOrUpdate方法,壞事就壞事在這上面。




這個東西還要從mysql數(shù)據(jù)庫的隔離級別開始說起,眾所周知,按照八股文里mysql隔離級別默認(rèn)情況下為可重復(fù)讀,那可重復(fù)讀隔離機(jī)制下避免了臟讀,不可重復(fù)讀,日常開發(fā)里也并沒有出現(xiàn)過幻讀,看似MVCC多版本并發(fā)控制幫我們避開了幻讀。

其實(shí)不然,幻讀的概念與不可重復(fù)讀相似,不可重復(fù)讀讀到了別人update的數(shù)據(jù),幻讀讀到了別人insert/delete的數(shù)據(jù)。一個事務(wù)在讀取了其他事務(wù)新增的數(shù)據(jù),仿佛出現(xiàn)了幻想。

這里先簡單說一下,mysql在可重復(fù)讀隔離級別下會為每個事務(wù)當(dāng)前讀的時候加間隙鎖,后續(xù)會寫一篇mysql在可重復(fù)讀的隔離級別下如何解決幻讀文章。

那怎么處理的呢,時間緊任務(wù)重,仔細(xì)一想這個數(shù)據(jù)庫基本上全是往里增刪改的動作,查詢的動作幾乎沒有,那為什么不把它隔離級別降級,降成讀已提交,這樣間隙鎖就不生效了。

很完美,后續(xù)也驗(yàn)證了這個問題,再也沒出現(xiàn)過數(shù)據(jù)庫的死鎖情況。

數(shù)據(jù)庫鏈接丟失


這個問題是真滴惡心,客戶買的服務(wù)器拉的一批,還買windows服務(wù)器,這年頭正經(jīng)人誰用windows,用客戶端連都經(jīng)常丟失鏈接。遇到這個問題十分棘手,那不解決數(shù)據(jù)就永遠(yuǎn)不準(zhǔn)確。

但是想根治這個問題又得不償失,甲方選windows還不就是看重了可視化界面了。這時候再讓他們遷移服務(wù)器肯定不可能。

于是為了解決這個就瘋狂在網(wǎng)上搜方案,什么改my.ini的wait_out_time,什么改jdbc的url都白搭,后來我一想,算球了,不靠數(shù)據(jù)庫了,本來想讓這么多數(shù)據(jù)每條都一次成功也不現(xiàn)實(shí)。

于是搞了個error_msg表,入庫的時候有問題就記在error_msg里,然后啟一個定時任務(wù),每1分鐘掃描表里所有插入失敗記錄,一次不行兩次,兩次不行三次,三次不行一直試。

這個重試補(bǔ)上之后確實(shí)數(shù)據(jù)庫這方面的坑基本踩的差不多了。

服務(wù)假死CPU打滿

這個情況是出在解決mysql鏈接丟失前,當(dāng)時我想,為什么要用多線程,是因?yàn)樾实停实推鋵?shí)是低在請求api上,也就是我可以先多線程請求到數(shù)據(jù)放到一個list里,然后用單個數(shù)據(jù)庫鏈接去寫,這樣降低mysql的連接數(shù)應(yīng)該就不會丟了吧。

這么容易就丟那還寫啥啊。結(jié)果我就一個月一個月的拉數(shù)據(jù),寫完數(shù)據(jù)清空list,于是搞了個CopyAndWriteList,白背了那么多八股文了,一次也沒用過,結(jié)果就cpu給人打的滿滿的。。。

原因其實(shí)也很簡單,就是這個容器內(nèi)部都用鎖保證了線程安全。我就把這個容器當(dāng)作參數(shù)傳給每個Thread,弄完直接啟動。結(jié)果吧是服務(wù)突然就沒有新增數(shù)據(jù)了,然后看日志也沒不打日志,jps看服務(wù)還在。

遇到這種問題先上三板斧,回到家連上服務(wù)器上來先看快照,Jstack -l pid。

又是死鎖了,這里就不深究了。后續(xù)直接換了用LinedblockingQueue的延遲隊(duì)列,另起消費(fèi)線程不斷消費(fèi)延遲隊(duì)列入庫的方案了。

線程池莫名丟失鏈接

本來以為解決了寫庫的問題就差不多了,沒想到啊沒想到,這個不丟那個丟,數(shù)據(jù)還是有很多差異,找error_msg又沒體現(xiàn)出來,一頓排查后來發(fā)現(xiàn)是線程池這邊的問題。這里的線程池用的Guava的線程池,重寫異常捕獲

等待隊(duì)列用無界隊(duì)列,客戶的服務(wù)器雖然拉,但是內(nèi)存挺大,訂單數(shù)撐不爆內(nèi)存,核心線程10個,感覺一切都是那么合理。但是就是有問題,我發(fā)現(xiàn)在afterExecute方法攔截掛掉的任務(wù)異常時發(fā)現(xiàn)有很多任務(wù)的異常是java.util.concurrent.RejectedExecutionException也就是被執(zhí)行了拒絕策略。

這就十分不合理了,只有當(dāng)隊(duì)列滿了且正在運(yùn)行的線程數(shù)量大于或等于maximumPoolSize,那么線程池才會啟動飽和拒絕策略。那我定義線程池的時候明明是無界隊(duì)列,來者不拒。為啥會被執(zhí)行拒絕策略。

這個問題困擾了老久老久,以致于我都不想管了,奈何客戶一直催一直催,逼得我不得不解決這問題。

后來在Stack Overflow上有個老哥在源碼中找到原因

注意注釋那里,線程池是用的offer方法。差別就在于offer方法是**不阻塞的**,插入不了了,就往下走;而put方法是一直阻塞,直到元素插入到阻塞隊(duì)列中。這問題一卡卡了我好久,弄得我好幾天坐地鐵光研究這玩意了。于是重寫拒絕策略強(qiáng)制它put回隊(duì)列:


【淺嘗高并發(fā)編程】接私活差點(diǎn)翻車的評論 (共 條)

分享到微博請遵守國家法律
巴东县| 易门县| 永顺县| 钟祥市| 内黄县| 襄樊市| 连城县| 淳安县| 桑植县| 罗源县| 迁安市| 白河县| 武穴市| 依兰县| 紫云| 邛崃市| 定远县| 宁夏| 郓城县| 塔河县| 久治县| 新乡市| 道孚县| 黑龙江省| 加查县| 乐平市| 丰原市| 沁阳市| 慈利县| 耒阳市| 花莲市| 铜陵市| 兴文县| 彭水| 丽水市| 县级市| 本溪市| 翁源县| 于田县| 磴口县| 九江市|