使用RabbitMQ进行RPC的必要
常见的RPC方法/协议包括CORBA,Java RMI,Web Service,Hessian,Thrift及Rest API,相对于前面提到的RPC方式,使用RabbbitMQ(JMS也是一样)方式需要在client-service provider中间增加MQ组件,这样做增加了部署的复杂性,但同时带来额外的好处。
- 可以对service provider进行保护,MQ对请求进行缓冲,处理不了的请求可以被MQ抛弃而不会压垮service provider。
- 可以隔离低安全区对高安全区的访问,此优点是其他rpc没有的。
一个典型的互联网访问方式如下(FW表示防火墙)
client—-FW1–>frontend server(DMZ)—–FW2—->service provider(高安全区)
通过MQ进行RPC则变成
client—-FW1–>frontend server(DMZ)—>MQ<–FW2—-service provider(高安全区)
这里一个重要的区别是service provider(高安全区)是主动访问位于DMZ区域的MQ,由此可以设定FW2只允许高安全区访问DMZ,而禁止DMZ访问高安全区。此方式对于安全性要求高的如银行,金融,政府系统尤为重要。
实现spring remoting实现使用MQ的RPC
原理
一个RPC交互大致分为以下几个步骤:
1. 服务端监听MQ队列
2. 客户端将调用请求(调用请求包括结果返回队列)发送到队列中。
3. 客户端在结果返回队列监听。
4. 服务端处理业务,将结果发送到结果返回队列。
公共内容
定义RabbitMQ的连接,定义service接口,进行通讯的queue。
public interface Service { String sayHello(String name);}
服务端实现
配置文件内容
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <rabbit:connection-factory id="rabbitConnectionFactory" /> <rabbit:admin connection-factory="rabbitConnectionFactory" /> <rabbit:direct-exchange name="exchange"> <rabbit:bindings> <rabbit:binding queue="queue.appgw" key="queue.appgw"> </rabbit:binding> </rabbit:bindings> </rabbit:direct-exchange> <rabbit:queue name="queue.appgw"> </rabbit:queue> <rabbit:template id="amqpTemplateInternetProxy" connection-factory="rabbitConnectionFactory"> </rabbit:template> <rabbit:listener-container acknowledge="none" max-concurrency="128" prefetch="10"> <rabbit:listener ref="service" queue-names="queue.appgw" /> </rabbit:listener-container> <bean id="serviceImpl" class="net.nxmax.atp.exporter.ServiceImpl"></bean> <bean id="service" class="org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter"> <property name="serviceInterface" value="net.nxmax.atp.exporter.Service" /> <property name="service" ref="serviceImpl" /> <property name="amqpTemplate" ref="amqpTemplateInternetProxy" /> </bean></beans>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
发布服务代码:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");BufferedReader r = new BufferedReader(new InputStreamReader(System.in));while (!r.readLine().equalsIgnoreCase("exit")) {}ctx.destroy();
客户端实现
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <rabbit:connection-factory id="rabbitConnectionFactory"/> <rabbit:template id="amqpTemplate" connection-factory="rabbitConnectionFactory" queue="queue.appgw" exchange="exchange" reply-timeout="60000" /> <bean class="org.springframework.amqp.remoting.client.AmqpProxyFactoryBean" p:serviceInterface="net.nxmax.atp.exporter.Service" p:routingKey="queue.appgw"> <property name="amqpTemplate" ref="amqpTemplate" /> </bean></beans>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
客户端调用代码:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("ctx-client.xml")Service service = ctx.getBean(Service.class)service.sayHello("name")ctx.destroy()
优化
问题
使用默认的spring实现存在以下的限制:
- 消息使用持久化,对于RPC来说消息是不需要持久化的。
- 消息没有过期时间,意味着后端可能需要处理很久以前的请求,对于RPC来说很久以前的请求应该要抛弃。
- 默认使用临时队列作为结果返回队列,意味着每次调用都需要创建队列,性能极差。
- 可以配置使用固定队列返回结果,但是如果多个节点使用一个配置,同时监听固定队列,可能造成节点收不到结果。
- 使用默认的java作为序列化实现,性能不如Kryo。
方案
针对默认实现存在的问题,可以使用以下优化方案:
- 每一个客户端使用独立规定的结果返回队列,避免创建临时队列。
- 设置消息为非持久化。
- 设置消息的超时时间,使用Kryo作为序列化库。
实现
需要做的关键修改有以下3个:
创建自定义的MessageConverter,将消息设置为非持久化,设定过期时间,以及使用kryo序列化。
public class MessageConverterWithExpire extends SimpleMessageConverter { /** Logger */ protected static final Logger log = LoggerFactory .getLogger(MessageConverterWithExpire.class); private ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() { protected Kryo initialValue() { Kryo kryo = new Kryo(); kryo.register(RemoteInvocation.class); Registration reg = kryo.register(RemoteInvocationResult.class); reg.setInstantiator(new ObjectInstantiator() { @Override public Object newInstance() { return new RemoteInvocationResult(null); } }); return kryo; }; }; @Override protected Message createMessage(Object object, MessageProperties messageProperties) throws MessageConversionException { messageProperties.setExpiration("45000"); messageProperties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); Kryo k = kryos.get(); Output output = new Output(4096, 4194304); k.writeClassAndObject(output, object); return new Message(output.toBytes(), messageProperties); } @Override public Object fromMessage(Message message) throws MessageConversionException { Kryo k = kryos.get(); return k.readClassAndObject(new Input(message.getBody())); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
使用自定义的listenerContainer,在监听前创建规定的结果返回队列。
public class DynamicReplyMessageListenerContainer extends SimpleMessageListenerContainer { @Override protected void doInitialize() throws Exception { super.doInitialize(); Object listener = getMessageListener(); if (listener instanceof RabbitTemplate) { RabbitTemplate template = (RabbitTemplate) listener; Queue queue = getRabbitAdmin().declareQueue(); template.setReplyQueue(queue); setQueues(queue); } }}
重写AmqpInvokerServiceExporter ,提供通过固定队列返回结果的支持。
public class AmqpInvokerServiceExporterCorrelate extends AmqpInvokerServiceExporter { @Override public void onMessage(Message message) { Address replyToAddress = message.getMessageProperties() .getReplyToAddress(); if (replyToAddress == null) { throw new AmqpRejectAndDontRequeueException( "No replyToAddress in inbound AMQP Message"); } Object invocationRaw = getMessageConverter().fromMessage(message); RemoteInvocationResult remoteInvocationResult; if (invocationRaw == null || !(invocationRaw instanceof RemoteInvocation)) { remoteInvocationResult = new RemoteInvocationResult( new IllegalArgumentException( "The message does not contain a RemoteInvocation payload")); } else { RemoteInvocation invocation = (RemoteInvocation) invocationRaw; remoteInvocationResult = invokeAndCreateResult(invocation, getService()); } send(remoteInvocationResult, replyToAddress, message); } private void send(Object object, Address replyToAddress, Message sourceMessage) { MessageProperties mp = new MessageProperties(); mp.setCorrelationId(sourceMessage.getMessageProperties() .getCorrelationId()); Message message = getMessageConverter().toMessage(object, mp); getAmqpTemplate().send(replyToAddress.getExchangeName(), replyToAddress.getRoutingKey(), message); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
测试结果
默认实现的tps在640左右。经过优化的TPS在4000左右。更多内容请参见测试代码。
测试代码下载
0 0