Redis入門教程

1. 什么是NoSql

NoSQL一詞最早出現于1998年,是Carlo Strozzi開發的一個輕量、開源、不提供SQL功能的關系數據庫。2009年,Last.fm的Johan Oskarsson發起了一次關于分布式開源數據庫的討論,來自Rackspace的Eric Evans再次提出了NoSQL的概念,這時的NoSQL主要指非關系型、分布式、不提供ACID的數據庫設計模式。它不同于傳統的關系數據庫,兩者存在許多顯著的不同點,其中最重要的是NoSQL不使用SQL作為查詢語言。其數據存儲可以不需要固定的表格模式。

2. Redis簡介

2008年,意大利的一家創業公司Merzia推出了一款基于MySQL的網站實時統計系統LLOOGG,然而沒過多久該公司的創始人 Salvatore Sanfilippo便對MySQL的性能感到失望,于是他決定親自為LLOOGG量身定做一個數據庫,并于2009年開發完成,這個數據庫就是Redis。 不過Salvatore Sanfilippo并不滿足只將Redis用于LLOOGG這一款產品,而是希望更多的人使用它,于是在同一年Salvatore Sanfilippo將Redis開源發布,并開始和Redis的另一名主要的代碼貢獻者Pieter Noordhuis一起繼續著Redis的開發,直到今天。短短的幾年時間,Redis就擁有了龐大的用戶群體。Hacker News在2012年發布了一份數據庫的使用情況調查,結果顯示有近12%的公司在使用Redis。國內如新浪微博、街旁網、知乎網,國外如GitHub、Stack Overflow、Flickr等都是Redis的用戶。VMware公司從2010年開始贊助Redis的開發, Salvatore Sanfilippo和Pieter Noordhuis也分別在3月和5月加入VMware,全職開發Redis。

Redis是使用c語言開發的一個高性能鍵值數據庫。常用于分布式系統中的緩存、電商秒殺、排行榜、訪問量統計、分布式會話共享等高并發應用場景。Redis可以通過一些鍵值類型來存儲數據。其數據類型包括字符類型、散列類型、列表類型、集合類型、有序集合類型。

3. 安裝Redis

訪問Redis官網https://redis.io/download下載最新的版本 。

image-20190127091355834

解壓并編譯安裝

$ tar xzf redis-5.0.3.tar.gz
$ cd redis-5.0.3
$ make install

Redis官網并沒有提供windows版本,但可以前往https://github.com/tporadowski/redis/releases下載windows的個人編譯版本(注意:并不是最新的版本)。

image-20190127091500286

4. 啟動服務

4.1 前端啟動

在redis的src目錄有一個redis-server文件,用于啟動一個redis服務。

image-20190128080006944

redis的默認端口為6379,當客戶端需要連接到redis服務時,就通過服務端的IP地址以及這個端口進行連接。也可以修改這個默認端口。在redis的根目錄下有一個redis.conf文件,它是redis的核心配置文件,redis的所有配置信息都在此文件中。如果需要修改端口,我們在配置文件中找到port配置,并將6379改為其他的端口號。

image-20190128081730591

修改完后需要重新啟動redis服務,需要注意的是,在使用redis-server啟動服務時需要指定redis.conf文件的絕對路徑,否則redis將以默認的配置啟動一個服務實例。

image-20190128082950744

前端啟動的模式我們可以在終端看到redis的啟動信息和相關的操作日志,但此時如果關閉了終端或者使用control+c將會立即停止redis服務。

4.2 后端啟動

所謂后端啟動,就是以一個獨立的進程來運行一個redis服務。首先修改redis.conf文件,找到daemonize選項并設置為yes,如下圖:

image-20190203082011881

保存退出后重新啟動redis服務,此時redis將以后臺進程的方式啟動服務。終端沒有顯示相關的啟動信息,并且啟動完成后,終端可以繼續執行其他的操作。

image-20190203082246538

5. 客戶端連接

5.1 Redis客戶端

在redis的src目錄下有一個redis-cli命令,這個就是官方提供的redis客戶端,可以使用它來連接和操作redis。當然,這僅僅只是一個命令行的客戶端程序,在實際的開發中會有不同的平臺語言,因此官網也提供了對各種語言的客戶端實現,在實際的項目開發中使用不同語言的客戶端來操作redis。例如官網提供了一個Java的客戶端Jedis。

image-20190203090500297

1)使用redis-cli

可以使用使用官方自帶的redis-cli客戶端來連接redis服務。參數-h為連接redis服務器的IP地址,-p為redis的端口號。連接完成后就可以對redis進行操作了。

image-20190203084322124

2)退出客戶端

如果想要退出客戶端的連接只需要在連接的狀態下輸入quit或者exit即可。

image-20190203084736497

3)身份認證

默認連接Redis時是不需要認證密碼的,我們可以為其設置一個連接的認證密碼。首先在redis.conf中找到requirepass配置項,取消注釋并設置一個密碼。

image-20190213160312170

保存后重啟服務,在連接客戶端時加上-a參數并輸入配置的密碼。

image-20190213160856278

連接時也可以不指定密碼也可以正常連接,但在操作Redis時候會提示一個錯誤,要求輸入認證密碼。這時使用auth命令來輸入密碼即可。

image-20190213161510434

5.2 可視化客戶端

也可以使用第三方的redis的可視化客戶端RDM(redis-desktop-manager),它同時提供了各種系統平臺的編譯版本,安裝后即可使用。下載地址:

image-20190203091654632

點擊左上角的Connect to Redis Server,在彈出的窗口中填寫相關的Name(連接名稱)、Address(連接地址)、端口號以及認證密碼(Auth),點擊OK即可。

image-20190203091505965

image-20190203091546340

這里我們看到連接redis后默認有16個庫(0 ~ 15),這是redis默認的配置,可以在redis.conf中可以找到相應的選項并修改默認數量。

image-20190203092012415

當我們使用客戶端連接redis時,默認選擇的是index為0的數據庫,然而也可以使用select命令選擇其他數據庫。例如選擇index為15的數據庫,如下操作:

image-20190203141448968

5.3. 停止服務

如果使用前端啟動redis,可以使用control+c或者kill命令來殺掉進程的方式關閉redis(注意:control+c并不能停止后端啟動的redis),但這些方式都是強制性的關閉redis,由于redis保存的數據先會存儲在內存,如果此時強制關閉,將導致redis還沒將數據持久化到文件中就退出,可能會照成部分的數據丟失。因此,應該使用正常的退出方式來停止redis服務,正常退出redis同樣使用redis-cli工具。

image-20190203085109054

如果設置了認證密碼,在關閉服務端時也同樣需要指定。

image-20190213163407190

6. 數據類型及常用API

Redis支持五種數據類型:string(字符串),hash(哈希),list(列表),set(集合)及sorted set(zset:有序集合)。

6.1 string(字符串)

String 是 redis 最基本的類型,一個 key 對應一個 value。它是二進制安全的,可以包含任何數據,如jpg圖片或者序列化的對象。

1)SET

語法:set key value

賦值操作。

image-20190204083836073

2)GET

語法:get key

取值操作。

image-20190204083932618

3)GETSET

語法:getset key value

取值后重新賦值。

image-20190204084114612

4)MSET

語法:mset key value [key value …]

同時設置多個鍵值。

image-20190204084331152

5)MGET

語法:mget key [key …]

獲取多個鍵值。

image-20190204084449235

6)DEL

語法:del key [key …]

刪除一個或多個鍵值對。

image-20190204084649996

7)INCR

語法:incr key

當存儲的字符串是整數時,讓當前鍵值遞增,并返回遞增或增加后的值。

image-20190204085509956

8)INCRBY

語法:incrby key increment*

當存儲的字符串是整數時,讓當前鍵值增加指定的數值,并返回遞增或增加后的值。

image-20190204085627802

9)DECR

語法:decr key

讓當前鍵值遞減,并返回遞減或減少后的值。

image-20190204090103737

10)DECRBY

語法:decrby key decrement

讓當前鍵值減少指定的數值,并返回遞減或減少后的值。

image-20190204090142201

11)APPEND

語法:append key value

向鍵值的末尾追加value。如果鍵不存在則將該鍵的值設置為value,即相當于 SET
key value。返回值是追加后字符串的總長度。

image-20190204090432914

12)獲取字符串長度(STRLEN)

STRLEN命令返回鍵值的長度,如果鍵不存在則返回0。

image-20190204090808672

6.2 hash(哈希)

hash是一個string類型的field和value的映射表,而field只能是String類型,hash特別適合用于存儲對象。

image-20190204091157360

1)HSET

語法:HSET key field value

HSET一次只能設置一個字段值。HSET命令不區分插入和更新操作,當執行插入操作時HSET命令返回1,當執行更新操作時返回0。

image-20190204134214619

2)HSETNX

語法:HSETNX key field value

當字段不存在時賦值,類似HSET。區別在于如果字段存在,該命令不執行任何操作。

例如:hsetnx user name zing

說明:如果user中不存在name字段則設置name的值為zing,否則不做任何操作。

image-20190204135606457

3)HGET

語法:HGET key field

HGET一次只能獲取一個字段值。

image-20190204140214213

4)HGETALL

語法:HGETALL key

獲取所有字段值。

image-20190204140544575

5)HDEL

語法:HDEL key field [field…]

可以刪除一個或多個字段,返回值是被刪除的字段個數。

image-20190204140809745

6)HINCRBY

語法:HINCRBY key field increment

為某個字段增加數值。

image-20190204141142522

7)HEXISTS

語法:HEXISTS key field

判斷字段是否存在,存在則返回1,否則返回0。

image-20190204141526465

8)HKEYS

語法:HKEYS key

獲取所有的字段名。

image-20190204141719881

9)HVALS

語法:HVALS key

獲取所有字段的值。

image-20190204141859820

10)HLEN

語法:HLEN key

獲取字段數量。

image-20190204143508898

6.3 list(列表)

Redis的list是采用來鏈表來存儲的,所以對于Redis的list數據類型的操作,是操作list的兩端數據來操作的。

1)LPUSH

語法:LPUSH key value [value …]

向列表左邊添加元素。

image-20190204150049467

2)RPUSH

語法:RPUSH key value [value …]

向列表右邊添加元素。

image-20190204150259434

3)LRANGE

語法:LRANGE key start stop

LRANGE命令是列表類型最常用的命令之一,用于獲取列表中的某一片段,將返回start到stop之間的所有元素(包含兩端的元素),索引從0開始。索引可以是負數,如:-1代表最后邊的一個元素。

image-20190204150640331

4)LPOP

語法:LPOP key

LPOP命令從列表左邊彈出一個元素,會分兩步完成:第一步是將列表左邊的元素從列表中移除。第二步是返回被移除的元素值。

image-20190204151255741

5)RPOP

語法:RPOP key

RPOP命令從列表右邊彈出一個元素,步驟與LPOP類似,第一步是將列表右邊的元素從列表中移除。第二步是返回被移除的元素值。

image-20190204151330740

6)LLEN

語法:LLEN key

獲取列表中元素的個數

image-20190204163548551

7)LREM

語法:LREM key count value

LREM命令會刪除列表中前count個值為value的元素,返回實際刪除的元素個數。根據count值的不同,該命令的執行方式會有所不同:

當count>0時, LREM會從列表左邊開始刪除。

當count<0時, LREM會從列表右邊開始刪除。

當count=0時,LREM刪除所有值為value的元素。

image-20190228095106676

8)LINDEX

語法:LINDEX key index

獲得指定索引的元素值。

image-20190204165017260

9)LSET

語法:LSET key index value

設置指定索引的元素值。

image-20190204165126605

10)LTRIM

語法:LTRIM key start stop

只保留列表的指定片段

image-20190204165405908

11)LINSERT

語法:LINSERT key BEFORE|AFTER pivot value

LINSERT首先會在列表中從左到右查找值為pivot的元素,然后根據第二個參數是BEFORE還是AFTER來決定將value插入到該元素的前面還是后面。

image-20190204165828783

12)RPOPLPUSH

語法:RPOPLPUSH source destination

將一個列表的最后一個元素轉移到另一個列表的最前面

image-20190204170459521

6.4 set(集合)

Redis 的 Set 是 String 類型的無序集合。集合成員是唯一的,這就意味著集合中不能出現重復的數據。

1)SADD

語法:SADD key member [member …]

增加一個或多個元素。

image-20190205085821980

2)SREM

語法:SREM key member [member …]

移除一個或多個元素。

image-20190205090001732

3)SMEMBERS

語法:SMEMBERS key

獲得集合中的所有元素。

image-20190205090235673

4)SISMEMBER

語法:SISMEMBER key member

判斷元素是否存在集合中。存在返回1,否則返回0。

image-20190205090445072

5)SDIFF

語法:SDIFF key [key …]

查找屬于集合A并且不屬于集合B的元素。(差集運算)

image-20190205090808028

6)SINTER

語法:SINTER key [key …]

查找屬于集合A且屬于集合B的元素。(交集運算)

image-20190205091715242

7)SUNION

語法:SUNION key [key …]

查找屬于集合A或者屬于集合B的元素。(合并運算)

image-20190205092119801

8)SCARD

語法:SCARD key

獲取集合中元素的個數。

image-20190205092312680

9)SPOP

語法:SPOP key [count]

從集合中彈出一個或多個元素,由count指定。如果不指定count,默認彈出一個。由于集合是無序的,所有SPOP命令會從集合中隨機選擇一個元素彈出。

image-20190205092708046

6.5 zset(有序集合)

zset又稱sorted set,稱之為有序集合,可排序的,但是唯一。和set的不同之處在于zset會給集合中的元素添加一個分數,然后通過這個分數進行排序。

1)ZADD

語法:ZADD key score member [score member …]

向有序集合中加入一個或多個元素和該元素的分數,如果該元素已經存在則會用新的分數替換原有的分數。返回值是新加入到集合中的元素個數,不包含之前已經存在的元素。

image-20190205095703328

2)ZSCORE

語法:ZSCORE key member

獲取元素的分數。

image-20190205095948874

3)ZREM

語法:ZREM key member [member …]

移除有序集合中的一個或多個成員,不存在的成員將被忽略。

image-20190205100315588

4)ZRANGE

語法:ZRANGE key start stop [WITHSCORES]

按照元素分數從小到大的順序返回索引從start到stop之間的所有元素(包含兩端的元素)。如果需要獲得元素的分數可以在命令尾部加上WITHSCORES參數。

image-20190206001217038

5)ZREVRANGE

語法:ZREVRANGE key start stop [WITHSCORES]

按照元素分數從大到小的順序返回索引從start到stop之間的所有元素(包含兩端的元素)。如果需要獲得元素的分數的可以在命令尾部加上WITHSCORES參數。

image-20190206001913448

6)ZRANK

語法:ZRANK key member

獲取元素排名(從小到大)。

image-20190206002508701

7)ZREVRANK

語法:ZREVRANK key member

獲取元素排名(從大到小)。

image-20190206002704642

8)ZRANGEBYSCORE

語法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

獲得指定分數范圍的元素。

image-20190206003420169

9)ZINCRBY

語法:ZINCRBY key increment member

增加某個元素的分數,并返回更改后的分數。

image-20190206003710077

10)ZCARD

語法:ZCARD key

獲取集合元素的數量。

image-20190206003840231

11)ZCOUNT

語法:ZCOUNT key min max

獲取指定分數范圍內的元素個數。

image-20190206004026075

12)ZREMRANGEBYRANK

語法:ZREMRANGEBYRANK key start stop

按照排名范圍刪除元素。

image-20190206004748350

13)ZREMRANGEBYSCORE

語法:ZREMRANGEBYSCORE key min max

按照分數范圍刪除元素。

image-20190206005026496

7. Redis鍵(Keys)

Redis鍵是二進制安全的,這意味著你可以使用任何二進制序列作為鍵,從像”foo” 這樣的字符串到一個 JPEG文件的內容。空字符串也是合法的鍵。

7.1 鍵的一些設計規則

  • 不要使用太長的鍵。例如,不要使用一個1024字節的鍵,不僅是因為占用內存,而且在數據集中查找key時需要多次耗時的key比較。

  • 不要使用太短的key。例如,user:1001比u1001更具有實際意義,相對于key本身以及value對象來說,增加的空間微乎其微。當然,短的鍵會消耗少的內存,需要找到平衡點。

  • 規范一種模式 (schema)。用冒號或者下橫線來連接多單詞字段,例如:”user:1001”或者"user_1001"。

7.2 Key的常用API

1)KEYS

語法:keys pattern

返回指定pattern的所有key

image-20190206005958619

2)EXISTS

語法:exists key

判斷一個key是否存在。存在返回后1,否則返回0。

image-20190206011145014

3)RENAME

語法:rename key newkey

重命名key

image-20190206011502678

4)TYPE

語法:type key

根據key返回value的類型。

image-20190206011729392

5)EXPIRE

語法:expire key seconds

設置key的生存時間。Redis的數據是緩存在內存中的,然后很多時候數據一般都會設置一個過期時間(即到期后銷毀數據,從而釋放更多的內存)。過期時間默認以秒為單位,默認值為-1,表示永不過期。

image-20190206012511668

也可以在設值的時候指定過期時間(秒)

image-20190301093522458

6)TTL

語法:ttl key

查看key剩余的過期時間。

image-20190206012555966

7)PERSIST

語法:persist key

清除key的過期時間。

image-20190206012956090

8)PEXPIRE

語法:pexpire key

以毫秒為單位設置key的過期時間。

image-20190206013625664

也可以在設值的時候指定過期的時間(毫秒)

image-20190301093643502

8. 持久化

8.1 簡介

Redis是一個支持持久化的內存數據庫,可以將內存中的數據同步到磁盤保證持久化。我們知道Redis會將數據緩存在內存中,如果沒有持久化,在服務器關閉或重啟之后數據會丟失。為了保證數據的安全以及效率,Redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件。而Redis提供了RDB和AOF兩種持久化策略。

8.2 RDB

Redis默認是會以快照RDB的形式將數據持久化到磁盤的一個dump.rdb二進 制文件。當Redis決定要持久化時,會 fork 一個子進程將數據寫到磁盤上一個臨時的RDB文件中,當子進程完成寫操作后,將原來的RDB替換掉。而Redis會在滿足某些條件后會進行持久化,并且可以對其進行配置。

配置RDB

在redis.conf文件中找到“Save the DB on disk”的配置,我們可以根據需要來修改這Redis的RDB持久化策略。

image-20190206101801079

說明:

save 900 1(如果在900秒之內有1次操作,則執行快照保存)

save 300 10(如果在300秒內有10次操作,則執行快照保存)

save 60 10000(如果在1分鐘之內有10000個次操作,則執行快照保存)

SAVE和BGSAVE

我們可以在客戶端直接使用SAVE或者BGSAVE命令立即將Redis的數據持久化到RDB文件中。他們兩者的區別在于BGSAVE命令會fork一個子進程在后臺進行持久化,主進程可以繼續處理客戶端發送的命令(非阻塞)。而SAVE命令需要等待Redis持久化完成后才可以繼續處理客戶端發送的命令(阻塞)。

image-20190208093203333

**RDB優點 **

RDB非常適合用于數據備份, 可以在當天內每小時備份一次,或者每個月的每天都進行備份。 如果遇到斷電或者宕機等其他一些災難情況,可以隨時將數據集還原。

RDB缺點

如果對數據的完整性和安全性要求非常高,要求每一次的操作數據都能持久化到文件中,這時RDB就不太適合了。因為RDB是按照時間范圍的操作次數為條件促發持久化,如果未滿足這些觸發條件,Redis并不會將數據保存到文件,導致數據丟失。例如:save 60 10000,如果在1分鐘之內有9000次的操作,如果此時服務器異常退出或宕機,由于未滿足條件,將導致丟失這1分鐘的數據。

8.3 AOF

AOF持久化可以記錄每個寫操作,將Redis執行過的所有寫指令(讀操作不記錄)保存到appendonly.aof文件中,并且只允許追加文件而不可以改寫文件。在Redis啟動的時候會讀取該文件重新構建緩存數據。在打開AOF持久化機制之后,Redis每當接收到一條寫命令,會先寫入系統緩存,然后每隔一定時間(默認是每秒鐘)fsync一次(寫入到指定文件)。

啟用AOF

AOF持久化默認是關閉的,如果要啟用AOF,需要在redis.conf配置文件中啟用該功能,將appendonly no設置為appendonly yes。

image-20190206225814217

所有寫操作默認保存在appendonly.aof文件中,可以自行修改保存的路徑和文件名。

image-20190206225946094

同步策略

AOF提供了三種同步策略:

  • always(每次寫操作就執行一次fsync)
  • everysec(每秒執行一次fsync,默認)
  • no(不執行fsync)

image-20190206230709591

AOF重寫

AOF會記錄Redis所有的寫操作命令,但這種方式會造成一個問題,就是隨著時間的推移,大量頻繁的操作將導致AOF文件體積的急劇增長,對系統會造成影響。為了解決以上的問題, Redis就需要對AOF文件進行重寫。重寫的過程會創建一個新的AOF文件來代替原有的AOF文件, 而新AOF文件和原有AOF 文件保存的數據狀態是一致的,但新文件的體積將變得盡可能地小。以下兩種方式會觸發AOF重寫。

1)手動出發

在客戶端直接向Redis發送BGREWRITEAOF命令,這個命令會通過移除AOF文件中的冗余命令來重寫(rewrite)AOF文件。

image-20190207235535101

2)自動觸發

其實在啟用了AOF之后(appendonly yes),Redis會依據redis.conf配置文件中的auto-aof-rewrite-percentage選項和auto-aof-rewrite-min-size選項來自動執行BGREWRITEAOF命令。

image-20190207234805782

說明:

例如設置了auto-aof-rewrite-percentage為100和auto-aof-rewrite-min-size為64mb,那么當AOF文件的體積大于64MB時,并且AOF文件的體積比上一次重寫之后的體積大一倍(100%)的,Redis將執行BGREWRITEAOF命令進行重寫。

AOF優點

AOF彌補了RDB按照時間范圍的操作次數為條件的缺點,即使在默認的策略中發生故障,最多也只會丟失一秒鐘的數據,更大程度的保證了數據的安全性。

AOF缺點

AOF會保存每一次的寫操作,這將導致AOF文件的體積通常要大于RDB文件。如果選用always策略,則表示每一次操作都會記錄到AOF文件中,從性能的角度上來說會低于RDB。當然,使用默認的everysec策略進行持久化性能還是非常可觀的。

8.4 混合持久化

在實際應用中,通常會同時使用RDB和AOF兩種持久化來找到一個最佳的平衡點,即能保證性能的同時最大程度保證數據的安全。因此需要RDB和AOF兩者同時進行合理的設置和調整。而從Redis 4.0開始,官方提供了一種更加方便的混合持久化配置。

未啟用混合持久化

在未啟用混合持久化之前,如果我們往Redis寫入一條記時,RDB文件會保存操作的鍵值數據,AOF文件則保存的是寫操作的指令,我們可以分別查看一下這兩個文件的內容。

使用cat命令查看RDB文件

image-20190208095514100

然而顯示的內容并不太直觀也不易理解,因此可以借助Redis提供的redis-check-rdb工具進行查看。

image-20190208090954360

RDB文件中會保存Redis的相關信息以及存儲的keys數量和相關的活期時間。接下來我們繼續查看AOF文件的內容,直接使用cat命名進行查看。

image-20190208093259751

結果顯示AOF文件中保存的是相關的操作指令。

混合持久化

要使用混合持久化,除了在redis.conf文件中啟用AOF(將appendonly設置為yes),還需要將aof-use-rdb-preamble設置為yes。

image-20190208093701377

設置完重新啟用Redis服務。啟用了混合持久化之后,使用BGREWRITEAOF命令執行一次AOF重寫,同時向Redis插入一條新的數據。

image-20190208095036363

然后再次使用cat命令再次查看AOF文件,這時會發現啟用混合持久化之后的AOF文件內容和未啟用時的AOF文件內容不一樣。這是因為此時產生的AOF文件是一個RDB-AOF的混合文件,Redis會基于某種協議將此文件的前半部分存儲RDB的數據,后半部分存儲的是AOF的操作命令。

image-20190208095307601

9. 事務

Redis事務可以一次執行多個命令(批量命令操作),并且是一個單獨的隔離操作。事務中的所有命令都會按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。

9.1 事務操作

Redis事務主要由MULTI 、 EXEC、DISCARD、WATCH和UNWATCH這些基礎命令構成。

1)MULTI

語法:MULTI

用于標記事務的開始,后續客戶端執行的命令都將被存入一個命令隊列,直到執行EXEC時,這些命令才會被執行。

image-20190213142811931

2)EXEC

語法:EXEC

執行命令隊列中的所有命令,但如果在啟用一個事務之前執行了WATCH命令,那么只有當WATCH所監控的keys沒有被修改的前提下,EXEC命令才能執行事務隊列中的所有命令,并返回所有命令的執行結果,否則EXEC將放棄當前事務中的所有命令。

image-20190213142848912

3)DISCARD

語法:DISCARD

取消事務隊列中的所有命令,并將當前連接的狀態恢復為非事務狀態。如果WATCH命令被使用,會自動執行UNWATCH取消監視的所有keys。

image-20190213143242324

4)WATCH

語法:WATCH key[key…]

WATCH命令類似于關系型數據庫的樂觀鎖,可以在啟用事務之前監視某些keys的變化。在MULTI命令執行之前,可以指定需要監視的keys,在執行EXEC之前,如果被監控的keys發生修改,EXEC將放棄執行該隊列中的所有指令。并且WATCH命令可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之后的事務就不會執行。

首先打開一個客戶端,并使用watch命令監視user:1001的key,接著使用multi啟用事務。

image-20190213144044183

然后打開第二個客戶端,并修改key為user:1001的value為user01。

image-20190213144332513

最后回到第一個客戶端再次對key為user:1001的value修改為user001,并執行exec命令。由于user:1001這個key被第一個客戶端所監視,而這個key在啟用事務前被第二個客戶端修改了,因此當第一個客戶端啟用事務后再對其進行修改時這是無效的,Redis將放棄隊列中的所有指令,返回了(nil)。

image-20190213145148061

5)UNWATCH

語法:WATCH key[key…]

取消當前事務中指定監控的keys。如果執行了EXEC或DISCARD命令,則無需再手工執行該命令了,因為在此之后UNWATCH命令會自動執行,事務中所有的keys都將自動取消監控。

image-20190213150349802

9.2 原子性

在關系型數據庫中的原子性代表一系列不可分割的操作,要么全部執行成功,要么全部不執行。如果執行過程中產生了錯誤或者異常,那么事務將會自動回滾。而在Redis的事務中是否具備原子性呢?我們看看以下兩種情況,并得出相關的結論。

錯誤指令

在使用multi命令開啟事務之后,然后輸入一些命令,其中包含一個錯誤的命令。

image-20190214100249171

從結果來看似乎有點符合我們對事務的理解。但仔細想想,這只是在輸入命令的時候產生語法的錯誤,Redis對其進行了校驗,報錯之后Redis就放棄了這個事務。因此得出的結論是:Redis在啟用事務輸入操作命令時是原子操作,它會對命任何一個命令進行語法檢查,當輸入有誤時,Redis會清空隊列并放棄事務。

運行時錯誤

如果輸入的命令都正確,而在執行這些命令時產生了錯誤,Redis是否會取消所有命令并放棄事務呢?看下面的例子。

image-20190214111004404

當執行到第二條命令時產生了錯誤(用戶名不是一個整型數值,并不能自增),但是前面和后面的命令都執行成功。并不會因為執行了一個錯誤的命令而回退所有已經執行成功的命令并放棄整個事務。因此得出的結論是:Redis在執行命令隊列時并不是原子性的,通俗點說就是Redis本身并不支持事務的回滾機制。

10. Java客戶端

10.1 使用Lettuce

Lettuce是Redis的一個java客戶端,同類的產品還有Jedis。但在多線程的環境下,使用Jedis是非線程安全的。而Lettuce的連接對象是基于Netty,在多線程并發訪問時是線程安全的,并且單個連接對象可以滿足多線程并發訪問的要求。Lettuce還支持同步、異步、反應式、發布/訂閱等多種通信方式。

添加依賴

<dependency><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId><version>5.2.2.RELEASE</version>
</dependency>

編寫LettuceUtils:

public class LettuceUtils {private static StatefulRedisConnection connection;static {RedisURI redisURI = RedisURI.Builder.redis("localhost").withPort(6379).withPassword("123456").withDatabase(0).withTimeout(Duration.ofSeconds(5)).build();connection = RedisClient.create(redisURI).connect();}/*** Sync* @return*/public static RedisCommands getCommands() {return connection.sync();}/*** Async* @return*/public static RedisAsyncCommands getAsyncCommands(){return connection.async();}/*** Reactive* @return*/public static RedisReactiveCommands getReactiveCommands(){return connection.reactive();}
}

示例:

@Test
public void testGetAndSet(){RedisCommands<String, String> commands = LettuceUtils.getCommands();commands.set("key", "Hello, Redis!");String value = commands.get("key");System.out.println(value);
}

參考:https://www.baeldung.com/java-redis-lettuce

11 整合Spring Boot

添加依賴

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId>
</dependency>

yml配置

spring:# redis配置redis:# 指定使用第幾個庫,默認是0database: 0# 主機地址host: 127.0.0.1# 端口port: 6379# 認證密碼(如果在redis.conf中配置了密碼)password: wangl# 連接超時時間timeout: 2000
# 連接池配置
#   lettuce:
#     pool:
#       # 最大連接數
#       max-active: 10
#       # 最大空閑連接
#       max-idle: 8
#       # 最小空閑連接
#       min-idle: 5
#       # 建立連接最大等待時間
#       max-wait: 2000
#       # 每間隔多少毫秒運行一次空閑連接回收
#       time-between-eviction-runs: 60000

由于Lettuce的連接是基于Netty的,一個連接對象(StatefulRedisConnection)就可以滿足多線程環境下的并發訪問,許多場景是不需要配置連接池的。如果在特定場景下需要用到連接池,那么在yml進行添加lettuce pool的配置,需要依賴commons-pool2。

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

10.1 使用RedisTemplate

我們可以在類中直接注入一個StringRedisTemplate,key和value都為String類型,它繼承自RedisTemplate。

@Autowired
private StringRedisTemplate stringRedisTemplate;

示例:

@Test
void testForValue() {//添加stringRedisTemplate.opsForValue().set("user:1001", "user1");//依據key獲取valueString name = stringRedisTemplate.opsForValue().get("user:1001");System.out.println(name);//刪除stringRedisTemplate.delete("user:1001");
}

Spring提供了多種Operations接口來對不同的數據結構進行操作,以下是常見的接口:

接口獲取方式說明
ValueOperationsredisTemplate.opsForValue()操作字符串
ListOperationsredisTemplate.opsForList()操作List
SetOperationsredisTemplate.opsForSet()操作Set
ZSetOperationsredisTemplate.opsForZSet()操作ZSet
HashOperationsredisTemplate.opsForHash()操作Hash

示例:

stringRedisTemplate.opsForList().leftPush("list","user1");
stringRedisTemplate.opsForList().rightPush("list","user2");

參考:https://www.jianshu.com/p/7bf5dc61ca06

自定義序列化器

StringRedisTemplate操作的key和value都是字符串,如果我們需要存儲其他類型的數據,那么可以自定義key和value的序列化器來裝配一個RedisTemplate。

編寫配置類

@Configuration
public class RedisConfig {/*** 自定義RedisTemplate,指定key和value的序列化器* @param connectionFactory* @return*/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){//創建RedisTemplate實例RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();//使用StringRedisSerializer作為key的序列化器StringRedisSerializer keySerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(keySerializer);redisTemplate.setHashKeySerializer(keySerializer);//使用Jackson2JsonRedisSerializer作為value的序列化器GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();redisTemplate.setValueSerializer(valueSerializer);redisTemplate.setHashValueSerializer(valueSerializer);redisTemplate.setConnectionFactory(connectionFactory);return redisTemplate;}}  

spring提供了多種序列化器,以下列出常見的Serializer:

序列化器說明
StringRedisSerializer簡單的字符串序列化
GenericToStringSerializer可以將任何對象泛化為字符串并序列化
GenericJackson2JsonRedisSerializer使用Jackson將對象序列化為JSON字符串
JdkSerializationRedisSerializer使用JDK提供的序列化功能

示例:

//注入redisTemplate
@Autowired
private RedisTemplate<String, Object> redisTemplate@Test
void test2(){Users user = new Users();user.setId(1001);user.setUserName("user1");user.setAge(21);redisTemplate.opsForValue().set("user:1001", user);Users u = (Users) redisTemplate.opsForValue().get("user:1001");log.info(u.getUserName());
}

10.2 使用緩存Cache

除了使用RedisTemplate,還可以使用Spring提供的Spring Cache來操作Redis。Spring Cache是一個非常靈活的緩存解決方案,對眾多的緩存框架進行了統一的封裝,當底層使用不同的緩存框架時,由對應的緩存管理器來進行管理。Spring 3.1內置了五個緩存管理器:

  • SimpleCacheManager
  • NoOpCacheManager
  • ConcrrentMapCacheManager
  • CompositeCacheManager
  • EhCacheCacheManager

Spring 3.2引入了另外的一個緩存管理器,這個緩存管理器可以在基于JCache(JSR-107)的緩存提供商之中。除了核心的Spring框架,Spring data又提供了兩個緩存管理器。

  • RedisCacheManager 來自于Spring-data-redis項目
  • GemfireCacheManager 來自于Spring-data-GemFire項目

所以我們選擇緩存管理器時候,取決于使用底層緩存供應商

10.2.1 啟用緩存

在配置類上添加@EnableCaching

@Configuration
@EnableCaching
public class RedisConfig {... 
}
10.2.2 配置緩存管理器

這里底層使用的Redis作為緩存技術,因此需要裝配RedisCacheManager這個緩存管理器。

@Configuration
@EnableCaching
public class RedisConfig {/*** 裝配RedisCacheManager,這里初始化了cache1和cache2兩個緩存,并存入Map中,* 后續在使用時可以指定操作哪一個緩存。* @param redisConnectionFactory* @return*/@Beanpublic RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {Map<String, RedisCacheConfiguration> map = new HashMap<>(2);map.put("cache1", initRedisCacheConfiguration(1800L));map.put("cache2", initRedisCacheConfiguration(3600L));RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).withInitialCacheConfigurations(map).build();return cacheManager;}/*** Redis緩存配置,配置相關的key和value序列化器以及緩存過期時間* serializeKeysWith()方法用于設置key的序列化器* serializeValuesWith()方法用于設置value的序列化器* entryTtl()方法用于設置過期時間* @param ttl* @return*/private RedisCacheConfiguration initRedisCacheConfiguration(Long ttl) {RedisCacheConfiguration cacheConfiguration = 	RedisCacheConfiguration.defaultCacheConfig();return cacheConfiguration//設置key的序列化器.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new 	StringRedisSerializer()))//設置value的序列化器.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new 	GenericJackson2JsonRedisSerializer()))//設置緩存過期時間.entryTtl(Duration.ofSeconds(ttl));}
}  

我們可以創建多個緩存實例(如:cache1、cache2),并對這些緩存做相關的配置(比如設置緩存key和value的序列化器以及指定緩存的活期時間等等)。在使用時可以指定要將數據放入哪個緩存實例中,最后將這些緩存實例納入緩存管理器中管理。

10.2.3 緩存注解

Spring 3.1 引入了基于注釋的緩存技術,通過少量的配置 annotation 注釋即可使得既有代碼支持緩存。

@Cacheable

該注解標記在一個方法上時表示該方法是支持緩存的,當標記在一個類上時則表示該類所有的方法都是支持緩存的。對于一個支持緩存的方法,Spring會在其標注的方法調用后將其返回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中獲取結果,而不需要再次執行該方法。

示例:

@Service
public class UserInfoServiceImpl {@Autowiredprivate UserInfoDaoImpl dao;//這里指定的緩存名為cache1,與配置中的名字一樣,緩存的過期時間為1800//如果指定的不是配置中的名字,就會使用默認的配置,比如過期時間是-1(永不過期)@Cacheable(value = "cache1",key = "#id")public UserInfo getUser(Integer id) {UserInfo result = dao.getById(id);return result;}}

在業務類方法上使用@Cacheable注解,當執行完此方法后會將dao返回的結果保存在緩存中。如果下次傳入相同id查詢時會從緩存獲取,不會再次調用getUser方法。

@Cacheable主要屬性:

參數說明示例
value緩存的名稱,在配置類中定義,必須指定至少一個@Cacheable(value=”cache1”)
或者
@Cacheable(value={”cache1”,”cache2”}
key緩存的key,可以為空,如果指定就要按照SpEL表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合@Cacheable(value=”cache1”,key=”#userName”)
condition緩存的條件,可以為空,使用SpEL編寫,返回true或者false,只有為true才進行緩存@Cacheable(value=”cache1”, key=”#userName” condition=”#userName.length()>2”)
unless用來排除緩存,返回true的時候不放在緩存中@Cacheable(value=“cache1”, key=“#id”, unless=“#result==null”) 表示方法返回值為null時不放置在緩存中

key的生成策略:

spring緩存中的key是按照一定規則來生成的,默認情況下是@Cacheable注解中value屬性的值加上"::" 再加上key屬性的值來生成key。例如:

@Cacheable(value="abc",key="#id")
public UserInfo getById(Integer id)

如果調用getById方法時傳入的參數為100,那么最終生成的key就是abc::100

自定義key的生成策略:

我們也可以在裝配RedisCacheConfiguration時設置key生成策略,例如:

RedisCacheConfiguration initRedisCacheConfiguration(Long ttl) {RedisCacheConfiguration cacheConfiguration = 	RedisCacheConfiguration.defaultCacheConfig();return cacheConfiguration//設置key的序列化器.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new 	StringRedisSerializer()))//設置value的序列化器.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new 	GenericJackson2JsonRedisSerializer()))//設置緩存過期時間.entryTtl(Duration.ofSeconds(ttl));設置緩存key的前綴,cacheName的值由@Cacheable注解的value屬性決定.computePrefixWith(cacheName -> "demo".concat(":").concat(cacheName).concat(":"));}
}  

上面調用RedisCacheConfiguration的computePrefixWith方法來設置key的前綴,此時再去調用getById方法時,最終生成的key為demo:abc:100

key表達式:

@Cacheable的key屬性支持SpringEL表達式,這里的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p加上參數下標”。例如:

@Cacheable(value="users", key="#id")
public User getUser(Integer id) {return dao.getUserById(id);
}@Cacheable(value="users", key="#p0")
public User getUser(Integer id) {return dao.getUserById(id);
}@Cacheable(value="users", key="#user.id")
public User getUser(User user) {return dao.getUserById(user.getId());
}@Cacheable(value="users", key="#p0.id")
public User getUser(User user) {return dao.getUserById(user.getId());
} 

除了上述使用方法參數作為key之外,Spring還為我們提供了一個root對象可以用來生成key。通過該root對象我們可以獲取到以下信息。

屬性名稱說明示例
methodName當前方法名#root.methodName
method當前方法#root.method.name
target當前被調用的對象#root.target
targetClass當前被調用對象的class#root.targetClass
args當前方法參數組成的數組#root.args[0]
caches當前被調用的方法使用的Cache名稱#root.caches[0].name

示例:

@Cacheable(value="users", key="#root.methodName")
public User getUser(Integer id) {return dao.getUserById(id);
}

此時將使用當前方法名作為緩存的key。

@CachePut

@CachePut標注的方法在執行前不會去檢查緩存中是否存有緩存的數據,而是每次都會執行該方法,并將執行結果再次保存到指定的緩存中,相當于覆蓋緩存。

示例:

@CachePut(value = "cache1",key = "#user.id")
public UserInfo insert(User user) {dao.insert(userInfo);return user;
}

@Cacheable主要屬性:

屬性說明
value緩存的名稱,在配置類中定義,必須指定至少一個
key緩存的key,可以為空,如果指定就要按照SpEL表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合
condition緩存的條件,可以為空,使用SpEL編寫,返回true或者false,只有為true才進行緩存

@CachEvict

該注解根據一定的條件對緩存進行清空

示例:

@CacheEvict(value="cache1", key="#id")
public void deleteById(Integer id) {dao.deleteById(id);
}

@Cacheable主要屬性:

屬性說明
value緩存的名稱,在配置類中定義,必須指定至少一個
key緩存的key,可以為空,如果指定就要按照SpEL表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合
condition緩存的條件,可以為空,使用SpEL編寫,返回true或者false,只有為true才進行緩存
allEntries是否清空所有緩存內容,缺省為 false,如果指定為 true,則方法調用后將立即清空所有緩存
beforeInvocation是否在方法執行前就清空,缺省為 false,如果指定為 true,則在方法還沒有執行的時候就清空緩存,缺省情況下,如果方法執行拋出異常,則不會清空緩存

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

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

相關文章

onnx導出報錯 | IndexError: index_select(): Index is supposed to be a vector

解決方案&#xff1a; 在torch.onnx.export鐘添加do_constant_foldingFalse&#xff0c;如下 torch.onnx.export(model,(None, text),text_fp32_onnx_path,input_names[text],output_names[unnorm_text_features],export_paramsTrue,opset_version13,verboseTrue,do_constant_…

編程參考 - C++ Code Review: 一個計算器的項目

GitHub - jroelofs/calc: Toy Calculator Toy Calculator 1&#xff0c;拿到一個project&#xff0c;第一眼看&#xff0c;沒有配置文件&#xff0c;說明沒有引入持續集成系統&#xff0c;continuous integration system。 2&#xff0c;然后看cmake文件&#xff0c;使用的子…

使用Python的turtle模塊繪制鋼鐵俠圖案

1.1引言&#xff1a; 在Python中&#xff0c;turtle模塊是一個非常有趣且強大的工具&#xff0c;它允許我們以一個可視化和互動的方式學習編程。在本博客中&#xff0c;我們將使用turtle模塊來繪制鋼鐵俠的圖案。通過調用各種命令&#xff0c;我們可以引導turtle繪制出指定的圖…

第十四章 控制值的轉換 - 在DISPLAYLIST中投影值

文章目錄 第十四章 控制值的轉換 - 在DISPLAYLIST中投影值在DISPLAYLIST中投影值 第十四章 控制值的轉換 - 在DISPLAYLIST中投影值 在DISPLAYLIST中投影值 對于 %String 類型&#xff08;或任何子類&#xff09;的屬性&#xff0c;XML 投影可以使用 DISPLAYLIST 參數。 簡單…

CrystalDiskInfo/CrystalDiskMark/DiskGenius系統遷移

CrystalDiskInfo 主要用于看硬盤的各種信息&#xff0c;包括但不限于硬盤通電時間、通電次數、硬盤好壞狀態 CrystalDiskMark 主要用于測試硬盤的讀寫速度、連續讀寫速度 DiskGenius 主要用于通過U盤裝操作系統后進行&#xff0c;磁盤分區&#xff0c;更改磁盤名、隱藏部分…

【前端知識】Node——http模塊url模塊的常用操作

一、創建簡易Server const http require(http); const URL require(url);const HTTP_PORT 8088;const server http.createServer((req, res) > {// req&#xff1a;request請求對象&#xff0c;包含請求相關的信息&#xff1b;// res&#xff1a;response響應對象&…

【MISRA C 2012】Rule 5.2 在同一作用域和名稱空間中聲明的標識符應該是不同的

1. 規則1.1 原文1.2 分類 2. 關鍵描述3. 代碼實例 1. 規則 1.1 原文 Rule 5.2 Identifiers declared in the same scope and name space shall be distinct Category Required Analysis Decidable, Single Translation Unit Applies to C90, C99 1.2 分類 規則4.2&#xff…

案例014:Java+SSM+uniapp+mysql基于微信小程序的健身管理系統

文末獲取源碼 開發語言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 數據庫&#xff1a;mysql 5.7 開發軟件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序開發軟件&#xff1a;HBuilder X 小程序…

【機器學習 | ARIMA】經典時間序列模型ARIMA定階最佳實踐,確定不來看看?

&#x1f935;?♂? 個人主頁: AI_magician &#x1f4e1;主頁地址&#xff1a; 作者簡介&#xff1a;CSDN內容合伙人&#xff0c;全棧領域優質創作者。 &#x1f468;?&#x1f4bb;景愿&#xff1a;旨在于能和更多的熱愛計算機的伙伴一起成長&#xff01;&#xff01;&…

SpringBoot:ch02 配置文件(日志)

前言 簡單介紹 Spring Boot 中常見的配置文件類型&#xff0c;如 application.properties 和 application.yml 等&#xff0c;并說明它們各自的特點和用途。 一、前期準備 1、新建項目&#xff0c;結構如下 2、添加依賴 <?xml version"1.0" encoding"UTF…

單片機語音芯片開發要解決的問題

在單片機語音芯片開發過程中&#xff0c;可能會遇到多種問題&#xff0c;這些問題可能來自于技術層面&#xff0c;也可能來自于芯片本身的設計和應用層面。下面讓我們具體從芯片的功耗、語音識別的準度、芯片的尺寸和芯片的可靠性四個方面開展討論。 1.芯片的功耗問題 首先&a…

【AIGC重塑教育】AI大爆發的時代,未來的年輕人怎樣獲得機會和競爭力?

目錄 AI浪潮來襲 AI與教育 AI的優勢 延伸閱讀 推薦語 ?作者&#xff1a;劉文勇 來源&#xff1a;IT閱讀排行榜 本文摘編自《AIGC重塑教育&#xff1a;AI大模型驅動的教育變革與實踐》&#xff0c;機械工業出版社出版 AI浪潮來襲 這次&#xff0c;狼真的來了。 AI正迅猛地…

81基于matlab GUI的圖像處理

基于matlab GUI的圖像處理&#xff0c;功能包括圖像顏色處理&#xff08;灰度圖像、二值圖像、反色變換、直方圖、拉伸變換&#xff09;&#xff1b;像素操作&#xff08;讀取像素、修改像素&#xff09;、平滑濾波&#xff08;均值平滑、高斯平滑、中值平滑&#xff09;、圖像…

Java多線程之線程安全問題

文章目錄 一. 線程安全概述1. 什么是線程安全問題2. 一個存在線程安全問題的程序 二. 線程不安全的原因和線程加鎖1. 案例分析2. 線程加鎖2.1 理解加鎖2.2 synchronized的使用2.3 再次分析案例 3. 線程不安全的原因 三. 線程安全的標準類 一. 線程安全概述 1. 什么是線程安全問…

基于C#實現赫夫曼樹

赫夫曼樹又稱最優二叉樹&#xff0c;也就是帶權路徑最短的樹&#xff0c;對于赫夫曼樹&#xff0c;我想大家對它是非常的熟悉&#xff0c;也知道它的應用場景&#xff0c;但是有沒有自己親手寫過&#xff0c;這個我就不清楚了&#xff0c;不管以前寫沒寫&#xff0c;這一篇我們…

【LeetCode刷題筆記】DFSBFS(二)

994. 腐爛的橘子(樹/圖的BFS問題) 解題思路: 多源BFS ,首選找到 所有的腐爛的橘子 ,放入隊列中,然后進行 BFS 廣搜,廣搜的 層數 - 1 就是所需要花費的分鐘數。 在最開始先掃描一遍二維數組,將所有的 腐爛的橘子 加入 隊列 ,同時統計新鮮橘子的數量 <

Blender烘焙AO操作及對應的python代碼

&#xff08;一&#xff09;Blender軟件操作 1. 導入模型&#xff08;這里省略&#xff09; 2. 材質設置 模型使用的所有材質都需要刪除Surface Shader&#xff0c;沒有其他多余的計算&#xff0c;可以大量縮短烘焙時間。刪除之后的只留下一個材質輸出節點&#xff0c;如圖所…

CentOS Stream 9系統Cgroup問題處理

安裝docker容器啟動失敗 之前適配過Ubuntu系統的容器&#xff0c;由于版本比較高&#xff0c;沒有掛載Cgroup的路徑。這次使用Centos Stream 9系統安裝docker容器時也遇到了這個情況。由于處理方式有些不一樣&#xff0c;所以記錄一下。 這是docker容器啟動過報錯的輸出日志。…

Windmill:最快的自托管開源工作流引擎

我們對 Windmill 進行了基準測試&#xff0c;認為它是 Airflow、Prefect 甚至 Temporal 中最快的自托管通用工作流引擎。對于 Airflow&#xff0c;有速度快了 10 倍&#xff01; 工作流引擎編排工作人員的有向無環圖 (DAG) 中定義的作業&#xff0c;同時尊重依賴性。 主要優點…