这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
命令模式
上一篇文章已经介绍了命令模式,其实 Go 中的 Redis 也是用了命令模式的。
命令模式中有几个参与者:
- 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) 拿到命令执行的返回结果