Skip to content

Instantly share code, notes, and snippets.

@Petelin
Last active June 4, 2019 09:12
Show Gist options
  • Save Petelin/6efe0f27500e5f80977518c49ddcb77b to your computer and use it in GitHub Desktop.
Save Petelin/6efe0f27500e5f80977518c49ddcb77b to your computer and use it in GitHub Desktop.
pop a set
// 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
}
@Petelin
Copy link
Author

Petelin commented May 27, 2019

这个东西track的地方是, 不直接用原子操作去get, 而是先watch一下, 直接get,这样是没有锁的. 在没有数据的情况下是并发的.
当然如果是繁忙的总是有资源返回, 这种方式反而拖慢了

@Petelin
Copy link
Author

Petelin commented Jun 4, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment