在这小节,使用RabitMQ构建RPC:一个客户端和一个可扩展的RPC 服务器。
回调队列(callback queue)
通常情况下在RabitMQ上实现RPC是很容易的。客户端发送请求消息,服务器给出响应消息。为了接收响应,需要发送有回调队列地址的请求。
以下demo使用的默认的队列:
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when usused
true, // exclusive
false, // noWait
nil, // arguments
)
err = ch.Publish(
"", // exchange
"rpc_queue", // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
CorrelationId: corrId,
ReplyTo: q.Name,
Body: []byte(strconv.Itoa(n)),
})
消息属性(message properties)
AMQP 0-9-1 协议为每个消息预定义了14个属性,但是除了以下这几个,其他的几乎没有被使用过。
persistent:标记该消息为持久化的;
content_type:用来描述编码的mime类型,例如经常使用json格式:application/json;
reply_to:通常用来命名一个回调队列;
correlation_id : 有助于将RPC的请求与响应联系起来。
correlation id
在上面呈现的方法中,我们建议为每个RPC请求创建回调队列。这相当低效,但是这里有个更好的办法:为每个客户端创建一个单独的回调队列。
但是这又出现了一个新的问题:在队列里接收到响应消息后,不清楚该响应应该属于哪个请求。
当设置correlation_id属性以后,为每个请求设置唯一的correlation_id,然后当在回调队列里接收到消息后就查看该correlation_id属性,基于此,就能将响应与请求匹配起来。
如果看到未知的correlation_id的值,我们就可以安全的丢弃该消息---不属于我们的请求。
也许你会问:在回调队列里为什么要忽略掉未知的消息,而不是仅仅以error报错。那是由于服务器端可能出现资源竞争。虽然不太可能,但是RPC服务器会在发送响应后并且发送请求的确认消息到达之前会死掉。如果这个发生,那么重启RPC服务器后将再次处理该请求。那就是为什么在客户端必须优雅的处理重复响应而且RPC应该是幂等的。
幂等:RPC 系统通常会提供至少一次或最多一次的语义,或者在两者之间选择。如果需要了解应用程序的性质和远程过程的功能是否安全,可以通过多次调用同一个函数来验证。如果一个函数可以运行任何次数而不影响结果,这是幂等(idempotent)函数的,如每天的时间、数学函数、读取静态数据等。否则,它是一个非幂等(nonidempotent)函数,如添加或修改一个文件)。
rpc工作原理:
1.当客户端启动,会创建一个异步专用的回调队列。对于一个RPC请求, 客户端会发送一个有reply_to属性(设置回调队列)和correlation_id属性(为该请求设置唯一值)的message。
2.该请求会被发送到rpc_queue。
3.服务器等待接收来自rpc_queue的请求。当有请求到达的时候,服务器接收请求并且使用reply_to指定的回调队列响应结果给客户端。
4.客户端等待来自回调队列的数据。当响应消息到达的时候,客户端会校验correlation_id属性,如果correlation_id的值与发送请求时候的值匹配,就会返回对应程序的响应。
通常,通过建立连接、通道和声明队列开始。如果想运行多个服务器端进程,为了实现多个服务器间的负载均衡,需要在通道设置prefetch属性。
使用Channel.Consume得到从队列接收消息的go channel。