-
-
Save Petelin/6efe0f27500e5f80977518c49ddcb77b to your computer and use it in GitHub Desktop.
// Copyright 2013 Gary Burd | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
// not use this file except in compliance with the License. You may obtain | |
// a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
// License for the specific language governing permissions and limitations | |
// under the License. | |
package redis_test | |
import ( | |
"fmt" | |
"github.com/gomodule/redigo/redis" | |
) | |
// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands. | |
func zpop(c redis.Conn, key string) (result string, err error) { | |
defer func() { | |
// Return connection to normal state on error. | |
if err != nil { | |
c.Do("DISCARD") | |
} | |
}() | |
// Loop until transaction is successful. | |
for { | |
if _, err := c.Do("WATCH", key); err != nil { | |
return "", err | |
} | |
members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0)) | |
if err != nil { | |
return "", err | |
} | |
if len(members) != 1 { | |
return "", redis.ErrNil | |
} | |
c.Send("MULTI") | |
c.Send("ZREM", key, members[0]) | |
queued, err := c.Do("EXEC") | |
if err != nil { | |
return "", err | |
} | |
if queued != nil { | |
result = members[0] | |
break | |
} | |
} | |
return result, nil | |
} | |
// zpopScript pops a value from a ZSET. | |
var zpopScript = redis.NewScript(1, ` | |
local r = redis.call('ZRANGE', KEYS[1], 0, 0) | |
if r ~= nil then | |
r = r[1] | |
redis.call('ZREM', KEYS[1], r) | |
end | |
return r | |
`) | |
// This example implements ZPOP as described at | |
// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting. | |
func Example_zpop() { | |
c, err := dial() | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
defer c.Close() | |
// Add test data using a pipeline. | |
for i, member := range []string{"red", "blue", "green"} { | |
c.Send("ZADD", "zset", i, member) | |
} | |
if _, err := c.Do(""); err != nil { | |
fmt.Println(err) | |
return | |
} | |
// Pop using WATCH/MULTI/EXEC | |
v, err := zpop(c, "zset") | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Println(v) | |
// Pop using a script. | |
v, err = redis.String(zpopScript.Do(c, "zset")) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Println(v) | |
// Output: | |
// red | |
// blue | |
} |
local message = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1], 'LIMIT', 0, ARGV[2]); if #message > 0 then redis.call('ZREM', KEYS[1], unpack(message)); return message; else return {}; end
➜ gitlib redis-benchmark -n 100000 -t -q evalsha 36990d55283ad0265fbb0f84fac937ff9f7d7e7d test 1559042241 1
====== evalsha 36990d55283ad0265fbb0f84fac937ff9f7d7e7d test 1559042241 1 ======
100000 requests completed in 1.30 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.86% <= 1 milliseconds
100.00% <= 1 milliseconds
77041.60 requests per second
➜ gitlib redis-benchmark -n 100000 -t -q ZRANGEBYSCORE test -inf 1559042241 limit 0 1
====== ZRANGEBYSCORE test -inf 1559042241 limit 0 1 ======
100000 requests completed in 1.25 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.83% <= 1 milliseconds
100.00% <= 1 milliseconds
80256.82 requests per second
这个东西track的地方是, 不直接用原子操作去get, 而是先watch一下, 直接get,这样是没有锁的. 在没有数据的情况下是并发的.
当然如果是繁忙的总是有资源返回, 这种方式反而拖慢了