一起幫忙解決難題,拯救IT 人的一天

文章推薦指數: 80 %
投票人數:10人

Day24 Gin with Cache ... 而我們這章節主要也是在講Gin在Server Cache的實作! ... "github.com/gin-gonic/gin" "ironman-2021/app/controller" ... 2021iThome鐵人賽 DAY 24 0 ModernWeb fmt.Println("從零開始的Golang生活")系列第 24篇 Day24GinwithCache 13th鐵人賽 flynncanfly 團隊GameMasters 2021-10-0900:30:45162瀏覽 前言 我們在併發HTTPServer的時候,經常有對接口內容做緩存的需求。

例如:某些熱點內容,我們希望在1分鐘內做緩存,避免短時間內不會對相同的內容進行重複性讀取與運算,同時也降低系統的整體負載。

有時我們需要把緩存邏輯放在Server內部,而非網觀測如Nginx等。

是因為這樣我們可以根據需求便捷地清除緩存,或者利用Redis等其他儲存空間做為緩存後端。

也因此需要快取的資料有兩種特徵: 經常被調用 資料不常變動 那快取的種類也可依不同類型分為三種: ClientCache ServerCache NetworkCache ClientCache ClientCache指的是伺服器與瀏覽器之間的快取機制,ServerSide透過伺服器的快取將不常變動的資訊儲存在瀏覽器快取之中,避免重複下載浪費效能。

ClientCache的設定方式是透過HTTPRequest的Header去帶params,當瀏覽器接收到特定params就會採取相對應的快取處理。

如果對ClientCache有興趣的人可以參考下方連結,胡立大寫的很好 https://blog.techbridge.cc/2017/06/17/cache-introduction/ ServerCache ServerCache主要指的是在Backend與Database間資料的Cache。

當大量的QueryRequest進來時,會導致Database的I/O操作過多,因而造成Session堵塞、效能低落等問題,即使進行讀寫分離在不同的叢集當中,也只能解決部份問題,因此我們會將經常被查詢的資料儲存在像是Redis之類的key-value資料庫,並以LRU等Strategy來進行快取資料的變更,以分擔資料庫I/O壓力。

而我們這章節主要也是在講Gin在ServerCache的實作! 有興趣者也可以參考Cloudflare的官方資訊,他們解說的挺詳細的 https://www.cloudflare.com/zh-tw/cdn/ NetworkCache 最後則是NetworkCache,他的理念即是User會從離他最近的Server去取資料,用以節省ResponseTime,也就是CDN(ContentDeliveryNetwork)的概念! 這樣的緩存場景無非是有緩存時從緩存取,無緩存時從下游服務取,並將數據放入緩存中。

這其實是非常通用的邏輯,應該可以將其抽象出來。

從而緩存邏輯無需侵入進業務邏輯。

GinwithCache 這邊我們選用的是yahui大神所重新封裝的gin-cachepackage,因其可以依據自身要求定義cachekey,且在性能方面也較官方的gin-contrib/cache優秀,因此選用它。

Installation goget-u[github.com/chenyahui/gin-cache](http://github.com/chenyahui/gin-cache%E3%80%82) RouterwithCache app/config/router.go packageconfig import( cache"github.com/chenyahui/gin-cache" "github.com/chenyahui/gin-cache/persist" "github.com/gin-gonic/gin" "ironman-2021/app/controller" "ironman-2021/app/middleware" _"ironman-2021/docs" "time" ) funcRouteUsers(r*gin.Engine,m*persist.RedisStore){ posts:=r.Group("/v1/users") { posts.POST("/",controller.NewUsersController().CreateUser) posts.GET("/:id",middleware.JWTAuthMiddleware(),cache.CacheByRequestURI(m,2*time.Hour), controller.QueryUsersController().GetUser) posts.POST("/login",controller.LoginUserController().AuthHandler) } } 我們讓GET/v1/users/:id這個endpoint吃得到快取,然後快取時間設定為2小時 main.go packagemain import( "github.com/chenyahui/gin-cache/persist" "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" "github.com/joho/godotenv" ginSwagger"github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" "ironman-2021/app/config" "ironman-2021/app/dao" "ironman-2021/app/middleware" "ironman-2021/app/model" "os" ) //@titleGinswagger //@version1.0 //@descriptionGinswagger //@contact.nameFlynnSun //@license.nameApache2.0 //@license.urlhttp://www.apache.org/licenses/LICENSE-2.0.html //@hostlocalhost:8080 //schemeshttp //@securityDefinitions.apikeyBearerAuth //@inheader //@nameAuthorization funcmain(){ envErr:=godotenv.Load() ifenvErr!=nil{ panic(envErr) } port:=os.Getenv("PORT") dbConfig:=os.Getenv("DB_CONFIG") db,ormErr:=dao.Initialize(dbConfig) iformErr!=nil{ panic(ormErr) } migrateErr:=db.AutoMigrate(&model.User{}) ifmigrateErr!=nil{ return } server:=gin.Default() server.Use(middleware.CORSMiddleware()) server.GET("/hc",func(c*gin.Context){ c.JSON(200,gin.H{ "message":"healthcheck", }) }) redisStore:=persist.NewRedisStore(redis.NewClient(&redis.Options{ Network:"tcp", Addr:"redis:6379", DB:0, })) config.RouteUsers(server,redisStore) url:=ginSwagger.URL("http://localhost:8080/swagger/doc.json") server.GET("/swagger/*any",ginSwagger.WrapHandler(swaggerFiles.Handler,url)) err:=server.Run(":"+port) iferr!=nil{ panic(err) } } Performance 我們可以看到下圖在第一次GET/v1/users/:id與之後幾次的ResponseTime都差距相當的多,因為第二次之後都是從Cache拿取資料 此外我們也可以進去redis的container,並觀察到當第一次GET/v1/users/:id 後,會多一個/v1/users/1的Key,我們就以Key-Value的資料去讀取快取 dockerexec-itredisbash root@e2c9049d9ac3:/data#redis-cli-n0 127.0.0.1:6379>KEYS* (emptyarray) 127.0.0.1:6379>KEYS* 1)"/v1/users/1" 127.0.0.1:6379> 題外話,能夠如此容易的使用各項服務與監測效能也是我們用docker與docker-compose的好處之一。

gin-contrib/cache的問題解析 接口設計 gin-contrib/cache對外提供的方式是wraphandler的方式,而非更優雅的middleware。

cache.CachePage(store,time.Minute,func(c*gin.Context){ c.String(200,"pong"+fmt.Sprint(time.Now().Unix())) }) 用戶無法根據自身要求自定義生成cachekey。

gin-contrib/cache只提供CachePage、CachePageWithoutQuery等函數,用戶可以根據url作為緩存的key。

但該組件並不支持自定義cachekey。

性能方面 該組件寫入緩存的方式是,重載了ResponseWriter的寫入函數。

每次在gin中調用寫入函數時,觸發一次性能的獲取和追加操作。

比較差的。

最糟糕的是關於並發安全的實現。

因為該組件寫緩存之前需要先得到原始內容進行生成,這個過程不是原子的。

加了一個互斥鎖來保證不會寫衝突,接口代碼如下。

funcCachePageAtomic(storepersistence.CacheStore,expiretime.Duration,handlegin.HandlerFunc)gin.HandlerFunc{ varmsync.Mutex p:=CachePage(store,expire,handle) returnfunc(c*gin.Context){ m.Lock() deferm.Unlock() p(c) } } 緩存擊穿問題 在緩存設計中,會遇到一個常見的問題:緩存擊穿。

緩存擊穿指的是:當某個熱點Key在其緩存過期的一瞬間,大量的請求將訪問不到這個Key對應的緩存。

這時請求將直接打到下游的儲存或服務當中。

一瞬間大量的請求,可能會對下游服務造成極大的壓力。

關於這個問題,golang官方有一個SingleFlight:golang.org/x/sync/singleflight,可以有效的解決緩存擊穿問題。

其原理非常簡單,有興趣的可以直接在Github搜源碼看就可以了,到此不再展開討論。

benchmark 使用LinuxCPU8核,16G內存系統配置下,Cyhone大使用wrk對gin-contrib/cache與gin-cache做benchmark壓力測試,這邊我則擷取他的實驗結果來解說,有興趣者可以去Reference的連結轉連。

wrk-c500-d1m-t5http://127.0.0.1:8080/hello 對於MemoryCache進程內緩存這個場景,gin-cache提升了23%。

對於Redis做緩存這個場景,gin-cache相比QPS更提升了30倍左右。

當然也使用gin-cache使用的redis客戶端庫的性能更好。

從口的設計對比來看,在當處理者請求接手,gin-cache的優勢將更加明顯。

Reference https://blog.techbridge.cc/2017/06/17/cache-introduction/ https://www.cloudflare.com/zh-tw/cdn/ https://www.cyhone.com/articles/gin-cache/ 留言 追蹤 檢舉 上一篇 Day23Ginwithi18n 下一篇 Day25GinwithAPITest 系列文 fmt.Println("從零開始的Golang生活") 共30篇 目錄 RSS系列文 訂閱系列文 3人訂閱 26 Day26GinwithLogger 27 Day27GinwithColly 28 Day28GinwithSMTPServer 29 Day29GinwithAsync 30 Day30GinwithDrone 完整目錄 尚未有邦友留言 立即登入留言 iT邦幫忙鐵人賽 參賽組數 1087組 團體組數 52組 累計文章數 20470篇 完賽人數 572人 鐵人賽最新文章 gotodie?那個goto到底能不能用啊? 2021/12/12更新 予焦啦!一夢終須醒...... 盤點清查與檢測掃描-資通安全健診 [13th][Day23]httpresponseheader(下) [13th][Day22]httpresponseheader(上) [13th][Day21]golangcontext 股票怎麼選?掌握這原則,你也能找到強勢股 Gitpush 盤點清查與檢測掃描-安全性檢測 前往鐵人賽 技術推廣專區 [Day2]抓取每日收盤價 [Day1]基本工具安裝 利用python取得永豐銀行API的Nonce [Day03]tinyML開發板介紹 永豐金融API測試員 [Day01]在享受tinyML這道美食之前 [Day3]使用ta-lib製作指標 [Day4]函數打包與買進持有報酬率試算 計算API所需要的參數:HashID 計算API所需要的參數:IV 前往鐵人賽 熱門問題 公司想要客製化一套ERP系統該選擇軟體開發?套裝系統?還是自行設立部門? 軟體工程師會被監控嗎 我有一個客戶中了mljx病毒勒索950美金我免費轉讓有人要接嗎? (以解決)請問GMAIL群組.假如公司之前有業務群[email protected].有新職員收不到這個.如何把新業務加入 訂房網站DB架構 加班提醒視窗 想找PDF編輯工具,Adobe太貴了,請問還有其他推薦的替代方案嗎? 家中的網路配置 切割vlan網段方式 【已解決】如何安裝舊版本的VS2019 IT邦幫忙 站方公告 【2021iThome鐵人賽】登登登!究竟獎落誰家,2021iThome鐵人賽得獎名單正式揭曉 熱門tag 看更多 13th鐵人賽 12th鐵人賽 11th鐵人賽 鐵人賽 2019鐵人賽 2018鐵人賽 javascript 2017鐵人賽 windows php python windowsserver linux c# 程式設計 資訊安全 css vue.js sql 分享 熱門回答 公司想要客製化一套ERP系統該選擇軟體開發?套裝系統?還是自行設立部門? 軟體工程師會被監控嗎 想找PDF編輯工具,Adobe太貴了,請問還有其他推薦的替代方案嗎? 防火牆如何設定DenyPolicy,以提高資安機制? 家中的網路配置 (以解決)請問GMAIL群組.假如公司之前有業務群[email protected].有新職員收不到這個.如何把新業務加入 RedhatEnterpriselinux5.5安裝Oracle問題 MYSQL如何將欄位中的json陣列資料拆分出來 請問目前php8版本的問題 切割vlan網段方式 熱門文章 予焦啦!一夢終須醒...... gotodie?那個goto到底能不能用啊? 盤點清查與檢測掃描-資通安全健診 自己在家接收來自飛機的ADS-B訊號! 第七隻狗勾 2021/12/12更新 第九隻狗勾 第六隻狗勾 第八隻狗勾 【前端效能優化】Lighthouse網站效能檢測工具 一週點數排行 更多點數排行 海綿寶寶(antijava) ㊣浩瀚星空㊣(yoching) 居然解出來了(partyyaya) raytracy(raytracy) ccenjor(ccenjor) mathewkl(mathewkl) japhenchen(japhenchen) 純真的人(jer5173) 小山丘(a243318490) fillano(fillano) × At 輸入對方的帳號或暱稱 Loading 找不到結果。

標記 {{result.label}} {{result.account}} 關閉



請為這篇文章評分?