《Netty实战-写一个RPC应用》

一、服务端

1.1、定义rpc服务代理注解

用于标识需要作为远程调用的接口,应用于类上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
}

1.2、定义handler处理器

这里只列出接收数据关键处理代码,rpcServices为所有需要rpc调用的实例类对象

private Map<String, Object> rpcServices;
public RpcServerInboundHandler(Map<String, Object> rpcServices) {
    this.rpcServices = rpcServices;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    RpcRequest rpcRequest = (RpcRequest) msg;
    RpcResponse response = new RpcResponse();
    response.setRequestType(rpcRequest.getRequestType());
    try {
        if(0 == rpcRequest.getRequestType()){
            log.info("接收到客户端请求, 请求接口 -> {}, 请求方法 -> {}", rpcRequest.getClassName(), rpcRequest.getMethodName());
            response.setRequestId(rpcRequest.getRequestId());
            this.handleRequest(rpcRequest, response);
        }
        else{
            log.info("服务器收到心跳");
        }
    } catch (Exception e) {
        e.printStackTrace();
        response.setSuccess(false);
        response.setErrorMessage(e.getMessage());
    }
    log.info("服务器响应 -> {}", JSON.toJSONString(response));
    ctx.channel().writeAndFlush(response);
}


private void handleRequest(RpcRequest rpcRequest, RpcResponse response) throws Exception {
    Object bean = rpcServices.get(rpcRequest.getClassName());
    if (bean == null) {
        throw new RuntimeException("未找到对应的服务: " + rpcRequest.getClassName());
    }
    Method method = bean.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());
    method.setAccessible(true);
    response.setReturnType(method.getReturnType());
    response.setResult(method.invoke(bean, rpcRequest.getParameters()));
}

1.3、定义编码解码器

解码器

public class JsonDecoder extends LengthFieldBasedFrameDecoder {
    public JsonDecoder() {
        super(Integer.MAX_VALUE, 0, 4, 0, 4);
    }
    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf msg = (ByteBuf) super.decode(ctx, in);
        byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);
        RpcRequest rpcRequest = JSON.parseObject(bytes, RpcRequest.class);
        // 类型转换
        if(null != rpcRequest.getParameters() && rpcRequest.getParameters().length > 0){
            for(int n =0; n < rpcRequest.getParameters().length; n++){
                if(null == rpcRequest.getParameters()[n]){
                    continue;
                }
                rpcRequest.getParameters()[n] = ((JSONObject) rpcRequest.getParameters()[n]).toJavaObject(rpcRequest.getParameterTypes()[n]);
            }
        }
        return rpcRequest;
    }
}

编码器

public class JsonEncoder extends MessageToByteEncoder<RpcResponse> {
    @Override
    protected void encode(ChannelHandlerContext ctx, RpcResponse rpcResponse, ByteBuf out) {
        byte[] bytes = JSON.toJSONBytes(rpcResponse);
        // 将消息体的长度写入消息头部
        out.writeInt(bytes.length);
        // 写入消息体
        out.writeBytes(bytes);
    }
}

1.4、定义消息结构体

RpcRequest

/**
 * 0 正常消息,1 心跳检查
 */
private Integer requestType;
/**
 * 请求ID 用来标识本次请求以匹配RPC服务器的响应
 */
private String requestId;
/**
 * 调用的类(接口)权限定名称
 */
private String className;
/**
 * 调用的方法名
 */
private String methodName;
/**
 * 方法参类型列表
 */
private Class<?>[] parameterTypes;
/**
 * 方法参数
 */
private Object[] parameters;

RpcResponse

/**
 * 0 正常消息,1 心跳检查
 */
private Integer requestType;
/**
 * 响应对应的请求ID
 */
private String requestId;
/**
 * 调用是否成功的标识
 */
private boolean success = true;
/**
 * 调用错误信息
 */
private String errorMessage;

/**
 * 返回类型
 */
private Class<?> returnType;

/**
 * 调用结果
 */
private Object result;

1.5、启动rpc服务

启动netty服务,监听指定端口

@Component
@Slf4j
public class RpcServiceConfig {
    @Value("${rpc.server.port}")
    private int port;
    
    @Bean
    public CustomRpcServer serverStart(){
        CustomRpcServer customRpcServer = new CustomRpcServer();
        customRpcServer.setPort(port);
        log.info("port={}", port);
        return customRpcServer;
    }
}

获取spring上下文容器中带有rpcservice注解的对象

@Slf4j
public class CustomRpcServer implements ApplicationContextAware, InitializingBean {
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    private Map<String, Object> rpcServices = new HashMap<>(5);
    private int port;
    public void setPort(int port) {
        this.port = port;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> services = applicationContext.getBeansWithAnnotation(RpcService.class);
        for (Map.Entry<String, Object> entry : services.entrySet()) {
            Object bean = entry.getValue();
            Class<?>[] interfaces = bean.getClass().getInterfaces();
            for (Class<?> inter : interfaces) {
                rpcServices.put(inter.getName(), bean);
            }
        }
        log.info("加载RPC服务数量:{}", rpcServices.size());
    }


    @Override
    public void afterPropertiesSet() {
        threadPoolTaskExecutor.submit(() -> {
            // 监听连接的 parent channel 的线程组
            EventLoopGroup boss = new NioEventLoopGroup(1);
            // 负责客户端连接读写的 child channel 线程组
            EventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap bootstrap = new ServerBootstrap();
                // 设置reactor 线程
                bootstrap.group(boss, worker)
                        // 装配流水线
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
                                // 超时机制
                                pipeline.addLast(new IdleStateHandler(0, 0, 30));
                                pipeline.addLast(new JsonDecoder());
                                pipeline.addLast(new JsonEncoder());
                                // pipeline管理channel中的Handler
                                // 在channel队列中添加一个handler来处理业务
                                pipeline.addLast(new RpcServerInboundHandler(rpcServices));
                            }
                        })
                        // 设置nio类型的channel
                        .channel(NioServerSocketChannel.class);
                // 开始绑定server
                // 通过调用sync同步方法阻塞直到绑定成功
                ChannelFuture future = bootstrap.bind(port).sync();
                log.info("RPC 服务器启动, 监听端口:" + port);
                // 监听通道关闭事件
                // 应用程序会一直等待,直到channel关闭
                future.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        });
    }
}

二、客户端

代码中复用客户端定义的结构体,其中codec与服务端相反

2.1、定义rpc调用代理注解

当该注解被调用时,导入RpcProxy、RpcBeanDefinitionRegistryPostProcessor

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
@Import({RpcProxy.class, RpcBeanDefinitionRegistryPostProcessor.class})
public @interface RpcCustomClient {
}

2.2、bean注册器

指定包加载类

@Slf4j
public class RpcBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        RpcScanner rpcScanner = new RpcScanner(registry);
        // RPC接口所在的包名
        rpcScanner.scan(BaseRpcConstant.BASE_RPC_PACKAGES);
    }
    略...
}

自定义rpc 扫描器

在IOC 容器初始化阶段,执行

public class RpcScanner extends ClassPathBeanDefinitionScanner {
    public RpcScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 获取指定包下的类
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
            // 获取构造函数参数结合,添加指定类型的泛型;
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            // 修改当前类bean的类型名称,当从BeanFactory获取对象时,实际调用RpcFactoryBean的 getObject方法
            log.info("doScan -> beanDefinition={}", beanDefinition.getBeanClassName());
            beanDefinition.setBeanClassName(RpcFactoryBean.class.getName());
        }
        return beanDefinitionHolders;
    }
    略...
}

rpc bean 工厂

在运行时创建一个动态代理类对象

public class RpcFactoryBean<T> implements FactoryBean<T> {
    private Class<T> interfaceClass;
    @Autowired
    private RpcProxy rpcProxy;


    public RpcFactoryBean(Class<T> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }
    @Override
    public T getObject() {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, rpcProxy);
    }
    @Override
    public Class<?> getObjectType() {
        return interfaceClass;
    }
}

2.3、RPC服务动态代理

public class RpcProxy implements InvocationHandler {
    @Autowired
    private RpcClient rpcClient;
    public void setRpcClient(RpcClient rpcClient) {
        this.rpcClient = rpcClient;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setRequestType(0);
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParameters(args);
        rpcRequest.setParameterTypes(method.getParameterTypes());
        log.info("invoke -> rpcRequest={}", JSON.toJSONString(rpcRequest));
        RpcResponse rpcResponse = rpcClient.send(rpcRequest);
        return rpcResponse.getResult();
    }
}

2.4、handler处理器

消息处理核心代码,利用SynchronousQueue的特性,实现接口消息回调

private Map<String, SynchronousQueue<RpcResponse>> results;
public RpcClientInboundHandler(Map<String, SynchronousQueue<RpcResponse>> results) {
    this.results = results;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    RpcResponse rpcResponse = (RpcResponse) msg;
    log.info("收到服务器响应 -> {}", JSON.toJSONString(rpcResponse));
    // 取出结果容器,将response放进queue中
    if(null != rpcResponse && 0 == rpcResponse.getRequestType()){
        SynchronousQueue<RpcResponse> queue = results.get(rpcResponse.getRequestId());
        queue.put(rpcResponse);
    }
}

2.5、启动客户端

加载指定注解

@RpcCustomClient
@Slf4j
public class RpcConfig {
    @Value("${rpc.remote.hosts}")
    private String remoteIpPorts;
    
    @Bean
    public RpcClient start(){
        RpcClient rpcClient = new RpcClient();
        rpcClient.setRemoteIps(remoteIpPorts);
        log.info("remoteIpPorts={}", remoteIpPorts);
        return rpcClient;
    }
}

启动服务,初始化信息

代码中,TcpPools为TCP连接池,管理tcp通道计数和连接地址,解决服务单点问题,此处暂略

@Slf4j
public class RpcClient implements InitializingBean {
    private String remoteIpPorts;
    private List<Bootstrap> bootstraps = new ArrayList<>();
    public void setRemoteIps(String remoteIpPorts) {
        this.remoteIpPorts = remoteIpPorts;
    }
    private final Map<String, SynchronousQueue<RpcResponse>> results = new ConcurrentHashMap<>();
    public RpcClient() {
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        for (String remoteIpPort : remoteIpPorts.split(";")) {
            String[] data = remoteIpPort.split(":");
            // 客户端启动类,绑定端口,host
            Bootstrap bootstrap = new Bootstrap().remoteAddress(data[0], Integer.valueOf(data[1]));
            bootstraps.add(bootstrap);
            NioEventLoopGroup worker = new NioEventLoopGroup(1);
            bootstrap.group(worker)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            ChannelPipeline pipeline = channel.pipeline();
                            // 超时设定
                            pipeline.addLast(new IdleStateHandler(0, 0, 10));
                            pipeline.addLast(new JsonEncoder());
                            pipeline.addLast(new JsonDecoder());
                            pipeline.addLast(new RpcClientInboundHandler(results));
                        }
                    });
        }
    }


    public RpcResponse send(RpcRequest rpcRequest) {
        RpcResponse rpcResponse;
        rpcRequest.setRequestId(UUID.randomUUID().toString());
        try {
            Channel channel = TcpPools.getCannle(bootstraps);
            log.info("发送请求 -> {}", JSON.toJSONString(rpcRequest));
            channel.writeAndFlush(rpcRequest);
            SynchronousQueue<RpcResponse> queue = new SynchronousQueue<>();
            results.put(rpcRequest.getRequestId(), queue);
            // 阻塞等待获取响应
            rpcResponse = queue.poll();
            int count = 0;
            while (null == rpcResponse && count < 100){
                Thread.sleep(100);
                rpcResponse = queue.poll();
                count ++;
            }
            if(null == rpcResponse){
                channel.close();
                throw new RuntimeException("调用rpc超时");
            }
            if (!rpcResponse.isSuccess()) {
                throw new RuntimeException("调用结果异常,异常信息:" + rpcResponse.getErrorMessage());
            }
        } catch (Exception e) {
            log.error("send -> error", e);
            throw new RuntimeException(e.getLocalizedMessage());
        } finally {
            results.remove(rpcRequest.getRequestId());
        }
        return rpcResponse;
    }
}

总结

通过这个rpc示例服务,知道了怎么构建并运行一个rpc客户端及服务器。示例虽然难度不大,但基于netty实现的服务原理都大同小异,只要掌握了基础,就可以基于此实现更为复杂优秀的应用了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值