转载自:http://blog.youkuaiyun.com/z69183787/article/details/52700274
http://blog.youkuaiyun.com/z69183787/article/details/52680941
采用Zookeeper、Netty和spring实现了一个轻量级的分布式RPC框架,这个RPC框架可以算是一个简易版的dubbo。框架虽小,但是麻雀虽小,五脏俱全。
使用了如下技术选型:
Spring:依赖注入框架。
Netty:它使 NIO 编程更加容易,屏蔽了 Java 底层的 NIO 细节。
Protostuff:它基于 Protobuf 序列化框架,面向 POJO,无需编写 .proto 文件。
ZooKeeper:提供服务注册与发现功能,开发分布式系统的必备选择。
下面只是些关键部分的代码示例,具体请参考原作者的git:https://github.com/luxiaoxun/NettyRpc 或则 转载的原文。
1.使用注解标注要发布的服务:
2. 定义服务接口和实现:
3. RpcRequest与RpcResponse如下:
4. 实现 RPC 服务器:
5. RpcDecoder提供 RPC 解码,只需扩展 Netty 的ByteToMessageDecoder抽象类的decode方法即可,RpcEncoder提供 RPC 编码,只需扩展 Netty 的MessageToByteEncoder抽象类的encode方法即可,代码如下:
6. 在RpcHandler中将处理 RPC 请求,扩展了Netty的SimpleChannelInboundHandler抽象类:
7. 客户端调用服务,使用代理模式调用服务:
8. RpcClient类实现 RPC 客户端:
9.测试:
以上的demo还只是个雏形,还存在一些性能问题需要改进,仅供学习参考之用。
http://blog.youkuaiyun.com/z69183787/article/details/52680941
采用Zookeeper、Netty和spring实现了一个轻量级的分布式RPC框架,这个RPC框架可以算是一个简易版的dubbo。框架虽小,但是麻雀虽小,五脏俱全。
使用了如下技术选型:
Spring:依赖注入框架。
Netty:它使 NIO 编程更加容易,屏蔽了 Java 底层的 NIO 细节。
Protostuff:它基于 Protobuf 序列化框架,面向 POJO,无需编写 .proto 文件。
ZooKeeper:提供服务注册与发现功能,开发分布式系统的必备选择。
下面只是些关键部分的代码示例,具体请参考原作者的git:https://github.com/luxiaoxun/NettyRpc 或则 转载的原文。
1.使用注解标注要发布的服务:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
Class<?> value();
} 使用RpcService注解定义在服务接口的实现类上,需要对该实现类指定远程接口的class,因为实现类可能会实现多个接口,一定要告诉框架哪个才是远程接口。
2. 定义服务接口和实现:
public interface HelloService {
……
}
@RpcService(HelloService.class)
public class HelloServiceImpl implements HelloService {
……
}
3. RpcRequest与RpcResponse如下:
public class RpcRequest {
private String requestId;
private String className;
private String methodName;
private Class<?>[] parameterTypes;
private Object[] parameters;
// getter/setter...
}
public class RpcResponse {
private String requestId;
private Throwable error;
private Object result;
// getter/setter...
}
4. 实现 RPC 服务器:
public class RpcServer implements ApplicationContextAware, InitializingBean {
private String serverAddress;
private ServiceRegistry serviceRegistry;
private Map<String, Object> handlerMap = new HashMap<>(); // 存放接口名与服务对象之间的映射关系
public RpcServer(String serverAddress) {
this.serverAddress = serverAddress;
}
public RpcServer(String serverAddress, ServiceRegistry serviceRegistry) {
this.serverAddress = serverAddress;
this.serviceRegistry = serviceRegistry;
}
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class); // 获取所有带有 RpcService 注解的 Spring Bean
if (MapUtils.isNotEmpty(serviceBeanMap)) {
for (Object serviceBean : serviceBeanMap.values()) {
String interfaceName = serviceBean.getClass().getAnnotation(RpcService.class).value().getName();
handlerMap.put(interfaceName, serviceBean);
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new RpcDecoder(RpcRequest.class)) // 将 RPC 请求进行解码(为了处理请求)
.addLast(new RpcEncoder(RpcResponse.class)) // 将 RPC 响应进行编码(为了返回响应)
.addLast(new RpcHandler(handlerMap)); // 处理 RPC 请求
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
String[] array = serverAddress.split(":");
String host = array[0];
int port = Integer.parseInt(array[1]);
ChannelFuture future = bootstrap.bind(host, port).sync();
if (serviceRegistry != null) {
serviceRegistry.register(serverAddress); // 注册服务地址
}
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
} 这里的serverAddress是”ip:port”,ServiceRegistry是使用 ZooKeeper实现服务注册类,将serverAddress在数据节点上。
5. RpcDecoder提供 RPC 解码,只需扩展 Netty 的ByteToMessageDecoder抽象类的decode方法即可,RpcEncoder提供 RPC 编码,只需扩展 Netty 的MessageToByteEncoder抽象类的encode方法即可,代码如下:
public class RpcDecoder extends ByteToMessageDecoder {
private Class<?> genericClass;
public RpcDecoder(Class<?> genericClass) {
this.genericClass = genericClass;
}
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 4) {
return;
}
in.markReaderIndex();
int dataLength = in.readInt();
if (dataLength < 0) {
ctx.close();
}
if (in.readableBytes() < dataLength) {
in.resetReaderIndex();
return;
}
byte[] data = new byte[dataLength];
in.readBytes(data);
Object obj = SerializationUtil.deserialize(data, genericClass);
out.add(obj);
}
}
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来实现序列化,代码略过。
6. 在RpcHandler中将处理 RPC 请求,扩展了Netty的SimpleChannelInboundHandler抽象类:
public class RpcHandler extends SimpleChannelInboundHandler<RpcRequest> {
private final Map<String, Object> handlerMap;
public RpcHandler(Map<String, Object> handlerMap) {
this.handlerMap = handlerMap;
}
@Override
public void channelRead0(final ChannelHandlerContext ctx, RpcRequest request) throws Exception {
RpcResponse response = new RpcResponse();
response.setRequestId(request.getRequestId());
try {
Object result = handle(request);
response.setResult(result);
} catch (Throwable t) {
response.setError(t);
}
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
private Object handle(RpcRequest request) throws Throwable {
String className = request.getClassName();
Object serviceBean = handlerMap.get(className);
Class<?> serviceClass = serviceBean.getClass();
String methodName = request.getMethodName();
Class<?>[] parameterTypes = request.getParameterTypes();
Object[] parameters = request.getParameters();
/*Method method = serviceClass.getMethod(methodName, parameterTypes);
method.setAccessible(true);
return method.invoke(serviceBean, parameters);*/
FastClass serviceFastClass = FastClass.create(serviceClass);
FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
return serviceFastMethod.invoke(serviceBean, parameters);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ctx.close();
}
} 为了避免使用 Java 反射带来的性能问题,使用了CGLib 提供的反射API,即上面用到的FastClass与FastMethod。
7. 客户端调用服务,使用代理模式调用服务:
public class RpcProxy {
private String serverAddress;
private ServiceDiscovery serviceDiscovery;
public RpcProxy(String serverAddress) {
this.serverAddress = serverAddress;
}
public RpcProxy(ServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
}
@SuppressWarnings("unchecked")
public <T> T create(Class<?> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest request = new RpcRequest(); // 创建并初始化 RPC 请求
request.setRequestId(UUID.randomUUID().toString());
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
if (serviceDiscovery != null) {
serverAddress = serviceDiscovery.discover(); // 发现服务
}
String[] array = serverAddress.split(":");
String host = array[0];
int port = Integer.parseInt(array[1]);
RpcClient client = new RpcClient(host, port); // 初始化 RPC 客户端
RpcResponse response = client.send(request); // 通过 RPC 客户端发送 RPC 请求并获取 RPC 响应
if (response.isError()) {
throw response.getError();
} else {
return response.getResult();
}
}
}
);
}
} ServiceDiscovery是Zookeeper实现的服务发现,取出已经注册过的”ip:port”,然后随机返回一个”ip:port”。
8. RpcClient类实现 RPC 客户端:
public class RpcClient extends SimpleChannelInboundHandler<RpcResponse> {
private String host;
private int port;
private RpcResponse response;
private final Object obj = new Object();
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;
synchronized (obj) {
obj.notifyAll(); // 收到响应,唤醒线程
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
public RpcResponse send(RpcRequest request) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new RpcEncoder(RpcRequest.class)) // 将 RPC 请求进行编码(为了发送请求)
.addLast(new RpcDecoder(RpcResponse.class)) // 将 RPC 响应进行解码(为了处理响应)
.addLast(RpcClient.this); // 使用 RpcClient 发送 RPC 请求
}
})
.option(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().writeAndFlush(request).sync();
synchronized (obj) {
obj.wait(); // 未收到响应,使线程等待
}
if (response != null) {
future.channel().closeFuture().sync();
}
return response;
} finally {
group.shutdownGracefully();
}
}
} 这里每次调用的send时候才去和服务端建立连接,使用的是短连接,这种短连接在高并发时会有连接数问题,也会影响性能。,使用了obj的wait和notifyAll来等待Response返回,会出现“假死等待”的情况:一个Request发送出去后,在obj.wait()调用之前可能Response就返回了,这时候在channelRead0里已经拿到了Response并且obj.notifyAll()已经在obj.wait()之前调用了,这时候send后再obj.wait()就出现了假死等待,客户端就一直等待在这里。应该使用CountDownLatch来解决这个问题。
9.测试:
RpcProxy rpcProxy = new RpcProxy(serviceDiscovery);
HelloService helloService = rpcProxy.create(HelloService.class);
String result = helloService.hello("World");
以上的demo还只是个雏形,还存在一些性能问题需要改进,仅供学习参考之用。
本文介绍了一种基于Zookeeper、Netty和Spring实现的轻量级分布式RPC框架,该框架具备服务注册与发现功能,并提供了RPC请求与响应的处理方式。通过简单的代码示例展示了如何发布服务、实现RPC服务器及客户端调用。
972

被折叠的 条评论
为什么被折叠?



