为什么要用rpc?
我们平时开发中方法调用都在一台机器内,一个堆栈内,同个线程里。假如我们要实现跨服务器远程调用,我们最先想到的是通过http、或者更底层的tcp协议等。在java里使用http调用我们可能会用到httpclient、restTemplate,请求前设置各种参数,完事调用post、get等一系列的方法。通常情况下可以达到我们想要的效果,但是不舒服、不严谨、不可靠。而rpc的作用就是在远程调用时就像调用本地方法一样方便,严格规定好参数类型,也可以实现错误重试等功能作保障。所以http和rpc不在一个讨论的层级,rpc可以通过不同的协议实现,正确的可以说feign是用http实现的rpc,借助rabbitmq也可以实现rpc调用。
一、客户端和服务端
在rabbitmq中有消费者和生产者,或者说消费者和生产者都作为rabbitmq服务的客户端。但通过rabbitmq实现rpc,生产者就相当于rpc调用中的客户端,消费者则为服务端。
二、spring-amqp实现
rpc服务端(mq消费者)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.remoting.service.AmqpInvokerServiceExporter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class ServerApplication {
@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter(){
ObjectMapper mapper=new ObjectMapper();
mapper.disableDefaultTyping().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
Jackson2JsonMessageConverter jackson2JsonMessageConverter=new Jackson2JsonMessageConverter(mapper);
return jackson2JsonMessageConverter;
}
@Bean
public AmqpInvokerServiceExporter amqpInvokerServiceExporter(RabbitTemplate rabbitTemplate,RemoteServiceImpl remoteService){
//conver
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter());
//真正的监听器
AmqpInvokerServiceExporter amqpInvokerServiceExporter=new AmqpInvokerServiceExporter();
//用于消息回应
amqpInvokerServiceExporter.setAmqpTemplate(rabbitTemplate);
//实际逻辑执行接口
amqpInvokerServiceExporter.setServiceInterface(RemoteService.class);
//实际逻辑执行接口实现
amqpInvokerServiceExporter.setService(remoteService);
//json序列化
amqpInvokerServiceExporter.setMessageConverter(jackson2JsonMessageConverter());
return amqpInvokerServiceExporter;
}
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(CachingConnectionFactory cachingConnectionFactory,AmqpInvokerServiceExporter amqpInvokerServiceExporter){
SimpleMessageListenerContainer simpleMessageListenerContainer=new SimpleMessageListenerContainer();
simpleMessageListenerContainer.setConnectionFactory(cachingConnectionFactory);
simpleMessageListenerContainer.setMessageListener(amqpInvokerServiceExporter);
simpleMessageListenerContainer.setQueueNames("rpc");
simpleMessageListenerContainer.setMessageConverter(jackson2JsonMessageConverter());
return simpleMessageListenerContainer;
}
@Bean
public Queue queue(){
return new Queue("rpc");
}
@Bean
public Exchange exchange(){
return new TopicExchange("rpc_exchange");
}
@Bean
public Binding binding(){
return BindingBuilder.bind(queue()).to(exchange()).with("rpc.#").and(null);
}
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
AmqpInvokerServiceExporter 和我们经常使用的 @RabbitListener 创建的customer类似,是对我们执行方法的一个包装代理类,帮我们做了客户端消息反序列化,将返回结果序列化并用resttemplate响应给客户端,而这里响应给客户端的方式默认不是通过临时队列或永久队列的方式,而是 Direct reply-to模式。
SimpleMessageListenerContainer 作为listener的容器,为我们的listener统一管理。
这里有一点要注意:
SimpleMessageListenerContainer 和 SimpleRabbitListenerContainerFactory :SimpleRabbitListenerContainerFactory 是对@RabbitListener 注解的listener的管理。
SimpleMessageListenerContainer 则是对其他方式创建的listener管理
具体执行接口:
public interface RemoteService {
String get(String param);
}
@Service
public class RemoteServiceImpl implements RemoteService {
@Override
public String get(String param) {
System.out.println(param);
return param+"return";
}
}
rpc客户端(mq生产者)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.UUID;
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
public static final String EXCHANGE="rpc_exchange";
public static final String ROUT_KEY="rpc.key";
public static final String QUEUE_NAME="rpc";
@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter(){
ObjectMapper mapper=new ObjectMapper();
mapper.disableDefaultTyping().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
Jackson2JsonMessageConverter jackson2JsonMessageConverter=new Jackson2JsonMessageConverter(mapper);
return jackson2JsonMessageConverter;
}
@Bean
public Queue queue(){
return new Queue(QUEUE_NAME);
}
@Bean
public Exchange exchange(){
return new TopicExchange(EXCHANGE);
}
@Bean
public RabbitTemplate rabbitTemplate(CachingConnectionFactory cachingConnectionFactory){
RabbitTemplate rabbitTemplate=new RabbitTemplate(cachingConnectionFactory){
};
rabbitTemplate.setExchange(EXCHANGE);
rabbitTemplate.setReplyTimeout(3000000);
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter());
rabbitTemplate.setUserCorrelationId(true);
rabbitTemplate.setRoutingKey(ROUT_KEY);
return rabbitTemplate;
}
}
具体执行接口的代理factoryBean配置
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.remoting.client.AmqpProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.RemoteProxyFailureException;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationResult;
import java.util.Arrays;
import java.util.UUID;
/**
* @description: TODO
* @author: cuishang
* @create: 2020/11/5 17:14
**/
@Configuration
public class AmqpConfig {
public static final String EXCHANGE="rpc_exchange";
@Bean
public AmqpProxyFactoryBean amqpProxyFactoryBean(RabbitTemplate rabbitTemplate){
//继承AmqpProxyFactoryBean 重写invoke方法 加自定义逻辑
AmqpProxyFactoryBean amqpProxyFactoryBean=new AmqpProxyFactoryBean(){
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
RemoteInvocation remoteInvocation = getRemoteInvocationFactory().createRemoteInvocation(invocation);
Object rawResult;
if (getRoutingKey() == null) {
// Use the template's default routing key
rawResult = this.getAmqpTemplate().convertSendAndReceive(remoteInvocation);
}
else {
rawResult = this.getAmqpTemplate().convertSendAndReceive(this.getRoutingKey(), remoteInvocation, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
return message;
}
});
}
if (rawResult == null) {
throw new RemoteProxyFailureException("No reply received from '" +
remoteInvocation.getMethodName() +
"' with arguments '" +
Arrays.asList(remoteInvocation.getArguments()) + // NOSONAR (null)
"' - perhaps a timeout in the template?", null);
}
else if (!(rawResult instanceof RemoteInvocationResult)) {
throw new RemoteProxyFailureException("Expected a result of type "
+ RemoteInvocationResult.class.getCanonicalName() + " but found "
+ rawResult.getClass().getCanonicalName(), null); // NOSONAR (null)
}
RemoteInvocationResult result = (RemoteInvocationResult) rawResult;
return result.recreate();
}
};
amqpProxyFactoryBean.setAmqpTemplate(rabbitTemplate);
amqpProxyFactoryBean.setServiceInterface(RemoteService.class);
amqpProxyFactoryBean.setRoutingKey("rpc");
// amqpProxyFactoryBean.
return amqpProxyFactoryBean;
}
}
和spring-mybatis实现类似, AmqpProxyFactoryBean 通过提供的接口 创建一个代理对象,RemoteInvocation包含的我们提供的接口信息、执行方法信息、方法参数,通过rabbitTemplate的方法放入队列。继承AmqpProxyFactoryBean 是为了提供一些自定义的逻辑如更改rabbitTemplate的默认routingKey,rabbitTemplate发送方法更改等。
被代理的接口:
public interface RemoteService {
String get(String param);
}
调用rpc方法:
@Autowired
private RemoteService remoteService;
@Override
public void run(String... args) throws Exception {
IntStream.range(1,100).forEach(i -> {
String hello=remoteService.get("hello");
System.out.println(hello);
});
}