Java面試題(六)--Redis
1 Redis基礎篇
1、簡單介紹一下Redis優點和缺點?
優點:
1、本質上是一個 Key-Value 型別的記憶體資料庫,很像memcached
2、整個資料庫統統載入在記憶體當中進行操作,定期通過非同步操作把資料庫資料 flush 到硬碟上進行儲存
3、因為是純記憶體操作,Redis 的效能非常出色,每秒可以處理超過 10 萬次讀寫操作,是已知效能最快的Key-Value DB
4、Redis最大的魅力是支援儲存多種資料結構(string,list,set,hash,sortedset),此外單個 value 的最大限制是 1GB,不像memcached只能儲存 1MB 的資料
5、Redis也可以對存入的 Key-Value 設定 expire 時間,因此也可以被當作一個功能加強版的memcached 來用
缺點:
1、Redis 的主要缺點是資料庫容量受到實體記憶體的限制,不能用作海量資料的高效能讀寫,因此 Redis 適合的場景主要侷限在較小資料量的高效能操作和運算上。
2、沒有豐富的搜尋功能
2、系統中為什麼要使用快取?
主要從“高效能”和“高併發”這兩點來看待這個問題。
高效能:
假如使用者第一次訪問資料庫中的某些資料。這個過程會比較慢,因為是從硬碟上讀取的。將該使用者訪問的資料存在數快取中,這樣下一次再訪問這些資料的時候就可以直接從快取中獲取了。操作快取就是直接操作記憶體,所以速度相當快。如果資料庫中的對應資料改變的之後,同步改變快取中相應的資料即可!
高併發:
直接操作快取能夠承受的請求是遠遠大於直接訪問資料庫的,所以我們可以考慮把資料庫中的部分資料轉移到快取中去,這樣使用者的一部分請求會直接到快取這裡而不用經過資料庫。
3、常見的快取同步方案都有哪些?(高頻)
同步方案:更改程式碼業務程式碼,加入同步操作快取邏輯的程式碼(資料庫操作完畢以後,同步操作快取)
非同步方案:
1、使用訊息佇列進行快取同步:更改程式碼加入非同步操作快取的邏輯程式碼(資料庫操作完畢以後,將要同步的資料傳送到MQ中,MQ的消費者從MQ中獲取資料,然後更新快取)
2、使用阿里巴巴旗下的canal元件實現資料同步:不需要更改業務程式碼,部署一個canal服務。canal服務把自己偽裝成mysql的一個從節點,當mysql資料更新以後,canal會讀取binlog資料,然後在通過canal的客戶端獲取到資料,更新快取即可。
4、Redis常見資料結構以及使用場景有哪些?(高頻)
1、 string
常見命令:set、get、decr、incr、mget等。
基本特點:string資料結構是簡單的key-value型別,value其實不僅可以是String,也可以是數字。
應用場景:常規計數:微博數,粉絲數等。
2、hash
常用命令: hget、hset、hgetall等。
基本特點:hash 是一個 string 型別的 field 和 value 的對映表,hash 特別適合用於儲存物件,後續操作的時候,你可以直接僅僅修改這個物件中的某個欄位的值。
應用場景:儲存使用者資訊,商品資訊等。
3、list
常用命令: lpush、rpush、lpop、rpop、lrange等。
基本特點:類似於Java中的list可以儲存多個數據,並且資料可以重複,而且資料是有序的。
應用場景:儲存微博的關注列表,粉絲列表等。
4、set
常用命令: sadd、spop、smembers、sunion 等
基本特點:類似於Java中的set集合可以儲存多個數據,資料不可以重複,使用set集合不可以保證資料的有序性。
應用場景:可以利用Redis的集合計算功能,實現微博系統中的共同粉絲、公告關注的使用者列表計算。
5、sorted set
常用命令: zadd、zrange、zrem、zcard 等。
基本特點:和set相比,sorted set增加了一個權重引數score,使得集合中的元素能夠按score進行有序排列。
應用場景:在直播系統中,實時排行資訊包含直播間線上使用者列表,各種禮物排行榜等。
5、Redis有哪些資料刪除策略?(高頻)
資料刪除策略:Redis中可以對資料設定資料的有效時間,資料的有效時間到了以後,就需要將資料從記憶體中刪除掉。而刪除的時候就需要按照指定的規則進行刪除,這種刪除規則就被稱之為資料的刪除策略。
Redis中資料的刪除策略:
① 定時刪除
-
概述:在設定某個key 的過期時間同時,我們建立一個定時器,讓定時器在該過期時間到來時,立即執行對其進行刪除的操作。
-
優點:定時刪除對記憶體是最友好的,能夠儲存記憶體的key一旦過期就能立即從記憶體中刪除。
-
缺點:對CPU最不友好,在過期鍵比較多的時候,刪除過期鍵會佔用一部分CPU時間,對伺服器的響應時間和吞吐量造成影響。
② 惰性刪除
-
概述:設定該key過期時間後,我們不去管它,當需要該key時,我們在檢查其是否過期,如果過期,我們就刪掉它,反之返回該key。
-
優點:對CPU友好,我們只會在使用該鍵時才會進行過期檢查,對於很多用不到的key不用浪費時間進行過期檢查。
-
缺點:對記憶體不友好,如果一個鍵已經過期,但是一直沒有使用,那麼該鍵就會一直存在記憶體中,如果資料庫中有很多這種使用不到的過期鍵,這些鍵便永遠不會被刪除,記憶體永遠不會釋放。
③ 定期刪除
-
概述:每隔一段時間,我們就對一些key進行檢查,刪除裡面過期的key(從一定數量的資料庫中取出一定數量的隨機鍵進行檢查,並刪除其中的過期鍵)。
-
優點:可以通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 的影響。另外定期刪除,也能有效釋放過期鍵佔用的記憶體。
-
缺點:難以確定刪除操作執行的時長和頻率。
如果執行的太頻繁,定期刪除策略變得和定時刪除策略一樣,對CPU不友好。如果執行的太少,那又和惰性刪除一樣了,過期鍵佔用的記憶體不會及時得到釋放。
另外最重要的是,在獲取某個鍵時,如果某個鍵的過期時間已經到了,但是還沒執行定期刪除,那麼就會返回這個鍵的值,這是業務不能忍受的錯誤。
Redis的過期刪除策略: 惰性刪除 + 定期刪除 兩種策略進行配合使用定期刪除函式的執行頻率,在Redis2.6版本中,規定每秒執行10次,大概100ms執行一次。在Redis2.8版本後,可以通過修改配置檔案redis.conf 的 hz 選項來調整這個次數。
6、Redis中有哪些資料淘汰策略?(高頻)
資料的淘汰策略:當Redis中的記憶體不夠用時,此時在向Redis中新增新的key,那麼Redis就會按照某一種規則將記憶體中的資料刪除掉,這種資料的刪除規則被稱之為記憶體的淘汰策略。
常見的資料淘汰策略:
noeviction # 不刪除任何資料,記憶體不足直接報錯(預設策略)
volatile-lru # 挑選最近最久使用的資料淘汰(舉例:key1是在3s之前訪問的, key2是在9s之前訪問的,刪除的就是key2)
volatile-lfu # 挑選最近最少使用資料淘汰 (舉例:key1最近5s訪問了4次, key2最近5s訪問了9次, 刪除的就是key1)
volatile-ttl # 挑選將要過期的資料淘汰
volatile-random # 任意選擇資料淘汰
allkeys-lru # 挑選最近最少使用的資料淘汰
allkeys-lfu # 挑選最近使用次數最少的資料淘汰
allkeys-random # 任意選擇資料淘汰,相當於隨機
注意:
1、不帶allkeys字樣的淘汰策略是隨機從Redis中選擇指定的數量的key然後按照對應的淘汰策略進行刪除,帶allkeys是對所有的key按照對應的淘汰策略進行刪除。
2、快取淘汰策略常見配置項
maxmemory-policy noeviction # 配置淘汰策略
maxmemory ?mb # 最大可使用記憶體,即佔用實體記憶體的比例,預設值為0,表示不限制。生產環境中根據需求設定,通常設定在50%以上。
maxmemory-samples count # 設定redis需要檢查key的個數
7、Redis中資料庫預設是多少個db即作用?
Redis預設支援16個數據庫,可以通過配置databases來修改這一數字。客戶端與Redis建立連線後會自動選擇0號資料庫,不過可以隨時使用select命令更換資料庫。
Redis支援多個數據庫,並且每個資料庫是隔離的不能共享,並且基於單機才有,如果是叢集就沒有資料庫的概念。
8、快取穿透、快取擊穿、快取雪崩解決方案?(高頻)
加入快取以後的資料查詢流程:
快取穿透:
概述:指查詢一個一定 不存在 的資料,如果從儲存層查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到 DB 去查詢,可能導致 DB 掛掉。
解決方案:
1、查詢返回的資料為空,仍把這個空結果進行快取,但過期時間會比較短
2、布隆過濾器:將所有可能存在的資料雜湊到一個足夠大的 bitmap 中,一個一定不存在的資料會被這個 bitmap 攔截掉,從而避免了對DB的查詢
快取擊穿:
概述:對於設定了過期時間的key,快取在某個時間點過期的時候,恰好這時間點對這個Key有大量的併發請求過來,這些請求發現快取過期一般都會從後端 DB 載入資料並回設到快取 ,這個時候大併發的請求可能會瞬間把 DB 壓垮。
解決方案:
1、使用互斥鎖:當快取失效時,不立即去load db,先使用如 Redis 的 setnx 去設定一個互斥鎖,當操作成功返回時再進行 load db的操作並回設快取,否則重試get快取的方法
2、永遠不過期:不要對這個key設定過期時間
快取雪崩:
概述:設定快取時採用了相同的過期時間,導致快取在某一時刻 同時失效 ,請求全部轉發到DB,DB 瞬時壓力過重雪崩。與快取擊穿的區別:雪崩是很多key,擊穿是某一個key快取。
解決方案:
將快取失效時間分散開,比如可以在原有的失效時間基礎上增加一個 隨機值 ,比如1-5分鐘隨機,這樣每一個快取的過期時間的重複率就會降低,就很難引發集體失效的事件。
9、什麼是布隆過濾器?(高頻)
概述:布隆過濾器(Bloom Filter)是1970年由布隆提出的。它實際上由一個很長的二進位制向量(二進位制陣列)和一系列隨機對映函式(hash函式)。
作用:布隆過濾器可以用於檢索一個元素是否在一個集合中。
新增元素:將商品的id(id1)儲存到布隆過濾器
假設當前的布隆過濾器中提供了三個hash函式,此時就使用三個hash函式對id1進行雜湊運算,運算結果分別為:1、4、9那麼就會陣列中對應的位置資料更改為1。
判斷資料是否存在:使用相同的hash函式對資料進行雜湊運算,得到雜湊值。然後判斷該雜湊值所對應的陣列位置是否都為1,如果不都是則說明該資料
肯定不存在。如果是說明該資料 可能 存在,因為雜湊運算可能就會存在重複的情況。如下圖所示:
假設新增完id1和id2資料以後,布隆過濾器中資料的儲存方式如上圖所示,那麼此時要判斷id3對應的資料在布隆過濾器中是否存在,按照上述的判斷規則應該是存在,但是id3這個資料在布隆過濾器中壓根就不存在,這種情況就屬於誤判。
誤判率:陣列越小誤判率就越大,陣列越大誤判率就越小,但是同時帶來了更多的記憶體消耗。
刪除元素:布隆布隆器不支援資料的刪除操作,因為如果支援刪除那麼此時就會影響判斷不存在的結果。
使用布隆過濾器:在谷歌的guava快取工具中提供了布隆過濾器的實現,使用方式如下所示:
pom.xml檔案
測試程式碼:
// 建立一個BloomFilter物件
// 第一個引數:布隆過濾器判斷的元素的型別
// 第二個引數:布隆過濾器儲存的元素個數
// 第三個引數:誤判率,預設值為0.03
int size = 100_000 ;
bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), size, 0.03);
for(int x = 0 ; x < size ; x++) {
bloomFilter.put("add" + x) ;
}
// 在向其中新增100000個數據測試誤判率
int count = 0 ; // 記錄誤判的資料條數
for(int x = size ; x < size * 2 ; x++) {
if(bloomFilter.mightContain("add" + x)) {
count++ ;
System.out.println(count + "誤判了");
}
}
// 輸出
System.out.println("總的誤判條數為:" + count);
Redis中使用布隆過濾器防止快取穿透流程圖如下所示:
10、Redis資料持久化有哪些方式?各自有什麼優缺點?(高頻)
在Redis中提供了兩種資料持久化的方式:1、RDB 2、AOF
RDB:定期更新,定期將Redis中的資料生成的快照同步到磁碟等介質上,磁碟上儲存的就是Redis的記憶體快照
優點:資料檔案的大小相比於aof較小,使用rdb進行資料恢復速度較快
缺點:比較耗時,存在丟失資料的風險
AOF:將Redis所執行過的所有指令都記錄下來,在下次Redis重啟時,只需要執行指令就可以了
優點:資料丟失的風險大大降低了
缺點:資料檔案的大小相比於rdb較大,使用aof檔案進行資料恢復的時候速度較慢
11、Redis都存在哪些叢集方案?
在Redis中提供的叢集方案總共有三種:
1、主從複製
-
保證高可用性
-
實現故障轉移需要手動實現
-
無法實現海量資料儲存
2、哨兵模式
-
保證高可用性
-
可以實現自動化的故障轉移
-
無法實現海量資料儲存
-
監控
-
故障轉移
-
通知客戶端
3、Redis分片叢集
-
保證高可用性
-
可以實現自動化的故障轉移
-
可以實現海量資料儲存
12、說說Redis雜湊槽的概念?
Redis 叢集沒有使用一致性 hash,而是引入了雜湊槽的概念,Redis 叢集有 16384 個雜湊槽,每個 key通過 CRC16 校驗後對 16384 取模來決定放置哪個槽,叢集的每個節點負責一部分 hash 槽。
13、Redis中的管道有什麼用?
一次請求/響應伺服器能實現處理新的請求即使舊的請求還未被響應,這樣就可以將多個命令傳送到服務 器,而不用等待回覆,最後在一個步驟中讀取該答覆。
14、談談你對Redis中事務的理解?(高頻)
事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。
Redis中的事務:Redis事務的本質是一組命令的集合。事務支援一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會按照順序序列化執行佇列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中。
總結說:Redis事務就是一次性、順序性、排他性的執行一個佇列中的一系列命令。
15、Redis事務相關的命令有哪幾個?(高頻)
事務相關的命令:
1、MULTI:用來組裝一個事務
2、EXEC:執行一個事務
3、DISCARD:取消一個事務
4、WATCH:用來監視一些key,一旦這些key在事務執行之前被改變,則取消事務的執行
5、UNWATCH:取消 WATCH 命令對所有key的監視
如下所示:
16、Redis如何做記憶體優化?
儘可能使用散列表(hash),散列表(是說散列表裡面儲存的數少)使用的記憶體非常小,所以你應該儘可能的將你的資料模型抽象到一個散列表裡面。
比如你的 web 系統中有一個使用者物件,不要為這個使用者的名稱,姓氏,郵箱,密碼設定單獨的key,而是應該把這個使用者的所有資訊儲存到一張散列表裡面。
17、Redis是單線的,但是為什麼還那麼快?(高頻)
Redis總體快的原因:
1、完全基於記憶體的
2、採用單執行緒,避免不必要的上下文切換可競爭條件
3、使用多路I/O複用模型,非阻塞IO
2 分散式鎖篇
18、什麼是分散式鎖?
概述:在分散式系統中,多個執行緒訪問共享資料就會出現資料安全性的問題。而由於jdk中的鎖要求多個執行緒在同一個jvm中,因此在分散式系統中無法使用jdk中的鎖保證資料的安全性,那麼此時就需要使用分散式鎖。
作用:可以保證在分散式系統中多個執行緒訪問共享資料時資料的安全性
舉例:
在電商系統中,使用者在進行下單操作的時候需要扣減庫存。為了提高下單操作的執行效率,此時需要將庫存的資料儲存到Redis中。訂單服務每一次生成訂單之前需要查詢一下庫存資料,如果存在則生成訂單同時扣減庫存。在高併發場景下會存在多個訂單服務操作Redis,此時就會出現執行緒安全問題。
分散式鎖的工作原理:
分散式鎖應該具備哪些條件:
1、在分散式系統環境下,一個方法在同一時間只能被一個機器的一個執行緒執行
2、高可用的獲取鎖與釋放鎖
3、高效能的獲取鎖與釋放鎖
4、具備可重入特性
5、具備鎖失效機制,防止死鎖
可重入特性:獲取到鎖的執行緒再次呼叫需要鎖的方法的時候,不需要再次獲取鎖物件。
使用場景:遍歷樹形選單的時候的遞迴呼叫。
注意:鎖具備可重入性的主要目的是為了防止死鎖。
19、分散式鎖的實現方案都有哪些?(高頻)
分散式鎖的實現方案:
1、資料庫
2、zookeeper
3、redis
20、Redis怎麼實現分散式鎖思路?(高頻)
Redis實現分散式鎖主要利用Redis的 setnx 命令。setnx是SET if not exists(如果不存在,則 SET)的簡寫。
127.0.0.1:6379> setnx lock value1 #在鍵lock不存在的情況下,將鍵key的值設定為value1
(integer) 1
127.0.0.1:6379> setnx lock value2 #試圖覆蓋lock的值,返回0表示失敗
(integer) 0
127.0.0.1:6379> get lock #獲取lock的值,驗證沒有被覆蓋
"value1"
127.0.0.1:6379> del lock #刪除lock的值,刪除成功
(integer) 1
127.0.0.1:6379> setnx lock value2 #再使用setnx命令設定,返回0表示成功
(integer) 1
127.0.0.1:6379> get lock #獲取lock的值,驗證設定成功
"value2"
上面這幾個命令就是最基本的用來完成分散式鎖的命令。
加鎖:使用 setnx key value
命令,如果key不存在,設定value(加鎖成功)。如果已經存在lock(也就是有客戶端持有鎖了),則設定失敗(加鎖失敗)。
解鎖:使用 del
命令,通過刪除鍵值釋放鎖。釋放鎖之後,其他客戶端可以通過 setnx
命令進行加鎖。
21、Redis實現分散式鎖如何防止死鎖現象?(高頻)
產生死鎖的原因:如果一個客戶端持有鎖的期間突然崩潰了,就會導致無法解鎖,最後導致出現死鎖的現象。
所以要有個 超時的機制 ,在設定key的值時,需要加上有效時間,如果有效時間過期了,就會自動失效,就不會出現死鎖。然後加鎖的程式碼就會變成這樣。
22、Redis實現分散式鎖如何合理的控制鎖的有效時長?(高頻)
有效時間設定多長,假如我的業務操作比有效時間長?我的業務程式碼還沒執行完就自動給我解鎖了,不就完蛋了嗎。
解決方案:
1、第一種:程式設計師自己去把握,預估一下業務程式碼需要執行的時間,然後設定有效期時間比執行時間長一些,保證不會因為自動解鎖影響到客戶端業務程式碼的執行。
2、第二種:給鎖續期。
鎖續期實現思路:當加鎖成功後,同時開啟守護執行緒,預設有效期是使用者所設定的,然後每隔10秒就會給鎖續期到使用者所設定的有效期,只要持有鎖的客戶端沒有宕機,就能保證一直持有鎖,直到業務程式碼執行完畢由客戶端自己解鎖,如果宕機了自然就在有效期失效後自動解鎖。
上述的第二種解決方案可以使用redis官方所提供的Redisson進行實現。
Redisson是Redis官方推薦的Java版的Redis客戶端。它提供的功能非常多,也非常強大分散式服務,使用Redisson可以輕鬆的實現分散式鎖。Redisson中進行鎖續期的這種機制被稱為" 看門狗 "機制。
redission支援4種連線redis方式,分別為 單機 、主從、Sentinel、Cluster 叢集。
23、Redis實現分散式鎖如何保證鎖服務的高可用?(高頻)
解決方案:
1、使用Redis的哨兵模式構建一個主從架構的Redis叢集
2、使用Redis Cluster叢集
24、當同步鎖資料到從節點之前,主節點宕機了導致鎖失效,那麼此時其他執行緒就可以再次獲取到鎖,這個問題怎麼解決?(高頻)
使用Redission框架中的 RedLock 進行處理。
RedLock的方案基於2個前提:
1、不再需要部署從庫和哨兵例項,只部署主庫
2、但主庫要部署多個,官方推薦至少5個例項
也就是說,想使用RedLock,你至少要部署5個Redis例項,而且都是主庫,它們之間沒有任何關係,都是一個個孤立的例項。
工作流程如下所示:
1、客戶端先獲取【當前時間戳T1】
2、客戶端依次向這個5個Redis例項發起加鎖請求,且每個請求會設定超時時間(毫秒級,要遠小於鎖的有效時間),如果某一個例項加鎖失敗(包括網路超時,鎖被其他的人持有等各種異常情況),就立即向下一個Redis例項申請加鎖
3、如果客戶端從 >=3 個(大多數)以上Redis例項加鎖成功,則再次獲取【當前時間戳T2】, 如果 T2 - T1 < 鎖的過期時間,此時,認為客戶端加鎖成功,否則加鎖失敗
4、加鎖成功,去操作共享資源
5、加鎖失敗,向【全部節點】發起釋放鎖請求
總結4個重點:
1、客戶端在多個Redis例項上申請加鎖
2、必須保證大多數節點加鎖成功
3、大多數節點加鎖的總耗時,要小於鎖設定的過期時間
4、鎖釋放,要向全部節點發起釋放鎖請求
24.1 為什麼要在多個例項上加鎖?
本質上是為了【容錯】, 部分例項異常宕機,剩餘的例項加鎖成功,整個鎖服務依舊可用。
24.2 為什麼步驟3加鎖成功後,還要計算加鎖的累計耗時?
因為操作的是多個節點,所以耗時肯定會比操作單個例項耗時更久,而且,因為是網路請求,網路情況是複雜的,有可能存在延遲、丟包、超時等情況發生,網路請求越多,異常發生的概率就越大。所以,即使大多數節點加鎖成功,如果加鎖的累計耗時已經超過了鎖的過期時間,那此時有些例項上的鎖可能已經失效了,這個鎖就沒有意義了。
程式碼大致如下所示:
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.0.1:5378").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient1 = Redisson.create(config1);
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.0.1:5379").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient2 = Redisson.create(config2);
Config config3 = new Config();
config3.useSingleServer().setAddress("redis://192.168.0.1:5380").setPassword("a123456").setDatabase(0);
RedissonClient redissonClient3 = Redisson.create(config3);
String resourceName = "REDLOCK_KEY";
RLock lock1 = redissonClient1.getLock(resourceName);
RLock lock2 = redissonClient2.getLock(resourceName);
RLock lock3 = redissonClient3.getLock(resourceName);
// 向3個redis例項嘗試加鎖
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLock;
try {
// isLock = redLock.tryLock();
// 500ms拿不到鎖, 就認為獲取鎖失敗。10000ms即10s是鎖失效時間。
isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
System.out.println("isLock = "+isLock);
if (isLock) {
//TODO if get lock success, do something;
}
} catch (Exception e) {
} finally {
// 無論如何, 最後都要解鎖
redLock.unlock();
}
- 記一次批量更新整型型別的列 → 探究 UPDATE 的使用細節
- 編碼中的Adapter,不僅是一種設計模式,更是一種架構理念與解決方案
- 執行緒池底層原理詳解與原始碼分析
- 30分鐘掌握 Webpack
- 線性迴歸大結局(嶺(Ridge)、 Lasso迴歸原理、公式推導),你想要的這裡都有
- Django 之路由層
- 【前端必會】webpack loader 到底是什麼
- day42-反射01
- 中心化決議管理——雲端分析
- HashMap底層原理及jdk1.8原始碼解讀
- 詳解JS中 call 方法的實現
- 列印 Logger 日誌時,需不需要再封裝一下工具類?
- 初識設計模式 - 代理模式
- 設計模式---享元模式
- 密碼學奇妙之旅、01 CFB密文反饋模式、AES標準、Golang程式碼
- [ML從入門到入門] 支援向量機:從SVM的推導過程到SMO的收斂性討論
- 從應用訪問Pod元資料-DownwardApi的應用
- Springboot之 Mybatis 多資料來源實現
- Java 泛型程式設計
- CAS核心思想、底層實現