forked from mirror/redis
Fix WrapProcess for Ring and Cluster. Add better example.
This commit is contained in:
parent
b148c1afd3
commit
82f21639bf
17
commands.go
17
commands.go
|
@ -185,7 +185,6 @@ type Cmdable interface {
|
||||||
ClientKill(ipPort string) *StatusCmd
|
ClientKill(ipPort string) *StatusCmd
|
||||||
ClientList() *StringCmd
|
ClientList() *StringCmd
|
||||||
ClientPause(dur time.Duration) *BoolCmd
|
ClientPause(dur time.Duration) *BoolCmd
|
||||||
ClientSetName(name string) *BoolCmd
|
|
||||||
ConfigGet(parameter string) *SliceCmd
|
ConfigGet(parameter string) *SliceCmd
|
||||||
ConfigResetStat() *StatusCmd
|
ConfigResetStat() *StatusCmd
|
||||||
ConfigSet(parameter, value string) *StatusCmd
|
ConfigSet(parameter, value string) *StatusCmd
|
||||||
|
@ -241,14 +240,6 @@ type cmdable struct {
|
||||||
process func(cmd Cmder) error
|
process func(cmd Cmder) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// WrapProcess replaces the process func. It takes a function createWrapper
|
|
||||||
// which is supplied by the user. createWrapper takes the old process func as
|
|
||||||
// an input and returns the new wrapper process func. createWrapper should
|
|
||||||
// use call the old process func within the new process func.
|
|
||||||
func (c *cmdable) WrapProcess(createWrapper func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) {
|
|
||||||
c.process = createWrapper(c.process)
|
|
||||||
}
|
|
||||||
|
|
||||||
type statefulCmdable struct {
|
type statefulCmdable struct {
|
||||||
process func(cmd Cmder) error
|
process func(cmd Cmder) error
|
||||||
}
|
}
|
||||||
|
@ -1625,15 +1616,15 @@ func (c *cmdable) ClientPause(dur time.Duration) *BoolCmd {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientSetName assigns a name to the one of many connections in the pool.
|
// ClientSetName assigns a name to the connection.
|
||||||
func (c *cmdable) ClientSetName(name string) *BoolCmd {
|
func (c *statefulCmdable) ClientSetName(name string) *BoolCmd {
|
||||||
cmd := NewBoolCmd("client", "setname", name)
|
cmd := NewBoolCmd("client", "setname", name)
|
||||||
c.process(cmd)
|
c.process(cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientGetName returns the name of the one of many connections in the pool.
|
// ClientGetName returns the name of the connection.
|
||||||
func (c *Client) ClientGetName() *StringCmd {
|
func (c *statefulCmdable) ClientGetName() *StringCmd {
|
||||||
cmd := NewStringCmd("client", "getname")
|
cmd := NewStringCmd("client", "getname")
|
||||||
c.process(cmd)
|
c.process(cmd)
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -26,14 +26,20 @@ var _ = Describe("Commands", func() {
|
||||||
|
|
||||||
Describe("server", func() {
|
Describe("server", func() {
|
||||||
|
|
||||||
// It("should Auth", func() {
|
It("should Auth", func() {
|
||||||
// auth := client.Auth("password")
|
_, err := client.Pipelined(func(pipe *redis.Pipeline) error {
|
||||||
// Expect(auth.Err()).To(MatchError("ERR Client sent AUTH, but no password is set"))
|
pipe.Auth("password")
|
||||||
// Expect(auth.Val()).To(Equal(""))
|
return nil
|
||||||
// })
|
})
|
||||||
|
Expect(err).To(MatchError("ERR Client sent AUTH, but no password is set"))
|
||||||
|
})
|
||||||
|
|
||||||
It("should Echo", func() {
|
It("should Echo", func() {
|
||||||
echo := client.Echo("hello")
|
pipe := client.Pipeline()
|
||||||
|
echo := pipe.Echo("hello")
|
||||||
|
_, err := pipe.Exec()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
Expect(echo.Err()).NotTo(HaveOccurred())
|
Expect(echo.Err()).NotTo(HaveOccurred())
|
||||||
Expect(echo.Val()).To(Equal("hello"))
|
Expect(echo.Val()).To(Equal("hello"))
|
||||||
})
|
})
|
||||||
|
@ -44,11 +50,15 @@ var _ = Describe("Commands", func() {
|
||||||
Expect(ping.Val()).To(Equal("PONG"))
|
Expect(ping.Val()).To(Equal("PONG"))
|
||||||
})
|
})
|
||||||
|
|
||||||
// It("should Select", func() {
|
It("should Select", func() {
|
||||||
// sel := client.Select(1)
|
pipe := client.Pipeline()
|
||||||
// Expect(sel.Err()).NotTo(HaveOccurred())
|
sel := pipe.Select(1)
|
||||||
// Expect(sel.Val()).To(Equal("OK"))
|
_, err := pipe.Exec()
|
||||||
// })
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(sel.Err()).NotTo(HaveOccurred())
|
||||||
|
Expect(sel.Val()).To(Equal("OK"))
|
||||||
|
})
|
||||||
|
|
||||||
It("should BgRewriteAOF", func() {
|
It("should BgRewriteAOF", func() {
|
||||||
Skip("flaky test")
|
Skip("flaky test")
|
||||||
|
@ -84,13 +94,18 @@ var _ = Describe("Commands", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should ClientSetName and ClientGetName", func() {
|
It("should ClientSetName and ClientGetName", func() {
|
||||||
isSet, err := client.ClientSetName("theclientname").Result()
|
pipe := client.Pipeline()
|
||||||
|
set := pipe.ClientSetName("theclientname")
|
||||||
|
get := pipe.ClientGetName()
|
||||||
|
_, err := pipe.Exec()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(isSet).To(BeTrue())
|
|
||||||
|
|
||||||
val, err := client.ClientGetName().Result()
|
Expect(set.Err()).NotTo(HaveOccurred())
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(set.Val()).To(BeTrue())
|
||||||
Expect(val).To(Equal("theclientname"))
|
|
||||||
|
Expect(get.Err()).NotTo(HaveOccurred())
|
||||||
|
Expect(get.Val()).To(Equal("theclientname"))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should ConfigGet", func() {
|
It("should ConfigGet", func() {
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package redis_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
redis "gopkg.in/redis.v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_instrumentation() {
|
||||||
|
ring := redis.NewRing(&redis.RingOptions{
|
||||||
|
Addrs: map[string]string{
|
||||||
|
"shard1": ":6379",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ring.ForEachShard(func(client *redis.Client) error {
|
||||||
|
wrapRedisProcess(client)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for {
|
||||||
|
ring.Ping()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapRedisProcess(client *redis.Client) {
|
||||||
|
const precision = time.Microsecond
|
||||||
|
var count, avgDur uint32
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for _ = range time.Tick(3 * time.Second) {
|
||||||
|
n := atomic.LoadUint32(&count)
|
||||||
|
dur := time.Duration(atomic.LoadUint32(&avgDur)) * precision
|
||||||
|
fmt.Printf("%s: processed=%d avg_dur=%s\n", client, n, dur)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
client.WrapProcess(func(oldProcess func(redis.Cmder) error) func(redis.Cmder) error {
|
||||||
|
return func(cmd redis.Cmder) error {
|
||||||
|
start := time.Now()
|
||||||
|
err := oldProcess(cmd)
|
||||||
|
dur := time.Since(start)
|
||||||
|
|
||||||
|
const decay = float64(1) / 100
|
||||||
|
ms := float64(dur / precision)
|
||||||
|
for {
|
||||||
|
avg := atomic.LoadUint32(&avgDur)
|
||||||
|
newAvg := uint32((1-decay)*float64(avg) + decay*ms)
|
||||||
|
if atomic.CompareAndSwapUint32(&avgDur, avg, newAvg) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atomic.AddUint32(&count, 1)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -331,21 +331,3 @@ func ExampleScanCmd_Iterator() {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleClient_instrumentation() {
|
|
||||||
client := redis.NewClient(&redis.Options{
|
|
||||||
Addr: ":6379",
|
|
||||||
})
|
|
||||||
client.WrapProcess(func(oldProcess func(cmd redis.Cmder) error) func(cmd redis.Cmder) error {
|
|
||||||
return func(cmd redis.Cmder) error {
|
|
||||||
start := time.Now()
|
|
||||||
err := oldProcess(cmd)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("command %s failed: %s", cmd, err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("command %q took %s", cmd, time.Since(start))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -152,7 +152,7 @@ var _ = Describe("Pipelining", func() {
|
||||||
const N = 1000
|
const N = 1000
|
||||||
|
|
||||||
pipeline := client.Pipeline()
|
pipeline := client.Pipeline()
|
||||||
wg := &sync.WaitGroup{}
|
var wg sync.WaitGroup
|
||||||
wg.Add(N)
|
wg.Add(N)
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
|
|
16
redis.go
16
redis.go
|
@ -19,6 +19,7 @@ type baseClient struct {
|
||||||
connPool pool.Pooler
|
connPool pool.Pooler
|
||||||
opt *Options
|
opt *Options
|
||||||
|
|
||||||
|
process func(Cmder) error
|
||||||
onClose func() error // hook called when client is closed
|
onClose func() error // hook called when client is closed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +79,21 @@ func (c *baseClient) initConn(cn *pool.Conn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *baseClient) Process(cmd Cmder) error {
|
func (c *baseClient) Process(cmd Cmder) error {
|
||||||
|
if c.process != nil {
|
||||||
|
return c.process(cmd)
|
||||||
|
}
|
||||||
|
return c.defaultProcess(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapProcess replaces the process func. It takes a function createWrapper
|
||||||
|
// which is supplied by the user. createWrapper takes the old process func as
|
||||||
|
// an input and returns the new wrapper process func. createWrapper should
|
||||||
|
// use call the old process func within the new process func.
|
||||||
|
func (c *baseClient) WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) {
|
||||||
|
c.process = fn(c.defaultProcess)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) defaultProcess(cmd Cmder) error {
|
||||||
for i := 0; i <= c.opt.MaxRetries; i++ {
|
for i := 0; i <= c.opt.MaxRetries; i++ {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
cmd.reset()
|
cmd.reset()
|
||||||
|
|
26
ring.go
26
ring.go
|
@ -230,7 +230,7 @@ func (c *Ring) addClient(name string, cl *Client) {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ring) shardByKey(key string) (*Client, error) {
|
func (c *Ring) shardByKey(key string) (*ringShard, error) {
|
||||||
key = hashtag.Key(key)
|
key = hashtag.Key(key)
|
||||||
|
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
|
@ -246,27 +246,27 @@ func (c *Ring) shardByKey(key string) (*Client, error) {
|
||||||
return nil, errRingShardsDown
|
return nil, errRingShardsDown
|
||||||
}
|
}
|
||||||
|
|
||||||
cl := c.shards[name].Client
|
shard := c.shards[name]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
return cl, nil
|
return shard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ring) randomShard() (*Client, error) {
|
func (c *Ring) randomShard() (*ringShard, error) {
|
||||||
return c.shardByKey(strconv.Itoa(rand.Int()))
|
return c.shardByKey(strconv.Itoa(rand.Int()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ring) shardByName(name string) (*Client, error) {
|
func (c *Ring) shardByName(name string) (*ringShard, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return c.randomShard()
|
return c.randomShard()
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
cl := c.shards[name].Client
|
shard := c.shards[name]
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
return cl, nil
|
return shard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ring) cmdShard(cmd Cmder) (*Client, error) {
|
func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
|
||||||
cmdInfo := c.cmdInfo(cmd.arg(0))
|
cmdInfo := c.cmdInfo(cmd.arg(0))
|
||||||
firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo))
|
firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo))
|
||||||
if firstKey == "" {
|
if firstKey == "" {
|
||||||
|
@ -276,12 +276,12 @@ func (c *Ring) cmdShard(cmd Cmder) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ring) Process(cmd Cmder) error {
|
func (c *Ring) Process(cmd Cmder) error {
|
||||||
cl, err := c.cmdShard(cmd)
|
shard, err := c.cmdShard(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.setErr(err)
|
cmd.setErr(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return cl.baseClient.Process(cmd)
|
return shard.Client.Process(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rebalance removes dead shards from the Ring.
|
// rebalance removes dead shards from the Ring.
|
||||||
|
@ -384,7 +384,7 @@ func (c *Ring) pipelineExec(cmds []Cmder) (firstErr error) {
|
||||||
resetCmds(cmds)
|
resetCmds(cmds)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := c.shardByName(name)
|
shard, err := c.shardByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setCmdsErr(cmds, err)
|
setCmdsErr(cmds, err)
|
||||||
if firstErr == nil {
|
if firstErr == nil {
|
||||||
|
@ -393,7 +393,7 @@ func (c *Ring) pipelineExec(cmds []Cmder) (firstErr error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cn, _, err := client.conn()
|
cn, _, err := shard.Client.conn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setCmdsErr(cmds, err)
|
setCmdsErr(cmds, err)
|
||||||
if firstErr == nil {
|
if firstErr == nil {
|
||||||
|
@ -403,7 +403,7 @@ func (c *Ring) pipelineExec(cmds []Cmder) (firstErr error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
retry, err := execCmds(cn, cmds)
|
retry, err := execCmds(cn, cmds)
|
||||||
client.putConn(cn, err, false)
|
shard.Client.putConn(cn, err, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue