# Go实战 | 记一次降低30%的CPU使用率的优化

01 背景

02 实现版本一

• 代表某个国家能接受的最大请求值的key表示规则：国家:max:req
• 代表已接收到某个国家当天的请求量key表示规则：国家:YYYYMMDD:req ，有效期为N天。

``````func HasExceedLimitReq() bool {
key := "CN:max:req"

maxReq := redis.Get(key)

day := time.Now().Format("20060102")
dailyKey := "CN:"+day+":req"
dailyReq := redis.Get(dailyKey)

if dailyReq > maxReq {
return true
}

redis.Incr(dailyKey, dailyReq)
redis.Expire(dailyKey, 7*24*time.Hour)

return false
}

03 实现版本二：减少Expire的执行次数

``````var hasUpdateExpire = make(map[string]struct{}) //全局变量

func HasExceedLimitReq() bool {
key := "CN:max:req"

maxReq := redis.Get(key)

day := time.Now().Format("20060102")
dailyKey := "CN:"+day+":req"
dailyReq := redis.Get(dailyKey)

if dailyReq > maxReq {
return true
}

redis.Incr(dailyKey, dailyReq)
if hasUpdateExpire[dailyKey]; !ok {
redis.Expire(dailyKey, 7*24*time.Hour)
hasUpdateExpire[dailyKey] = struct{}{}
}

return false
}

``````    if hasUpdateExpire[dailyKey]; !ok {
redis.Expire(dailyKey, 7*24*time.Hour)
hasUpdateExpire[dailyKey] = struct{}{}
}

``````redis.Expire(dailyKey, 7*24*time.Hour)
hasUpdateExpire[dailyKey] = struct{}{}

04 实现版本三：异步批量写入

``````import (
"sync"
"time"

"github.com/go-redis/redis"
)

const (
DefaultExpiration  = 86400 * time.Second * 7
)

type CounterCache struct {
rwMu        sync.RWMutex
redisClient redis.Cmdable

countCache   map[string]int64
hasUpdateExpire map[string]struct{}
}

func NewCounterCache(redisClient redis.Cmdable) *CounterCache {
c := &CounterCache{
redisClient: redisClient,
countCache:    make(map[string]int64),
}
go c.startFlushTicker()
return c
}

func (c *CounterCache) IncrBy(key string, value int64) int64 {
val := c.incrCacheBy(key, value)
redisCount, _ := c.redisClient.Get(key).Int64()
return val + redisCount
}

func (c *CounterCache) incrCacheBy(key string, value int64) int64 {
c.rwMu.Lock()
defer c.rwMu.Unlock()

count := c.countCache[key]
count += value
c.countCache[key] = count
return count
}

func (c *CounterCache) Get(key string) (int64, error) {
cacheVal := c.get(key)
redisValue, err := c.redisClient.Get(key).Int64()
if err != nil && err != redis.Nil {
return cacheVal, err
}

return redisValue + cacheVal, nil
}

func (c *CounterCache) get(key string) int64 {
c.rwMu.RLock()
defer c.rwMu.RUnlock()
return c.countCache[key]
}

func (c *CounterCache) startFlushTicker() {
ticker := time.NewTicker(time.Second * 5)
for {
select {
case <-ticker.C:
c.flush()
}
}
}

func (c *CounterCache) flush() {
var oldCountCache map[string]int64
c.rwMu.Lock()
oldCountCache = c.countCache
c.countCache = make(map[string]int64)
c.rwMu.Unlock()

for key, value := range oldCountCache {
c.redisClient.IncrBy(key, value)
if _, ok := c.hasUpdateExpire[key]; !ok {
err := c.redisClient.Expire(key, DefaultExpiration)
if err == nil {
c.hasUpdateExpire[key] = struct{}{}
}
}
}
}

``````package main

import (
"net/http"
"sync"
"time"

"github.com/go-redis/redis"
)

var counterCache *CounterCache

func main() {
redisClient := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "",
})
counterCache = NewCounterCache(redisClient)

http.HandleFunc("/", IndexHandler)
http.ListenAndServe(":8080", nil)
}

func IndexHandler(w http.ResponseWriter, r *http.Request) {
if HasExceedLimitReq() {
return
}
//处理正常逻辑
}

func HasExceedLimitReq() bool {
maxKey := "CN:max:req"
maxCount, _ := counterCache.Get(maxKey)

dailyKey := "CN:" + time.Now().Format("20060102") + ":req"
dailyCount, _ := counterCache.Get(dailyKey)

if dailyCount > maxCount {
return true
}

counterCache.IncrBy(dailyKey, 1)
return false
}

05 总结