<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      冪等的雙倍快樂,你值得擁有

      hello, 這是有態(tài)度馬甲的第xxx篇原創(chuàng)口水文。有趣指數(shù)5顆星,有用指數(shù)5顆星。

      ????本文是國(guó)外技術(shù)網(wǎng)站medium上點(diǎn)贊超過200+的翻譯/筆記文,有關(guān)規(guī)避/解決冪等請(qǐng)求的編程指南。

      1. 軟件領(lǐng)域二次請(qǐng)求無(wú)法避免

      我們生活的每時(shí)每刻都是獨(dú)一無(wú)二的,事情/動(dòng)作可能不會(huì)相同的形式再次發(fā)生。

      在軟件領(lǐng)域,同一動(dòng)作請(qǐng)求并不總會(huì)只產(chǎn)生一次,這可能會(huì)帶來(lái)一些問題: 想象你月底發(fā)薪,公司的轉(zhuǎn)賬指令錯(cuò)誤的觸發(fā)了2次,這豈不是雙倍快樂。

      為什么冪等性很重要?

      • 網(wǎng)絡(luò)不可靠:客戶端超時(shí)后,可以放心地重試冪等的請(qǐng)求(如PUT, DELETE),而不用擔(dān)心產(chǎn)生意外后果。

      • 分布式系統(tǒng):在微服務(wù)架構(gòu)中,服務(wù)間的重試機(jī)制依賴于冪等性來(lái)保證數(shù)據(jù)一致性。

      二次請(qǐng)求的來(lái)源 能避免出現(xiàn)嗎? 怎么避免出現(xiàn)?
      前端的頻繁點(diǎn)擊提交 提交后置灰按鈕/提交后切換頁(yè)面/防誤觸來(lái)解決
      客戶端/中間服務(wù)器的重試動(dòng)作 不能 -

      image

      根據(jù)雙將軍理論,即使A/B將軍不斷確認(rèn)收到對(duì)方的上一條信息, 也沒辦法確保對(duì)方與自己達(dá)成(同一時(shí)間攻擊的共識(shí))。

      兩將軍問題是無(wú)解的,間歇性重試是一種工程解。 (還有散彈打鳥)

      :我們一直發(fā)送相同的服務(wù)請(qǐng)求,直到我們確定收到它(雖然可能會(huì)多次收到), 這就叫至少一次交付。

      但是我們不希望被扣款兩次,那我們就必須確保多次處理相同的請(qǐng)求不會(huì)改變最初的應(yīng)用狀態(tài), 這是冪等請(qǐng)求的重點(diǎn)。

      除此之外,重試還可能帶來(lái) 重試風(fēng)暴、資源雪崩等衍生問題。

      2. 某些請(qǐng)求天然冪等,你不需要做什么

      想象你正在銀行開戶。

      public sealed class Account
      {
          public Guid Id { get; }
          public decimal Balance { get; private set; }
      
          public Account(Guid id, decimal balance)
          {
              if (id == default)
                  throw new InvalidOperationException("Account id must be provided");
      
              if (balance < 0)
                  throw new InvalidOperationException("Balance cannot be negative");
      
              Id = id;
              Balance = balance;
          }
         
          // 取錢
          public void Withdraw(decimal amount)
          {
              if (amount < 0)
                  throw new InvalidOperationException("Cannot withdraw negative amount");
              
              if (amount > Balance)
                  throw new InvalidOperationException("Cannot withdraw more than existing balance");
      
              Balance -= amount;
          }
      
          // 存錢
          public void Deposit(decimal amount)
          {
              if (amount < 0)
                  throw new InvalidOperationException("Cannot deposit negative amount");
              
              Balance += amount;
          }
      }
      

      前端發(fā)起的開戶請(qǐng)求OpenAccountRequest是冪等的, 只需要在開戶邏輯里面檢查 數(shù)據(jù)表是不是存在這個(gè)AccountId

      你甚至可在數(shù)據(jù)庫(kù)設(shè)置AccountId為唯一索引,讓重試動(dòng)作爆出異常。

      public async Task HandleAsync(OpenAccountRequest request, CancellationToken token = default)
      {
          var account = new Account(request.AccountId, request.Balance); 
          
          try
          {
              await _repository.InsertAsync(account, token);
          }
          catch (DuplicateKeyException)
          {
              //Ignore
          }
      }
      

      對(duì)于存錢(WithDraw)取錢(Deposit)就不行了,如果因?yàn)榫W(wǎng)絡(luò)原因而重試了2次存錢請(qǐng)求(deposit),豈不就是雙倍快樂。

      3. 樂觀鎖的介入一定合理嗎?

      一種處理重復(fù)請(qǐng)求的方式是質(zhì)詢實(shí)體的狀態(tài),嚴(yán)格意義來(lái)講, 這個(gè)方案是來(lái)解決更大敘事背景(樂觀鎖)下的方案。

      首先我們知道高并發(fā)場(chǎng)景下,有一個(gè)叫樂觀鎖的并發(fā)控制機(jī)制,樂觀地認(rèn)為數(shù)據(jù)在操作時(shí)不會(huì)沖突, 因此在操作前不加鎖,在提交時(shí)檢查數(shù)據(jù)是否被修改。

      文中一開始: 讓前端在請(qǐng)求時(shí)帶上需要保護(hù)的Balance,
      在更新時(shí)利用AccountId+原Balance來(lái)定位并更新賬戶。

      // 下面的前端DTO需要帶上賬戶余額,(二次請(qǐng)求也是這個(gè)值)。
      public sealed class DepositToAccountRequest
      {
          public Guid AccountId { get; }
          public decimal Amount { get; }   // 操作金額
          public decimal AccountBalance { get; }
      
          public DepositToAccountRequest(Guid accountId, decimal amount, decimal accountBalance)
          {
              AccountId = accountId;
              Amount = amount;
              AccountBalance = accountBalance;
          }
      }
      
      
      public async Task HandleAsync(DepositToAccountRequest request, CancellationToken token = default)
      {
          var account = await _repository.GetAsync(request.AccountId, token) ?? 
                        throw new EntityNotFoundException();
      
          account.Deposit(request.Amount);
      
          await _repository.UpdateAsync(account, request.AccountBalance, token);
          
          
      public sealed class AccountRepository : IAccountRepository
      {
          //....
      
          public async Task UpdateAsync(Account account, decimal expectedBalance, CancellationToken token = default)
          {
              var sql = "UPDATE Accounts SET Balance = @Balance WHERE Id = @Id AND Balance = @ExpectedBalance";
              var sqlParams = new
              {
                  Id = account.Id, 
                  Balance = account.Balance,  // 新余額
                  ExpectedBalance = expectedBalance  // 原余額
              };
      
              await using var connection = new SqlConnection(_connectionString);
              await connection.OpenAsync(token);
              
              var rowsAffected = await connection.ExecuteAsync(sql, sqlParams);
              if (rowsAffected == 0)
                  throw new InvalidStateException();
          }
      
          //....
      }
      

      讀者肯定也發(fā)現(xiàn)了:

      ① 這個(gè)方式不靈活,如果不是Balance,或者不只是Balance, 那么這個(gè)sql邏輯就得變化;

      ② 另一方面,這個(gè)方式歸根到底不識(shí)別重復(fù)請(qǐng)求,不知道這是重復(fù)請(qǐng)求,還是底層的數(shù)據(jù)真的發(fā)生了變化。

      想象你被觸發(fā)了第二次取錢請(qǐng)求, 若此時(shí)剛好有人給你存了一筆錢(剛好等于你第一次取錢金額),促使你的第二次取錢請(qǐng)求成功了,這豈不是新的雙倍悲傷。

      3.1 適用于更新Put請(qǐng)求的狀態(tài)版本方案

      所以文中提出了基于宏達(dá)敘事的正經(jīng)方案: 前端介入 + 狀態(tài)版本
      在前端DTO請(qǐng)求帶上AccountVersion,每次更新時(shí)用AccoundId+原AccountVersion去定位、更新狀態(tài)版本, 如果where條件失敗說(shuō)明實(shí)體狀態(tài)已經(jīng)變化,需要報(bào)錯(cuò)給到前端,讓前端重新拉取數(shù)據(jù), 如果where條件成功,則說(shuō)明狀態(tài)版本無(wú)變更,遞增version,并給到前端。

          public async Task UpdateAsync(Account account, int expectedVersion, CancellationToken token = default)
          {
              var sql = "UPDATE Accounts SET Balance = @Balance, Version = @Version WHERE Id = @Id AND Version = @ExpectedVersion";
              var sqlParams = new
              {
                  Id = account.Id, 
                  Balance = account.Balance, 
                  Version = account.Version,
                  ExpectedVersion = expectedVersion
              };
      
              await using var connection = new SqlConnection(_connectionString);
              await connection.OpenAsync(token);
              
              var rowsAffected = await connection.ExecuteAsync(sql, sqlParams);
              if (rowsAffected == 0)
                  throw new InvalidStateException();
          }
      

      grafana 修改數(shù)據(jù)源的示例

      curl 'https://grafana-chinese.observe.dev.eks.gainetics.io/api/datasources/uid/tempo' \
        -X 'PUT' \
        -H 'content-type: application/json' \
        --data-raw '{"id":2,"uid":"tempo","orgId":1,"name":"Tempo","type":"tempo","typeLogoUrl":"public/plugins/tempo/img/tempo_logo.svg","access":"proxy","url":"http://tempo:3200","user":"","database":"","basicAuth":false,"basicAuthUser":"","withCredentials":false,"isDefault":true,"jsonData":{"pdcInjected":false,"tracesToLogsV2":{"customQuery":false,"datasourceUid":"opensearch","filterBySpanID":true,"filterByTraceID":true,"spanEndTimeShift":"1m","spanStartTimeShift":"-1m","tags":[{"key":"beast","value":""}]}},"secureJsonFields":{},"version":18,"readOnly":false,"accessControl":{"alert.instances.external:read":true,"alert.instances.external:write":true,"alert.notifications.external:read":true,"alert.notifications.external:write":true,"alert.rules.external:read":true,"alert.rules.external:write":true,"datasources.id:read":true,"datasources:delete":true,"datasources:query":true,"datasources:read":true,"datasources:write":true},"apiVersion":""}'
      

      里面有一個(gè)version就是狀態(tài)版本,每次前端嘗試去更細(xì)時(shí), 會(huì)帶上version,去后端定位。

            ds = &datasources.DataSource{
      			ID:              cmd.ID,
      			OrgID:           cmd.OrgID,
                  .....
      			Version:         cmd.Version + 1,
      			.....
      		}
      
      	var updateSession *xorm.Session
      	if cmd.Version != 0 {
      			// the reason we allow cmd.version > db.version is make it possible for people to force
      			// updates to datasources using the datasource.yaml file without knowing exactly what version
      			// a datasource have in the db.
      			updateSession = sess.Where("id=? and org_id=? and version < ?", ds.ID, ds.OrgID, ds.Version)
      	} else {
      			updateSession = sess.Where("id=? and org_id=?", ds.ID, ds.OrgID)
      	}
      
      	affected, err := updateSession.Update(ds)
      	if err != nil {
      		return err
      	}
      

      image

      這種樂觀鎖的思想去解決冪等問題有一個(gè)小弊端, 因?yàn)闃酚^鎖的思想本是針對(duì)并發(fā)控制,它解決了并發(fā)請(qǐng)求中的重復(fù)請(qǐng)求這一子集場(chǎng)景,但是帶來(lái)的副作用就是高并發(fā)時(shí),很多請(qǐng)求會(huì)被拒絕(重試請(qǐng)求會(huì)被拒絕,并發(fā)請(qǐng)求也會(huì)被拒絕),效率變低,但數(shù)據(jù)不一致問題沒有了,雙倍悲傷也不會(huì)有。

      以上是”用于更新的PUT請(qǐng)求“,restful規(guī)范強(qiáng)烈要求冪等性,通常用”狀態(tài)版本“實(shí)現(xiàn),

      POST 的冪等性是強(qiáng)烈推薦的,但它不能使用狀態(tài)版本,而應(yīng)該使用”冪等鍵“(Idempotency Key) 或業(yè)務(wù)唯一標(biāo)識(shí)來(lái)實(shí)現(xiàn)。

      4. 用冪等鍵實(shí)現(xiàn)Post請(qǐng)求冪等

      put更新請(qǐng)求,冪等性可以用 狀態(tài)版本保證, 是因?yàn)樵谡?qǐng)求時(shí)已經(jīng)有 “狀態(tài)版本” 來(lái)定義了實(shí)體快照,

      Post新增請(qǐng)求,一開始并沒有實(shí)體, 我們需要一個(gè)在創(chuàng)建動(dòng)作發(fā)生前就生成的唯一標(biāo)識(shí),來(lái)保證整個(gè)創(chuàng)建過程的唯一性

      ① 客戶端在發(fā)起創(chuàng)建資源的POST請(qǐng)求時(shí),在HTTP頭(如 Idempotency-Key: <unique_key>)或請(qǐng)求體中生成并攜帶一個(gè)全局唯一的冪等鍵。

      ② 服務(wù)器收到 新增的動(dòng)作,利用這個(gè)冪等鍵 從redis或者數(shù)據(jù)庫(kù)定位是不是已經(jīng)存在該冪等鍵,存在則返回關(guān)聯(lián)的實(shí)體;
      如果不存在, 則用事務(wù)插入冪等鍵和關(guān)聯(lián)實(shí)體。

      ③ 這個(gè)冪等鍵的保存可以設(shè)置過期時(shí)間,或者自動(dòng)清理機(jī)制來(lái)刪除。

      一張表來(lái)存儲(chǔ) 客戶端產(chǎn)生的全局requestId, 這個(gè)表保證requestId唯一。

      那么通過事務(wù): requestId 插入歷史記錄表 & 實(shí)際的請(qǐng)求實(shí)體,便可以真實(shí)解決冪等問題, 這是真的冪等, 因?yàn)檫@個(gè)事務(wù)真正識(shí)別出了重復(fù)請(qǐng)求。

      public sealed class AccountRepository : IAccountRepository
      {
          //....
      
          public async Task UpdateAsync(Account account, Guid requestId, CancellationToken token = default)
          {
              var requestSql = "INSERT INTO RequestIds VALUES (@Id)";
              var requestSqlParams = new 
              { 
                  Id = requestId.ToString() 
              };
      
              var accountSql = "UPDATE Accounts SET Balance = @Balance WHERE Id = @Id";
              var accountSqlParams = new
              {
                  Id = account.Id,
                  Balance = account.Balance
              };
      
              await using var connection = new SqlConnection(_connectionString);
              await connection.OpenAsync(token);
              await using var transaction = await connection.BeginTransactionAsync(token);
      
              try
              {
                  await connection.ExecuteAsync(requestSql, requestSqlParams);
              }
              catch (Exception e) when (IsDuplicateKeyException(e))
              {
                  throw new DuplicateKeyException();
              }
      
              await connection.ExecuteAsync(accountSql, accountSqlParams);
              await transaction.CommitAsync(token);
          }
      
          //....
      }
      

      總結(jié)

      1. 沒有最佳的方式去處理冪等,只有最合適的。

      2. 有些業(yè)務(wù)天然冪等, 使用簡(jiǎn)單的全局唯一id就可以定位出二次請(qǐng)求。

      3. 如果你的實(shí)體更新的不頻繁, 可以考慮使用基于樂觀鎖的版本狀態(tài)來(lái)解決(總體上樂觀鎖是更宏達(dá)敘事的一個(gè)思路,在頻繁更新場(chǎng)景下能處理冪等問題,但體驗(yàn)不佳,是一味猛藥)。

      4. 更常見的冪等解決方式是:基于客戶端產(chǎn)生的冪等鍵, 構(gòu)建請(qǐng)求的唯一性,利用redis鍵值對(duì)或mysql事務(wù)識(shí)別出二次請(qǐng)求, 是真正的實(shí)現(xiàn)了冪等語(yǔ)義。

      ????????????

      https://medium.com/swlh/retry-requests-fearlessly-with-idempotence-f6bc23f1c721

      posted @ 2025-10-18 23:40  碼甲哥不卷  閱讀(181)  評(píng)論(1)    收藏  舉報(bào)
      主站蜘蛛池模板: 日本欧美大码a在线观看| 亚洲欧美牲交| 在线观看国产成人av天堂| 国产成人啪精品视频免费APP | 成人免费无遮挡在线播放| 国产伦精品一区二区亚洲| 亚洲乱码一区二区三区视色| 久久精品国产亚洲欧美| 日韩激情一区二区三区| 日韩有码中文字幕一区二区| 白丝乳交内射一二三区| 一本大道色婷婷在线| 亚洲欧美在线一区中文字幕| 日韩精品亚洲国产成人av| 日韩国产精品中文字幕| 3d动漫精品一区二区三区| 国产视频 视频一区二区| 十八禁国产精品一区二区| 欧美裸体xxxx极品| 亚洲第一国产综合| 一区二区三区四区精品黄| 亚洲精品男男一区二区| 文中字幕一区二区三区视频播放| 四虎永久免费精品视频| 亚洲av激情久久精品人| 色偷偷女人的天堂亚洲网| 90后极品粉嫩小泬20p| 亚洲综合色区另类av| 婷婷国产亚洲性色av网站| av午夜福利一片免费看久久| 精品少妇爆乳无码aⅴ区| 国产真实伦在线观看视频| 通辽市| 日韩精品中文字幕国产一| 精品人妻av区乱码| 天天狠天天透天天伊人| 久久精品视频一二三四区| 成人国产精品一区二区网站公司 | 亚洲一区二区中文字幕| 狠狠躁天天躁中文字幕无码| 中文字幕一区二区网站|