
Redis是一个非常灵活的工具,利用Redis我们可以很方便的完成一些数据的缓存和交换。这种数据交换不依赖语言,在各种语言下都有很简单方便的实现,并且redis-cli可以直接命令行操作,在脚本中也能很方便的实现数据交换。正是因为它具备这样的特性,就算是shell脚本,也可以很容易模拟实现RPC。
思路
实现RPC如果不考虑底层如何实现,不考虑信息如何在网络中传输,那么主要操作就是在客户端发起请求,服务端进行响应并且通知客户端完成了该操作返回数据。也就是说只要客户端和服务端都可以与Redis数据库建立连接,两端就可以通过它实现相互通讯。Redis支持订阅消息事件,支持事件广播,还有BRPOP这样的阻塞式数据弹出方式。所以我们只要在Server中订阅与Client约定好的Channel,就可以实现通知,再把完成操作之后希望返回的数据PUSH到队列中,在Client中利用BRPOP就可以实现超时或者取出Server返回的消息。
BRPOP key [key …] timeout
BRPOP 是列表的阻塞式(blocking)弹出原语。
它是 RPOP 命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被 BRPOP 命令阻塞,直到等待超时或发现可弹出元素为止。
具体实现
以Golang为例,利用了redigo,伪代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func mockRPC(channel, command string) error {
var data string
redisConn.Do("PUBLISH", channel, command)
reply, err := redis.Values(redisConn.Do("BRPOP", "foo", 5))
// BRPOP会在5秒内取出数据,否则返回错误提示
if err != nil {
if strings.LastIndexAny(err.Error(), "nil returned") != -1 {
//判断err返回的消息内容,redigo中BRPOP无内容返回会返回"redigo: nil returned"
//可以模拟为timeout
return errors.New("timeout")
} else {
return err
}
}
// 没有错误返回则说明有数据返回,将数据解析传回需要的地方
redis.Scan(reply, &data, &data)
// parse data, return if necessary
}
Server端进行相反的操作,即获取订阅事件触发指定的function,然后将数据PUSH到约定的List中即可,大概是这样子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18go func() {
psc := redis.PubSubConn{c}
psc.Subscribe("foo")
for {
switch v := psc.Receive().(type) {
case redis.Message:
fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
if string(v.Data) == "command" {
// do something
redisConn.Do("LPUSH", "someList", "some data")
}
case redis.Subscription:
fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
case error:
return v
}
}
}()
就这样就实现了~也算一种奇技淫巧吧哈哈