RPC框架

介绍

RPC(远程过程调用)可以分为两部分:远程过程以及过程调用。远程过程是指每台机器上提供的服务,过程调用就是对远程过程调用以及数据传输。

优势

RPC带来的优势其实就是分布式架构带来的优势。在RPC的支持下,可以实现模块的分布式部署,可以实现更好的维护性,扩展性以及协同式开发。

缺点

RPC的出现为构建分布式架构带来了便利,但是分布式系统本身的问题也被暴漏了下来。带来的问题有四:

  • 通信延迟
    跨机器、网络出现的通信延迟的概率一定比同一台机器的进程间通信大。编解码也会带来性能损耗。而且网络通信也是不可靠的会出现乱序、错误、丢数据等问题。
  • 地址空间隔离
    内存地址在一台机器上才有效。
  • 局部故障
    故障的发现和通知需要引入新的组件,故障的类型判断也会变得复杂,比如是网络链路故障还是机器故障,继而会存在数据不一致问题,故障节点和正常节点会出现数据不一致问题。
  • 并发问题

RPC和分布式框架的区别

RPC实现了服务消费者调用方Client与服务提供实现方Server之间的点对点调用流程,一般来说,包括stub、通信、数据的序列化/反序列化。调用方与服务提供方一般采用直连的调用方式。而分布式服务框架,除了包括RPC的特性,还包括多台Server提供服务的负载均衡策略及实现,服务的注册、发布与引入,以及服务的高可用策略、服务治理等特性。

这里挖个小坑 下几篇文章我来分享我搭建SpringCloudAlibaba的过程。

rpc的网络交互大致流程:

client向server发送request后开始等待。
server收到、处理、回复client。
client收到response做出反应。

搭建过程

我会使用到Netty进行RPC的搭建。

  • 首先我们先要建立公共辅助类
  • RPC 通信实体
/**
 * RPC 通信实体
 * @author lx
 * @date 2022/8/2 15:38
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RpcMessage implements Serializable {

    private static final long serialVersionUID = 430507739718447406L;
    /**
     * interface接口名
     */
    private String name;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 参数类型
     */
    private Class<?>[] parTypes;
    /**
     * 参数
     */
    private Object[] pars;
    /**
     * 结果值
     */
    private Object result;


}
  • 其次是RPC使用的辅助标签
/**
 * RPC标签
 * @author lx
 * @date 2022/8/2 15:36
 */
@Target(value = {ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RpcServer {
}
  • 然后是生产者端
    需要配置如下
@Configuration
public class ClientBeanConfig {

    @Bean
    public NettyClient nettyClient() {
        return new NettyClient();
    }

    @Bean
    public NettyClientBeanPostProcessor nettyClientBeanPostProcessor(NettyClient nettyClient) {
        return new NettyClientBeanPostProcessor(nettyClient);
    }
}
@ChannelHandler.Sharable
public class ClientHandle extends SimpleChannelInboundHandler<RpcMessage> {
    /**
     * 定义消息Map,将连接通道Channel作为key,消息返回值作为value
     */
    private final ConcurrentMap<Channel, RpcMessage> rpcMessageConcurrentMap;

    public ClientHandle(ConcurrentMap<Channel, RpcMessage> rpcMessageConcurrentMap) {
        this.rpcMessageConcurrentMap = rpcMessageConcurrentMap;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcMessage rpcMessage) throws Exception {
        System.out.println("客户端收到服务端消息:"+ rpcMessage);
        rpcMessageConcurrentMap.put(channelHandlerContext.channel(), rpcMessage);
    }
}

开启标签

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration({ClientBeanConfig.class})
public @interface EnableNettyClient {
}
public class NettyClient {

    private Channel channel;
    /**
     * 存放请求编号与响应对象的映射关系
     */
    private final ConcurrentMap<Channel, RpcMessage> rpcMessageConcurrentMap = new ConcurrentHashMap<>();

    public RpcMessage send(int port, final RpcMessage rpcMessage) {
        //客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast(new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())))
                                    .addLast(new ObjectEncoder())
                                    .addLast(new ClientHandle(rpcMessageConcurrentMap));
                        }
                    });
            final ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", port).syncUninterruptibly();
            System.out.println("连接服务端成功: " + channelFuture.channel().remoteAddress());
            channel = channelFuture.channel();
            channel.writeAndFlush(rpcMessage);
            System.out.println("发送数据成功:"+ rpcMessage);
            channel.closeFuture().syncUninterruptibly();
            return rpcMessageConcurrentMap.get(channel);
        } catch (Exception e) {
            System.out.println("client exception"+ e);
            return null;
        } finally {
            group.shutdownGracefully();
            //移除请求编号和响应对象直接的映射关系
            rpcMessageConcurrentMap.remove(channel);
        }
    }

    public void stop() {
        channel.close();
    }
}

连接发送信息

/**
 * Netty客户端Bean后置处理器
 * 实现Spring后置处理器接口:BeanPostProcessor
 * 在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的
 * @author lx
 * @date 2022/8/2 15:50
 */
public class NettyClientBeanPostProcessor implements BeanPostProcessor {

    private final NettyClient nettyClient;

    public NettyClientBeanPostProcessor(NettyClient nettyClient) {
        this.nettyClient = nettyClient;
    }

    /**
     * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
     * 注意:方法返回值不能为null
     * 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到Bean实例对象
     * 因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, @Nullable String beanName) throws BeansException {
        //获取实例Class
        Class<?> beanClass = bean.getClass();
        do {
            //获取该类所有字段
            Field[] fields = beanClass.getDeclaredFields();
            for (Field field : fields) {
                //判断该字段是否拥有@RpcServer
                if (field.getAnnotation(RpcServer.class) != null) {
                    field.setAccessible(true);
                    try {
                        //通过JDK动态代理获取该类的代理对象
                        Object o = Proxy.newProxyInstance(field.getType().getClassLoader(), new Class[]{field.getType()}, new ClientInvocationHandle(nettyClient));
                        //将代理类注入该字段
                        field.set(bean, o);
                        System.out.println("创建代理类 ===>>> "+beanName);
                    } catch (IllegalAccessException e) {
                        System.out.println(e.getMessage());
                    }
                }
            }
        } while ((beanClass = beanClass.getSuperclass()) != null);
        return bean;
    }

    /**
     * 实例化、依赖注入、初始化完毕时执行
     * 注意:方法返回值不能为null
     * 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到Bean实例对象
     * 因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 可以根据beanName不同执行不同的处理操作
        return bean;
    }

    /**
     * JDK动态代理处理器
     */
    static class ClientInvocationHandle implements InvocationHandler {
        private final NettyClient nettyClient;

        public ClientInvocationHandle(NettyClient nettyClient) {
            this.nettyClient = nettyClient;
        }

        /**
         * 代理方法调用
         *
         * @param proxy  代理类
         * @param method 方法
         * @param args   参数
         * @return 返回值
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            //组装Netty参数
            RpcMessage rpcMessage = RpcMessage.builder()
                    .name(method.getDeclaringClass().getName())
                    .methodName(method.getName())
                    .parTypes(method.getParameterTypes())
                    .pars(args)
                    .build();
            //调用Netty,发送数据
            RpcMessage send = nettyClient.send(1111, rpcMessage);
            System.out.println("接收到服务端数据:"+send+", 返回结果值 ====》》》》"+  send.getResult());
            return send.getResult();
        }
    }
}

controller 业务加标签@RpcServer
最后启动项加上该标签@EnableNettyClient

  • 消费端配置
/**
 * Netty服务端
 *
 * @author lx
 * @date 2022/8/2 16:30
 **/
@Slf4j
public class NettyServer {

    /**
     * server端处理器
     */
    private final ServerHandle serverHandle;
    /**
     * 服务端通道
     */
    private Channel channel;

    /**
     * 构造器
     *
     * @param serverHandle server处理器
     */
    public NettyServer(ServerHandle serverHandle) {
        this.serverHandle = serverHandle;
    }

    /**
     * 启动
     *
     * @param port 启动端口
     */
    public void start(int port) {
        EventLoopGroup boss = new NioEventLoopGroup(1);
        EventLoopGroup worker = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast(new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())))
                                    .addLast(new ObjectEncoder())
                                    .addLast(serverHandle);
                        }
                    });

            final ChannelFuture channelFuture = serverBootstrap.bind(port).syncUninterruptibly();
            log.info("服务端启动-端口: {}", port);
            channel = channelFuture.channel();
            channel.closeFuture().syncUninterruptibly();
        } catch (Exception e) {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

    /**
     * 关闭当前通道
     */
    public void stop() {
        channel.close();
    }

}

收到后调用方法

/**
 * Netty Server端Handle处理类,消息体RpcMessage
 * 实现ApplicationContextAware接口:该接口可以加载获取到所有的 spring bean。
 * 实现了这个接口的bean,当spring容器初始化的时候,会自动的将ApplicationContext注入进来
 *
 * @author ZC
 * @date 2021/3/1 22:15
 */
@Slf4j
@ChannelHandler.Sharable
public class ServerHandle extends SimpleChannelInboundHandler<RpcMessage> implements ApplicationContextAware {

    private Map<String, Object> serviceMap;

    /**
     * 在类被Spring容器加载时会自动执行setApplicationAware
     *
     * @param applicationContext Spring上下文
     * @throws BeansException 异常信息
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //从Spring容器中获取到所有拥有@RpcServer注解的Beans集合,Map<Name(对象类型,对象全路径名),实例对象>
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(RpcServer.class);
        log.info("被@RpcServer注解加载的Bean: {}", beansWithAnnotation);
        if (beansWithAnnotation.size() > 0) {
            Map<String, Object> map = new ConcurrentHashMap<>(16);
            for (Object o : beansWithAnnotation.values()) {
                //获取该实例对象实现的接口Class
                Class<?> anInterface = o.getClass().getInterfaces()[0];
                //获取该接口类名,作为Key,实例对象作为Value
                map.put(anInterface.getName(), o);
            }
            //使用变量接住map
            serviceMap = map;
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("客户端连接了: {}", ctx.channel().remoteAddress());
        super.channelActive(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("异常信息");
        cause.printStackTrace();
        super.exceptionCaught(ctx, cause);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcMessage rpcMessage) throws Exception {
        log.info("客户端发送的消息:{}", rpcMessage);
        //从Map中获取实例对象
        Object service = serviceMap.get(rpcMessage.getName());
        //获取调用方法
        Method method = service.getClass().getMethod(rpcMessage.getMethodName(), rpcMessage.getParTypes());
        method.setAccessible(true);
        //反射调用实例对象方法,获取返回值
        Object result = method.invoke(service, rpcMessage.getPars());
        rpcMessage.setResult(JSONUtil.toJsonStr(result));
        log.info("回给客户端的消息:{}", rpcMessage);
        //Netty服务端将数据写会Channel并发送给客户端,同时添加一个监听器,当所有数据包发送完成后,关闭通道
        channelHandlerContext.writeAndFlush(rpcMessage).addListener(ChannelFutureListener.CLOSE);
    }

}

实现类加上该标签 @RpcServer
启动项加上启动标签@EnableNettyServer

这套基础的RPC就搭建完成了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值