前言:
我们知道使用redis计数可以使用incrby, hincrby 等计数指令,因为redis的工作线程只有一个,所以保证了并发原子的控制。 由于我们的业务的特殊性,有增有减,有各类状态值的判断,尤其在异常情况下,计数会减到0以下,负数是不能忍的。 所以,我们需要尽量保证各个计数器值是正整数。
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新。 http://xiaorui.cc/?p=4737
我先前的做法是这样, 先判断各种业务在redis中的各种标志位,然后尝试去操作计数,如果拿到的计数值小于0,那么我们给他摆正。 逻辑还是稍显复杂,来回的状态判断就有十几回.
redis command
先前的代码片段如下 (实际逻辑代码过多,就简单举个例子说明):
func CounterDecrByAppid(app_id string, process string) error { rc := RedisClient.Get() defer rc.Close() app_id = mergeCounterKey(app_id) c, err := redis.Int(rc.Do("HINCRBY", app_id, process, -1)) if c < 0 { fmt.Println("ccc", c) rc.Do("HINCRBY", app_id, process, 1) } return err }
上面的代码没问题,可以跑… 但是首先不够原子,比如并发都拿到了 -1 值,接着都去 set 1, 这样显而损失了不少精度. 另外一个大点就是性能…
pipeline批量 ? 是可以解决… 但我们知道pipeline可以减少网络来回的RTT,你也可以在一个管道中混入读写,但问题是,你还是需要做判断… 你的判断比如又要来回…
后面,我们改用redis lua来实现这一块逻辑. 下面的例子是代码片段,其实线上的逻辑要比这复杂的多。 简单说,如果每次判断值都要io来往,那么时间的消耗是必然的。 所以,我们可以把相关的逻辑注册到redis eval方法里,这样只需一次io访问就完成了该逻辑链。
如果没有使用redis lua的朋友,可以看看我以前写过的redis lua eval的文章。 http://xiaorui.cc/?p=3052
性能测试
性能方面,针对这一块逻辑测试,redis lua要比单条redis性能高很好。如果redis lua方法只是单条指令,那么他的速度要比redis慢…. 原因你懂得…
// xiaorui.cc const ( // key, field, num SCRIPT_INCR = ` local v = redis.call("HINCRBY", KEYS[1], ARGV[1], ARGV[2]) if v < 0 then local c = tonumber(ARGV[2]) if c < 0 then return redis.call("HSET", KEYS[1], ARGV[1], 0) and 1 else return redis.call("HSET", KEYS[1], ARGV[1], 1) and 1 end else return 0 end ` ) func CounterRegScript(app_id string, status string) (int, error) { rc := RedisClient.Get() defer rc.Close() script := redis.NewScript(1, SCRIPT_INCR) resp, err := redis.Int(script.Do(rc, app_id, status, int64(-1))) return resp, err }
中间遇到一些问题,mac上的redis-server是3.2,测试主机2.4 … 对的,相当古老的 2.4 . 也不知道哪位大哥这么仇恨我,居然给我转了个2.4… 导致我忙活了好长时间… 因为先前确认这逻辑没问题,就没有在判断 err 的状态…
你虽然传入的是int, 但是 redis的lua解释器不认,所以需要用toNumber去转换到 数字对象. 不然会出现下面的问题.
ERR Error running script (call to f_7778767932161a6c2339144e0c2daefa12c51609): @user_script:7: user_script:7: attempt to compare string with number
redis注册lua脚本的时候,会根据内容hash一个code,通过这个code是可以映射相关的lua自定义函数.
502873561.856256 [0 127.0.0.1:56216] "SELECT" "0" 1502873561.856356 [0 127.0.0.1:56216] "EVALSHA" "a1ca4f6db2d614463cb4fef83fd9894168423bb5" "1" "app_id_99" "RUNNING" "-1" 1502873561.856390 [0 lua] "HINCRBY" "app_id_99" "RUNNING" "-1" 1502873561.856417 [0 lua] "HSET" "app_id_99" "RUNNING" "0"
END.