分散式緩存:愛我你怕了嗎?

来源:https://www.cnblogs.com/lfs2640666960/archive/2018/08/28/9551519.html
-Advertisement-
Play Games

俗話說得好,工欲善其事,必先利其器,有了好的工具肯定得知道如何用好這些工具,本篇將介紹如何利用好緩存。 ...


分散式緩存:愛我你怕了嗎?

 

背景

俗話說得好,工欲善其事,必先利其器,有了好的工具肯定得知道如何用好這些工具,本篇將介紹如何利用好緩存。

1.確認是否需要緩存

在使用緩存之前,需要確認你的項目是否真的需要緩存。使用緩存會引入的一定的技術複雜度,後文也將會一一介紹這些複雜度。一般來說從兩個方面來個是否需要使用緩存:

  1. CPU占用:如果你有某些應用需要消耗大量的cpu去計算,比如正則表達式,如果你使用正則表達式比較頻繁,而其又占用了很多CPU的話,那你就應該使用緩存將正則表達式的結果給緩存下來。
  2. 資料庫IO占用:如果你發現你的資料庫連接池比較空閑,那麼不應該用緩存。但是如果資料庫連接池比較繁忙,甚至經常報出連接不夠的報警,那麼是時候應該考慮緩存了。筆者曾經有個服務,被很多其他服務調用,其他時間都還好,但是在每天早上10點的時候總是會報出資料庫連接池連接不夠的報警,經過排查,發現有幾個服務選擇了在10點做定時任務,大量的請求打過來,DB連接池不夠,從而報出連接池不夠的報警。這個時候有幾個選擇,我們可以通過擴容機器來解決,也可以通過增加資料庫連接池來解決,但是沒有必要增加這些成本,因為只有在10點的時候才會出現這個問題。後來引入了緩存,不僅解決了這個問題,而且還增加了讀的性能。

如果並沒有上述兩個問題,那麼你不必為了增加緩存而緩存。

2.選擇合適的緩存

緩存又分進程內緩存和分散式緩存兩種。很多人包括筆者在開始選緩存框架的時候都感到了困惑:網上的緩存太多了,大家都吹噓自己很牛逼,我該怎麼選擇呢?

2.1 選擇合適的進程緩存

首先看看幾個比較常用的緩存的比較,具體原理可以參考你應該知道的緩存進化史:

分散式緩存:愛我你怕了嗎?

 

  • 對於ConcurrentHashMap來說,比較適合緩存比較固定不變的元素,且緩存的數量較小的。雖然從上面表格中比起來有點遜色,但是其由於是jdk自帶的類,在各種框架中依然有大量的使用,比如我們可以用來緩存我們反射的Method,Field等等;也可以緩存一些鏈接,防止其重覆建立。在Caffeine中也是使用的ConcurrentHashMap來存儲元素。
  • 對於LRUMap來說,如果不想引入第三方包,又想使用淘汰演算法淘汰數據,可以使用這個。
  • 對於Ehcache來說,由於其jar包很大,較重量級。對於需要持久化和集群的一些功能的,可以選擇Ehcache。筆者沒怎麼使用過這個緩存,如果要選擇的話,可以選擇分散式緩存來替代Ehcache。
  • 對於Guava Cache來說,Guava這個jar包在很多Java應用程式中都有大量的引入,所以很多時候其實是直接用就好了,並且其本身是輕量級的而且功能較為豐富,在不瞭解Caffeine的情況下可以選擇Guava Cache。
  • 對於Caffeine來說,筆者是非常推薦的,其在命中率,讀寫性能上都比Guava Cache好很多,並且其API和Guava cache基本一致,甚至會多一點。在真實環境中使用Caffeine,取得過不錯的效果。

總結一下:如果不需要淘汰演算法則選擇ConcurrentHashMap,如果需要淘汰演算法和一些豐富的API,這裡推薦選擇Caffeine。

2.2 選擇合適的分散式緩存

這裡選取三個比較出名的分散式緩存來作為比較,MemCache(沒有實戰使用過),Redis(在美團又叫Squirrel),Tair(在美團又叫Cellar)。不同的分散式緩存功能特性和實現原理方面有很大的差異,因此他們所適應的場景也有所不同。

分散式緩存:愛我你怕了嗎?

 

3.多級緩存

很多人一想到緩存馬上腦子裡面就會出現下麵的圖:

分散式緩存:愛我你怕了嗎?

 

Redis用來存儲熱點數據,Redis中沒有的數據則直接去資料庫訪問。

在之前介紹本地緩存的時候,很多人都問我,我已經有Redis了,我幹嘛還需要瞭解Guava,Caffeine這些進程緩存呢。我基本統一回覆下麵兩個答案:

  1. Redis如果掛了或者使用老版本的Redis,其會進行全量同步,此時Redis是不可用的,這個時候我們只能訪問資料庫,很容易造成雪崩。
  2. 訪問Redis會有一定的網路I/O以及序列化反序列化,雖然性能很高但是其終究沒有本地方法快,可以將最熱的數據存放在本地,以便進一步加快訪問速度。這個思路並不是我們做互聯網架構獨有的,在電腦系統中使用L1,L2,L3多級緩存,用來減少對記憶體的直接訪問,從而加快訪問速度。
分散式緩存:愛我你怕了嗎?

 

所以如果僅僅是使用Redis,能滿足我們大部分需求,但是當需要追求更高的性能以及更高的可用性的時候,那就不得不瞭解多級緩存。

3.1使用進程緩存

對於進程內緩存,其本來受限於記憶體的大小的限制,以及進程緩存更新後其他緩存無法得知,所以一般來說進程緩存適用於:

  1. 數據量不是很大,數據更新頻率較低,之前我們有個查詢商家名字的服務,在發送簡訊的時候需要調用,由於商家名字變更頻率較低,並且就算是變更了沒有及時變更緩存,簡訊裡面帶有老的商家名字客戶也能接受。利用Caffeine作為本地緩存,size設置為1萬,過期時間設置為1個小時,基本能在高峰期解決問題。
  2. 如果數據量更新頻繁,也想使用進程緩存的話,那麼可以將其過期時間設置為較短,或者設置其較短的自動刷新的時間。這些對於Caffeine或者Guava Cache來說都是現成的API。

3.2使用多級緩存

俗話說得好,世界上沒有什麼是一個緩存解決不了的事,如果有,那就兩個。

一般來說我們選擇一個進程緩存和一個分散式緩存來搭配做多級緩存,一般來說引入兩個也足夠了,如果使用三個,四個的話,技術維護成本會很高,反而有可能會得不償失,如下圖所示:

分散式緩存:愛我你怕了嗎?

 

利用Caffeine做一級緩存,Redis作為二級緩存。

  1. 首先去Caffeine中查詢數據,如果有直接返回。如果沒有則進行第2步。
  2. 再去Redis中查詢,如果查詢到了返回數據併在Caffeine中填充此數據。如果沒有查到則進行第3步。
  3. 最後去Mysql中查詢,如果查詢到了返回數據併在Redis,Caffeine中依次填充此數據。

對於Caffeine的緩存,如果有數據更新,只能刪除更新數據的那台機器上的緩存,其他機器只能通過超時來過期緩存,超時設定可以有兩種策略:

  • 設置成寫入後多少時間後過期
  • 設置成寫入後多少時間刷新

對於Redis的緩存更新,其他機器立馬可見,但是也必須要設置超時時間,其時間比Caffeine的過期長。

為瞭解決進程內緩存的問題,設計進一步優化:

分散式緩存:愛我你怕了嗎?

 

4.緩存更新

一般來說緩存的更新有兩種情況:

  • 先刪除緩存,再更新資料庫。
  • 先更新資料庫,再刪除緩存。 這兩種情況在業界,大家對其都有自己的看法。具體怎麼使用還得看各自的取捨。當然肯定會有人問為什麼要刪除緩存呢?而不是更新緩存呢?你可以想想當有多個併發的請求更新數據,你並不能保證更新資料庫的順序和更新緩存的順序一致,那就會出現資料庫中和緩存中數據不一致的情況。所以一般來說考慮刪除緩存。

4.1先刪除緩存,再更新資料庫

對於一個更新操作簡單來說,就是先去各級緩存進行刪除,然後更新資料庫。這個操作有一個比較大的問題,在對緩存刪除完之後,有一個讀請求,這個時候由於緩存被刪除所以直接會讀庫,讀操作的數據是老的並且會被載入進入緩存當中,後續讀請求全部訪問的老數據。

分散式緩存:愛我你怕了嗎?

 

對緩存的操作不論成功失敗都不能阻塞我們對資料庫的操作,那麼很多時候刪除緩存可以用非同步的操作,但是先刪除緩存不能很好的適用於這個場景。

先刪除緩存也有一個好處是,如果對資料庫操作失敗了,那麼由於先刪除的緩存,最多只是造成Cache Miss。

4.2先更新資料庫,再刪除緩存(推薦)

如果我們使用更新資料庫,再刪除緩存就能避免上面的問題。但是同樣的引入了新的問題,試想一下有一個數據此時是沒有緩存的,所以查詢請求會直接落庫,更新操作在查詢請求之後,但是更新操作刪除資料庫操作在查詢完之後回填緩存之前,就會導致我們緩存中和資料庫出現緩存不一致。

為什麼我們這種情況有問題,很多公司包括Facebook還會選擇呢?因為要觸發這個條件比較苛刻。

  1. 首先需要數據不在緩存中。
  2. 其次查詢操作需要在更新操作先到達資料庫。
  3. 最後查詢操作的回填比更新操作的刪除後觸發,這個條件基本很難出現,因為更新操作的本來在查詢操作之後,一般來說更新操作比查詢操作稍慢。但是更新操作的刪除卻在查詢操作之後,所以這個情況比較少出現。

對比上面4.1的問題來說這種問題的概率很低,況且我們有超時機制保底所以基本能滿足我們的需求。如果真的需要追求完美,可以使用二階段提交,但是其成本和收益一般來說不成正比。

當然還有個問題是如果我們刪除失敗了,緩存的數據就會和資料庫的數據不一致,那麼我們就只能靠過期超時來進行兜底。對此我們可以進行優化,如果刪除失敗的話 我們不能影響主流程那麼我們可以將其放入隊列後續進行非同步刪除。

5.緩存挖坑三劍客

大家一聽到緩存有哪些註意事項,肯定首先想到的是緩存穿透,緩存擊穿,緩存雪崩這三個挖坑的小能手,這裡簡單介紹一下他們具體是什麼以及應對的方法。

5.1緩存穿透

緩存穿透是指查詢的數據在資料庫是沒有的,那麼在緩存中自然也沒有,所以,在緩存中查不到就會去資料庫取查詢,這樣的請求一多,那麼我們的資料庫的壓力自然會增大。

為了避免這個問題,可以採取下麵兩個手段:

  1. 約定:對於返回為NULL的依然緩存,對於拋出異常的返回不進行緩存,註意不要把拋異常的也給緩存了。採用這種手段的會增加我們緩存的維護成本,需要在插入緩存的時候刪除這個空緩存,當然我們可以通過設置較短的超時時間來解決這個問題。
分散式緩存:愛我你怕了嗎?

 

  1. 制定一些規則過濾一些不可能存在的數據,小數據用BitMap,大數據可以用布隆過濾器,比如你的訂單ID 明顯是在一個範圍1-1000,如果不是1-1000之內的數據那其實可以直接給過濾掉。
分散式緩存:愛我你怕了嗎?

 

5.2緩存擊穿

對於某些key設置了過期時間,但是其是熱點數據,如果某個key失效,可能大量的請求打過來,緩存未命中,然後去資料庫訪問,此時資料庫訪問量會急劇增加。

為了避免這個問題,我們可以採取下麵的兩個手段:

  1. 加分散式鎖:載入數據的時候可以利用分散式鎖鎖住這個數據的Key,在Redis中直接使用setNX操作即可,對於獲取到這個鎖的線程,查詢資料庫更新緩存,其他線程採取重試策略,這樣資料庫不會同時受到很多線程訪問同一條數據。
  2. 非同步載入:由於緩存擊穿是熱點數據才會出現的問題,可以對這部分熱點數據採取到期自動刷新的策略,而不是到期自動淘汰。淘汰其實也是為了數據的時效性,所以採用自動刷新也可以。

5.3緩存雪崩

緩存雪崩是指緩存不可用或者大量緩存由於超時時間相同在同一時間段失效,大量請求直接訪問資料庫,資料庫壓力過大導致系統雪崩。

為了避免這個問題,我們採取下麵的手段:

  1. 增加緩存系統可用性,通過監控關註緩存的健康程度,根據業務量適當的擴容緩存。
  2. 採用多級緩存,不同級別緩存設置的超時時間不同,及時某個級別緩存都過期,也有其他級別緩存兜底。
  3. 緩存的key值可以取個隨機值,比如以前是設置10分鐘的超時時間,那每個Key都可以隨機8-13分鐘過期,儘量讓不同Key的過期時間不同。

6.緩存污染

緩存污染一般出現在我們使用本地緩存中,可以想象,在本地緩存中如果你獲得了緩存,但是你接下來修改了這個數據,但是這個數據並沒有更新在資料庫,這樣就造成了緩存污染:

分散式緩存:愛我你怕了嗎?

 

要想避免這個問題需要開發人員從編碼上註意,並且代碼必須經過嚴格的review,以及全方位的回歸測試,才能從一定程度上解決這個問題。

7.序列化

序列化是很多人都不註意的一個問題,很多人忽略了序列化的問題,上線之後馬上報出一下奇怪的錯誤異常,造成了不必要的損失,最後一排查都是序列化的問題。列舉幾個序列化常見的問題:

  1. key-value對象過於複雜導致序列化不支持:筆者之前出過一個問題,在美團的Tair內部預設是使用protostuff進行序列化,而美團使用的通訊框架是thfift,thrift的TO是自動生成的,這個TO裡面很多複雜的數據結構,但是將其存放到了Tair中。查詢的時候反序列化也沒有報錯,單測也通過,但是到qa測試的時候發現這一塊功能有問題,發現有個欄位是boolean類型預設是false,把它改成true之後,序列化到tair中再反序列化還是false。定位到是protostuff對於複雜結構的對象(比如數組,List
  2. 添加了欄位或者刪除了欄位,導致上線之後老的緩存獲取的時候反序列化報錯,或者出現一些數據移位。
  3. 不同的JVM的序列化不同,如果你的緩存有不同的服務都在共同使用(不提倡),那麼需要註意不同JVM可能會對Class內部的Field排序不同,而影響序列化。比如下麵的代碼,在Jdk7和Jdk8中對象A的排列順序不同,最終會導致反序列化結果出現問題:

//jdk 7

classA

{

int a;

int b;

}

//jdk 8

class A

{

int b;

int a;

}

序列化的問題必須得到重視,解決的辦法有如下幾點:

  1. 測試:對於序列化需要進行全面的測試,如果有不同的服務並且他們的JVM不同那麼你也需要做這一塊的測試,在上面的問題中筆者的單測通過的原因是用的預設數據false,所以根本沒有測試true的情況,還好QA給力,將其給測試出來了。
  2. 對於不同的序列化框架都有自己不同的原理,對於添加欄位之後如果當前序列化框架不能相容老的,那麼可以換個序列化框架。 對於protostuff來說他是按照Field的順序來進行反序列化的,對於添加欄位我們需要放到末尾,也就是不能插在中間,否則會出現錯誤。對於刪除欄位來說,用@Deprecated註解進行標註棄用,如果貿然刪除,除非是最後一個欄位,否則肯定會出現序列化異常。
  3. 可以使用雙寫來避免,對於每個緩存的key值可以加上版本號,每次上線版本號都加1,比如現線上上的緩存用的是Key1,即將要上線的是Key2,上線之後對緩存的添加是會寫新老兩個不同的版本(Key1,Key2)的Key-Value,讀取數據還是讀取老版本Key_1的數據,假設之前的緩存的過期時間是半個小時,那麼上線半個小時之後,之前的老緩存存量的數據都會被淘汰,此時線上老緩存和新緩存他們的數據基本是一樣的,切換讀操作到新緩存,然後停止雙寫。採用這種方法基本能平滑過渡新老Model交替,但是不好的點就是需要短暫的維護兩套新老Model,下次上線的時候需要刪除掉老Model,增加了維護成本。

8. GC調優

對於大量使用本地緩存的應用,由於涉及到緩存淘汰,那麼GC問題必定是常事。如果出現GC較多,STW時間較長,那麼必定會影響服務可用性。這一塊給出下麵幾點建議:

  1. 經常查看GC監控,如何發現不正常,需要想辦法對其進行優化。
  2. 對於CMS垃圾收集器,如果發現remark過長,如果是大量本地緩存應用的話這個過長應該很正常,因為在併發階段很容易有很多新對象進入緩存,從而remark階段掃描很耗時,remark又會暫停。可以開啟-XX:CMSScavengeBeforeRemark,在remark階段前進行一次YGC,從而減少remark階段掃描gc root的開銷。
  3. 可以使用G1垃圾收集器,通過-XX:MaxGCPauseMillis設置最大停頓時間,提高服務可用性。
  4. 說到這裡順便給大家推薦一個Java架構方面的交流學習社群:650385180,裡面不僅可以交流討論,還有面試經驗分享以及免費的資料下載,包括Spring,MyBatis,Netty源碼分析,高併發、高性能、分散式、微服務架構的原理,JVM性能優化這些成為架構師必備的知識體系。相信對於已經工作和遇到技術瓶頸的碼友,在這個群里會有你需要的內容。

9. 緩存的監控

很多人對於緩存的監控也比較忽略,基本上線之後如果不報錯然後就預設他就生效了。但是存在這個問題,很多人由於經驗不足,有可能設置了不恰當的過期時間,或者不恰當的緩存大小導致緩存命中率不高,讓緩存就成為了代碼中的一個裝飾品。所以對於緩存各種指標的監控,也比較重要,通過其不同的指標數據,我們可以對緩存的參數進行優化,從而讓緩存達到最優化:

分散式緩存:愛我你怕了嗎?

 

上面的代碼中用來記錄get操作的,通過Cat記錄了獲取緩存成功,緩存不存在,緩存過期,緩存失敗(獲取緩存時如果拋出異常,則叫失敗),通過這些指標,我們就能統計出命中率,我們調整過期時間和大小的時候就可以參考這些指標進行優化。

10. 一款好的框架

一個好的劍客沒有一把好劍怎麼行呢?如果要使用好緩存,一個好的框架也必不可少。在最開始使用的時候大家使用緩存都用一些util,把緩存的邏輯寫在業務邏輯中:

分散式緩存:愛我你怕了嗎?

 

上面的代碼把緩存的邏輯耦合在業務邏輯當中,如果我們要增加成多級緩存那就需要修改我們的業務邏輯,不符合開閉原則,所以引入一個好的框架是不錯的選擇。

推薦大家使用JetCache這款開源框架,其實現了Java緩存規範JSR107並且支持自動刷新等高級功能。筆者參考JetCache結合Spring Cache, 監控框架Cat以及美團的熔斷限流框架Rhino實現了一套自有的緩存框架,讓操作緩存,打點監控,熔斷降級,業務人員無需關心。上面的代碼可以優化成:

分散式緩存:愛我你怕了嗎?

 

對於一些監控數據也能輕鬆從大盤上看到:

分散式緩存:愛我你怕了嗎?

 

最後

想要真正的使用好一個緩存,必須要掌握很多的知識,並不是看幾個Redis原理分析,就能把Redis緩存用得爐火純青。對於不同場景,緩存有各自不同的用法,同樣的不同的緩存也有自己的調優策略,進程內緩存你需要關註的是他的淘汰演算法和GC調優,以及要避免緩存污染等。分散式緩存你需要關註的是他的高可用,如果其不可用瞭如何進行降級,以及一些序列化的問題。一個好的框架也是必不可少的,對其如果使用得當再加上上面介紹的經驗,相信能讓你很好的駕馭住這頭野馬——緩存。

最後打個廣告,如果你覺得這篇文章對你有文章,可以關註我的技術公眾號:Java技術zhai,最近作者收集了很多最新的學習資料視頻以及面試資料,關註之後即可領取,你的關註和轉發是對我最大的支持,O(∩_∩)O


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 有人讓我寫一下關於數據挖掘在金融方面的應用,再加上現在金融對數據方面的要求不斷提高,準備用兩篇隨筆來做個入門總結。 首先,在看這篇隨筆以前稍微補充一點金融方面的知識,因為我不是金融專業的,以下補充知識來自互聯網與個人整理,歡迎批評指正並補充說明。 1 先來瞭解一下什麼是金融市場呢? 通常狹義的金融市 ...
  • 變數定義 1.基礎定義 變數類型在變數名後 2.定義並賦值 3.類型推導 不用定義變數類型 4.簡寫(只能在函數內) 用":="代替"var" 5.定義多個變數並賦值 測試代碼 ...
  • 詳細教程 參考struts教程https://www.w3cschool.cn/struts_2/struts_configuration.html Struts2 基於MVC設計模式的web應用程式框架,它不僅僅是Struts1 的升級版本,更是一個全新的Struts架構。最初,是以WebWork ...
  • 一.文件的打開,open函數 打開模式有很多種 1. 'r': 以只讀方式打開文件。文件的指針將會放在文件的開頭。這是預設模式。 2. 'r+': 打開一個文件用於讀寫。文件指針將會放在文件的開頭,但寫入內容會寫到文件內容末尾。 3. 'w': 打開一個文件只用於寫入。如果該文件已存在則打開文件,並 ...
  • 1.什麼是servlet? Servlet(Servlet Applet),全稱Java Servlet,是用Java編寫的伺服器端程式。而這些Servlet都要實現Servlet這個介面。其主要功能在於互動式的瀏覽和修改數據,生成動態Web內容。Servlet運行於支持Java的應用伺服器中。 2 ...
  • <!--done--> 知識預覽 Django 如何使用admin組件來對後臺數據進行管理的? Django admin如何實現後臺數據管理的?(admin源碼解析) 如何仿照admin實現一個自定義的增刪改查的組件? 回到頂部 Django 如何使用admin組件來對後臺數據進行管理的? 在每個a ...
  • 給定一組字元,使用原地演算法將其壓縮。 壓縮後的長度必須始終小於或等於原數組長度。 數組的每個元素應該是長度為1 的字元(不是 int 整數類型)。 在完成原地修改輸入數組後,返回數組的新長度。 進階: 你能否僅使用O(1) 空間解決問題? 示例 1: 輸入: ["a","a","b","b","c" ...
  • JPA是java Persistence API簡稱,中文名:java持久層API,JPA是JCP組織發佈的J2EE標準之一 1.創建DataSource連接池對象 1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <art ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...