day 16 - 开启git worktree 进行redis lua-script 测试比较

今天要来换个redis写法, 看看能不能缩短执行时间。

首先我会用git worktree再开一个专案资料夹, 新资料夹就留在调整前, 称为v1版, 再修改版本就是v2版。

git worktree 可以让本机同时有两个分支相互比较或进行开发, 有时候A分支功能写到一半突然收到需求要改B分支的时候, 就需要先把分支commit或是做git stash, 後来改用worktree就可以直接再开一个资料夹对分支进行修改, 不会影响原本的开发进度; 另外像是这次要比较v1, v2 版本差异的时候, 就可以启动两个不同分支的版本进行比较。

  • 使用 git worktree 开启另一个专案

    git worktree add -b ba_master ../coconut_2
    
  • 修改 redis/limit.go
    第二版采用 redis支援的lua-script方式修改, 写好的整串lua-script是采原子式的方式排队执行的, 要等到前一句script执行完才会执行下一个, 它可以保证script里面的语法都执行完了才开始新的一组指令, 这在我们专案里面很常使用到。

    type LimitSetting struct {
        Level1 int
        Level2 int
        Level3 int
    }
    
    // set point
    func PointSetBatch(conn *redis.Client, keys []string, point int, limitSetting map[string]int, expired int) (err error) {
        // LimitSetting , lua-script json decode 使用  
        tmp := &LimitSetting{
            Level1: limitSetting["0"],
            Level2: limitSetting["1"],
            Level3: limitSetting["2"],
        }
        // script
        luaScript := `
        local point = tonumber(ARGV[1])
        local limit = cjson.decode(ARGV[2])
        local expired = tonumber(ARGV[3])
    
        -- 先GET一次KEY, 没有KEY的要SET, SET 同时要EXPIRE
        if( redis.call('GET', KEYS[1]) == nil or redis.call('GET', KEYS[1]) == false) then
            redis.call('SETEX', KEYS[1], expired, 0)
        end
    
        if( redis.call('GET', KEYS[2]) == nil or redis.call('GET', KEYS[2]) == false) then
            redis.call('SETEX', KEYS[2], expired, 0)
        end
    
        if( redis.call('GET', KEYS[3]) == nil or redis.call('GET', KEYS[3]) == false) then
            redis.call('SETEX', KEYS[3], expired, 0)
        end
    
         -- level 1
        if(redis.call('GET', KEYS[1]) + point <= tonumber(limit.Level1)) then
            redis.call('INCRBY',KEYS[1], point)
        else
             -- 定义不同的回传值来区分踩到的限额是哪一个
            return tostring(-99)
        end
    
        -- level 2
        if(redis.call('GET', KEYS[2]) + point <= tonumber(limit.Level2)) then
            redis.call('INCRBY',KEYS[2], point)
        else
            -- 定义不同的回传值来区分踩到的限额是哪一个
            return tostring(-98)
        end
    
        -- level 3
        if(redis.call('GET', KEYS[3]) + point <= tonumber(limit.Level3)) then
            redis.call('INCRBY',KEYS[3], point)
        else
            -- 定义不同的回传值来区分踩到的限额是哪一个
            return tostring(-97)
        end
    
        return 'ok'
        `
        script, err := conn.ScriptLoad(luaScript).Result()
        if err != nil {
            return err
        }
    
        reply, err := conn.EvalSha(script, keys, point, tmp.MarshalBinary(), expired).Result()
        if err != nil {
            return err
        }
        fmt.Println("reply:", reply)
        // TODO: 处理 reply 回传值对应资讯
        return
    }
    
    // lua-script json decode 使用 
    func (s *LimitSetting) MarshalBinary() (ret string) {
        data, _ := json.Marshal(s)
        ret = string(data)
        return ret
    }
    
    // lua-script json decode 使用 
    func (s *LimitSetting) UnmarshalBinary(data []byte) error {
        return json.Unmarshal(data, s)
    }
    
  • rpc.go 改打新的 function PointSetBatch

    err = coconut_redis.PointSetBatch(s.RedisClient, keys, int(in.Point), limitSettings, 30)
        if err != nil {
            Logger.WithFields(map[string]interface{}{
                "test": 111,
                "time": time.Now().UnixNano(),
                "err:": err.Error(),
            }).Errorf("redis.PointSetBatch")
            return nil, coconutError.ParseError(coconutError.ErrRedis, err)
        }
    
  • 测试v1, v2 版本差异
    在不同的port启动两个不同分支的服务&不同台redis, 同时进行测试。

    func main() {
        // 连线到远端 gRPC 服务器。
        conn1, err := grpc.Dial("localhost:3100", grpc.WithInsecure())
        if err != nil {
            log.Fatalf("conn 连线失败:%v", err)
        }
    
        defer conn1.Close()
    
        coco1 := coconut.NewCoconutClient(conn1)
    
        // 连线到远端 gRPC 服务器。
        conn2, err := grpc.Dial("localhost:3200", grpc.WithInsecure())
        if err != nil {
            log.Fatalf("conn 连线失败:%v", err)
        }
    
        defer conn2.Close()
    
        coco2 := coconut.NewCoconutClient(conn2)
    
        n, _ := strconv.Atoi(os.Args[1])
    
        var (
            sum1 float64
            sum2 float64
        )
        req := &coconut.PointsRequest{
            Level_1: "aaa",
            Level_2: "bbb",
            Level_3: "ccc",
            Point:   100,
        }
        wait := &sync.WaitGroup{}
    
        for i := 0; i < n; i++ {
            wait.Add(1)
            go func() {
                defer func() {
                    wait.Done()
                }()
                start := time.Now()
                _, _ = coco1.UpdatePoints(context.Background(), req)
                sum1 += time.Since(start).Seconds()
            }()
        }
    
        for i := 0; i < n; i++ {
            wait.Add(1)
            go func() {
                defer func() {
                    wait.Done()
                }()
                start := time.Now()
                _, _ = coco2.UpdatePoints(context.Background(), req)
                sum2 += time.Since(start).Seconds()
            }()
        }
    
        wait.Wait()
    
        fmt.Println("[v1] total count:", n, ", avg execute_time:", (sum1 / float64(n)))
        fmt.Println("[v2] total count:", n, ", avg execute_time:", (sum2 / float64(n)))
    }
    
  • 测试 10次, 100次, 1000次的执行结果
    https://i.imgur.com/yTcE6dP.png

经过多次分别测试10,100,1000的结果可以观察到, 10次的时候两个版本处理时间差异不大, 100~1000次就可以看出明显的差异, 这样就可以把版本调整为v2版了。


<<:  练习实作的轮回

>>:  Day09-流量限制(四)

Day 01:Hello Computer Science!

前言 初次参加铁人赛,开赛第一天,先放轻松暖个身,把前辈的文章看过一遍吧! 相关文章 Mike Fa...

企划实现(7)

立案流程 第一步: 到公司网查询是否有同名的公司,输入 1-5 个想要设立的公司名称,确认公司名称是...

[DAY 26] 章节3-6: 阿伯日後谈-监督式、半监督式、非监督式学习

3-6 阿伯日後谈 博览会後,太阳渐渐下山了,看着人渐渐散,阿伯也准备收拾摊子回家了,远处看见飞哥跟...

Data layer implementation (2)

上一篇的 repository 还欠一个 mapper 把 EtaResponse 转成 EtaRe...

详解资料仓库的实施步骤,实战全解!(1)

建立资料仓库是一个解决企业资料问题应用的过程,是企业资讯化发展到一定阶段必不可少的一步,也是发展资料...