自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我們一起聊聊冪等設(shè)計(jì)

開(kāi)發(fā) 前端
我們開(kāi)發(fā)一個(gè)轉(zhuǎn)賬功能,假設(shè)我們調(diào)用下游接口超時(shí)了。一般情況下,超時(shí)可能是網(wǎng)絡(luò)傳輸丟包的問(wèn)題,也可能是請(qǐng)求時(shí)沒(méi)送到,還有可能是請(qǐng)求到了,返回結(jié)果卻丟了。這時(shí)候我們是否可以重試呢?如果重試的話,是否會(huì)多轉(zhuǎn)了一筆錢呢?

前言

大家好,我是程序員田螺。今天我們一起來(lái)聊聊冪等設(shè)計(jì)。

  • 什么是冪等
  • 為什么需要冪等
  • 接口超時(shí),如何處理呢?
  • 如何設(shè)計(jì)冪等?
  • 實(shí)現(xiàn)冪等的8種方案
  • HTTP的冪等

1. 什么是冪等?

冪等是一個(gè)數(shù)學(xué)與計(jì)算機(jī)科學(xué)概念。

  • 在數(shù)學(xué)中,冪等用函數(shù)表達(dá)式就是:f(x) = f(f(x))。比如求絕對(duì)值的函數(shù),就是冪等的,abs(x) = abs(abs(x))。
  • 計(jì)算機(jī)科學(xué)中,冪等表示一次和多次請(qǐng)求某一個(gè)資源應(yīng)該具有同樣的副作用,或者說(shuō),多次請(qǐng)求所產(chǎn)生的影響與一次請(qǐng)求執(zhí)行的影響效果相同。

2. 為什么需要冪等

舉個(gè)例子:

我們開(kāi)發(fā)一個(gè)轉(zhuǎn)賬功能,假設(shè)我們調(diào)用下游接口超時(shí)了。一般情況下,超時(shí)可能是網(wǎng)絡(luò)傳輸丟包的問(wèn)題,也可能是請(qǐng)求時(shí)沒(méi)送到,還有可能是請(qǐng)求到了,返回結(jié)果卻丟了。這時(shí)候我們是否可以重試呢?如果重試的話,是否會(huì)多轉(zhuǎn)了一筆錢呢?

轉(zhuǎn)賬超時(shí)

當(dāng)前互聯(lián)網(wǎng)的系統(tǒng)幾乎都是解耦隔離后,會(huì)存在各個(gè)不同系統(tǒng)的相互遠(yuǎn)程調(diào)用。調(diào)用遠(yuǎn)程服務(wù)會(huì)有三個(gè)狀態(tài):成功,失敗,或者超時(shí)。前兩者都是明確的狀態(tài),而超時(shí)則是未知狀態(tài)。我們轉(zhuǎn)賬超時(shí)的時(shí)候,如果下游轉(zhuǎn)賬系統(tǒng)做好冪等控制,我們發(fā)起重試,那即可以保證轉(zhuǎn)賬正常進(jìn)行,又可以保證不會(huì)多轉(zhuǎn)一筆。

其實(shí)除了轉(zhuǎn)賬這個(gè)例子,日常開(kāi)發(fā)中,還有很多很多例子需要考慮冪等。比如:

  • MQ(消息中間件)消費(fèi)者讀取消息時(shí),有可能會(huì)讀取到重復(fù)消息。(重復(fù)消費(fèi))
  • 比如提交form表單時(shí),如果快速點(diǎn)擊提交按鈕,可能產(chǎn)生了兩條一樣的數(shù)據(jù)(前端重復(fù)提交)

3. 接口超時(shí)了,到底如何處理?

如果我們調(diào)用下游接口超時(shí)了,我們應(yīng)該怎么處理呢?

有兩種方案處理:

  • 方案一:就是下游系統(tǒng)提供一個(gè)對(duì)應(yīng)的查詢接口。如果接口超時(shí)了,先查下對(duì)應(yīng)的記錄,如果查到是成功,就走成功流程,如果是失敗,就按失敗處理。

拿我們的轉(zhuǎn)賬例子來(lái)說(shuō),轉(zhuǎn)賬系統(tǒng)提供一個(gè)查詢轉(zhuǎn)賬記錄的接口,如果渠道系統(tǒng)調(diào)用轉(zhuǎn)賬系統(tǒng)超時(shí)時(shí),渠道系統(tǒng)先去查詢一下這筆記錄,看下這筆轉(zhuǎn)賬記錄成功還是失敗,如果成功就走成功流程,失敗再重試發(fā)起轉(zhuǎn)賬。

方案二:下游接口支持冪等,上游系統(tǒng)如果調(diào)用超時(shí),發(fā)起重試即可。

兩種方案都是挺不錯(cuò)的,但是如果是MQ重復(fù)消費(fèi)的場(chǎng)景,方案一處理并不是很妥,所以,我們還是要求下游系統(tǒng)對(duì)外接口支持冪等。

4. 如何設(shè)計(jì)冪等

既然這么多場(chǎng)景需要考慮冪等,那我們?nèi)绾卧O(shè)計(jì)冪等呢?

冪等意味著一條請(qǐng)求的唯一性。不管是你哪個(gè)方案去設(shè)計(jì)冪等,都需要一個(gè)全局唯一的ID,去標(biāo)記這個(gè)請(qǐng)求是獨(dú)一無(wú)二的。

  • 如果你是利用唯一索引控制冪等,那唯一索引是唯一的
  • 如果你是利用數(shù)據(jù)庫(kù)主鍵控制冪等,那主鍵是唯一的
  • 如果你是悲觀鎖的方式,底層標(biāo)記還是全局唯一的ID

4.1 全局的唯一性ID

全局唯一性ID,我們?cè)趺慈ド赡?你可以回想下,數(shù)據(jù)庫(kù)主鍵Id怎么生成的呢?

是的,我們可以使用UUID,但是UUID的缺點(diǎn)比較明顯,它字符串占用的空間比較大,生成的ID過(guò)于隨機(jī),可讀性差,而且沒(méi)有遞增。

我們還可以使用雪花算法(Snowflake) 生成唯一性ID。

雪花算法是一種生成分布式全局唯一ID的算法,生成的ID稱為Snowflake IDs。這種算法由Twitter創(chuàng)建,并用于推文的ID。

一個(gè)Snowflake ID有64位。

  • 第1位:Java中l(wèi)ong的最高位是符號(hào)位代表正負(fù),正數(shù)是0,負(fù)數(shù)是1,一般生成ID都為正數(shù),所以默認(rèn)為0。
  • 接下來(lái)前41位是時(shí)間戳,表示了自選定的時(shí)期以來(lái)的毫秒數(shù)。
  • 接下來(lái)的10位代表計(jì)算機(jī)ID,防止沖突。
  • 其余12位代表每臺(tái)機(jī)器上生成ID的序列號(hào),這允許在同一毫秒內(nèi)創(chuàng)建多個(gè)Snowflake ID。

雪花算法

當(dāng)然,全局唯一性的ID,還可以使用百度的Uidgenerator,或者美團(tuán)的Leaf。

4.2 冪等設(shè)計(jì)的基本流程

冪等處理的過(guò)程,說(shuō)到底其實(shí)就是過(guò)濾一下已經(jīng)收到的請(qǐng)求,當(dāng)然,請(qǐng)求一定要有一個(gè)全局唯一的ID標(biāo)記哈。然后,怎么判斷請(qǐng)求是否之前收到過(guò)呢?把請(qǐng)求儲(chǔ)存起來(lái),收到請(qǐng)求時(shí),先查下存儲(chǔ)記錄,記錄存在就返回上次的結(jié)果,不存在就處理請(qǐng)求。

一般的冪等處理就是這樣啦,如下:

5. 實(shí)現(xiàn)冪等的8種方案

冪等設(shè)計(jì)的基本流程都是類似的,我們簡(jiǎn)簡(jiǎn)單單來(lái)過(guò)一下冪等實(shí)現(xiàn)的8中方案哈

5.1 select+insert+主鍵/唯一索引沖突

日常開(kāi)發(fā)中,為了實(shí)現(xiàn)交易接口冪等,我是這樣實(shí)現(xiàn)的:

交易請(qǐng)求過(guò)來(lái),我會(huì)先根據(jù)請(qǐng)求的唯一流水號(hào) bizSeq字段,先select一下數(shù)據(jù)庫(kù)的流水表

  • 如果數(shù)據(jù)已經(jīng)存在,就攔截是重復(fù)請(qǐng)求,直接返回成功;
  • 如果數(shù)據(jù)不存在,就執(zhí)行insert插入,如果insert成功,則直接返回成功,如果insert產(chǎn)生主鍵沖突異常,則捕獲異常,接著直接返回成功。

流程圖如下

偽代碼如下:

  1. /** 
  2.  * 冪等處理 
  3.  */ 
  4. Rsp idempotent(Request req){ 
  5.   Object requestRecord =selectByBizSeq(bizSeq); 
  6.    
  7.   if(requestRecord !=null){ 
  8.     //攔截是重復(fù)請(qǐng)求 
  9.      log.info("重復(fù)請(qǐng)求,直接返回成功,流水號(hào):{}",bizSeq); 
  10.      return rsp; 
  11.   } 
  12.    
  13.   try{ 
  14.     insert(req); 
  15.   }catch(DuplicateKeyException e){ 
  16.     //攔截是重復(fù)請(qǐng)求,直接返回成功 
  17.     log.info("主鍵沖突,是重復(fù)請(qǐng)求,直接返回成功,流水號(hào):{}",bizSeq); 
  18.     return rsp; 
  19.   } 
  20.    
  21.   //正常處理請(qǐng)求 
  22.   dealRequest(req); 
  23.    
  24.   return rsp; 

為什么前面已經(jīng)select查詢了,還需要try...catch...捕獲重復(fù)異常呢?

是因?yàn)楦卟l(fā)場(chǎng)景下,兩個(gè)請(qǐng)求去select的時(shí)候,可能都沒(méi)查到,然后都走到insert的地方啦。

當(dāng)然,用唯一索引代替數(shù)據(jù)庫(kù)主鍵也是可以的哈,都是全局唯一的ID即可。

5.2. 直接insert + 主鍵/唯一索引沖突

在5.1方案中,都會(huì)先查一下流水表的交易請(qǐng)求,判斷是否存在,然后不存在再插入請(qǐng)求記錄。如果重復(fù)請(qǐng)求的概率比較低的話,我們可以直接插入請(qǐng)求,利用主鍵/唯一索引沖突,去判斷是重復(fù)請(qǐng)求。

流程圖如下:

偽代碼如下:

  1. /** 
  2.  * 冪等處理 
  3.  */ 
  4. Rsp idempotent(Request req){ 
  5.    
  6.   try{ 
  7.     insert(req); 
  8.   }catch(DuplicateKeyException e){ 
  9.      //攔截是重復(fù)請(qǐng)求,直接返回成功 
  10.     log.info("主鍵沖突,是重復(fù)請(qǐng)求,直接返回成功,流水號(hào):{}",bizSeq); 
  11.     return rsp; 
  12.   } 
  13.    
  14.   //正常處理請(qǐng)求 
  15.   dealRequest(req); 
  16.   return rsp; 

溫馨提示 :

大家別搞混哈,防重和冪等設(shè)計(jì)其實(shí)是有區(qū)別的。防重主要為了避免產(chǎn)生重復(fù)數(shù)據(jù),把重復(fù)請(qǐng)求攔截下來(lái)即可。而冪等設(shè)計(jì)除了攔截已經(jīng)處理的請(qǐng)求,還要求每次相同的請(qǐng)求都返回一樣的效果。不過(guò)呢,很多時(shí)候,它們的處理流程可以是類似的。

5.3 狀態(tài)機(jī)冪等

很多業(yè)務(wù)表,都是有狀態(tài)的,比如轉(zhuǎn)賬流水表,就會(huì)有0-待處理,1-處理中、2-成功、3-失敗狀態(tài)。轉(zhuǎn)賬流水更新的時(shí)候,都會(huì)涉及流水狀態(tài)更新,即涉及狀態(tài)機(jī) (即狀態(tài)變更圖)。我們可以利用狀態(tài)機(jī)實(shí)現(xiàn)冪等,一起來(lái)看下它是怎么實(shí)現(xiàn)的。

比如轉(zhuǎn)賬成功后,把處理中的轉(zhuǎn)賬流水更新為成功狀態(tài),SQL這么寫:

  1. update transfr_flow set status=2 where biz_seq=‘666’ and status=1; 

簡(jiǎn)要流程圖如下:

偽代碼實(shí)現(xiàn)如下:

  1. Rsp idempotentTransfer(Request req){ 
  2.    String bizSeq = req.getBizSeq(); 
  3.    int rows"update transfr_flow set status=2 where biz_seq=#{bizSeq} and status=1;" 
  4.    if(rows==1){ 
  5.       log.info(“更新成功,可以處理該請(qǐng)求”); 
  6.       //其他業(yè)務(wù)邏輯處理 
  7.       return rsp; 
  8.    }else if(rows==0){ 
  9.       log.info(“更新不成功,不處理該請(qǐng)求”); 
  10.       //不處理,直接返回 
  11.       return rsp; 
  12.    } 
  13.     
  14.    log.warn("數(shù)據(jù)異常"
  15.    return rsp: 

狀態(tài)機(jī)是怎么實(shí)現(xiàn)冪等的呢?

第1次請(qǐng)求來(lái)時(shí),bizSeq流水號(hào)是 666,該流水的狀態(tài)是處理中,值是 1,要更新為2-成功的狀態(tài),所以該update語(yǔ)句可以正常更新數(shù)據(jù),sql執(zhí)行結(jié)果的影響行數(shù)是1,流水狀態(tài)最后變成了2。

第2請(qǐng)求也過(guò)來(lái)了,如果它的流水號(hào)還是 666,因?yàn)樵摿魉疇顟B(tài)已經(jīng)2-成功的狀態(tài)了,所以更新結(jié)果是0,不會(huì)再處理業(yè)務(wù)邏輯,接口直接返回。

5.4 抽取防重表

5.1和5.2的方案,都是建立在業(yè)務(wù)流水表上bizSeq的唯一性上。很多時(shí)候,我們業(yè)務(wù)表唯一流水號(hào)希望后端系統(tǒng)生成,又或者我們希望防重功能與業(yè)務(wù)表分隔開(kāi)來(lái),這時(shí)候我們可以單獨(dú)搞個(gè)防重表。當(dāng)然防重表也是利用主鍵/索引的唯一性,如果插入防重表沖突即直接返回成功,如果插入成功,即去處理請(qǐng)求。

5.5 token令牌

token 令牌方案一般包括兩個(gè)請(qǐng)求階段:

客戶端請(qǐng)求申請(qǐng)獲取token,服務(wù)端生成token返回

客戶端帶著token請(qǐng)求,服務(wù)端校驗(yàn)token

流程圖如下:

客戶端發(fā)起請(qǐng)求,申請(qǐng)獲取token。

服務(wù)端生成全局唯一的token,保存到redis中(一般會(huì)設(shè)置一個(gè)過(guò)期時(shí)間),然后返回給客戶端。

客戶端帶著token,發(fā)起請(qǐng)求。

服務(wù)端去redis確認(rèn)token是否存在,一般用 redis.del(token)的方式,如果存在會(huì)刪除成功,即處理業(yè)務(wù)邏輯,如果刪除失敗不處理業(yè)務(wù)邏輯,直接返回結(jié)果。

5.6 悲觀鎖(如select for update)

什么是悲觀鎖?

通俗點(diǎn)講就是很悲觀,每次去操作數(shù)據(jù)時(shí),都覺(jué)得別人中途會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖。官方點(diǎn)講就是,共享資源每次只給一個(gè)線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程。

悲觀鎖如何控制冪等的呢?就是加鎖呀,一般配合事務(wù)來(lái)實(shí)現(xiàn)。

舉個(gè)更新訂單的業(yè)務(wù)場(chǎng)景:

假設(shè)先查出訂單,如果查到的是處理中狀態(tài),就處理完業(yè)務(wù),再然后更新訂單狀態(tài)為完成。如果查到訂單,并且是不是處理中的狀態(tài),則直接返回

整體的偽代碼如下:

  1. begin;  # 1.開(kāi)始事務(wù) 
  2. select * from order where order_id='666' # 查詢訂單,判斷狀態(tài) 
  3. if(status !=處理中){ 
  4.    //非處理中狀態(tài),直接返回; 
  5.    return ; 
  6. ## 處理業(yè)務(wù)邏輯 
  7. update order set status='完成' where order_id='666' # 更新完成 
  8. commit; # 5.提交事務(wù) 

這種場(chǎng)景是非原子操作的,在高并發(fā)環(huán)境下,可能會(huì)造成一個(gè)業(yè)務(wù)被執(zhí)行兩次的問(wèn)題:

當(dāng)一個(gè)請(qǐng)求A在執(zhí)行中時(shí),而另一個(gè)請(qǐng)求B也開(kāi)始狀態(tài)判斷的操作。因?yàn)檎?qǐng)求A還未來(lái)得及更改狀態(tài),所以請(qǐng)求B也能執(zhí)行成功,這就導(dǎo)致一個(gè)業(yè)務(wù)被執(zhí)行了兩次。

可以使用數(shù)據(jù)庫(kù)悲觀鎖(select ...for update)解決這個(gè)問(wèn)題.

  1. begin;  # 1.開(kāi)始事務(wù) 
  2. select * from order where order_id='666' for update # 查詢訂單,判斷狀態(tài),鎖住這條記錄 
  3. if(status !=處理中){ 
  4.    //非處理中狀態(tài),直接返回; 
  5.    return ; 
  6. ## 處理業(yè)務(wù)邏輯 
  7. update order set status='完成' where order_id='666' # 更新完成 
  8. commit; # 5.提交事務(wù) 

這里面order_id需要是索引或主鍵哈,要鎖住這條記錄就好,如果不是索引或者主鍵,會(huì)鎖表的!

悲觀鎖在同一事務(wù)操作過(guò)程中,鎖住了一行數(shù)據(jù)。別的請(qǐng)求過(guò)來(lái)只能等待,如果當(dāng)前事務(wù)耗時(shí)比較長(zhǎng),就很影響接口性能。所以一般不建議用悲觀鎖做這個(gè)事情。

5.7 樂(lè)觀鎖

悲觀鎖有性能問(wèn)題,可以試下樂(lè)觀鎖。

什么是樂(lè)觀鎖?

樂(lè)觀鎖在操作數(shù)據(jù)時(shí),則非常樂(lè)觀,認(rèn)為別人不會(huì)同時(shí)在修改數(shù)據(jù),因此樂(lè)觀鎖不會(huì)上鎖。只是在執(zhí)行更新的時(shí)候判斷一下,在此期間別人是否修改了數(shù)據(jù)。

怎樣實(shí)現(xiàn)樂(lè)觀鎖呢?

就是給表的加多一列version版本號(hào),每次更新記錄version都升級(jí)一下(version=version+1)。具體流程就是先查出當(dāng)前的版本號(hào)version,然后去更新修改數(shù)據(jù)時(shí),確認(rèn)下是不是剛剛查出的版本號(hào),如果是才執(zhí)行更新

比如,我們更新前,先查下數(shù)據(jù),查出的版本號(hào)是version =1

  1. select order_id,version from order where order_id='666'; 

然后使用version =1和訂單Id一起作為條件,再去更新

  1. update order set version = version +1,status='P' where order_id='666' and version =1 

最后更新成功,才可以處理業(yè)務(wù)邏輯,如果更新失敗,默認(rèn)為重復(fù)請(qǐng)求,直接返回。

流程圖如下:

為什么版本號(hào)建議自增的呢?

因?yàn)闃?lè)觀鎖存在ABA的問(wèn)題,如果version版本一直是自增的就不會(huì)出現(xiàn)ABA的情況啦。

5.8 分布式鎖

分布式鎖實(shí)現(xiàn)冪等性的邏輯就是,請(qǐng)求過(guò)來(lái)時(shí),先去嘗試獲得分布式鎖,如果獲得成功,就執(zhí)行業(yè)務(wù)邏輯,反之獲取失敗的話,就舍棄請(qǐng)求直接返回成功。執(zhí)行流程如下圖所示:

分布式鎖可以使用Redis,也可以使用ZooKeeper,不過(guò)還是Redis相對(duì)好點(diǎn),因?yàn)檩^輕量級(jí)。

Redis分布式鎖,可以使用命令SET EX PX NX + 唯一流水號(hào)實(shí)現(xiàn),分布式鎖的key必須為業(yè)務(wù)的唯一標(biāo)識(shí)哈

Redis執(zhí)行設(shè)置key的動(dòng)作時(shí),要設(shè)置過(guò)期時(shí)間哈,這個(gè)過(guò)期時(shí)間不能太短,太短攔截不了重復(fù)請(qǐng)求,也不能設(shè)置太長(zhǎng),會(huì)占存儲(chǔ)空間。

6. HTTP的冪等

我們的接口,一般都是基于http的,所以我們?cè)賮?lái)聊聊Http的冪等吧。HTTP 請(qǐng)求方法主要有以下這幾種,我們看下各個(gè)接口是否都是冪等的。

  • GET方法
  • HEAD方法
  • OPTIONS方法
  • DELETE方法
  • POST 方法
  • PUT方法

6.1 GET 方法

HTTP 的GET方法用于獲取資源,可以類比于數(shù)據(jù)庫(kù)的select查詢,不應(yīng)該有副作用,所以是冪等的。它不會(huì)改變資源的狀態(tài),不論你調(diào)用一次還是調(diào)用多次,效果一樣的,都沒(méi)有副作用。

如果你的GET方法是獲取最近最新的新聞,不同時(shí)間點(diǎn)調(diào)用,返回的資源內(nèi)容雖然不一樣,但是最終對(duì)資源本質(zhì)是沒(méi)有影響的哈,所以還是冪等的。

6.2 HEAD 方法

HTTP HEAD和GET有點(diǎn)像,主要區(qū)別是HEAD不含有呈現(xiàn)數(shù)據(jù),而僅僅是HTTP的頭信息,所以它也是冪等的。如果想判斷某個(gè)資源是否存在,很多人會(huì)使用GET,實(shí)際上用HEAD則更加恰當(dāng)。即HEAD方法通常用來(lái)做探活使用。

6.3 OPTIONS方法

HTTP OPTIONS 主要用于獲取當(dāng)前URL所支持的方法,也是有點(diǎn)像查詢,因此也是冪等的。

6.4 DELETE方法

HTTP DELETE 方法用于刪除資源,它是的冪等的。比如我們要?jiǎng)h除id=666的帖子,一次執(zhí)行和多次執(zhí)行,影響的效果是一樣的呢。

6.5 POST 方法

HTTP POST 方法用于創(chuàng)建資源,可以類比于提交信息,顯然一次和多次提交是有副作用,執(zhí)行效果是不一樣的,不滿足冪等性。

比如:POST http://www.tianluo.com/articles的語(yǔ)義是在http://www.tianluo.com/articles下創(chuàng)建一篇帖子,HTTP 響應(yīng)中應(yīng)包含帖子的創(chuàng)建狀態(tài)以及帖子的 URI。兩次相同的POST請(qǐng)求會(huì)在服務(wù)器端創(chuàng)建兩份資源,它們具有不同的 URI;所以,POST方法不具備冪等性。

6.6 PUT 方法

HTTP PUT 方法用于創(chuàng)建或更新操作,所對(duì)應(yīng)的URI是要?jiǎng)?chuàng)建或更新的資源本身,有副作用,它應(yīng)該滿足冪等性。

比如:PUT http://www.tianluo.com/articles/666的語(yǔ)義是創(chuàng)建或更新 ID 為666的帖子。對(duì)同一 URI 進(jìn)行多次 PUT 的副作用和一次 PUT 是相同的;因此,PUT 方法具有冪等性。

參考資料

[1]彈力設(shè)計(jì)篇之“冪等性設(shè)計(jì)”: https://time.geekbang.org/column/article/4050

 

責(zé)任編輯:武曉燕 來(lái)源: 撿田螺的小男孩
相關(guān)推薦

2024-03-13 15:18:00

接口冪等性高并發(fā)

2021-08-27 07:06:10

IOJava抽象

2024-02-20 21:34:16

循環(huán)GolangGo

2023-08-04 08:20:56

DockerfileDocker工具

2022-05-24 08:21:16

數(shù)據(jù)安全API

2023-08-10 08:28:46

網(wǎng)絡(luò)編程通信

2023-09-10 21:42:31

2023-06-30 08:18:51

敏捷開(kāi)發(fā)模式

2023-11-30 07:40:05

URLCMS

2024-10-29 11:19:23

點(diǎn)贊系統(tǒng)同步

2024-07-12 08:28:09

聊天系統(tǒng)架構(gòu)

2022-02-14 07:03:31

網(wǎng)站安全MFA

2022-06-26 09:40:55

Django框架服務(wù)

2022-10-28 07:27:17

Netty異步Future

2023-04-26 07:30:00

promptUI非結(jié)構(gòu)化

2022-04-06 08:23:57

指針函數(shù)代碼

2023-12-28 09:55:08

隊(duì)列數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)

2022-11-12 12:33:38

CSS預(yù)處理器Sass

2025-03-27 02:00:00

SPIJava接口

2024-02-26 00:00:00

Go性能工具
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)