一、 RPC基础知识
1.1、RPC是什么
RPC 【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,是一种技术思想,而不是规范。它允许程序调用另一个地址空间(网络的另一台机器上)的过程或函数,而不用开发人员显式编码这个调用的细节。调用本地方法和调用远程方法一样。
1.2、 RPC基本原理
- 服务调用方 client(客户端)以本地调用方式调用服务;
- client stub(客户端存根:存放服务端地址信息,将客户端的请求参数编组成网络消息,再通过网络发送给服务方)接收到调用后负责将方法、参数等编组成能够进行网络传输的消息体;在Java中就是序列化的过程
- client stub找到服务地址,并将消息通过网络发送到服务端(server);
- server stub(服务端存根:接受客户端发送过来的消息并解组,再调用本地服务,再将本地服务执行结果发送给客户端)收到消息后进行解组;
- server stub根据解组结果调用本地的服务;
- 本地服务执行处理逻辑;
- 本地服务将结果返回给server stub;
- server stub将返回结果编组成网络消息;
- server stub将编组后的消息通过网络并发送客户端
- client stub接收到消息,并进行解组;
- 服务调用方client得到最终结果。
1.3、 RPC协议是什么
RPC调用过程中需要将参数编组为消息进行发送,接受方需要解组消息为参数,过程处理结果同样需要经编组、解组。消息由哪些部分构成及消息的表示形式就构成了消息协议。 RPC调用过程中采用的消息协议称为RPC协议
RPC协议规定请求、响应消息的格式
在TCP(网络传输控制协议)上可选用或自定义消息协议来完成RPC消息交互
我们可以选用通用的标准协议(如:http、https),也也可根据自身的需要定义自己的消息协议。
1.4、 RPC框架是什么
封装好参数编组、消息解组、底层网络通信的RPC程序,可直接在其基础上只需专注与过程业务代码编码,无需再关注其调用细节
目前常见的RPC框架 Dubbo、gRPC、gRPC、Apache Thrift、RMI…等
二、手写RPC框架
下面将一步步来写一个精简版的RPC框架,使项目引入该框架后,通过简单的配置让项目拥有提供远程服务与调用的能力
2.1、 服务端编写
2.1.1、 服务端都需要完成哪些?
首先服务端要停工远程服务,就必须具备服务注册及暴露的能力;在这之后还需要开启网络服务,供客户端连接。有些项目可能即使服务提供者同时又是服务消费者,那么什么时候注册暴露服务,什么时候注入消费服务呢?在这我就引入了一个RPC监听处理器的概念,就有这个处理器来完成服务的注册暴露,以及服务消费注入
2.1.2、具体实现
2.1.2.1、 服务暴露注解
哪些服务需要注册暴露这里使用自定义注解的方式来标注:@Service
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
@Inherited
public @interface Service {
String value() default "";
String version() default "";
long timeout() default 0L;
}
2.1.2.2、服务注册(暴露)
public interface ServiceRegister {
void register(List<ServiceObject> so) throws Exception;
ServiceObject getServiceObject(String name) throws Exception;
}
public abstract class DefaultServiceRegister implements ServiceRegister{
private Map<String, ServiceObject> serviceMap = new HashMap<>();
protected String protocol;
protected int port;
@Override
public void register(List<ServiceObject> soList) throws Exception {
if (soList==null&&soList.size()>0){
throw new IllegalAccessException("Service object information cannot be empty");
}
soList.forEach(so -> this.serviceMap.put(so.getName(), so));
}
@Override
public ServiceObject getServiceObject(String name) {
return this.serviceMap.get(name);
}
}
public class ZookeeperExportServiceRegister extends DefaultServiceRegister {
private ZkClient client;
public ZookeeperExportServiceRegister(String zkAddress, int port, String protocol){
this.client = new ZkClient(zkAddress);
this.client.setZkSerializer(new ZookeeperSerializer());
this.port = port;
this.protocol = protocol;
}
@Override
public void register(List<ServiceObject> soList) throws Exception {
super.register(soList);
for (ServiceObject so : soList) {
ServiceInfo serviceInfo = new ServiceInfo();
String host = InetAddress.getLocalHost().getHostAddress();
String address = host + ":" + port;
serviceInfo.setAddress(address);
serviceInfo.setName(so.getName());
serviceInfo.setProtocol(protocol);
this.exportService(serviceInfo);
}
}
private void exportService(ServiceInfo serviceInfo) {
String serviceName = serviceInfo.getName();
String uri = JSON.toJSONString(serviceInfo);
try {
uri = URLEncoder.encode(uri, CommonConstant.UTF_8);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String servicePath = CommonConstant.ZK_SERVICE_PATH + CommonConstant.PATH_DELIMITER + serviceName + CommonConstant.PATH_DELIMITER + "service";
if (!client.exists(servicePath)){
client.createPersistent(servicePath,true);
}
String uriPath = servicePath + CommonConstant.PATH_DELIMITER + uri;
if (client.exists(uriPath)){
client.delete(uriPath);
}
client.createEphemeral(uriPath);
}
}
这个过程其实没有详说的必要,就是将指定ServiceObject对象序列化后保存到ZK上,供客户端发现。同时会将服务对象缓存起来,在客户端调用服务时,通过缓存的ServiceObject对象反射指定服务,调用方法;其实看方法上打的注释也能看明白每步都做了什么
2.1.2.3、开启网络服务、处理接收到的客户端请求
public abstract class RpcServer {
protected int port;
protected String protocol;
protected RequestHandler handler;
public RpcServer(int port, String protocol, RequestHandler handler){
super();
this.port = port;
this.protocol = protocol;
this.handler = handler;
}
public abstract void start();
public abstract void stop();
}
@Slf4j
public class NettyRpcServer extends RpcServer{
private Channel channel;
public NettyRpcServer(int port, String protocol, RequestHandler handler){
super(port,protocol,handler);
}
@Override
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new ChannelRequestHandler());
}
});
ChannelFuture future = sb.bind(port).sync();
log.info("Server starteed successfully.");
channel = future.channel();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
@Override
public void stop() {
if (this.channel!=null){
this.channel.close();
}
}
private class ChannelRequestHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("Channel active: {}",ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("The server receives a message: {}",msg);
ByteBuf msgBuf = (ByteBuf) msg;
byte[] req = new byte[msgBuf.readableBytes()];
msgBuf.readBytes(req);
byte[] res = handler.handleRequest(req);
log.info("Send response: {}",msg);
ByteBuf respBuf = Unpooled.buffer(res.length);
respBuf.writeBytes(res);
ctx.write(respBuf);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
log.error("Exception occurred: {}",cause.getMessage());
ctx.close();
}
}
}
public class RequestHandler {
private MessageProtocol protocol;
private ServiceRegister serviceRegister;
public RequestHandler(MessageProtocol protocol,ServiceRegister serviceRegister){
super();
this.protocol = protocol;
this.serviceRegister = serviceRegister;
}
public byte[] handleRequest(byte[] data) throws Exception{
Request request = this.protocol.unmarshallingRequest(data);
ServiceObject so = this.serviceRegister.getServiceObject(request.getServiceName());
Response response = null;
if (so==null){
response = Response.builder().status(Status.NOT_FOUND).build();
}else{
try {
Method method = so.getClazz().getMethod(request.getMethod(), request.getParameterTypes());
Object returnVal = method.invoke(so.getObj(), request.getParameters());
response = Response.builder()
.status(Status.SUCCESS)
.returnValue(returnVal)
.build();
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
response = Response.builder()
.status(Status.ERROR)
.exception(e)
.build();
}
}
return this.protocol.marshallingResponse(response);
}
}
这段算是服务端的核心部分,控制服务段Netty网络服务的开启关闭;接收客户端发起的请求,将客户端发送的请求参数解组并查询客户端远程调用的过程业务过程接口,并通过反射调用返回调用结果
2.1.2.3、RPC监听处理器
开始有提到RPC监听处理器的概念,用于服务的注册暴露与服务的消费注入,这里先说下服务开启服务注册,后面说的客户端时在补充服务注入
@Slf4j
public class DefaultRpcProcessor implements ApplicationListener<ContextRefreshedEvent>, DisposableBean {
@Autowired
private ClientProxyFactory clientProxyFactory;
@Autowired
private ServiceRegister serviceRegister;
@Autowired
private RpcServer rpcService;
@SneakyThrows
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
if (Objects.isNull(applicationContext.getParent())){
startServer(applicationContext);
injectService(applicationContext);
}
}
private void startServer(ApplicationContext applicationContext) throws Exception {
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Service.class);
if (beans.size()!=0){
for (Object o : beans.values()) {
List<ServiceObject> soList = new ArrayList<>();
Class<?> clazz = o.getClass();
Service service = clazz.getAnnotation(Service.class);
String version = service.version();
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces.length>1){
for (Class<?> aClass : interfaces) {
String aClassName = aClass.getName();
if (StringUtils.hasLength(version)){
aClassName +=":"+version;
}
soList.add(new ServiceObject(aClassName,aClass,o));
}
}else{
Class<?> superClass = interfaces[0];
String aClassName = superClass.getName();
if (StringUtils.hasLength(version)){
aClassName +=":"+version;
}
soList.add(new ServiceObject(aClassName, superClass, o));
}
this.serviceRegister.register(soList);
}
rpcService.start();
}
}
private void injectService(ApplicationContext applicationContext) {
}
@Override
public void destroy() {
log.info("The application will stop and the zookeeper connection will be closed");
rpcService.stop();
}
}
DefaultRpcProcessor实现了ApplicationListener,并监听了ContextRefreshedEvent事件,在Spring启动完毕过后会收到一个事件通知,基于这个机制,就可以在这里开启服务,以及注入服务。
2.2、客户端编写
2.2.1、客户端需要完成哪些
客户端想要调用远程服务,必须具备服务发现的能力;在知道有哪些服务后,还必须有服务代理来执行服务调用;客户端想要与服务端通信,必须要有相同的消息协议与网络请求的能力,即网络层功能。
2.2.2、具体实现
2.2.2.1、服务发现
public interface IDiscovererService {
List<ServiceInfo> getService(String name);
}
public class ZookeeperServiceDiscoverer implements IDiscovererService{
private ZkClient zkClient;
public ZookeeperServiceDiscoverer(String zkAddress){
zkClient = new ZkClient(zkAddress);
zkClient.setZkSerializer(new ZookeeperSerializer());
}
@Override
public List<ServiceInfo> getService(String name) {
String servicePath = CommonConstant.ZK_SERVICE_PATH+CommonConstant.PATH_DELIMITER+name+CommonConstant.PATH_DELIMITER+"service";
List<String> children = zkClient.getChildren(servicePath);
return Optional.ofNullable(children).orElse(new ArrayList<>()).stream().map(str -> {
String deCh = null;
try {
deCh = URLDecoder.decode(str,CommonConstant.UTF_8);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return JSON.parseObject(deCh, ServiceInfo.class);
}).collect(Collectors.toList());
}
}
服务端是将服务注册进了Zookeeper中,所以服务发现者也使用Zookeeper来实现,通过ZkClient我们很容易发现已经注册在ZK上的服务。当然我们也可以使用其他组件作为注册中心,例如Redis
2.2.2.2、网络客户端,连接服务端,编组请求参数,解组响应参数
public interface NetWorkClient {
byte[] sendRequest(byte[] data, ServiceInfo serviceInfo) throws InterruptedException;
}
@Slf4j
public class NettyNetWorkClient implements NetWorkClient{
@Override
public byte[] sendRequest(byte[] data, ServiceInfo serviceInfo) throws InterruptedException {
String[] addrInfoArray = serviceInfo.getAddress().split(":");
String serverAddress = addrInfoArray[0];
String serverPort = addrInfoArray[1];
SendHandler sendHandler = new SendHandler(data);
byte[] respData;
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(sendHandler);
}
});
bootstrap.connect(serverAddress,Integer.parseInt(serverPort)).sync();
respData = (byte[]) sendHandler.rspData();
log.info("SendRequest get reply: {}",respData);
} finally {
group.shutdownGracefully();
}
return respData;
}
}
@Slf4j
public class SendHandler extends ChannelInboundHandlerAdapter {
private CountDownLatch cdl;
private Object readMsg = null;
private byte[] data;
public SendHandler(byte[] data){
this.cdl = new CountDownLatch(1);
this.data = data;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("Successful connection to server: {}",ctx);
ByteBuf buffer = Unpooled.buffer(data.length);
buffer.writeBytes(data);
log.info("Client sends message: {}",buffer);
ctx.writeAndFlush(buffer);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("Client reads message: {}",msg);
ByteBuf msgBuf = (ByteBuf) msg;
byte[] resp = new byte[msgBuf.readableBytes()];
msgBuf.readBytes(resp);
readMsg = resp;
cdl.countDown();
}
public Object rspData() throws InterruptedException {
cdl.await();
return readMsg;
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
log.error("Exception occurred: {}",cause.getMessage());
ctx.close();
}
}
这部分前面讲的服务端请求处理类似,连接服务端(Netty),将请求参数编组后发送到服务端,待服务端接收处理后返回,再这里将服务端返回的结果进行解组
2.2.2.3、服务动态代理
public class ClientProxyFactory {
@Getter
@Setter
private IDiscovererService iDiscovererService;
@Getter
@Setter
private Map<String, MessageProtocol> supportMessageProtocols;
@Getter
@Setter
private NetWorkClient netWorkClient;
private Map<Class<?>,Object> objectCache = new HashMap<>();
public <T> T getProxy(Class<T> clazz,String version){
return (T) this.objectCache.computeIfAbsent(clazz, aClass ->
Proxy.newProxyInstance(aClass.getClassLoader(), new Class<?>[]{aClass}, new ClientInvocationHandler(aClass,version)));
}
private class ClientInvocationHandler implements InvocationHandler {
private Class<?> aClass;
private String version;
public ClientInvocationHandler(Class<?> aClass,String version){
super();
this.aClass = aClass;
this.version = version;
}
private Random random = new Random();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("toString".equals(methodName)){
return proxy.getClass().toString();
}
if (("hashCode").equals(methodName)){
return 0;
}
String serviceName = this.aClass.getName();
if (StringUtils.hasLength(this.version)){
serviceName += ":"+version;
}
List<ServiceInfo> serviceInfoList = iDiscovererService.getService(serviceName);
if (serviceInfoList==null||serviceInfoList.isEmpty()){
throw new ClassCastException("No provider available");
}
ServiceInfo serviceInfo = serviceInfoList.get(random.nextInt(serviceInfoList.size()));
Request request = new Request();
request.setServiceName(serviceInfo.getName());
request.setMethod(methodName);
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
MessageProtocol messageProtocol = supportMessageProtocols.get(serviceInfo.getProtocol());
byte[] data = messageProtocol.marshallingRequest(request);
byte[] requestData = netWorkClient.sendRequest(data, serviceInfo);
Response response = messageProtocol.unmarshallingResponse(requestData);
if (response.getException()!=null){
throw response.getException();
}
return response.getReturnValue();
}
}
}
服务代理类由客户端代理工厂类产生,代理方式是基于Java的动态代理。在处理类ClientInvocationHandler的invoke函数中,定义了一系列的操作,包括获取服务、选择服务提供者、构造请求对象、编组请求对象、网络请求客户端发送请求、解组响应消息、异常处理等
2.2.2.3、注入远程服务注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Inherited
public @interface Reference {
String value() default "";
String version() default "";
long timeout() default 0L;
}
2.2.2.4、注入远程服务
private void startServer(ApplicationContext applicationContext) throws Exception {
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Service.class);
if (beans.size()!=0){
for (Object o : beans.values()) {
List<ServiceObject> soList = new ArrayList<>();
Class<?> clazz = o.getClass();
Service service = clazz.getAnnotation(Service.class);
String version = service.version();
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces.length>1){
for (Class<?> aClass : interfaces) {
String aClassName = aClass.getName();
if (StringUtils.hasLength(version)){
aClassName +=":"+version;
}
soList.add(new ServiceObject(aClassName,aClass,o));
}
}else{
Class<?> superClass = interfaces[0];
String aClassName = superClass.getName();
if (StringUtils.hasLength(version)){
aClassName +=":"+version;
}
soList.add(new ServiceObject(aClassName, superClass, o));
}
this.serviceRegister.register(soList);
}
rpcService.start();
}
}
2.2.2.5、消息协议
public interface MessageProtocol {
byte[] marshallingRequest(Request request) throws Exception;
Request unmarshallingRequest(byte[] data) throws Exception;
byte[] marshallingResponse(Response response) throws Exception;
Response unmarshallingResponse(byte[] data) throws Exception;
}
public class HessianSerializeMessageProrocol implements MessageProtocol{
public byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = null;
HessianOutput hessianOutput = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
hessianOutput = new HessianOutput(byteArrayOutputStream);
hessianOutput.writeObject(object);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw e;
} finally {
try {
if (byteArrayOutputStream!=null){
byteArrayOutputStream.close();
}
} catch (IOException e) {
throw e;
}
try {
if (hessianOutput!=null){
hessianOutput.close();
}
} catch (IOException e) {
throw e;
}
}
}
public Object deserialize(byte[] employeeArray) throws Exception {
ByteArrayInputStream byteArrayInputStream = null;
HessianInput hessianInput = null;
try {
byteArrayInputStream = new ByteArrayInputStream(employeeArray);
hessianInput = new HessianInput(byteArrayInputStream);
return hessianInput.readObject();
} catch (IOException e) {
throw e;
} finally {
try {
if (byteArrayInputStream!=null){
byteArrayInputStream.close();
}
} catch (IOException e) {
throw e;
}
try {
if (hessianInput!=null){
hessianInput.close();
}
} catch (Exception e) {
throw e;
}
}
}
@Override
public byte[] marshallingRequest(Request request) throws Exception {
return serialize(request);
}
@Override
public Request unmarshallingRequest(byte[] data) throws Exception {
return (Request) deserialize(data);
}
@Override
public byte[] marshallingResponse(Response response) throws Exception {
return serialize(response);
}
@Override
public Response unmarshallingResponse(byte[] data) throws Exception {
return (Response) deserialize(data);
}
}
public class JavaSerializeMessageProtocol implements MessageProtocol{
private byte[] serialize(Object obj) throws Exception{
try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);) {
oos.writeObject(obj);
return baos.toByteArray();
}
}
private Object deserialize(byte[] data)throws Exception{
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
return ois.readObject();
}
}
@Override
public byte[] marshallingRequest(Request request) throws Exception {
return this.serialize(request);
}
@Override
public Request unmarshallingRequest(byte[] data) throws Exception {
return (Request) this.deserialize(data);
}
@Override
public byte[] marshallingResponse(Response response) throws Exception {
return this.serialize(response);
}
@Override
public Response unmarshallingResponse(byte[] data) throws Exception {
return (Response) this.deserialize(data);
}
}
消息协议主要是定义了客户端如何编组请求、解组响应,服务端如何解组请求、编组响应这四个操作规范。本文提供了Hessian、Java序列化与反序列化的实现,感兴趣的可以加入其他序列化技术,例如:kryo、protostuff等。
2.3、自动装配
这里是spring自动装配的一些配置很简单,就不做赘述了,不了解的可看这篇手写spring-boot-starter
@Configuration
@EnableConfigurationProperties({ProtocolProperties.class,RegistryProperties.class})
public class AutoConfiguration {
private ProtocolProperties protocolProperties;
private RegistryProperties registryProperties;
public AutoConfiguration(ProtocolProperties protocolProperties,RegistryProperties registryProperties){
this.protocolProperties = protocolProperties;
this.registryProperties = registryProperties;
}
@Bean
public DefaultRpcProcessor defaultRpcProcessor(){
return new DefaultRpcProcessor();
}
@Bean
public ClientProxyFactory clientProxyFactory(){
ClientProxyFactory clientProxyFactory = new ClientProxyFactory();
String address = registryProperties.getAddress();
int port = registryProperties.getPort();
clientProxyFactory.setIDiscovererService(new ZookeeperServiceDiscoverer(address+":"+port));
Map<String, MessageProtocol> supportMessageProtocols = new HashMap<>(16);
supportMessageProtocols.put(protocolProperties.getName(), protocolProperties.getMessageProtocol());
clientProxyFactory.setSupportMessageProtocols(supportMessageProtocols);
clientProxyFactory.setNetWorkClient(new NettyNetWorkClient());
return clientProxyFactory;
}
@Bean
public ServiceRegister serviceRegister(){
String address = registryProperties.getAddress();
int port = registryProperties.getPort();
return new ZookeeperExportServiceRegister(address+":"+port, protocolProperties.getPort(), protocolProperties.getName());
}
@Bean
public RequestHandler requestHandler(ServiceRegister serviceRegister){
return new RequestHandler(protocolProperties.getMessageProtocol(), serviceRegister);
}
@Bean
public RpcServer rpcServer(RequestHandler requestHandler){
return new NettyRpcServer(protocolProperties.getPort(), protocolProperties.getName(), requestHandler);
}
}
三、RPC框架使用
因为我这是直接封装成starter了,所以使用起来非常简单,只需要准备一个zookeeper与简单的五步配置编写,当然创建服务端与客户端程序除外
3.1 步骤一
分别再创建好的服务端工程与客户端工程中引入依赖
<dependency>
<groupId>com.huawei</groupId>
<artifactId>simple-rpc-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
3.2 步骤二
服务端配置zookeeper地址与协议端口
spring:
rpc:
registry:
address: 127.0.0.1
port: 2181
protocol:
port: 12138
3.3 步骤三
客户端配置zookeeper地址与协议端口
spring:
rpc:
registry:
address: 127.0.0.1
port: 2181
protocol:
port: 12138
3.4 步骤四
将你的远程服务使用@Service注解,例如: 在需要暴露的服务上使用@Service注解申明,注意该注解是自定义注解
@Service(version = "1.0")
public class TestServiceImpl implements TestService, ITestService {
@Override
public TestDto getTestData() {
System.out.println("远程调用.");
TestDto testDto = new TestDto();
testDto.setId(1);
testDto.setData("测试远程调用");
return testDto;
}
@Override
public TestDto getTestDtoData() {
System.out.println("远程调用.....");
TestDto testDto = new TestDto();
testDto.setId(2);
testDto.setData("测试远程调用");
return testDto;
}
}
3.5 步骤五
使用@Reference注解注入远程服务
@RestController
public class TestController {
@Reference(version = "1.0")
private TestService testService;
@Reference(version = "1.0")
private ITestService iTestService;
@PostMapping(value = "/test")
public void test(){
TestDto testData = this.testService.getTestData();
TestDto testDtoData = this.iTestService.getTestDtoData();
System.out.println(testData.toString()+"------"+testDtoData.toString());
}
}
完整代码
主要用于理解RPC调用原理,功能写的比较简单,待完善的地方还有很多,例如调用超时、负载均衡,容错等…,拉的有feature分支,感兴趣的小伙伴可在上补充修改自己所想到的内容
|