一起幫忙解決難題,拯救IT 人的一天
文章推薦指數: 80 %
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}}
關閉
延伸文章資訊
- 1gin+gorm+router 快速搭建crud restful API 接口| Go 技术论坛
根据上一篇"gorm基础"封装crud api接口下载扩展go get github.com/go-sql-driver/mysql go get github.com/jinzhu/gorm ...
- 2go系列之一:gin+gorm開發一個簡單的熱榜介面站 - IT人
controller: 控制器,負責接收引數、驗證引數,呼叫service,統一輸出 ... package controller import ( "fmt" "github.com/gin-...
- 3gin controller 自动注册 - 简书
反射入门可以先看这个http://colobu.com/2015/09/23/laws-of-goang-reflection/ 因为在使用gin框架,在配置路由的时候需要手...
- 4使用gin封装一个web脚手架(一):控制器和路由 - 掘金
创建一个go项目,名称为myGin go.mod文件为安装gin 创建一个main.go文件创建routes文件夹和controller 项目结构为在controller下创建一个Index控制.
- 5sqrtcat/easy-gin: 一套基于Gin 框架的MVC 脚手架 - GitHub
Contribute to sqrtcat/easy-gin development by creating an account on GitHub. ... index.html - ind...