Redis--day9--黑馬點評--分布式鎖(二)

請添加圖片描述
(以下所有內容全部來自上述課程)
在這里插入圖片描述

分布式鎖

1. Redisson功能介紹

基于setnx實現的分布式鎖存在下面的問題:

  • 不可重入:同一個線程無法多次獲取同一把鎖
  • 不可重試:獲取鎖只嘗試一次就返回false,沒有重試機制
  • 超時釋放:鎖超時釋放雖然可以避免死鎖,但如果是業務執行耗時較長,也會導致鎖釋放,存在安全隱患
  • 主從一致性:如果Redis提供了主從集群,主從同步存在延遲,當主宕機時,如果從并同步主中的鎖數據,則會出現鎖實現

Redisson是一個在Redis的基礎上實現的lava駐內存數據網格(In-Memory Data Grid)。它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務,其中就包含了各種分布式鎖的實現。
在這里插入圖片描述
官網地址:https://redisson.org
GitHub地址:https://github.com/redisson/redisson
jar包下載:https://mvnrepository.com/artifact/org.redisson/redisson/3.50.0

2. Redis入門

  1. 引入依賴:
<dependency>
<groupId>org.redisson</groupId><artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
  1. 配置Redisson客戶端:
@Configuration
public class RedisConfig {@Beanpublic RedissonClient redissonClient(){// 配置類Config config =new Config();//添加redis地址,這里添加了單點的地址,也可以使用config.useClusterServers()添加集群地址	config.useSingleServer().setAddress("redis://192.168.150.101:6379").setPassword("123321");// 創建客戶端return Redisson.create(config);}
}	
  1. 使用Redisson的分布式鎖
@Resource
private RedissonClient redissonClient;
@Test
void testRedisson()throws InterruptedException {// 獲取鎖(可重入),指定鎖的名稱RLock lock=redissonClient.getLock("anyLock");//嘗試獲取鎖,參數分別是:獲取鎖的最大等待時間(期間會重試),鎖自動釋放時間,時間單位boolean isLock =lock.tryLock(110,TimeUnit.SECONDS);// 判斷釋放獲取成功if(isLock){try {System.out.println("執行業務");}finally {// 釋放鎖lock.unlock();}}
}

3. Redisson的可重入鎖原理

// 創建鎖對象
RLock lock=redissonClient.getLock("lock");@Test
void method1(){boolean isLock = lock.tryLock();if(!isLock){log.error("獲取鎖失敗,1");return;}try{log.info("獲取鎖成功,1");method2();} finally {log.info("釋放鎖,1");lock.unlock();}
}
void method2(){boolean isLock =lock.tryLock();if(!isLock){log.error("獲取鎖失敗,2");return;}try {log.info("獲取鎖成功,2");} finally {log.info("釋放鎖,2");lock.unlock();}
}

請添加圖片描述
獲取鎖的lua腳本:

local key = KEYS[1]; -- 鎖的key
local threadId= ARGV[1];-- 線程唯一標識
local releaseTime =ARGV[2];-- 鎖的自動釋放時間
--判斷是否存在
if(redis.call('exists',key)== 0)then-- 不存在,獲取鎖redis.call('hset',key, threadId,"1');-- 設置有效期redis.call('expire',key, releaseTime);return 1;--返回結果
end ;
--鎖已經存在,判斷threadId是否是自己
if(redis.call("hexists",key,threadId)== 1) then--不存在,獲取鎖,重入次數+1redis.call("hincrby", key, threadId, '1');--設置有效期redis.call('expire',key, releaseTime);return 1;--返回結果
end ;
return 0; --代碼走到這里,說明獲取鎖的不是自己,獲取鎖失敗

釋放鎖的lua腳本:

local key =KEYS[1];-- 鎖的key
local threadId= ARGV[1];-- 線程唯一標識
local releseTime=ARGV[2];--鎖的自動釋放時間
--判斷當前鎖是否還是被自己持有
if(redis.call("HEXISTs",key,threadId)==0)thenreturn nil;--如果已經不是自己,則直接返回
end ;
--是自己的鎖,則重入次數-1
local count = redis.call('HINCRBY', key, threadId, -1);
--判斷是否重入次數是否已經為0
if(count>0)then--大于0說明不能釋放鎖,重置有效期然后返回redis.call('EXPIRE',key, releaseTime);return nil;
else--等于0說明可以釋放鎖,直接刪除redis.call('DEL', key);return nil;
end ;

4. Redisson的鎖重試和WatchDog機制

tryLock ctrl+alt+B 打開源碼

   private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) { //第三處查看//阻塞等待Future結果,就是剩余有效期 把Long接著返回-->第二處查看(往下滑)return get(tryAcquireAsync0(waitTime, leaseTime, unit, threadId));  //查看tryAcquireAsync0(往下滑)}private RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {  //第四處查看RFuture<Long> ttlRemainingFuture;if (leaseTime > 0) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {  //命令行已經發出去了,但是結果拿沒拿到還不清楚-->FuturettlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,  //查看tryLockInnerAsync(向下滑)TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}  //返回第三處查看(向上滑)CompletionStage<Long> s = handleNoSync(threadId, ttlRemainingFuture);ttlRemainingFuture = new CompletableFutureWrapper<>(s);CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {// lock acquiredif (ttlRemaining == null) {  //剩余有效期=nullif (leaseTime > 0) {  internalLockLeaseTime = unit.toMillis(leaseTime);} else {scheduleExpirationRenewal(threadId);  //自動更新有效期續約 --> 查看scheduleExpirationRenewal(下一個板塊的代碼)}}return ttlRemaining;});return new CompletableFutureWrapper<>(f);}@Overridepublic boolean tryLock() {return get(tryLockAsync());}<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { //第五處查看 return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,"if ((redis.call('exists', KEYS[1]) == 0) " +"or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " + //上面是獲取成功,下面是沒成功"return redis.call('pttl', KEYS[1]);",  //pttl(毫秒的單位)=ttl(秒的單位) 獲取指定key的剩余有效期Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));}  //返回第四處(向上滑)@Overridepublic boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { //第二處查看long time = unit.toMillis(waitTime);    //把等待時間轉換為毫秒long current = System.currentTimeMillis();  //獲取當前時間long threadId = Thread.currentThread().getId();  //獲取當前線程id 也就是線程標識Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);  //嘗試獲取鎖 -->看tryAcquire方法(往上滑)// lock acquiredif (ttl == null) {return true;  //獲取成功。直接返回}//獲取失敗,繼續嘗試(重點!)time -= System.currentTimeMillis() - current;  //現在的時間減去之前的當前時間(就是獲取鎖消耗的時間)//然后最大等待時間減去消耗的時間,就是剩余等待時間if (time <= 0) {acquireFailed(waitTime, unit, threadId);return false;  //沒有剩余等待時間,就直接獲取失敗}//獲取失敗,繼續嘗試(但沒有立即-->subscribe)current = System.currentTimeMillis();CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);  //訂閱了別人釋放鎖的信號(publish)try {subscribeFuture.get(time, TimeUnit.MILLISECONDS);  //因為時間不確定,所以也是Future} catch (TimeoutException e) {if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(  //最大剩余時間結束-->返回false,繼續往下走"Unable to acquire subscription lock after " + time + "ms. " +"Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {subscribeFuture.whenComplete((res, ex) -> {if (ex == null) {  //判斷有沒有時間了unsubscribe(res, threadId);  //超時,取消訂閱}});}acquireFailed(waitTime, unit, threadId);return false;  } catch (ExecutionException e) {acquireFailed(waitTime, unit, threadId);return false;  //獲取鎖失敗}try {time -= System.currentTimeMillis() - current;  //又計算一次等待消耗時間if (time <= 0) {acquireFailed(waitTime, unit, threadId);return false;  //套娃,沒時間了還是沒獲取到鎖,直接返回獲取失敗}//小總結:上方一直計算剩余時間,有就直接到這兒,沒有就一直重復計算//計算終于有剩余時間(true)while (true) {long currentTime = System.currentTimeMillis();ttl = tryAcquire(waitTime, leaseTime, unit, threadId);  //第一次重試(tryAcquire)// lock acquiredif (ttl == null) {return true;  //成功}//失敗就再計算剩余時間time -= System.currentTimeMillis() - currentTime;if (time <= 0) {acquireFailed(waitTime, unit, threadId);return false;  //沒有就失敗}// waiting for message  失敗就繼續計算時間currentTime = System.currentTimeMillis();if (ttl >= 0 && ttl < time) {  //ttl和time ,哪個小就先等哪個(time是根本底線,沒了也不用等了)//getLatch()  信號量,類似與publish拋出獲取信號//ttl是施放時間,更靈活一些,鎖釋放了就重試(重新獲取鎖)commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {  //time到期了,還沒釋放也就不用等了commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);}//再獲取時間time -= System.currentTimeMillis() - currentTime;if (time <= 0) {  //沒時間了,還是直接失敗acquireFailed(waitTime, unit, threadId);return false;}}  //時間充足就while(true),返回上面又開始重試} finally {unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);}
//        return get(tryLockAsync(waitTime, leaseTime, unit));}  //至此,重試問題解決  -->  去看超時問題:第四處(向上滑)
  protected void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry = new ExpirationEntry();//EntryName:當前鎖的名稱//一個鎖一個entry,第一次來創建新的entry,之后來的返回舊的entry//保證這把鎖不管重復了幾次,返回的都是同一個entryExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry != null) {oldEntry.addThreadId(threadId);  //同一個線程多次來獲取} else {entry.addThreadId(threadId);try {renewExpiration();  //第一次來:續約,更新有效期  -->看renewExpiration} finally {if (Thread.currentThread().isInterrupted()) {cancelExpirationRenewal(threadId);}}}}
//renewExpiration:
private void renewExpiration() {  //更新有效期ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());  //先得到entryif (ee == null) {return;}Timeout task = getServiceManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());  //拿出entryif (ent == null) {return;}Long threadId = ent.getFirstThreadId();  //取出線程idif (threadId == null) {return;}CompletionStage<Boolean> future = renewExpirationAsync(threadId);  //刷新有效期-->查看renewExpirationAsyncfuture.whenComplete((res, e) -> {if (e != null) {log.error("Can't update lock {} expiration", getRawName(), e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {// reschedule itselfrenewExpiration();  //自己調用自己:遞歸--重復每十秒更新一次有效期,解決超時問題(重點!)} else {cancelExpirationRenewal(null);}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);  //內部鎖施放時間/3ee.setTimeout(task);}//renewExpirationAsync:
//重置有效期protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +//判斷當前線程的鎖是不是自己線程拿的--肯定能成功,不是自己線程進不來"redis.call('pexpire', KEYS[1], ARGV[1]); " +//更新有效期"return 1; " +"end; " +"return 0;",Collections.singletonList(getRawName()),internalLockLeaseTime, getLockName(threadId));}//釋放鎖的邏輯:
@Overridepublic void unlock() {try {get(unlockAsync(Thread.currentThread().getId()));  //查看unlockAsync} catch (RedisException e) {if (e.getCause() instanceof IllegalMonitorStateException) {throw (IllegalMonitorStateException) e.getCause();} else {throw e;}}//unlockAsync:
@Overridepublic RFuture<Void> unlockAsync(long threadId) {return getServiceManager().execute(() -> unlockAsync0(threadId));}private RFuture<Void> unlockAsync0(long threadId) {CompletionStage<Boolean> future = unlockInnerAsync(threadId);CompletionStage<Void> f = future.handle((opStatus, e) -> {cancelExpirationRenewal(threadId);  //取消更新任務 -->查看:cancelExpirationRenewalif (e != null) {if (e instanceof CompletionException) {throw (CompletionException) e;}throw new CompletionException(e);}if (opStatus == null) {IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "+ id + " thread-id: " + threadId);throw new CompletionException(cause);}return null;});return new CompletableFutureWrapper<>(f);}//cancelExpirationRenewal:
//定時任務的刪除
protected void cancelExpirationRenewal(Long threadId) {ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());  //從map中取if (task == null) {return;}if (threadId != null) {task.removeThreadId(threadId);  //先把id干掉}if (threadId == null || task.hasNoThreads()) {Timeout timeout = task.getTimeout();if (timeout != null) {timeout.cancel();  //然后再把任務取消}EXPIRATION_RENEWAL_MAP.remove(getEntryName());  //最后再把entry取消掉}}

請添加圖片描述

小總結

Redisson分布式鎖原理

  1. 可重入:利用hash結構記錄線程id和重入次數
  2. 可重試:利用信號量和PubSub功能實現等待、喚醒,獲取鎖失敗的重試機制
  3. 超時續約:利用watchDog,每隔一段時間(releaseTime/3),重置超時時間

5. Redisson的multiLock原理

請添加圖片描述
RedisConfig.java:

package com.hmdp.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissionConfig {@Beanpublic RedissonClient redissonClient(){// 配置類Config config =new Config();//添加redis地址,這里添加了單點的地址,也可以使用config.useClusterServers()添加集群地址config.useSingleServer().setAddress("redis://192.168.150.101:6379").setPassword("123456");// 創建客戶端return Redisson.create(config);}@Beanpublic RedissonClient redissonClient2(){// 配置類Config config =new Config();//添加redis地址,這里添加了單點的地址,也可以使用config.useClusterServers()添加集群地址config.useSingleServer().setAddress("redis://192.168.150.101:6380");// 創建客戶端return Redisson.create(config);}@Beanpublic RedissonClient redissonClient3(){// 配置類Config config =new Config();//添加redis地址,這里添加了單點的地址,也可以使用config.useClusterServers()添加集群地址config.useSingleServer().setAddress("redis://192.168.150.101:6381");// 創建客戶端return Redisson.create(config);}
}

RedissonTest.java:

package com.hmdp;import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;@Slf4j
@SpringBootTest
class RedissonTest {@Resourceprivate RedissonClient redissonClient;@Resourceprivate RedissonClient redissonClient2;@Resourceprivate RedissonClient redissonClient3;private RLock lock;@BeforeEachvoid setUp(){RLock lock1 = redissonClient.getLock("order");RLock lock2 = redissonClient2.getLock("order");RLock lock3 = redissonClient3.getLock("order");//創建聯鎖lock = redissonClient.getMultiLock(lock1,lock2,lock3);}@Testvoid method1() throws InterruptedException{//嘗試獲取鎖boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS);if (!isLock){log.error("獲取鎖失敗....1");return;}try {log.info("獲取鎖成功....1");method2();log.info("開始執行業務....1");}finally {log.warn("準備釋放鎖....1");lock.unlock();}}void method2(){//嘗試獲取鎖boolean isLock = lock.tryLock();if (!isLock){log.error("獲取鎖失敗....2");return;}try {log.info("獲取鎖成功....2");log.info("開始執行業務....2");}finally {log.warn("準備釋放鎖....2");lock.unlock();}}
}

總結

  1. 不可重入Redis分布式鎖:
    原理:利用setnx的互斥性;利用ex避免死鎖;釋放鎖時判斷線程標示
    缺陷:不可重入、無法重試、鎖超時失效
  2. 可重入的Redis分布式鎖:
    原理:利用hash結構,記錄線程標示和重入次數;利用watchDog延續鎖時間;利用信號量控制鎖重試等待
    缺陷:redis宕機引起鎖失效問題
  3. Redisson的multiLock:
    原理:多個獨立的Redis節點,必須在所有節點都獲取重入鎖,才算獲取鎖成功
    缺陷:運維成本高、實現復雜

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96257.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96257.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96257.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ES入門教程 (python 版)

ES入門教程 1. 創建ES對象from elasticsearch import Elasticsearch # 實例化一個ip為localhost&#xff0c;端口為9200&#xff0c;允許超時一小時的es對象 es Elasticsearch(hosts"localhost",port9200,timeout3600) # 1. 創建 索引 index_name "test"…

Gateway中Forward配置+源碼觀賞

系列文章目錄 文章目錄系列文章目錄一、ForwardPathFilter二、RouteToRequestUrlFilter三、ForwardRoutingFilteryaml forward配置gateway:routes:- id: user-route # uri: lb://useruri: forward:///user/indexpredicates:- Path/user/**- YoGET # filt…

BAS16XV2T1G ON安森美半導體 高速開關二極管 電子元器件IC

BAS16XV2T1G ON Semiconductor 高速開關二極管專業解析1. 產品技術檔案BAS16XV2T1G是安森美半導體(ON Semiconductor)推出的高速開關二極管&#xff0c;采用SOT-523超微型封裝&#xff08;1.60.80.95mm&#xff09;&#xff0c;專為現代高密度電子設備設計&#xff0c;以其超快…

親測可用 [安卓]《神秘來電》V1.1無需登入無廣告離線打開即用手機模擬發起虛假來電免費版

神秘來電是一款可以模擬虛擬電話的應用程序&#xff0c;它能夠在用戶需要的時候模擬各種來電&#xff0c;以便用戶能夠在尷尬的場合脫身。用戶可以預設多個不同的來電號碼和鈴聲&#xff0c;并可隨時觸發這些虛擬電話&#xff0c;在特殊情況下幫助用戶擺脫尷尬。它為那些社交恐…

8.20 dp

lc73矩陣置零queue隊列標記// 整行置零for(int y0; y<n; y) matrix[i][y] 0; // 整列置零for(int x0; x<m; x) matrix[x][j] 0; class Solution { public:void setZeroes(vector<vector<int>>& matrix) {int m matrix.size(), n matrix[0].size();//…

STL庫——string(類模擬實現)

? ? ? ? ? づ?ど &#x1f389; 歡迎點贊支持&#x1f389; 個人主頁&#xff1a;勵志不掉頭發的內向程序員&#xff1b; 專欄主頁&#xff1a;C語言&#xff1b; 文章目錄 前言 一、基本框架 二、構造函數 三、析構函數 四、拷貝構造 五、運算符重載 5.1、賦值重載 5.2…

Linux I/O 多路復用實戰:深入剖析 Select 與 Poll

## 引言:從“阻塞”的餐廳到“事件驅動”的盛宴 想象一下,你是一家小餐館的服務員。餐廳只有5張桌子。你的工作流程是這樣的:走到1號桌,問他們是否要點菜,然后站在那里等他們決定;等他們點完,再去2號桌,同樣站在那里等... 如果1號桌的客人看菜單看了半個小時,那么其他…

【clion】cmake腳本1:調試腳本并構建Fargo項目win32版本

調試腳本并構建 【clion】visual studio的sln轉cmakelist并使用clion構建32位 報錯 "D:\Program Files\JetBrains\CLion 2022.3.1\bin\cmake\win\x64\bin\cmake.exe" --debugger --debugger-pipe=\\<

VS2005里的快捷鍵

VS2005是微軟在2005發布的一款支持C、C#、.net開發語言的集成開發工具&#xff0c;它支持的C版本為C03&#xff0c;但不支持C11&#xff0c;到VS2013才支持大部分的C11(簡稱C11)&#xff0c;到VS2015 update3才完全支持C11。既然VS2005不支持C11&#xff0c;而智能指針是C11才引…

前后端聯合實現文件下載,實現 SQL Server image 類型文件下載

1、前端 Vue3QualityFile.vue<script setup lang"ts" name"QualityFile"> ...... // 下載&#xff0c;實現 SQL Server image 類型文件下載 const onDownloadClick async (fileNo: string) > {// const result await qualityFileDownloadFileWi…

【OneAI】使用Rust構建的輕量AI網關

LLM網關 統一大模型API入口&#xff0c;使用一個令牌調用多家模型&#xff0c;無需切換API Key兼容OpenAI輸入輸出規范內置10提供商和50模型&#xff0c;開箱即用支持自動負載、限流、IP限制、Token用量限制等功能支持釘釘、飛書、企微消息預警支持對不同提供商設置代理支持主…

Jenkins服務器配置SSH

1. 創建Jenkins用戶的SSH配置ssh-keygen -t rsa -b 4096 -f /tmp/jenkins_ssh_key -N ""2. 在Jenkins服務器上執行以下命令# 切換到root用戶 sudo su -# 創建Jenkins用戶的SSH目錄 mkdir -p /var/lib/jenkins/.ssh chown jenkins:jenkins /var/lib/jenkins/.ssh chmo…

nginx-下載功能-狀態統計-訪問控制

nginx-下載功能-狀態統計-訪問控制一、利用nginx做網站提供下載功能1. 進入nginx存放配置文件目錄2. 編輯nginx.conf文件&#xff0c;開啟下載功能3. 檢查nginx.conf主配置文件是否正確4. 重啟nginx服務5. 修改首頁文件index.html6. 訪問首頁7. 去網頁根目錄下新建download目錄…

GitLab CI/CD、Jenkins與GitHub Actions在Kubernetes環境中的方案對比分析

GitLab CI/CD、Jenkins與GitHub Actions在Kubernetes環境中的方案對比分析 隨著容器化和微服務的普及&#xff0c;基于Kubernetes的部署已經成為主流。在實際的生產環境中&#xff0c;如何選擇合適的CI/CD流水線方案以實現自動化構建、測試、部署和發布&#xff0c;直接關系到團…

tcp會無限次重傳嗎

tcp作為面向連接的&#xff0c;可靠的&#xff0c;字節流。最重要的特點就是可靠&#xff0c;其中重傳又是保證可靠的重要前提。那么當tcp發送數據之后&#xff0c;收不到ack的情況下&#xff0c;會無限次重傳嗎。不會。# cat /proc/sys/net/ipv4/tcp_retries1 3 # cat /proc/s…

EasyAIoT平臺部署

EasyAIoT官方文檔專注于 AIoT 智能硬件與工業軟件解決方案&#xff0c;提供從設備接入到云端管理的全棧服務http://pro.basiclab.top:9988/

功能測試相關問題

1.功能測試流程&#xff08;工作流程&#xff09;需求分析 -- 測試點分析&#xff08;xmind&#xff09;-- 編寫測試計劃/用例及評審 -- 執行測試用例&#xff08;開發提交測試&#xff09;-- 發現缺陷通過缺陷管理工具提交 -- 回歸測試及bug驗證&#xff08;開發提測新版本&am…

微服務網關中數據權限傳遞的那些坑:從 Feign 兼容性問題到解決方案

在微服務架構中&#xff0c;網關作為流量入口&#xff0c;常常需要承擔身份認證、權限校驗等職責。其中&#xff0c;用戶數據權限的傳遞看似簡單&#xff0c;卻隱藏著不少兼容性陷阱。本文將結合實際項目經驗&#xff0c;聊聊如何解決 Feign 調用時請求頭中 JSON 數據的傳遞問題…

基于SpringBoot的旅游攻略系統網站【2026最新】

作者&#xff1a;計算機學姐 開發技術&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源碼”。 專欄推薦&#xff1a;前后端分離項目源碼、SpringBoot項目源碼、Vue項目源碼、SSM項目源碼、微信小程序源碼 精品專欄&#xff1a;…

thingsboard 服務器在2核CPU、2G內存資源配置下如何調優提速,適合開發/演示

物聯網設備管理平臺致力于為客戶提供高效、可靠的物聯網解決方案。基于開源物聯網平臺進行深度二次開發&#xff0c;我們打造了功能強大、靈活易用的物聯網平臺&#xff0c;廣泛應用于智能家居、智能工廠、智能城市等多個領域 一、標準資源要求 CPU&#xff1a;建議至少 8 vCP…