Redis之分布式鎖與緩存設計

1、分布式鎖

1.1、超賣問題

    /*** 存在庫存超賣的不安全問題*/private void deductStock() {int stockTotal = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stockTotal > 0) { // 這里存在多個線程、進程同時判斷通過,然后超買的問題int realStock = stockTotal - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣減成功,剩余庫存:" + realStock);} else {System.out.println("扣減失敗,庫存不足");}}

1.2、線程鎖

    /*** 單進程線程安全,但是多進程不安全(分布式不安全)*/private void threadStock() {synchronized (this) { // 同一進程內串行執行,因此僅在同一個進程內線程安全int stockTotal = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stockTotal > 0) {int realStock = stockTotal - 1;stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣減成功,剩余庫存:" + realStock);} else {System.out.println("扣減失敗,庫存不足");}}}

1.3、分布式鎖

1.3.1、基礎版

借用setnx(set if not exists)實現分布式鎖

setnx的作用是:當目標key不存在時才會設置成功,否則設置失敗。這樣的話當多個進程同時嘗試set同一個key時,同一時刻只能有一個成功。

    /*** 一般添加分布式鎖的方式*/private void stockLock1() {// id為10086的商品String lockKey = "stock:10086";// setIfAbsent其實就是setnx的實現Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked");// 設置成功才會返回trueif (!result) {throw new RuntimeException("該商品正在被其他買家扣減庫存,請稍后再試~");}try {int stockTotal = Integer.parseInt(stringRedisTemplate.opsForValue().get(lockKey));if (stockTotal > 0) {int realStock = stockTotal - 1;stringRedisTemplate.opsForValue().set(lockKey, realStock + "");System.out.println("扣減成功,剩余庫存:" + realStock);} else {System.out.println("扣減失敗,庫存不足");}} finally {// 釋放鎖stringRedisTemplate.delete(lockKey);}}

這種方式存在一個死鎖問題,如果進程還沒執行到finally代碼塊中釋放分布式鎖的邏輯時就宕機了,會導致分布式鎖一直無法釋放。

1.3.2、帶過期時間

    /*** 帶超時時間的分布式鎖的方式*/private void stockLock2() {// id為10086的商品String lockKey = "stock:10086";// 設置10秒的超時時間,10秒后自動釋放鎖// 這里需要注意,這里是通過一行命令執行的加鎖 + 超時,而不是2行。 因為如果放開為2行也會有剛加上鎖,還沒來得及設置超時就宕機成為死鎖的情況。Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);// 設置成功才會返回trueif (!result) {throw new RuntimeException("該商品正在被其他買家扣減庫存,請稍后再試~");}try {int stockTotal = Integer.parseInt(stringRedisTemplate.opsForValue().get(lockKey));if (stockTotal > 0) {int realStock = stockTotal - 1;stringRedisTemplate.opsForValue().set(lockKey, realStock + "");System.out.println("扣減成功,剩余庫存:" + realStock);} else {System.out.println("扣減失敗,庫存不足");}} finally {// 釋放鎖stringRedisTemplate.delete(lockKey);}}

這里也有一個問題,如果業務在10s內沒執行完,鎖也釋放了。這樣也會出現不安全的問題。

1.3.3、鎖續命(watch dog)

針對上面的情況,可以在加鎖成功后,進程單獨開啟一個線程,每隔幾秒的時間檢測一下鎖是否還持有,如果持有就去將鎖的過期時間重置。這樣就不會出現進程還持有鎖,但是Redis中這個鎖已經過期被強制被動釋放掉的情況。

偽代碼

/*** 帶鎖續命的分布式鎖的方式*/private void stockLock3() {// id為10086的商品String lockKey = "stock:10086";// 設置10秒的超時時間,10秒后自動釋放鎖// 這里需要注意,這里是通過一行命令執行的加鎖 + 超時,而不是2行。 因為如果放開為2行也會有剛加上鎖,還沒來得及設置超時就宕機成為死鎖的情況。Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);// 設置成功才會返回trueif (!result) {throw new RuntimeException("該商品正在被其他買家扣減庫存,請稍后再試~");}// 開啟一個線程(watch dog)專門去掃描鎖是否還持有new Thread(watch dog).start();try {int stockTotal = Integer.parseInt(stringRedisTemplate.opsForValue().get(lockKey));if (stockTotal > 0) {int realStock = stockTotal - 1;stringRedisTemplate.opsForValue().set(lockKey, realStock + "");System.out.println("扣減成功,剩余庫存:" + realStock);} else {System.out.println("扣減失敗,庫存不足");}} finally {// 釋放鎖stringRedisTemplate.delete(lockKey);}}

watch dog的邏輯實現比較復雜,現在已經有現成的開源框架實現,直接用即可,不需要重復造比較復雜的輪子,因為容易出錯。

1.3.4、redisson

這是一個類似jedis的客戶端,它就實現了帶watch dog很完善的分布式鎖功能。

pom

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.6.5</version>
</dependency>

參考代碼

    /*** redisson*/private void stockLock4() {// id為10086的商品String lockKey = "stock:10086";//獲取鎖對象RLock redissonLock = redisson.getLock(lockKey);//加分布式鎖redissonLock.lock();  // .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);try {int stockTotal = Integer.parseInt(stringRedisTemplate.opsForValue().get(lockKey));if (stockTotal > 0) {int realStock = stockTotal - 1;stringRedisTemplate.opsForValue().set(lockKey, realStock + "");System.out.println("扣減成功,剩余庫存:" + realStock);} else {System.out.println("扣減失敗,庫存不足");}} finally {// 釋放鎖redissonLock.unlock();}}
1.3.4.1、核心加鎖流程

在這里插入圖片描述
這里補充一下,鎖超時時間默認30s

1.3.4.2、核心源碼

加鎖

    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {internalLockLeaseTime = unit.toMillis(leaseTime);return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,"if (redis.call('exists', KEYS[1]) == 0) then " +"redis.call('hset', KEYS[1], ARGV[2], 1); " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return nil; " +"end; " +"if (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]);",Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));}

核心是使用lua腳本依靠hset進行加鎖 和 支持重入鎖

這里提一下,lua腳本其中某個指令執行失敗時不會回滾數據,但是會中斷后續指令執行。 – 因此如果要回滾數據,需要lua自身保證

redis.call('set', 'key1', 'value1')  -- 成功
redis.call('incr', 'key1')           -- 失敗(key1為字符串)
redis.call('set', 'key2', 'value2')  -- 不會執行

鎖續命

private void scheduleExpirationRenewal(final long threadId) {if (expirationRenewalMap.containsKey(getEntryName())) {return;}Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), 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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));future.addListener(new FutureListener<Boolean>() {@Overridepublic void operationComplete(Future<Boolean> future) throws Exception {expirationRenewalMap.remove(getEntryName());if (!future.isSuccess()) {log.error("Can't update lock " + getName() + " expiration", future.cause());return;}if (future.getNow()) {// reschedule itself 這里就是循環續命邏輯scheduleExpirationRenewal(threadId);}}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {task.cancel();}}

核心lua

"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return 1; " +"end; " +"return 0;",

如果沒過期,直接重置超時時間。

加鎖失敗時自旋重試

    public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();Long ttl = tryAcquire(leaseTime, unit, threadId);// lock acquiredif (ttl == null) {return;}RFuture<RedissonLockEntry> future = subscribe(threadId);commandExecutor.syncSubscription(future);try {while (true) {ttl = tryAcquire(leaseTime, unit, threadId);// lock acquiredif (ttl == null) {break;}// waiting for messageif (ttl >= 0) {// 等待上一把鎖超時,這里會阻塞ttl超時的時間,這里使用的Semaphore,會阻塞時會讓出CPU時間片// 這里有一個巧妙的地方,如果目標鎖提前釋放,這里就會提前退出阻塞。不會固定等ttl時間getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {getEntry(threadId).getLatch().acquire();}}} finally {unsubscribe(future, threadId);}
//        get(lockAsync(leaseTime, unit));}

鎖釋放
本質上是用到了Redis的發布訂閱功能,也就是Stream流類型。

    protected RFuture<Boolean> unlockInnerAsync(long threadId) {return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('exists', KEYS[1]) == 0) then " +// 鎖不存在時,會發布一個鎖釋放的Stream事件, 這樣Java客戶端就可以感知到,走釋放邏輯,提前通知其它線程可以嘗試獲取鎖了"redis.call('publish', KEYS[2], ARGV[1]); " +"return 1; " +"end;" +// 使用線程ID判斷是否鎖是自己線程加的,如果不是則不進行后續處理// 防止其它線程因為代碼錯誤或者惡意直接釋放代碼// 這里需要注意,getLockName(threadId) 實際上是uuid + 線程id,能保證唯一"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +"return nil;" +"end; " +// 重入鎖邏輯,釋放一重鎖,依舊會重置超時時間"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +"if (counter > 0) then " +"redis.call('pexpire', KEYS[1], ARGV[2]); " +"return 0; " +"else " +"redis.call('del', KEYS[1]); " +"redis.call('publish', KEYS[2], ARGV[1]); " +"return 1; "+"end; " +"return nil;",Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));}

1.3.5、鎖優化

上面的方式本質上就是將請求串行化,這樣的話并發會一點起不來。為了保證數據安全的同時,又要提供并發,可以從鎖粒度上面去思考,例如一個商品100個庫存,是否可以將這個商品拆成5個鎖,每個鎖管20個庫存,這樣的話就可以支持五個并發了(分段鎖

2、緩存設計

2.1、需要注意的問題

2.1.1、緩存穿透

一般情況下,會將從數據庫中查詢到的數據放到Redis緩存中,然后下次查詢時在緩存中能查到該條數據,就直接返回。

但是這里存在一個特殊的情況,如果對應數據確實在Redis緩存和數據庫中都不存在,那么每次空查詢都會打到數據庫中,導致數據庫壓力劇增。 - 這種情況,Redis緩存就失去了保護作用,即緩存穿透。

造成這種情況的原因:
1、業務代碼本身問題,導致正常的數據一直查不到數據,或者沒有放入緩存。

2、惡意攻擊,或者爬蟲,產生大量惡意空查詢。

解決方案
1、將空對象也緩存到本地緩存之中,然后設置緩存淘汰策略。

    public static String get(String key) {// 從緩存中獲取數據String cacheValue = cache.get(key);// 緩存為空if (StringUtils.isBlank(cacheValue)) {// 從存儲中獲取String storageValue = storage.get(key);cache.set(key, storageValue);// 如果存儲數據為空, 需要設置一個過期時間(300秒)if (storageValue == null) {cache.expire(key, 60 * 5);}return storageValue;} else {// 緩存非空return cacheValue;}}

本地緩存比Redis緩存還快,加上設置淘汰策略,能更好的屏蔽掉無效請求。

2、使用布隆過濾器

布隆過濾器作用:如果認為一個請求存在,那么這個請求可能不存在。但是如果認為一個請求存在,那么這個請求必然不存在

布隆過濾器本身就是一個很大的位數組 + 一個無偏的hash函數構成。能夠將對應key值通過函數分攤到位數組中的不同位。 這樣僅需要檢查對應key是否在位數組中存在重合,就可以判定是否存在。

使用布隆過濾器的場景:數據量大(實時性低)、數據命中率不高、數據相對固定(沒有頻繁增量數據)的場景。另外值得一提的是,布隆過濾器屬于CPU換空間,占用的內存很低

注意:已經加入布隆過濾器的數據不能移除,除非重構布隆過濾器。

示例(基于Redisson)

    public static void main(String[] args) {Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379");//構造RedissonRedissonClient redisson = Redisson.create(config);RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");//初始化布隆過濾器:預計元素為100000000L,誤差率為3%,根據這兩個參數會計算出底層的bit數組大小bloomFilter.tryInit(100000L, 0.03);//將zhuge插入到布隆過濾器中bloomFilter.add("wangxiaoyi");bloomFilter.add("hh");//判斷下面號碼是否在布隆過濾器中System.out.println(bloomFilter.contains("wangxiaoyi"));//trueSystem.out.println(bloomFilter.contains("3"));//false}

即使使用布隆過濾器,也有插入數據需要刪除布隆過濾器中某個key的場景。好像可以使用內存增加幾倍的變種過濾器。

2.1.2、緩存擊穿(失效)

一般將數據放到緩存中時會設置過期時間,這里存在一個問題,如果大量的緩存在一個集中的時間被加載,那么也會存在同一時刻大量的失效。緩存失效就意味著大量的請求會直接打到數據庫,這也會導致數據庫瞬間壓力劇增。 這就是緩存失效造成的擊穿緩存。

解決方案的話,可以給緩存的過期時間設置一個波動區間,這樣的話在同一時間失效的數據量就會小很多。

另外就是,基本上熱點數據相對冷數據較少,也可以在每次查詢時,給對應key進行過期時間續期,這樣也能更快的分離冷熱數據。

示例

    public static String get(String key) {// 從緩存中獲取數據String cacheValue = cache.get(key);// 緩存為空if (StringUtils.isBlank(cacheValue)) {// 從存儲中獲取String storageValue = storage.get(key);cache.set(key, storageValue); //設置一個過期時間(300到600之間的一個隨機數)int expireTime = new Random().nextInt(300) + 300;if (storageValue == null) {cache.expire(key, expireTime);}return storageValue;} else {// 緩存非空return cacheValue;}}

2.1.3、緩存雪崩

有一句話:雪崩之下,沒有一篇雪花是無辜的。

在這里,對應的也就是,當redis中存在某個bigkey,或者網絡波動導致響應變慢。這樣就會導致第一個請求拉慢第二個請求,第二個拉慢第三個。 最終導致整體響應變慢,最后Redis被拉垮(或者說Redis直接蹦掉),服務調用鏈一層影響一層,最終整個系統不再響應,最終觸發OOM崩掉。 – 這就是緩存雪崩,由一片超時雪花造成。

解決方向
1、保證Redis的高可用,例如主從哨兵、集群。

2、支持服務限流、熔斷。比如使用Sentinel或Hystrix限流降級組件。
如果是非核心業務,例如用戶信息,則可以暫時直接返回友好的提示。 如果是核心業務,則考慮限流,允許小水細流的請求直接查詢數據庫。

3、把本地緩存也使用起來,先查本地緩存,再查Redis,這樣也可以過濾一波大流量。這里需要對本地緩存的淘汰策略有嚴格控制。

2.1.4、熱點緩存重建

查詢數據都是先查詢緩存,再查詢數據庫。 如果突然除了一個熱點key,被大量的請求同時訪問,這時就會出現大量的請求都沒有被緩存擋住(緩存還沒被及時加載到緩存中),那么這些大量的請求就直接打到數據庫中了,這樣也會導致數據庫壓力突增。 – 這里要做的就是在key從數據庫加載到緩存的操作需要控制在第一個到達的請求來做,其它請求要么阻塞,要么直接拒接。 這就是熱點緩存重建策略。

示例

    public static String get(String key) {// 從Redis中獲取數據String value = redis.get(key);// 如果value為空, 則開始重構緩存if (value == null) {// 只允許一個線程重建緩存, 使用nx, 并設置過期時間exString mutexKey = "mutext:key:" + key;if (redis.set(mutexKey, "1", "ex 180", "nx")) {// 從數據源獲取數據value = db.get(key);// 回寫Redis, 并設置過期時間redis.setex(key, timeout, value);// 刪除key_mutexredis.delete(mutexKey);}// 其他線程休息50毫秒后重試else {Thread.sleep(50);get(key);}}return value;}

2.1.5、緩存與數據庫雙寫不一致

出現雙寫不一致的原因:先來的請求,在給Redis發請求時,不一定就先執行,存在被其他Redis請求加塞的情況,因為網絡、CPU等因素都是不確定的,會造成寫丟失。

解決方案
1、對于并發度很小的數據(比如個人維度的購物車,訂單),可以不用考慮這個問題,按照常規的設置緩存過期時間,到時失效后,就會重新寫緩存最新的數據。

2、就算并發度很高,有些數據也是能容忍短暫的數據不一致的,例如商品名稱。

3、如果必須要保證一致性,就需要加Redis讀寫鎖。

4、也可以使用阿里開源的canal通過監聽數據庫binlog的方式,及時去修改緩存,但是這樣的話就引入了新的中間件,增加了系統的復雜度,同時在讀的時候存在一定時間的延遲。

2.1.6、總結

1、使用Redis緩存的應該主要是讀多寫少的場景。

2、如果是寫多讀也多的場景,并且還有保證數據強一致性就沒有必要十余年緩存了,直接使用數據庫。

3、如果確實數據庫抗不住,就只能將Redis作為讀寫主存,異步將數據同步給數據庫,這時的數據庫是作為一個備份的角色。 總之就是同步扛不住就只能轉異步(例如RabbitMQ),或者限流。

4、總之,Redis是用來緩存,解決那些能容忍一點時間不一致的數據查詢壓力的,如果為了其它一致性過度設計就得不償失了。

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

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

相關文章

靜態住宅IP的特點

穩定性高&#xff1a;與動態IP地址相比&#xff0c;靜態住宅IP不會不定時變更&#xff0c;能確保業務在網絡環境中的一致性和連貫性&#xff0c;適合需要長期維持同一身份的場景&#xff0c;如跨境電商業務等3。安全性強&#xff1a;由于其住宅屬性&#xff0c;看起來更像是正常…

Linux 編譯 Android 版 QGroundControl 軟件并運行到手機上

Linux 編譯 Android 版 QGroundControl 軟件并運行到手機上環境說明操作步驟一、參考上一篇文章在電腦端把環境搭建好二、配置 Qt Creator 的 Android 環境環境說明 電腦系統 Ubuntu 22.04 qgroundcontrol master 分支 Qt 6.8.3 操作步驟 一、參考上一篇文章在電腦端把環境搭…

Python 2025:量化金融與智能交易的新紀元

當Python遇見金融大數據&#xff0c;算法交易正迎來前所未有的技術變革在2025年的技術浪潮中&#xff0c;Python已經從一個"膠水語言"蛻變為金融科技領域的核心驅動力。根據GitHub 2025年度報告&#xff0c;Python在量化金融項目中的使用率增長了217%&#xff0c;在對…

[論文閱讀] 人工智能 + 軟件工程 | TDD痛點破解:LLM自動生成測試骨架靠譜嗎?靜態分析+專家評審給出答案

TDD痛點破解&#xff1a;LLM自動生成測試骨架靠譜嗎&#xff1f;靜態分析專家評審給出答案 論文信息項目詳情論文原標題Evaluation of Large Language Models for Generating RSpec Test Skeletons in Ruby on Rails論文鏈接https://arxiv.org/pdf/2509.04644一段話總結 該研究…

開源PSS解析器1

本章介紹另一個開源PSS解析工具zuspec&#xff1a; zuspec 提供了一組用于處理 actions relationship level 的工具 &#xff08;ARL&#xff09; 模型&#xff0c;主要是使用 Accellera 便攜式測試和刺激 &#xff08;PSS&#xff09; 語言描述的模型。ARL 模型用于為數字設計…

26考研——內存管理_內存管理策略(3)

408答疑 文章目錄一、內存管理策略1、內存管理的基本原理和要求1.1、相關概念1.2、邏輯地址與物理地址1.3、程序的鏈接與裝入1.4、進程的內存映像1.5、內存保護1.6、內存共享1.7、內存分配與回收1.8、在存儲管理中涉及到兩個問題2、連續分配管理方式2.1、相關概念2.2、單一連續…

Python爬蟲實戰:研究Event Handling機制,構建在線教育平臺的課程數據采集和分析系統

1. 引言 1.1 研究背景與意義 在大數據時代,互聯網作為全球最大的信息載體,蘊含著海量有價值的數據。這些數據涵蓋了商業交易、用戶行為、社會趨勢等多個領域,對企業決策、學術研究和社會管理具有重要參考價值。如何高效、準確地獲取這些數據并進行深度分析,成為當前數據科…

docker 安裝 redis 并設置 volumes 并修改 修改密碼(四)

設置新密碼: 127.0.0.1:6379> CONFIG SET requirepass newpassword OK驗證新密碼: 127.0.0.1:6379> AUTH newpassword OK更新配置文件: 編輯主機的配置文件/data/redis/conf/redis.conf,將requirepass的值修改為新密碼: requirepass newpassword重啟容器以使配置…

NBA球星知識大挑戰:基于 PyQt5 的球星認識小游戲

NBA球星知識大挑戰&#xff1a;基于 PyQt5 的球星認識小游戲 代碼詳見&#xff1a;https://github.com/xiaozhou-alt/NBA_Players_Recognition 文章目錄 NBA球星知識大挑戰&#xff1a;基于 PyQt5 的球星認識小游戲一、項目介紹二、文件夾結構三、項目實現1. 自定義動畫按鈕&a…

電磁波成像(X射線、CT成像)原理簡介

電磁波成像&#xff08;X射線、CT成像&#xff09;原理簡介一、圖像形成的一般形式二、可見光成像2.1可見光2.2可見光成像三、其他電磁波成像3.1X射線成像3.2CT成像3.2.1CT成像原理3.2.2CT成像與X射線成像對比3.2.3CT生成三維描述3.3PET成像一、圖像形成的一般形式 大多數圖像…

k8s部署2:前置條件:docker部署

前兩天發布了k8s的前置發布條件,對于防火墻的處理,我看大家反響還不錯,所以作為先行者,我感覺自己多了不少動力,所以今天來說說k8s部署前置條件中docker部分的部署。在此先感謝一下那些點贊和添加收藏的朋友們,你們的支持是我永遠的動力!三克油喂給馬吃! 之前寫過docke…

某開源漫畫系統RCE代碼審計

免責聲明 本文檔所述漏洞詳情及復現方法僅限用于合法授權的安全研究和學術教育用途。任何個人或組織不得利用本文內容從事未經許可的滲透測試、網絡攻擊或其他違法行為。使用者應確保其行為符合相關法律法規&#xff0c;并取得目標系統的明確授權。 對于因不當使用本文信息而造…

Pandas DataFrame 指南

&#x1f4ca; Pandas DataFrame 常用操作代碼示例 下面用表格匯總了 DataFrame 的常用操作&#xff0c;方便你快速查閱和實踐。 操作類別代碼示例說明&#xff08;簡要&#xff09;數據讀取df pd.read_csv(data.csv)讀取 CSV 文件df pd.read_excel(data.xlsx, sheet_nameS…

React學習教程,從入門到精通, React 樣式語法知識點與案例詳解(13)

React 樣式語法知識點與案例詳解 作為React初學者&#xff0c;掌握樣式語法是構建美觀UI的關鍵。本文將詳細介紹React中所有主要的樣式方法&#xff0c;并提供詳細注釋的案例代碼。 一、React樣式語法知識點總覽 1. 行內樣式 (Inline Styles) 使用style屬性&#xff0c;值為Jav…

Proxychains 配置全解析:從入門到高級應用

引言 在數字時代&#xff0c;網絡隱私與安全至關重要。無論是繞過地理限制訪問內容&#xff0c;還是在滲透測試中隱藏蹤跡&#xff0c;代理工具都不可或缺。Proxychains&#xff08;或稱 Proxychains-NG&#xff09;作為一款經典的開源代理鏈工具&#xff0c;以其高效靈活的特性…

二叉樹的前中后序遍歷(迭代法)

目錄 題目鏈接&#xff1a; 題目&#xff1a; 解題思路&#xff1a; 代碼&#xff1a; 前序遍歷&#xff1a; 中序遍歷&#xff1a; 后序遍歷&#xff1a; 總結&#xff1a; 題目鏈接&#xff1a; 144. 二叉樹的前序遍歷 - 力扣&#xff08;LeetCode&#xff09; 94. …

redis的數據類型:string

文章目錄String類型介紹redis采用的字符集json類型介紹String類型的命令set key value [EX seconds] [NX|XX]incr keyincr對操作的key對應的value類型有限制嗎&#xff1f;incr key操作的返回值是什么&#xff1f;incr操作的key可以不存在嗎&#xff1f;多個客戶端同時針對同…

傳統神經網絡實現-----手寫數字識別(MNIST)項目

完整代碼&#xff1a;# import torch # print(torch.__version__)#1.X 1、驗證安裝的開發環境是否正確&#xff0c; MNIST包含70,000張手寫數字圖像: 60,000張用于訓練&#xff0c;10,000張用于測試。 圖像是灰度的&#xff0c;28x28像素的&#xff0c;并且居中的&#xff…

工業機器人標桿的數字化突圍,珞石機器人如何以CRM實現業務重塑

在智能制造浪潮下&#xff0c;工業機器人行業正迎來快速增長。作為國內領先的機器人制造商&#xff0c;珞石機器人面對業務規模的迅速擴張&#xff0c;意識到傳統的管理方式已無法滿足企業發展需求&#xff0c;急需通過數字化升級破解管理難題。因此珞石機器人選擇引入紛享銷客…

NVIDIA GPU的指令集詳細介紹

這是一個非常核心且深入的話題。GPU的指令集架構&#xff08;Instruction Set Architecture, ISA&#xff09;是理解GPU如何工作的關鍵&#xff0c;它直接體現了GPU為大規模并行計算而生的設計哲學。下面我將詳細、全面地介紹GPU的指令集。 第一部分&#xff1a;核心哲學 —— …