Go-Redis 中的命令模式

2,802 阅读7分钟

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

命令模式

上一篇文章已经介绍了命令模式,其实 Go 中的 Redis 也是用了命令模式的。

image.png

命令模式中有几个参与者:

  • Client 客户端
  • Invoker 调用者
  • Receiver 接收者
  • Command 命令接口
  • ConcreteCommand 具体命令接口实现

要实现命令模式,需要再 Command 中设置 receiver ,在 Invoker 中设置 Command。

先看下 receiver 和 invoker 接口

invoker 接口

commands.go

type Cmdable interface {
   Pipeline() Pipeliner
   Pipelined(fn func(Pipeliner) error) ([]Cmder, error)

   ClientGetName() *StringCmd
   Echo(message interface{}) *StringCmd
   Ping() *StatusCmd
   Quit() *StatusCmd
   Del(keys ...string) *IntCmd
   Unlink(keys ...string) *IntCmd
   Dump(key string) *StringCmd
   Exists(keys ...string) *IntCmd
   Expire(key string, expiration time.Duration) *BoolCmd
   HSetExpire(key string, expiration time.Duration) *BoolCmd
   LSetExpire(key string, expiration time.Duration) *BoolCmd
   SSetExpire(key string, expiration time.Duration) *BoolCmd
   ZSetExpire(key string, expiration time.Duration) *BoolCmd
   ExpireAt(key string, tm time.Time) *BoolCmd
   Keys(pattern string) *StringSliceCmd
   Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd
   Move(key string, db int64) *BoolCmd
   ObjectRefCount(key string) *IntCmd
   ObjectEncoding(key string) *StringCmd
   ObjectIdleTime(key string) *DurationCmd
   Persist(key string) *BoolCmd
   PExpire(key string, expiration time.Duration) *BoolCmd
   PExpireAt(key string, tm time.Time) *BoolCmd
   PTTL(key string) *DurationCmd
   RandomKey() *StringCmd
   Rename(key, newkey string) *StatusCmd
   RenameNX(key, newkey string) *BoolCmd
   Restore(key string, ttl time.Duration, value string) *StatusCmd
   RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd
   Sort(key string, sort Sort) *StringSliceCmd
   SortInterfaces(key string, sort Sort) *SliceCmd
   TTL(key string) *DurationCmd
   HTTL(key string) *DurationCmd
   ZTTL(key string) *DurationCmd
   LTTL(key string) *DurationCmd
   STTL(key string) *DurationCmd
   Type(key string) *StatusCmd
   GetShardCount() *IntCmd
   IScan(shard uint64, cursor string, match string, count int64) *IScanCmd
   Scan(cursor uint64, match string, count int64) *ScanCmd
   SScan(key string, cursor uint64, match string, count int64) *ScanCmd
   HScan(key string, cursor uint64, match string, count int64) *ScanCmd
   ZScan(key string, cursor uint64, match string, count int64) *ScanCmd
   Append(key, value string) *IntCmd
   BitCount(key string, bitCount *BitCount) *IntCmd
   BitOpAnd(destKey string, keys ...string) *IntCmd
   BitOpOr(destKey string, keys ...string) *IntCmd
   BitOpXor(destKey string, keys ...string) *IntCmd
   BitOpNot(destKey string, key string) *IntCmd
   BitPos(key string, bit int64, pos ...int64) *IntCmd
   Decr(key string) *IntCmd
   DecrBy(key string, decrement int64) *IntCmd
   XDecrBy(key string, decrement int64) *IntCmd
   Get(key string) *StringCmd
   CGet(key string) *IntCmd
   DpsAddKey(key string) *StatusCmd
   GetBit(key string, offset int64) *IntCmd
   GetRange(key string, start, end int64) *StringCmd
   GetSet(key string, value interface{}) *StringCmd
   Incr(key string) *IntCmd
   CIncr(key string) *IntCmd
   IncrBy(key string, value int64) *IntCmd
   CIncrBy(key string, value int64) *IntCmd
   IncrByFloat(key string, value float64) *FloatCmd
   MGet(keys ...string) *SliceCmd
   CMGet(keys ...string) *SliceCmd
   MSet(pairs ...interface{}) *StatusCmd
   MSetNX(pairs ...interface{}) *BoolCmd
   Set(key string, value interface{}, expiration time.Duration) *StatusCmd
   CSet(key string, value int64, expiration time.Duration) *StatusCmd
   CasEx(key string, oldvalue interface{}, newvalue interface{}, expiration time.Duration) *IntCmd
   Cas(key string, oldvalue interface{}, newvalue interface{}) *IntCmd
   Cad(key string, value interface{}) *IntCmd
   SetBit(key string, offset int64, value int) *IntCmd
   SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd
   SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd
   SetRange(key string, offset int64, value string) *IntCmd
   StrLen(key string) *IntCmd
   HDel(key string, fields ...string) *IntCmd
   HExists(key, field string) *BoolCmd
   HGet(key, field string) *StringCmd
   HGetAll(key string) *StringStringMapCmd
   HIncrBy(key, field string, incr int64) *IntCmd
   HXDecrBy(key, field string, decrement int64) *IntCmd
   HIncrByFloat(key, field string, incr float64) *FloatCmd
   HKeys(key string) *StringSliceCmd
   HLen(key string) *IntCmd
   HMGet(key string, fields ...string) *SliceCmd
   HMSet(key string, fields map[string]interface{}) *StatusCmd
   HSet(key, field string, value interface{}) *BoolCmd
   HSetNX(key, field string, value interface{}) *BoolCmd
   HVals(key string) *StringSliceCmd
   HGetSet(key, field string, value interface{}) *StringCmd
   BLPop(timeout time.Duration, keys ...string) *StringSliceCmd
   BRPop(timeout time.Duration, keys ...string) *StringSliceCmd
   BRPopLPush(source, destination string, timeout time.Duration) *StringCmd
   LIndex(key string, index int64) *StringCmd
   LInsert(key, op string, pivot, value interface{}) *IntCmd
   LInsertBefore(key string, pivot, value interface{}) *IntCmd
   LInsertAfter(key string, pivot, value interface{}) *IntCmd
   LLen(key string) *IntCmd
   LPop(key string) *StringCmd
   LPush(key string, values ...interface{}) *IntCmd
   LPushX(key string, value interface{}) *IntCmd
   LRange(key string, start, stop int64) *StringSliceCmd
   LRem(key string, count int64, value interface{}) *IntCmd
   LSet(key string, index int64, value interface{}) *StatusCmd
   LTrim(key string, start, stop int64) *StatusCmd
   RPop(key string) *StringCmd
   RPopLPush(source, destination string) *StringCmd
   RPush(key string, values ...interface{}) *IntCmd
   RPushX(key string, value interface{}) *IntCmd
   SAdd(key string, members ...interface{}) *IntCmd
   SCard(key string) *IntCmd
   SDiff(keys ...string) *StringSliceCmd
   SDiffStore(destination string, keys ...string) *IntCmd
   SInter(keys ...string) *StringSliceCmd
   SInterStore(destination string, keys ...string) *IntCmd
   SIsMember(key string, member interface{}) *BoolCmd
   SMembers(key string) *StringSliceCmd
   SMove(source, destination string, member interface{}) *BoolCmd
   SPop(key string) *StringCmd
   SPopN(key string, count int64) *StringSliceCmd
   SRandMember(key string) *StringCmd
   SRandMemberN(key string, count int64) *StringSliceCmd
   SRem(key string, members ...interface{}) *IntCmd
   SUnion(keys ...string) *StringSliceCmd
   SUnionStore(destination string, keys ...string) *IntCmd
   XGet(key string) *XGetCmd
   XSet(key string, value interface{}, generation int64, expiration time.Duration) *XSetCmd
   ScanRow(key string, limit int, offset int, target string) *ScanRowCmd
   TtlQPush(key string, ttl time.Duration, item []byte) *IntCmd
   TtlQPopTo(key string, cursor int64) *StatusCmd
   TtlQDelete(key string) *StatusCmd
   TtlQLen(key string) *IntCmd
   TtlQScan(key string, startCursor, endCursor, limit int64) *TtlQScanCmd
   TtlQGet(key string, cursor int64) *StringCmd
   TtlQGetLatestCursor(key string) *IntCmd
   HDrop(keys string) *IntCmd
   ZDrop(keys string) *IntCmd
   LDrop(keys string) *IntCmd
   SDrop(keys string) *IntCmd
   ZAdd(key string, members ...Z) *IntCmd
   ZAddNX(key string, members ...Z) *IntCmd
   ZAddXX(key string, members ...Z) *IntCmd
   ZAddCh(key string, members ...Z) *IntCmd
   ZAddNXCh(key string, members ...Z) *IntCmd
   ZAddXXCh(key string, members ...Z) *IntCmd
   ZIncr(key string, member Z) *FloatCmd
   ZIncrNX(key string, member Z) *FloatCmd
   ZIncrXX(key string, member Z) *FloatCmd
   ZAddWithLimit(key string, limit int64, members ...Z) *IntCmd
   ZIncrWithLimit(key string, limit int64, member Z) *FloatCmd
   ZCard(key string) *IntCmd
   ZCount(key, min, max string) *IntCmd
   ZLexCount(key, min, max string) *IntCmd
   ZIncrBy(key string, increment float64, member string) *FloatCmd
   ZInterStore(destination string, store ZStore, keys ...string) *IntCmd
   ZRange(key string, start, stop int64) *StringSliceCmd
   ZRangeWithScores(key string, start, stop int64) *ZSliceCmd
   ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd
   ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd
   ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd
   ZRank(key, member string) *IntCmd
   ZRem(key string, members ...interface{}) *IntCmd
   ZRemRangeByRank(key string, start, stop int64) *IntCmd
   ZRemRangeByScore(key, min, max string) *IntCmd
   ZRemRangeByLex(key, min, max string) *IntCmd
   ZRevRange(key string, start, stop int64) *StringSliceCmd
   ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd
   ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd
   ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd
   ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd
   ZRevRank(key, member string) *IntCmd
   ZScore(key, member string) *FloatCmd
   ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd
   PFAdd(key string, els ...interface{}) *IntCmd
   PFCount(keys ...string) *IntCmd
   PFMerge(dest string, keys ...string) *StatusCmd
   BgRewriteAOF() *StatusCmd
   BgSave() *StatusCmd
   AlchemyBgSave() *SliceCmd
   ClientKill(ipPort string) *StatusCmd
   ClientList() *StringCmd
   ClientPause(dur time.Duration) *BoolCmd
   ConfigGet(parameter string) *SliceCmd
   ConfigResetStat() *StatusCmd
   ConfigSet(parameter, value string) *StatusCmd
   DBSize() *IntCmd
   FlushAll() *StatusCmd
   FlushAllAsync() *StatusCmd
   FlushDB() *StatusCmd
   FlushDBAsync() *StatusCmd
   Info(section ...string) *StringCmd
   LastSave() *IntCmd
   Save() *StatusCmd
   Shutdown() *StatusCmd
   ShutdownSave() *StatusCmd
   ShutdownNoSave() *StatusCmd
   SlaveOf(host, port string) *StatusCmd
   Time() *TimeCmd
   Eval(script string, keys []string, args ...interface{}) *Cmd
   EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd
   ScriptExists(scripts ...string) *BoolSliceCmd
   ScriptFlush() *StatusCmd
   ScriptKill() *StatusCmd
   ScriptLoad(script string) *StringCmd
   DebugObject(key string) *StringCmd
   PubSubChannels(pattern string) *StringSliceCmd
   PubSubNumSub(channels ...string) *StringIntMapCmd
   PubSubNumPat() *IntCmd
   ClusterSlots() *ClusterSlotsCmd
   ClusterNodes() *StringCmd
   ClusterMeet(host, port string) *StatusCmd
   ClusterForget(nodeID string) *StatusCmd
   ClusterReplicate(nodeID string) *StatusCmd
   ClusterResetSoft() *StatusCmd
   ClusterResetHard() *StatusCmd
   ClusterInfo() *StringCmd
   ClusterKeySlot(key string) *IntCmd
   ClusterCountFailureReports(nodeID string) *IntCmd
   ClusterCountKeysInSlot(slot int) *IntCmd
   ClusterDelSlots(slots ...int) *StatusCmd
   ClusterDelSlotsRange(min, max int) *StatusCmd
   ClusterSaveConfig() *StatusCmd
   ClusterSlaves(nodeID string) *StringSliceCmd
   ClusterFailover() *StatusCmd
   ClusterAddSlots(slots ...int) *StatusCmd
   ClusterAddSlotsRange(min, max int) *StatusCmd
   GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd
   GeoPos(key string, members ...string) *GeoPosCmd
   GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd
   GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd
   GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd
   GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd
   GeoDist(key string, member1, member2, unit string) *FloatCmd
   GeoHash(key string, members ...string) *StringSliceCmd
   Command() *CommandsInfoCmd
}

Invoker 接口的具体实现

可以看到 cmdable 是实现了 Cmdable 接口的


type cmdable struct {
   process func(cmd Cmder) error
}

func (c *cmdable) setProcessor(fn func(Cmder) error) {
   c.process = fn
}

func (c *cmdable) Del(keys ...string) *IntCmd {
   return c.del("del", keys...)
}

func (c *cmdable) HDrop(key string) *IntCmd {
   keys := []string{key}
   return c.del("hdrop", keys...)
}

func (c *cmdable) ZDrop(key string) *IntCmd {
   keys := []string{key}
   return c.del("zdrop", keys...)
}

command 接口

command.go 中

type Cmder interface {
   Args() []interface{}
   arg(int) string
   Name() string

   readReply(*pool.Conn) error
   SetErr(error)

   readTimeout() *time.Duration

   Err() error
   fmt.Stringer
}

Cmder接口的具体实现

command 接口的具体实现 Cmder 接口

先封装一个 baseCmd 来实现 cmd

type baseCmd struct {
   _args []interface{}
   err   error

   _readTimeout *time.Duration
}

func (cmd *baseCmd) Err() error {
   return cmd.err
}

func (cmd *baseCmd) Args() []interface{} {
   return cmd._args
}

func (cmd *baseCmd) arg(pos int) string {
   if pos < 0 || pos >= len(cmd._args) {
      return ""
   }
   s, _ := cmd._args[pos].(string)
   return s
}

func (cmd *baseCmd) Name() string {
   if len(cmd._args) > 0 {
      // Cmd name must be lower cased.
      s := pkg.ToLower(cmd.arg(0))
      cmd._args[0] = s
      return s
   }
   return ""
}

func (cmd *baseCmd) readTimeout() *time.Duration {
   return cmd._readTimeout
}

func (cmd *baseCmd) setReadTimeout(d time.Duration) {
   cmd._readTimeout = &d
}

func (cmd *baseCmd) SetErr(e error) {
   cmd.err = e
}

基于 baseCmd 接口实现具体的命令接口

type SliceCmd struct {
   baseCmd

   val []interface{}
}

func NewSliceCmd(args ...interface{}) *SliceCmd {
   return &SliceCmd{
      baseCmd: baseCmd{_args: args},
   }
}

func (cmd *SliceCmd) Val() []interface{} {
   return cmd.val
}

func (cmd *SliceCmd) SetVal(val []interface{}) {
   cmd.val = val
}

func (cmd *SliceCmd) Result() ([]interface{}, error) {
   return cmd.val, cmd.err
}

func (cmd *SliceCmd) String() string {
   return cmdString(cmd, cmd.val)
}

func (cmd *SliceCmd) readReply(cn *pool.Conn) error {
   var v interface{}
   v, cmd.err = cn.Rd.ReadArrayReply(sliceParser)
   if cmd.err != nil {
      cmd.err = addRemoteAddrToErr(cn, cmd.err)
      return cmd.err
   }
   cmd.val = v.([]interface{})
   return nil
}

Invoker 中设置 command

在 Invoker 的 具体实现中,可以看到是用 Cmd 接口的实现设置

type cmdable struct {
   process func(cmd Cmder) error
}

func (c *cmdable) setProcessor(fn func(Cmder) error) {
   c.process = fn
}
func (c *cmdable) Del(keys ...string) *IntCmd {
   return c.del("del", keys...)
}

func (c *cmdable) HDrop(key string) *IntCmd {
   keys := []string{key}
   return c.del("hdrop", keys...)
}

func (c *cmdable) ZDrop(key string) *IntCmd {
   keys := []string{key}
   return c.del("zdrop", keys...)
}

func (c *cmdable) RandomKey() *StringCmd {
   cmd := NewStringCmd("randomkey")
   c.process(cmd)
   return cmd
}

比如上述代码中的 RandomKey 就是通过设置 command ( NewStringCmd) 来实现,Invoker 中设置 cmd .

commmand 没有设置 receiver?

command 其实并没有设置 receiver,那么 receiver 是怎么接受到命令的呢?

type cmdable struct {
   process func(cmd Cmder) error
}

func (c *cmdable) setProcessor(fn func(Cmder) error) {
   c.process = fn
}

关键在这 process 是一个函数,可以设置 setProcessor, 具体看看是哪设置的

redis.go

type Client struct {
   baseClient
   cmdable
}

func newClient(opt *Options, pool pool.Pooler) *Client {
   client := Client{
      baseClient: baseClient{
         opt:      opt,
         connPool: pool,
      },
   }
   client.setProcessor(client.Process)
   return &client
}

然后 会发现设置了一个 client.Process, 看下这个方法是啥

func (c *baseClient) Process(cmd Cmder) error {
   if c.process != nil {
      return c.process(cmd)
   }
   return c.defaultProcess(cmd)
}

然后看到 defaultProcess 上获得链接,然后将命令发送 writeCmd 发送到集群去执行。

func (c *baseClient) defaultProcess(cmd Cmder) error {
   for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
      if attempt > 0 {
         if c.connPool.RetryDG() {
            break
         }
         time.Sleep(c.retryBackoff(attempt))
      }

      cn, _, err := c.getConn()
      if err != nil {
         cmd.SetErr(err)
         if pkg.IsRetryableError(err) {
            continue
         }
         return err
      }

      cn.SetWriteTimeout(c.opt.WriteTimeout)
      if err := writeCmd(cn, cmd); err != nil {
         c.releaseConn(cn, err)
         cmd.SetErr(err)
         if pkg.IsRetryableError(err) {
            continue
         }
         return err
      }

      cn.SetReadTimeout(c.cmdTimeout(cmd))
      err = cmd.readReply(cn)
      c.releaseConn(cn, err)
      if err != nil && pkg.IsRetryableError(err) {
         continue
      }
      cmd.SetErr(err)
      return err
   }

   return cmd.Err()
}

最后通过 cmd.readReply(cn) 拿到命令执行的返回结果