基于Netty的分布式 RPC 框架

本文介绍了一种基于Zookeeper、Netty和Spring实现的轻量级分布式RPC框架,该框架具备服务注册与发现功能,并提供了RPC请求与响应的处理方式。通过简单的代码示例展示了如何发布服务、实现RPC服务器及客户端调用。
转载自: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.使用注解标注要发布的服务:
@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还只是个雏形,还存在一些性能问题需要改进,仅供学习参考之用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值