有域名 有主機(jī) 怎么建設(shè)網(wǎng)站獨(dú)立站seo外鏈平臺(tái)
6.14項(xiàng)目一話術(shù)
- Rediit
- 1.項(xiàng)目整體介紹
- 2. 核心開(kāi)發(fā)框架:Gin + Zap + Viper
- 3.在 Gin 當(dāng)中集成 JWT 鑒權(quán)中間件實(shí)現(xiàn)無(wú)狀態(tài)認(rèn)證
- 4.數(shù)據(jù)存儲(chǔ):sqlx + MySQL
- 5.使用 Redis 緩存用戶投票數(shù)
- 6.限流和壓測(cè)
Rediit
1.項(xiàng)目整體介紹
我獨(dú)立開(kāi)發(fā)了一個(gè)名為 Bluebell 的項(xiàng)目,它是一個(gè)功能完整的社交媒體平臺(tái),核心功能類似于 Reddit。用戶可以在平臺(tái)上注冊(cè)登錄、創(chuàng)建和瀏覽帖子、加入不同的社區(qū)、并對(duì)帖子進(jìn)行投票。
這個(gè)項(xiàng)目的目標(biāo)是構(gòu)建一個(gè)高并發(fā)、高可用且易于維護(hù)的后端服務(wù)。為此,我選擇了這套以 Go 語(yǔ)言為核心的技術(shù)棧,包括使用 Gin 框架來(lái)處理 Web 請(qǐng)求,MySQL 作為持久化數(shù)據(jù)庫(kù),Redis 作為高性能緩存,并計(jì)劃通過(guò) Docker 實(shí)現(xiàn)容器化部署。
補(bǔ)充:問(wèn)docker
在 Bluebell 這個(gè)項(xiàng)目中,由于目前主要是我個(gè)人在開(kāi)發(fā)和本地測(cè)試,所以暫時(shí)還沒(méi)有走完容器化部署的最后一步。不過(guò),我對(duì) Docker 并不陌生,在我之前的實(shí)驗(yàn)室項(xiàng)目中,我就有比較深入的使用經(jīng)驗(yàn),當(dāng)時(shí)主要是用 Docker 來(lái)封裝和運(yùn)行我們的深度學(xué)習(xí)訓(xùn)練任務(wù)。
通過(guò)之前的經(jīng)驗(yàn),我認(rèn)識(shí)到 Docker 的價(jià)值遠(yuǎn)不止于隔離環(huán)境和方便移植,我認(rèn)為 Docker 的核心價(jià)值在于它將應(yīng)用及其所有依賴打包成一個(gè)標(biāo)準(zhǔn)化的、自包含的單元——也就是鏡像,這個(gè)標(biāo)準(zhǔn)化的單元帶來(lái)了幾個(gè)巨大的好處:
- 極大地簡(jiǎn)化了部署流程:運(yùn)維人員不需要關(guān)心我的應(yīng)用是用 Go 寫的,依賴什么版本的庫(kù)。他們只需要 docker run 我提供的鏡像就可以了
- 為自動(dòng)化運(yùn)維 (CI/CD,持續(xù)集成/持續(xù)交付/持續(xù)部署) 鋪平了道路:在現(xiàn)代化的開(kāi)發(fā)流程中,我們可以設(shè)置一個(gè) CI/CD 管道。當(dāng)我向代碼倉(cāng)庫(kù)(比如 Git)提交新代碼后,可以自動(dòng)觸發(fā)構(gòu)建一個(gè)新的 Docker 鏡像,運(yùn)行自動(dòng)化測(cè)試,測(cè)試通過(guò)后自動(dòng)將新鏡像部署到服務(wù)器上
- 高效的資源利用:相比于傳統(tǒng)的虛擬機(jī),容器非常輕量,啟動(dòng)速度是秒級(jí)
2. 核心開(kāi)發(fā)框架:Gin + Zap + Viper
為什么選擇Gin作為Web框架?
我選擇 Gin 是因?yàn)樗且粋€(gè)基于 Radix 樹(shù)路由的高性能 Web 框架,非常輕量級(jí),中間件生態(tài)也很豐富。相比其他框架,它在提供強(qiáng)大功能的同時(shí),幾乎沒(méi)有性能損耗,這對(duì)于需要處理大量并發(fā)請(qǐng)求的社交平臺(tái)至關(guān)重要。
具體怎么用
在項(xiàng)目中,我遵循了 RESTful API 的設(shè)計(jì)原則。我將項(xiàng)目結(jié)構(gòu)劃分為清晰的層次:Routes (路由層)、Controllers (控制器/視圖函數(shù)層)、Logic/Services (業(yè)務(wù)邏輯層) 和 DAO (數(shù)據(jù)訪問(wèn)層)。這種分層結(jié)構(gòu)使得代碼邏輯清晰,易于維護(hù)和測(cè)試。例如,一個(gè)發(fā)帖請(qǐng)求會(huì)先經(jīng)過(guò)路由匹配,然后由 Controller 層校驗(yàn)基礎(chǔ)參數(shù),接著調(diào)用 Logic 層處理復(fù)雜的業(yè)務(wù)邏輯(如檢查用戶權(quán)限、內(nèi)容審查等),最后通過(guò) DAO 層與數(shù)據(jù)庫(kù)交互。
為什么選擇Zap管理日志?
對(duì)于一個(gè)線上項(xiàng)目來(lái)說(shuō),日志是排查問(wèn)題的關(guān)鍵。我沒(méi)有使用 Go 自帶的 log 庫(kù),而是集成了 Uber 開(kāi)源的 Zap 日志庫(kù)。Zap 是一個(gè)結(jié)構(gòu)化、高性能的日志庫(kù)。它會(huì)將日志輸出為 JSON 格式,而不是純文本。
使用Zap日志庫(kù)的好處
結(jié)構(gòu)化日志最大的好處是機(jī)器可讀。在生產(chǎn)環(huán)境中,這些日志可以被輕松地收集到 ELK (Elasticsearch, Logstash, Kibana) 中進(jìn)行搜集、存儲(chǔ)和可視化日志,極大地提高了問(wèn)題排查的效率。
為什么選擇 Viper管理配置?
為了實(shí)現(xiàn)配置與代碼的分離,我使用了 Viper。它能夠從多種來(lái)源(如 YAML 文件、環(huán)境變量、遠(yuǎn)程配置中心)讀取配置,并支持配置熱加載。
具體怎么用
我將應(yīng)用信息、數(shù)據(jù)庫(kù)地址、日志級(jí)別等配置信息都放在一個(gè) conf.yaml 文件中。而不是硬編碼在代碼里。Viper 的主要好處是它能讓我非常靈活地管理不同環(huán)境下的配置。比如,開(kāi)發(fā)時(shí)我可以用本地的 MySQL 和 Redis 地址,而部署到生產(chǎn)環(huán)境時(shí),我只需要修改 conf.yaml 文件中的 mysql.host 和 redis.host,不需要改動(dòng)和重新編譯任何一行代碼。
3.在 Gin 當(dāng)中集成 JWT 鑒權(quán)中間件實(shí)現(xiàn)無(wú)狀態(tài)認(rèn)證
為什么是JWT?
我采用了基于 JWT (JSON Web Token) 的無(wú)狀態(tài)認(rèn)證方案。相比于傳統(tǒng)的 Session-Cookie 方案,JWT 不需要服務(wù)端存儲(chǔ)用戶的會(huì)話信息,這使得后端服務(wù)可以水平擴(kuò)展(stateless),非常適合分布式和微服務(wù)架構(gòu)。
補(bǔ)充:水平擴(kuò)展(無(wú)狀態(tài),stateless)
在傳統(tǒng)的 session-cookie 模式下:
-
用戶登錄后,服務(wù)端要保存“會(huì)話信息(session)”;
-
每次用戶發(fā)請(qǐng)求,服務(wù)端要查一下這個(gè) session 信息,看看是誰(shuí)登錄了;
-
問(wèn)題來(lái)了:如果你有多臺(tái)服務(wù)器,這個(gè) session 信息到底存在哪一臺(tái)呢?不好統(tǒng)一管理,或者還得用專門的共享存儲(chǔ)。
而 JWT 是“無(wú)狀態(tài)”的,登錄成功后用戶拿到一個(gè)加密的 token(令牌),之后:
-
用戶自己帶著這個(gè) token 發(fā)請(qǐng)求;
-
后端只需要校驗(yàn)這個(gè) token 是否有效;
-
不需要查數(shù)據(jù)庫(kù)、不需要存 session,服務(wù)器彼此之間也不用共享信息。
這樣你想加幾臺(tái)后端服務(wù)器都可以,大家都能獨(dú)立處理請(qǐng)求,不需要“互相溝通”誰(shuí)存了哪個(gè)用戶的登錄狀態(tài)。
詳細(xì)的認(rèn)證流程是怎樣的?
-
登錄與簽發(fā):用戶使用賬號(hào)密碼登錄。服務(wù)器驗(yàn)證通過(guò)后,會(huì)生成兩個(gè) Token:一個(gè)短生命周期的 Access Token(例如 2 小時(shí))和一個(gè)長(zhǎng)生命周期的 Refresh Token(例如 7 天)。項(xiàng)目中沒(méi)有重復(fù)造輪子,而是使用現(xiàn)成的"github.com/dgrijalva/jwt-go"來(lái)完成 JWT 的生成。
-
令牌傳遞:服務(wù)器將這兩個(gè) Token 返回給客戶端。客戶端在后續(xù)請(qǐng)求中,會(huì)將 Access Token 放在 HTTP 請(qǐng)求的 Authorization Header 中(格式為 Bearer )。
-
鑒權(quán)中間件:我編寫了一個(gè)全局的 JWT 鑒權(quán)中間件。這個(gè)中間件會(huì)攔截所有需要登錄才能訪問(wèn)的 API 請(qǐng)求(比如發(fā)表帖子,給帖子點(diǎn)贊)。它會(huì):
-
解析 Header 中的 Access Token,驗(yàn)證其簽名和有效期。
-
如果驗(yàn)證通過(guò),就從 Token 的 Payload 中解析出 用戶 ID 等信息。
-
關(guān)鍵一步:為了避免在后續(xù)的業(yè)務(wù)邏輯中重復(fù)解析或查詢用戶信息,我將解析出的用戶 ID 存入 Gin 的 Context 中 (c.Set(controller.CtxUserIDKey, mc.UserID))。這樣,后續(xù)的 Controller 或 Service 層函數(shù)就可以直接從 Context 中獲取當(dāng)前登錄用戶的身份信息,非常高效。
-
-
令牌刷新機(jī)制:當(dāng) Access Token 過(guò)期后,客戶端會(huì)使用 Refresh Token 向特定的刷新接口請(qǐng)求一對(duì)新的 Token,從而實(shí)現(xiàn)用戶無(wú)感知的登錄狀態(tài)續(xù)期,提升了用戶體驗(yàn)。
補(bǔ)充:限制一個(gè)賬號(hào)只能在一個(gè)設(shè)備上登陸或者多臺(tái)設(shè)備只能同時(shí)登錄n個(gè)
核心思想:在用戶每次登錄并獲取到雙 token 后,服務(wù)器端記錄該登錄會(huì)話的信息,并在用戶后續(xù)操作時(shí)進(jìn)行校驗(yàn)。當(dāng)?shù)卿浽O(shè)備數(shù)量達(dá)到上限時(shí),拒絕新的登錄請(qǐng)求,或者強(qiáng)制下線最老的登錄會(huì)話。
4.數(shù)據(jù)存儲(chǔ):sqlx + MySQL
為什么選擇 sqlx 而不是 GORM?
在數(shù)據(jù)庫(kù)交互方面,我選擇了 sqlx 庫(kù)而不是像 GORM 這樣的全功能 ORM。主要原因是 sqlx 是對(duì) Go 原生 database/sql 包的一個(gè)輕量級(jí)擴(kuò)展,它在提供便利性(如將查詢結(jié)果直接掃描到結(jié)構(gòu)體中)的同時(shí),讓我可以完全掌控 SQL 語(yǔ)句。對(duì)于復(fù)雜的查詢,手寫 SQL 往往比 ORM 生成的 SQL 更高效。這是一種在開(kāi)發(fā)效率和底層控制之間的平衡。
如何組織的?
我創(chuàng)建了一個(gè) DAO 層來(lái)專門負(fù)責(zé)所有數(shù)據(jù)庫(kù)操作。比如,mysql文件夾下的community.go用來(lái)負(fù)責(zé)所有和社區(qū)相關(guān)的操作,像是展示社區(qū)列表,根據(jù)社區(qū)id查詢社區(qū)分類詳情,又比如redis文件夾下的vote.go負(fù)責(zé)帖子的投票操作。這樣做的好處是業(yè)務(wù)邏輯層(Logic)不需要關(guān)心具體的 SQL 實(shí)現(xiàn),實(shí)現(xiàn)了邏輯和數(shù)據(jù)的解耦。
5.使用 Redis 緩存用戶投票數(shù)
為什么用 Redis?
對(duì)于社交平臺(tái),投票和帖子排序是讀寫非常頻繁的操作。如果每次投票都直接讀寫 MySQL,會(huì)給數(shù)據(jù)庫(kù)帶來(lái)巨大壓力。因此,我引入了 Redis 做緩存,利用其基于內(nèi)存的高速讀寫能力來(lái)優(yōu)化性能。
具體實(shí)現(xiàn)細(xì)節(jié)
- 投票數(shù)據(jù)緩存:當(dāng)用戶對(duì)帖子進(jìn)行投票時(shí),我并不是直接更新 MySQL。而是先在 Redis 中記錄投票信息。我使用了 Redis 的 ZSet 和 Set。例如:
- KeyPostTimeZSet = “post:time” 是一個(gè)帖子按照發(fā)布時(shí)間排序的 ZSet(有序集合),用于實(shí)現(xiàn)項(xiàng)目中的按時(shí)間排序帖子;
- KeyPostScoreZSet = “post:score” 是一個(gè)帖子按照得分排序的ZSet,用于實(shí)現(xiàn)項(xiàng)目中的根據(jù)熱度(投票分?jǐn)?shù))排序帖子;
- KeyPostVotedZSetPrefix = “post:voted:” 是一個(gè)用于記錄某個(gè)帖子下哪些用戶投了票,以及投的什么票的ZSet;
- KeyCommunitySetPrefix = “community:” 是一個(gè)用于記錄每個(gè)社區(qū)下有哪些帖子的Set
package redis//redis key
//redis key盡量用命名空間的方式區(qū)分不同的keyconst (KeyPrefix = "bluebell:"KeyPostTimeZSet = "post:time" //ZSet 帖子及發(fā)帖時(shí)間KeyPostScoreZSet = "post:score" //ZSet 帖子及投票的分?jǐn)?shù)KeyPostVotedZSetPrefix = "post:voted:" //ZSet 記錄用戶及投票的類型,參數(shù)是post idKeyCommunitySetPrefix = "community:" //set 保存每個(gè)分區(qū)下帖子的id
)//給rediskey加前綴
func getRedisKey(key string) string {return KeyPrefix + key
}
當(dāng)我們創(chuàng)建帖子時(shí),需要同時(shí)保存帖子的創(chuàng)建時(shí)間、帖子的初始熱度分?jǐn)?shù)(與創(chuàng)建時(shí)間相等)、以及帖子的社區(qū) ID:
func CreatePost(postID, community_id int64) error {pipeline := rdb.TxPipeline()//帖子時(shí)間pipeline.ZAdd(getRedisKey(KeyPostTimeZSet), redis.Z{Score: float64(time.Now().Unix()),Member: strconv.FormatInt(postID, 10),})//帖子分?jǐn)?shù)pipeline.ZAdd(getRedisKey(KeyPostScoreZSet), redis.Z{//初始的分?jǐn)?shù)仍然與時(shí)間相關(guān)聯(lián),這與直覺(jué)相符,越新的帖子分?jǐn)?shù)應(yīng)該越高,使得新的帖子盡可能靠前顯示Score: float64(time.Now().Unix()),Member: strconv.FormatInt(postID, 10),})//補(bǔ)充 把帖子id加到社區(qū)的setpipeline.SAdd(getRedisKey(KeyCommunitySetPrefix+strconv.Itoa(int(community_id))), postID)_, err := pipeline.Exec()return err
}
第一個(gè)業(yè)務(wù)邏輯是根據(jù)社區(qū) ID 獲取帖子按發(fā)布時(shí)間分?jǐn)?shù)或者得分熱度分?jǐn)?shù)降序排序的結(jié)果,其實(shí)現(xiàn)如下:
// GetCommunityPostIDsInOrder 按社區(qū)查詢ids
func GetCommunityPostIDsInOrder(p *models.ParamPostlist) ([]string, error) {//使用zinterstore 把分區(qū)的帖子set與帖子分?jǐn)?shù)的zset生成一個(gè)新的zset//針對(duì)新的zset按之前的邏輯取數(shù)據(jù)orderkey := getRedisKey(KeyPostTimeZSet)if p.Order == models.Orderscore {orderkey = getRedisKey(KeyPostScoreZSet)}ckey := getRedisKey(KeyCommunitySetPrefix + strconv.Itoa(int(p.CommunityID))) //社區(qū)的key//利用緩存key減少zinterstore執(zhí)行的次數(shù)key := orderkey + strconv.Itoa(int(p.CommunityID))if rdb.Exists(key).Val() < 1 {//不存在,需要計(jì)算pipeline := rdb.Pipeline()//用 ZINTERSTORE 把社區(qū)下的帖子集合(Set)全局排序的 ZSet(時(shí)間/得分)的帖子ID做一個(gè)交集,生成一個(gè)新的 ZSet://key 是上面新拼的 key,分?jǐn)?shù)來(lái)自原排序 ZSet,用 MAX 保留最大的分?jǐn)?shù)(通常其實(shí)只有一個(gè)來(lái)源)//得到一個(gè):"某社區(qū)下的帖子,按時(shí)間/得分排序的新 ZSet"pipeline.ZInterStore(key, redis.ZStore{Aggregate: "MAX",}, ckey, orderkey)pipeline.Expire(key, 60*time.Second)_, err := pipeline.Exec()if err != nil {return nil, err}}//存在的話就直接根據(jù)key查詢idsreturn getIDsFormKey(key, p.Offset, p.Limit)
}
第二個(gè)業(yè)務(wù)邏輯是帖子的點(diǎn)贊功能,帖子點(diǎn)贊的功能實(shí)現(xiàn)如下:
const (oneWeekInSeconds = 7 * 24 * 3600scorePerVote = 432 //每一票值多少分
)var (ErrorVoteTimeExpire = errors.New("投票時(shí)間已過(guò)")ErrorVoteRepeat = errors.New("不許重復(fù)投票")
)func VoteForPost(userID, postID string, value float64) error {//1.判斷投票限制//去redis取發(fā)布時(shí)間post_time := rdb.ZScore(getRedisKey(KeyPostTimeZSet), postID).Val()if float64(time.Now().Unix())-post_time > oneWeekInSeconds {return ErrorVoteTimeExpire}//2和3需要放到一個(gè)pipeline事務(wù)里面//2.更新帖子的分?jǐn)?shù)//先查當(dāng)前用戶給當(dāng)前帖子的投票記錄ov := rdb.ZScore(getRedisKey(KeyPostVotedZSetPrefix+postID), userID).Val()//不能重復(fù)投相同的票if value == ov {return ErrorVoteRepeat}var symbol float64if value > ov {symbol = 1} else {symbol = -1}diff := math.Abs(ov - value)pipeline := rdb.TxPipeline()pipeline.ZIncrBy(getRedisKey(KeyPostScoreZSet), symbol*diff*scorePerVote, postID)//3.記錄用戶為該則帖子投票的數(shù)據(jù)if value == 0 {pipeline.ZRem(getRedisKey(KeyPostVotedZSetPrefix+postID), userID)} else {//如果用戶之前已經(jīng)使用了 ZAdd 添加相同的 Member 到 Sorted Set,那么本次 ZAdd 將會(huì)把之前的 Member 和 Score 覆蓋掉//也就是說(shuō)如果用戶之前對(duì)該帖子投了up,但是這次投了down,那么redis KeyPostVotedZSetPrefix就只會(huì)記錄最新的pipeline.ZAdd(getRedisKey(KeyPostVotedZSetPrefix+postID), redis.Z{Score: value,Member: userID,})}_, err := pipeline.Exec()return err
}
在為帖子進(jìn)行投票時(shí),首先要判斷帖子是否已經(jīng)過(guò)了投票時(shí)間,如果超時(shí)直接返回。(防止“挖墳”操作,讓首頁(yè)展示的帖子盡可能富有時(shí)效性)
之后,我們進(jìn)一步去 Redis 的ZSet:bluebell:post:voted:查找當(dāng)前用戶是否已經(jīng)為帖子投過(guò)票,如果當(dāng)前用戶上一次投票行為和本次相同,那么禁止重復(fù)投票。(避免已經(jīng)點(diǎn)贊下次還是點(diǎn)贊)
然后根據(jù)用戶的投票行為,對(duì) Redis 中保存的帖子分?jǐn)?shù)進(jìn)行修改。比如:
-
第一次點(diǎn)贊:ov=0, value=1 → diff=1, op=1 → 加 432 分
-
取消點(diǎn)贊:ov=1, value=0 → diff=1, op=-1 → 減 432 分
-
反對(duì)變點(diǎn)贊:ov=-1, value=1 → diff=2, op=1 → 加 864 分
由于帖子的初始分?jǐn)?shù)與創(chuàng)建時(shí)間相關(guān),因此不斷為帖子投票可以提高帖子的分?jǐn)?shù),使帖子的分?jǐn)?shù)權(quán)重上升。由于我們?cè)O(shè)置每票分?jǐn)?shù)為 432,因此 200 張贊成票可以為帖子續(xù)一天的熱度。
最后在 Redis 中記錄用戶本次的投票行為。
- 帖子排序算法:首頁(yè)的帖子不能簡(jiǎn)單按票數(shù)或時(shí)間排序。為了讓新的、受歡迎的帖子更容易被看到,我實(shí)現(xiàn)了一個(gè)隨時(shí)間權(quán)重下降的評(píng)價(jià)系統(tǒng),算法靈感來(lái)源于 Reddit 的 Hot Ranking 算法。
可以簡(jiǎn)單介紹算法公式: Score = log ? 10 ( ∣ upvotes ? downvotes ∣ ) + sign ( upvotes ? downvotes ) ? t 45000 \text{Score} = \log_{10}(|\text{upvotes} - \text{downvotes}|) + \frac{\text{sign}(\text{upvotes} - \text{downvotes}) \cdot t}{45000} Score=log10?(∣upvotes?downvotes∣)+45000sign(upvotes?downvotes)?t?
其中,t 是從一個(gè)固定的時(shí)間點(diǎn)(例如項(xiàng)目上線時(shí)間)到帖子發(fā)布時(shí)間的秒數(shù)差。
解釋算法:“這個(gè)公式主要由兩部分組成:第一部分是票數(shù)得分,通過(guò)取對(duì)數(shù),可以使得票數(shù)從 10 票到 100 票的得分增長(zhǎng)遠(yuǎn)大于從 1010 票到 1100 票的增長(zhǎng),避免了高票帖子霸榜。第二部分是時(shí)間權(quán)重,帖子的發(fā)布時(shí)間越新,t 值越大,得分也越高。這個(gè)分?jǐn)?shù)會(huì)隨著時(shí)間的流逝而自然下降。我通過(guò)一個(gè)定時(shí)的任務(wù),周期性地計(jì)算熱門帖子的分?jǐn)?shù),并將排好序的帖子 ID 列表緩存到 Redis 的 ZSET (Sorted Set) 中,獲取熱門帖子列表時(shí),直接從 ZSET 中讀取,速度極快?!?/p>
6.限流和壓測(cè)
項(xiàng)目中限流的實(shí)現(xiàn)方式
使用 Token Bucket(令牌桶) 或 Leaky Bucket(漏桶) 原理的中間件,比如:
項(xiàng)目中使用了第三方庫(kù):github.com/juju/ratelimit
// gin.HandlerFunc 就是 func(*gin.Context) 的別名
// 在 RateLimitMiddleware 中,內(nèi)部函數(shù)就“捕獲”了在外部函數(shù)中創(chuàng)建的那個(gè) bucket 變量
// 這意味著,無(wú)論有多少個(gè)請(qǐng)求經(jīng)過(guò)這個(gè)中間件,每次執(zhí)行內(nèi)部函數(shù)時(shí),它訪問(wèn)到的都是同一個(gè) bucket 實(shí)例
// 這對(duì)于限流器來(lái)說(shuō)是至關(guān)重要的,因?yàn)樗枰粉櫵姓?qǐng)求的狀態(tài)
func RateLimitMiddleware(fillInterval time.Duration, cap int64) func(c *gin.Context) {bucket := ratelimit.NewBucket(fillInterval, cap)return func(c *gin.Context) {// 如果取不到令牌就中斷本次請(qǐng)求返回 rate limit...if bucket.TakeAvailable(1) < 1 {c.String(http.StatusOK, "rate limit...")c.Abort()return}//取到令牌就放行c.Next()}
}
壓測(cè)工具
為了驗(yàn)證限流是否生效,以及系統(tǒng)抗壓能力, 項(xiàng)目常用以下壓測(cè)工具
wrk(推薦):一個(gè)高性能 HTTP 壓測(cè)工具,支持 Lua 腳本,適合測(cè)試高并發(fā)接口。
示例命令:
wrk -t4 -c100 -d30s http://localhost:8080/api/v1/post
說(shuō)明:
-t4: 4 個(gè)線程
-c100: 100 個(gè)連接
-d30s: 壓測(cè) 30 秒