RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样。
RPC 可基于 HTTP 或 TCP 协议,Web Service 就是基于 HTTP 协议的 RPC,它具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC。有两方面会直接影响 RPC 的性能,一是传输方式,二是序列化。
众所周知,TCP 是传输层协议,HTTP 是应用层协议,而传输层较应用层更加底层,在数据传输方面,越底层越快,因此,在一般情况下,TCP 一定比 HTTP 快。就序列化而言,Java 提供了默认的序列化方式,但在高并发的情况下,这种方式将会带来一些性能上的瓶颈,于是市面上出现了一系列优秀的序列化框架,比如:Protobuf、Kryo、Hessian、Jackson 等,它们可以取代 Java 默认的序列化,从而提供更高效的性能。
为了支持高并发,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO,即 NIO。Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持,用 Java 实现 NIO 并不是遥不可及的事情,只是需要我们熟悉 NIO 的技术细节。
我们需要将服务部署在分布式环境下的不同节点上,通过服务注册的方式,让客户端来自动发现当前可用的服务,并调用这些服务。这需要一种服务注册表(Service Registry)的组件,让它来注册分布式环境下所有的服务地址(包括:主机名与端口号)。
应用、服务、服务注册表之间的关系见下图:
每台 Server 上可发布多个 Service,这些 Service 共用一个 host 与 port,在分布式环境下会提供 Server 共同对外提供 Service。此外,为防止 Service Registry 出现单点故障,因此需要将其搭建为集群环境。
该框架基于 TCP 协议,提供了 NIO 特性,提供高效的序列化方式,同时也具备服务注册与发现的能力。
根据以上技术需求,我们可使用如下技术选型:
Spring:它是最强大的依赖注入框架,也是业界的权威标准。
Netty:它使 NIO 编程更加容易,屏蔽了 Java 底层的 NIO 细节。
Protostuff:它基于 Protobuf 序列化框架,面向 POJO,无需编写 .proto 文件。
ZooKeeper:提供服务注册与发现功能,开发分布式系统的必备选择,同时它也具备天生的集群能力。
HelloService接口
package api;
public interface HelloService {
String hello(String name);
String hello(Person person);
}
Person实体类
package api;
import lombok.Data;
@Data
public class Person {
/**
* 年龄
*/
int age;
/**
* 姓名
*/
String name;
/**
* 性别
*/
String sex;
}
将上述代码打包成jar包,提供给服务调用方和服务提供方引用。maven依赖如下:
com.service.api
serviceApi
1.0-SNAPSHOT
package serviceprovider.demo.service;
import api.HelloService;
import api.Person;
import com.myrpc.server.RpcService;
@RpcService(HelloService.class)
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "Hello! " + name;
}
@Override
public String hello(Person person) {
return "my name is " + person.getName() + ", my sex is " + person.getSex() + ", i am " + person.getAge() + " years old";
}
}
使用RpcService注解定义在服务接口的实现类上,需要对该实现类指定远程接口,因为实现类可能会实现多个接口,一定要告诉框架哪个才是远程接口。
RpcService代码如下:
package com.myrpc.server;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-02 22:23
* @desc rpc服务注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
Class> value();
}
配置服务端
服务端 Spring 配置文件名为spring.xml,内容如下:
具体的配置参数在rpc.properties文件中,内容如下:
# zookeeper server
registry.address=127.0.0.1:2181
# rpc server
server.address=127.0.0.1:18866
以上配置表明:连接本地的 ZooKeeper 服务器,端口号为2181, 并在 18866 端口上发布 RPC 服务。
启动服务端并加载spring配置
package serviceprovider.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("spring.xml");
SpringApplication.run(DemoApplication.class, args);
}
}
运行上面的main方法即可启动服务端,但还有两个重要的组件尚未实现,它们分别是:ServiceRegistry与RpcServer,下文会给出具体实现细节。
使用 ZooKeeper 客户端可轻松实现服务注册功能,ServiceRegistry代码如下:
package com.myrpc.registry;
import org.I0Itec.zkclient.ZkClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-02 22:30
* @desc 服务注册类
*/
public class ServiceRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRegistry.class);
private final ZkClient zkClient;
/**
* 服务注册
*
* @param zkAddress
*/
public ServiceRegistry(String zkAddress) {
// 创建 ZooKeeper 客户端
zkClient = new ZkClient(zkAddress, Constant.ZK_SESSION_TIMEOUT, Constant.ZK_CONNECTION_TIMEOUT);
LOGGER.debug("connect zookeeper");
}
public void register(String serviceName, String serviceAddress) {
// 创建 registry 节点(持久)
String registryPath = Constant.ZK_REGISTRY_PATH;
if (!zkClient.exists(registryPath)) {
zkClient.createPersistent(registryPath);
LOGGER.debug("create registry node: {}", registryPath);
}
// 创建 service 节点(持久)
String servicePath = registryPath + "/" + serviceName;
if (!zkClient.exists(servicePath)) {
zkClient.createPersistent(servicePath);
LOGGER.debug("create service node: {}", servicePath);
}
// 创建 address 节点(临时)
String addressPath = servicePath + "/address-";
String addressNode = zkClient.createEphemeralSequential(addressPath, serviceAddress);
LOGGER.debug("create address node: {}", addressNode);
}
}
其中,通过Constant配置了所有的常量:
package com.myrpc.registry;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-02 22:30
* @desc rpc常量
*/
public interface Constant {
int ZK_SESSION_TIMEOUT = 5000;
int ZK_CONNECTION_TIMEOUT = 1000;
String ZK_REGISTRY_PATH = "/registry";
}
注意:首先需要使用 ZooKeeper 客户端命令行创建/registry永久节点,用于存放所有的服务临时节点。
使用 Netty 可实现一个支持 NIO 的 RPC 服务器,需要使用ServiceRegistry注册服务地址,RpcServer代码如下:
package com.myrpc.server;
import com.myrpc.protocol.RpcDecoder;
import com.myrpc.protocol.RpcEncoder;
import com.myrpc.protocol.RpcRequest;
import com.myrpc.protocol.RpcResponse;
import com.myrpc.registry.ServiceRegistry;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-01 22:23
* @desc rpc服务初始化类
*/
public class RpcServer implements ApplicationContextAware, InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(RpcServer.class);
private String serviceAddress;
private ServiceRegistry serviceRegistry;
/**
* 存放 服务名 与 服务对象 之间的映射关系
*/
private Map handlerMap = new HashMap<>();
public RpcServer(String serviceAddress) {
this.serviceAddress = serviceAddress;
}
public RpcServer(String serviceAddress, ServiceRegistry serviceRegistry) {
this.serviceAddress = serviceAddress;
this.serviceRegistry = serviceRegistry;
}
/**
* Spring在完成Bean的初始化后,
* 会将ApplicationContext上下文对象注入至该Bean对象中,
* 注入方法为调用Bean的setApplicationContext方法
*
* @param ctx
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
// 扫描带有 RpcService 注解的类并初始化 handlerMap 对象
Map serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class);
if (MapUtils.isNotEmpty(serviceBeanMap)) {
for (Object serviceBean : serviceBeanMap.values()) {
RpcService rpcService = serviceBean.getClass().getAnnotation(RpcService.class);
String serviceName = rpcService.value().getName();
handlerMap.put(serviceName, serviceBean);
}
}
}
/**
* 这个方法将在所有的属性被初始化后调用
*
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建并初始化 Netty 服务端 Bootstrap 对象
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// 解码 RPC 请求
pipeline.addLast(new RpcDecoder(RpcRequest.class));
// 编码 RPC 响应
pipeline.addLast(new RpcEncoder(RpcResponse.class));
// 处理 RPC 请求
pipeline.addLast(new RpcHandler(handlerMap));
}
});
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
// 获取 RPC 服务器的 IP 地址与端口号
String[] addressArray = StringUtils.splitByWholeSeparator(serviceAddress, ":");
String ip = addressArray[0];
int port = Integer.parseInt(addressArray[1]);
// 启动 RPC 服务器
ChannelFuture future = bootstrap.bind(ip, port).sync();
// 注册 RPC 服务地址
if (serviceRegistry != null) {
for (String interfaceName : handlerMap.keySet()) {
serviceRegistry.register(interfaceName, serviceAddress);
LOGGER.debug("register service: {} => {}", interfaceName, serviceAddress);
}
}
LOGGER.debug("server started on port {}", port);
// 关闭 RPC 服务器
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
以上代码中,有两个重要的 POJO 需要描述一下,它们分别是RpcRequest与RpcResponse。
使用RpcRequest封装 RPC 请求,代码如下:
package com.myrpc.protocol;
import lombok.Data;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-07 22:23
* @desc rpc请求实体
*/
@Data
public class RpcRequest {
private String requestId;
private String className;
private String methodName;
private Class>[] parameterTypes;
private Object[] parameters;
}
使用RpcResponse封装 RPC 响应,代码如下:
package com.myrpc.protocol;
import lombok.Data;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-06 22:23
* @desc rpc返回实体
*/
@Data
public class RpcResponse {
private String requestId;
private Exception exception;
private Object result;
public boolean hasException() {
return exception != null;
}
}
使用RpcDecoder提供 RPC 解码,只需扩展 Netty 的ByteToMessageDecoder抽象类的decode方法即可,代码如下:
package com.myrpc.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-06 22:23
* @desc rpc解码
*/
public class RpcDecoder extends ByteToMessageDecoder {
private Class> genericClass;
public RpcDecoder(Class> genericClass) {
this.genericClass = genericClass;
}
@Override
public final void decode(ChannelHandlerContext ctx, ByteBuf in, List
使用RpcEncoder提供 RPC 编码,只需扩展 Netty 的MessageToByteEncoder抽象类的encode方法即可,代码如下:
package com.myrpc.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-06 22:23
* @desc rpc编码
*/
public class RpcEncoder extends MessageToByteEncoder {
private Class> genericClass;
public RpcEncoder(Class> genericClass) {
this.genericClass = genericClass;
}
@Override
public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
if (genericClass.isInstance(in)) {
byte[] data = SerializationUtil.serialize(in);
out.writeInt(data.length);
out.writeBytes(data);
}
}
}
编写一个SerializationUtil工具类,使用Protostuff实现序列化:
package com.myrpc.protocol;
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-07 10:34
* @desc rpc序列化工具类
*/
public class SerializationUtil {
private static Map, Schema>> cachedSchema = new ConcurrentHashMap<>();
private static Objenesis objenesis = new ObjenesisStd(true);
private SerializationUtil() {
}
@SuppressWarnings("unchecked")
private static Schema getSchema(Class cls) {
return (Schema) cachedSchema.computeIfAbsent(cls, RuntimeSchema::createFrom);
}
/**
* 序列化(对象 -> 字节数组)
*/
@SuppressWarnings("unchecked")
public static byte[] serialize(T obj) {
Class cls = (Class) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema schema = getSchema(cls);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
/**
* 反序列化(字节数组 -> 对象)
*/
public static T deserialize(byte[] data, Class cls) {
try {
T message = (T) objenesis.newInstance(cls);
Schema schema = getSchema(cls);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
以上了使用 Objenesis 来实例化对象,它是比 Java 反射更加强大。
注意:如需要替换其它序列化框架,只需修改SerializationUtil即可。当然,更好的实现方式是提供配置项来决定使用哪种序列化方式。
使用RpcHandler中处理 RPC 请求,只需扩展 Netty 的SimpleChannelInboundHandler抽象类即可,代码如下:
package com.myrpc.server;
import com.myrpc.protocol.RpcRequest;
import com.myrpc.protocol.RpcResponse;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Map;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-06 22:23
* @desc rpc解码
*/
public class RpcHandler extends SimpleChannelInboundHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RpcHandler.class);
private final Map handlerMap;
public RpcHandler(Map handlerMap) {
this.handlerMap = handlerMap;
}
@Override
public void channelRead0(final ChannelHandlerContext ctx, RpcRequest request) throws Exception {
// 创建并初始化 RPC 响应对象
RpcResponse response = new RpcResponse();
response.setRequestId(request.getRequestId());
try {
Object result = handle(request);
response.setResult(result);
} catch (Exception e) {
LOGGER.error("handle result failure", e);
response.setException(e);
}
// 写入 RPC 响应对象并自动关闭连接
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
private Object handle(RpcRequest request) throws Exception {
// 获取服务对象
String serviceName = request.getClassName();
Object serviceBean = handlerMap.get(serviceName);
if (serviceBean == null) {
throw new RuntimeException(String.format("can not find service bean by key: %s", serviceName));
}
// 获取反射调用所需的参数
Class> serviceClass = serviceBean.getClass();
String methodName = request.getMethodName();
Class>[] parameterTypes = request.getParameterTypes();
Object[] parameters = request.getParameters();
// 使用 CGLib 执行反射调用
FastClass serviceFastClass = FastClass.create(serviceClass);
FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
return serviceFastMethod.invoke(serviceBean, parameters);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
LOGGER.error("server caught exception", cause);
ctx.close();
}
}
为了避免使用 Java 反射带来的性能问题,我们可以使用 CGLib 提供的反射 API,如上面用到的FastClass与FastMethod。
同样使用 Spring 配置文件来配置 RPC 客户端,spring.xml代码如下:
其中rpc.properties提供了具体的配置:
registry.address=127.0.0.1:2181
同样使用 ZooKeeper 实现服务发现功能,见如下代码:
package com.myrpc.registry;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.I0Itec.zkclient.ZkClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-03 22:20
* @desc 服务发现类
*/
public class ServiceDiscovery {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceDiscovery.class);
private String zkAddress;
public ServiceDiscovery(String zkAddress) {
this.zkAddress = zkAddress;
}
/**
* 服务发现
*
* @param name
* @return
*/
public String discover(String name) {
// 创建 ZooKeeper 客户端
ZkClient zkClient = new ZkClient(zkAddress, Constant.ZK_SESSION_TIMEOUT, Constant.ZK_CONNECTION_TIMEOUT);
LOGGER.debug("connect zookeeper");
try {
// 获取 service 节点
String servicePath = Constant.ZK_REGISTRY_PATH + "/" + name;
if (!zkClient.exists(servicePath)) {
throw new RuntimeException(String.format("can not find any service node on path: %s", servicePath));
}
List addressList = zkClient.getChildren(servicePath);
if (CollectionUtils.isEmpty(addressList)) {
throw new RuntimeException(String.format("can not find any address node on path: %s", servicePath));
}
// 获取 address 节点
String address;
int size = addressList.size();
if (size == 1) {
// 若只有一个地址,则获取该地址
address = addressList.get(0);
LOGGER.debug("get only address node: {}", address);
} else {
// 若存在多个地址,则随机获取一个地址
address = addressList.get(ThreadLocalRandom.current().nextInt(size));
LOGGER.debug("get random address node: {}", address);
}
// 获取 address 节点的值
String addressPath = servicePath + "/" + address;
return zkClient.readData(addressPath);
} finally {
zkClient.close();
}
}
}
这里使用 Java 提供的动态代理技术实现 RPC 代理(当然也可以使用 CGLib 来实现),具体代码如下:
package com.myrpc.client;
import com.myrpc.protocol.RpcRequest;
import com.myrpc.protocol.RpcResponse;
import com.myrpc.registry.ServiceDiscovery;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-05 23:54
* @desc
*/
public class RpcProxy {
private static final Logger LOGGER = LoggerFactory.getLogger(RpcProxy.class);
private String serviceAddress;
private ServiceDiscovery serviceDiscovery;
public RpcProxy(String serviceAddress) {
this.serviceAddress = serviceAddress;
}
public RpcProxy(ServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
}
@SuppressWarnings("unchecked")
public T create(final Class> interfaceClass) {
return create(interfaceClass, "");
}
@SuppressWarnings("unchecked")
public T create(final Class> interfaceClass, final String serviceVersion) {
// 创建动态代理对象
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class>[]{interfaceClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 创建 RPC 请求对象并设置请求属性
RpcRequest request = new RpcRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
// 获取 RPC 服务地址
if (serviceDiscovery != null) {
String serviceName = interfaceClass.getName();
serviceAddress = serviceDiscovery.discover(serviceName);
LOGGER.debug("discover service: {} => {}", serviceName, serviceAddress);
}
if (StringUtils.isEmpty(serviceAddress)) {
throw new RuntimeException("server address is empty");
}
// 从 RPC 服务地址中解析主机名与端口号
String[] array = StringUtils.splitByWholeSeparator(serviceAddress, ":");
String host = array[0];
int port = Integer.parseInt(array[1]);
// 创建 RPC 客户端对象并发送 RPC 请求
RpcClient client = new RpcClient(host, port);
long time = System.currentTimeMillis();
RpcResponse response = client.send(request);
LOGGER.debug("time: {}ms", System.currentTimeMillis() - time);
if (response == null) {
throw new RuntimeException("response is null");
}
// 返回 RPC 响应结果
if (response.hasException()) {
throw response.getException();
} else {
return response.getResult();
}
}
}
);
}
}
使用RpcClient类实现 RPC 客户端,只需扩展 Netty 提供的SimpleChannelInboundHandler抽象类即可,代码如下:
package com.myrpc.client;
import com.myrpc.protocol.RpcDecoder;
import com.myrpc.protocol.RpcEncoder;
import com.myrpc.protocol.RpcRequest;
import com.myrpc.protocol.RpcResponse;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author chengzhengda
* @version 1.0
* @date 2019-09-04 22:23
* @desc rpc客户端类
*/
public class RpcClient extends SimpleChannelInboundHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RpcClient.class);
private final String host;
private final int port;
private RpcResponse response;
public RpcClient(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
this.response = response;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
LOGGER.error("api caught exception", cause);
ctx.close();
}
/**
* 发送rpc请求
*
* @param request
* @return
* @throws Exception
*/
public RpcResponse send(RpcRequest request) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建并初始化 Netty 客户端 Bootstrap 对象
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// 编码 RPC 请求
pipeline.addLast(new RpcEncoder(RpcRequest.class));
// 解码 RPC 响应
pipeline.addLast(new RpcDecoder(RpcResponse.class));
// 处理 RPC 响应
pipeline.addLast(RpcClient.this);
}
});
bootstrap.option(ChannelOption.TCP_NODELAY, true);
// 连接 RPC 服务器
ChannelFuture future = bootstrap.connect(host, port).sync();
// 写入 RPC 请求数据并关闭连接
Channel channel = future.channel();
channel.writeAndFlush(request).sync();
channel.closeFuture().sync();
// 返回 RPC 响应对象
return response;
} finally {
group.shutdownGracefully();
}
}
}
package serviceconsumer.demo;
import api.HelloService;
import api.Person;
import com.myrpc.client.RpcProxy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "spring.xml")
public class RpcTest {
@Autowired
RpcProxy rpcProxy;
@Test
public void helloTest1() {
HelloService helloService = rpcProxy.create(HelloService.class);
String result = helloService.hello("World");
System.out.println(result);
Person person = new Person();
person.setAge(18);
person.setName("dada");
person.setSex("male");
System.out.println(helloService.hello(person));
}
}