文章目录

  • Netty实现RPC框架
  • 一、RPC简介
  • 二、代码实现
  • 1. 需求介绍
  • 2. 实现步骤
  • 3. 公共代码
  • 4. 服务端代码
  • 5. 客户端代码


一、RPC简介

RPC全称为remote procedure call,即远程过程调用。借助RPC可以做到像本地调用一样调用远程服务,是一种进程间的通信方式。

比如两台服务器A和B,A服务器上部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上的应用提供的方法。由于两个应用不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。

注意:RPC并不是一个具体的技术,而是指整个网络远程调用过程。


哪些框架使用的是RPC_哪些框架使用的是RPC

二、代码实现

1. 需求介绍

要求用Netty实现一个简单的RPC框架,消费者和提供者约定接口和协议,消费者远程调用提供者的服务。

2. 实现步骤

  1. 创建一个接口,定义抽象方法。用于消费者和提供者之间的约定。
  2. 创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据。
  3. 创建一个消费者,该类需要透明的调用自己不存在的提供者的方法,内部需要使用Netty进行数据通信。
  4. 提供者与消费者数据传输使用 json 字符串数据格式。
  5. 使用 Netty 集成 SpringBoot 环境实现。

哪些框架使用的是RPC_rpc_02

注意:服务端注解要使用在实现类上,用于标记服务是否对外暴露。

3. 公共代码

  1. 公共接口,消费者与提供者的约定
public interface IUserService {

    /**
     * 根据ID查询用户
     */
    User getById(int id);
}
  1. 封装的请求对象
@Data
public class RpcRequest {

    /**
     * 请求对象的ID
     */
    private String requestId;
    /**
     * 要调用方法所在类名
     */
    private String className;
    /**
     * 要调用的方法名
     */
    private String methodName;
    /**
     * 要调用方法的参数类型
     */
    private Class<?>[] parameterTypes;
    /**
     * 要调用方法的入参
     */
    private Object[] parameters;
}
  1. 封装的响应对象
@Data
public class RpcResponse {

    /**
     * 响应ID
     */
    private String requestId;

    /**
     * 错误信息
     */
    private String error;

    /**
     * 返回的结果
     */
    private Object result;
}
  1. pojo类
@Data
public class User {
    private int id;
    private String name;
}

4. 服务端代码

  1. 定义注解,用于暴露服务接口,暴露服务端的getUserById方法
@Target(ElementType.TYPE) //用于类上
@Retention(RetentionPolicy.RUNTIME) //在运行时可以获取到
public @interface RpcService {
}
  1. 服务端实现公共接口,客户端要调用的方法
@RpcService //对外暴露,加自定义注解
@Service
public class UserServiceImpl implements IUserService {
    Map<Object, User> userMap = new HashMap();

    @Override
    public User getById(int id) {
        if (userMap.size() == 0) {
            User user1 = new User();
            user1.setId(1);
            user1.setName("张三");
            User user2 = new User();
            user2.setId(2);
            user2.setName("李四");
            userMap.put(user1.getId(), user1);
            userMap.put(user2.getId(), user2);
        }
        return userMap.get(id);
    }
}
  1. 服务端代码
/**
 * Netty的服务端
 * 启动服务端监听端口
 * 实现Spring容器生命周期接口DisposableBean,在Spring容器关闭的时候本类中的资源也要关闭
 */
@Component
public class NettyRpcServer implements DisposableBean {

    /**
     * 注入自定义的处理器
     */
    @Autowired
    NettyServerHandler nettyServerHandler;

    // 定义boss和worker线程
    EventLoopGroup bossGroup = null;
    EventLoopGroup workerGroup = null;

    /**
     * 启动服务端,参数表示ip和端口号
     */
    public void start(String host, int port) {
        try {
            //1.创建bossGroup和workerGroup
            bossGroup = new NioEventLoopGroup(1);
            workerGroup = new NioEventLoopGroup();

            //2.设置启动助手
            ServerBootstrap bootstrap = new ServerBootstrap();

            //3.设置启动参数
            bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //添加String的编解码器,使得客户端和服务器可以直接使用String类型进行交流
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());
                        
                        //添加自定义处理器
                        ch.pipeline().addLast(nettyServerHandler);
                    }
                });

            //绑定ip和端口号
            ChannelFuture channelFuture = bootstrap.bind(host, port).sync();

            System.out.println("======Netty服务端启动成功======");

            //监听通道的关闭状态(此处并没有真正的关闭),阻塞直到close事件发生
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
            //关闭资源
            if (bossGroup != null) {
                bossGroup.shutdownGracefully();
            }
            if (workerGroup != null) {
                workerGroup.shutdownGracefully();
            }
        }
    }

    /**
     * 实现DisposableBean接口要重写的方法,Spring关闭的时候执行
     */
    @Override
    public void destroy() throws Exception {
        //关闭资源
        if (bossGroup != null) {
            bossGroup.shutdownGracefully();
        }
        if (workerGroup != null) {
            workerGroup.shutdownGracefully();
        }
    }
}
  1. 服务端自定义处理器
/**
 * 服务端自定义处理器
 * 1.将标有@RpcService注解的bean进行缓存,方便后续查找调用
 * 2.接收客户端的请求
 * 3.根据传递过来的beanName从缓存中查找bean
 * 4.通过反射调用bean的方法
 * 5.给客户端响应
 */
@Component
@ChannelHandler.Sharable //设置通道共享,NettyServerHandler默认是单例的,不能由多个客户端所共享
public class NettyServerHandler extends SimpleChannelInboundHandler<String> implements ApplicationContextAware {

    /**
     * 定义缓存,将将标有@RpcService注解的bean缓存至此map
     */
    static Map<String, Object> SERVICE_INSTANCE_MAP = new HashMap<>();

    /**
     * 1.将标有@RpcService的注解的bean进行缓存,实现ApplicationContextAware接口后重写的方法
     * @param applicationContext ioc容器
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //1.1 通过注解获取bean的集合(可能有多个类都使用了这个注解)
        Map<String, Object> serviceMap = applicationContext.getBeansWithAnnotation(RpcService.class);

        //1.2 循环遍历
        Set<Map.Entry<String, Object>> entries = serviceMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {

            //serviceBean就是自定义的UserServiceImpl的对象
            Object serviceBean = entry.getValue();
            if (serviceBean.getClass().getInterfaces().length == 0) {
                throw new RuntimeException("对外暴露的服务必须实现接口");
            }

            //默认处理第一个接口作为缓存bean的名字(一个类可以实现多个接口)
            String serviceName = serviceBean.getClass().getInterfaces()[0].getName();
            SERVICE_INSTANCE_MAP.put(serviceName, serviceBean);

            System.out.println(SERVICE_INSTANCE_MAP);
            //{包名.IUserService=包名.UserServiceImpl@44c73c26}
        }
    }

    /**
     * 2.接收客户端的请求,通道读取就绪事件,读取客户端的消息
     * @param ctx 每一个Handler实例与Pipeline之间的桥梁就是ChannelHandlerContext实例
     * @param msg 客户端发送过来的消息,是一个JSON格式的RpcRequest的对象
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

        // 接收客户端的请求
        RpcRequest rpcRequest = JSON.parseObject(msg, RpcRequest.class);

        //创建响应对象,请求id是请求中传递过来的
        RpcResponse rpcResponse = new RpcResponse();
        rpcResponse.setRequestId(rpcRequest.getRequestId());

        //调用自定义的业务处理方法(上述的3、4步),并将处理结果保存到响应中
        try {
            rpcResponse.setResult(handler(rpcRequest));
        } catch (Exception e) {
            e.printStackTrace();
            //业务处理过程中出现的异常也要返回给客户端
            rpcResponse.setError(e.getMessage());
        }

        //5. 将响应对象给客户端响应
        ctx.writeAndFlush(JSON.toJSONString(rpcResponse));
    }

    /**
     * 定义业务处理方法(上述的3、4步)
     */
    private Object handler(RpcRequest rpcRequest) throws InvocationTargetException {
        //3.根据传递过来的beanName从缓存中查找,className是请求对象中的属性
        Object serviceBean = SERVICE_INSTANCE_MAP.get(rpcRequest.getClassName());
        if (serviceBean == null) {
            throw new RuntimeException("服务端没有找到服务");
        }

        // 4.通过反射调用bean的方法
        // 调用cglid提供的FastClass中的方法,创建指定类的代理对象
        FastClass proxyClass = FastClass.create(serviceBean.getClass());

        //参数分别表示客户端调用的服务端的方法和参数类型,通过返回的method完成最终的方法调用
        FastMethod method = proxyClass.getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());
        return method.invoke(serviceBean, rpcRequest.getParameters()); //反射
    }
}

通过上述代码可知,必须加了自定义注解 @RpcService 的 bean 才会被缓存(服务才可以被调用),即自定义注解用来暴露服务,且服务端必须实现接口,约定协议,否则抛出异常。

  1. 服务端启动类
/**
 * 项目启动的时候需要将服务端启动并监听端口
 * 启动类实现CommandLineRunner接口
 * CommandLineRunner接口是在容器启动成功后的最后一步回调(类似开机自启动)
 */
@SpringBootApplication
public class ServerBootstrapApplication implements CommandLineRunner {

    /**
     * 注入Netty服务端
     */
    @Autowired
    NettyRpcServer rpcServer;

    public static void main(String[] args) {
        SpringApplication.run(ServerBootstrapApplication.class, args);
    }

    /**
     * 实现CommandLineRunner接口后要重写的方法,创建线程调用服务端的方法启动服务端
     */
    @Override
    public void run(String... args) throws Exception {
        new Thread(new Runnable() {
            @Override
            public void run() {
                rpcServer.start("127.0.0.1", 8899);
            }
        }).start();
    }
}

流程总结:

哪些框架使用的是RPC_nio_03

5. 客户端代码

  1. 客户端业务处理类,向服务端发送数据并接收返回结果
@Component
public class NettyRpcClientHandler extends SimpleChannelInboundHandler<String> implements Callable {

    ChannelHandlerContext context; //全局的ChannelHandlerContext对象

    private String reqMsg; //发送的消息

    private String respMsg; //接收的消息

    //给发送的消息赋值
    public void setReqMsg(String reqMsg) {
        this.reqMsg = reqMsg;
    }

    /**
     * 通道读取就绪事件,读取服务端消息
     * @param msg 接收到的服务端数据
     */
    @Override
    protected synchronized void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        respMsg = msg;
        //唤醒call方法的等待线程
        notify();
    }

    /***
     * 通道连接就绪事件,给全局的ChannelHandlerContext对象赋值
     * 不能连接建立之后就给服务端发送消息,因为不知道要发送什么消息,客户端调用controller方法才可发送消息,所以实现Callable接口,调用call方法时才发送消息
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        context = ctx;
    }
    
     /***
     * 使用线程的等待与唤醒完成消息的同步处理,因为netty仅支持异步,不能发送完消息然后等待服务端的消息返回。
     */
    
    /**
     * 实现Callable接口重写的方法
     * 通过全局的ChannelHandlerContext对象给服务端发送消息,调用call方法才会发送
     */
    @Override
    public synchronized Object call() throws Exception {
        context.writeAndFlush(reqMsg);
        //客户端向服务端发送消息之后,要等待服务端的响应,调用本类的channelRead0方法读取服务端的数据
        //将线程处于等待状态
        wait();
        return respMsg; //被唤醒之后,返回从服务端接收到的消息
    }
}
  1. 客户端代码
/**
 * Netty客户端
 * 1.连接服务端,工程一启动就去连接服务端,实现InitializingBean接口
 * 2.提供发送消息的方法
 * 3.工程停止后关闭资源,实现DisposableBean接口
 */
@Component
public class NettyRpcClient implements InitializingBean, DisposableBean {

    EventLoopGroup group = null;
    Channel channel = null;

    //注入自定义的客户端处理器
    @Autowired
    NettyRpcClientHandler nettyRpcClientHandler;

    //创建线程池,用于执行自定义处理器中重写的call方法,向服务端发送消息
    ExecutorService service = Executors.newCachedThreadPool();

    /**
     * 实现InitializingBean接口要重写的方法
     * 1.连接服务端
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        try {
            //1.1 创建线程组
            group = new NioEventLoopGroup();
            //1.2 创建客户端启动助手
            Bootstrap bootstrap = new Bootstrap();
            //1.3 设置参数
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //添加编解码器
                            ch.pipeline().addLast(new StringDecoder());
                            ch.pipeline().addLast(new StringEncoder());
                            //添加自定处理类
                            ch.pipeline().addLast(nettyRpcClientHandler);
                        }
                    });
            //1.4 连接服务端
            channel = bootstrap.connect("127.0.0.1", 8899).sync().channel();
        } catch (Exception e) {
            e.printStackTrace();
            if (channel != null) {
                channel.close();
            }
            if (group != null) {
                group.shutdownGracefully();
            }
        }
    }

    /**
     * 3.工程停止后关闭资源
     * 实现DisposableBean接口要重写的方法
     */
    @Override
    public void destroy() throws Exception {
        if (channel != null) {
            channel.close();
        }
        if (group != null) {
            group.shutdownGracefully();
        }
    }

    /**
     * 2. 消息发送的方法,调用自定义处理器中的方法定义发送的消息
     * @param msg 要发送的消息
     * @return 服务端返回的消息
     */
    public Object send(String msg) throws ExecutionException, InterruptedException {
        
        //调用自定义处理器的方法给要发送的消息赋值
        nettyRpcClientHandler.setReqMsg(msg);

        //执行自定义处理器中的call方法,向服务端发送消息
        Future submit = service.submit(nettyRpcClientHandler);

        //得到call方法的返回值,也就是服务端返回的消息
        return submit.get();
    }
}
  1. 公共接口IUserService的代理
    调用 IUserService 接口的 getById() 方法时,自动的封装RpcRequest,不然每调用一次方法就手动封装一个RpcRequest对象,不合理。
    而且不论其他的controller访问公共接口时,不用再去创建公共接口,直接访问缓存中的代理对象即可。
/**
 * IUserService的代理
 * 1. 发送消息之前将消息封装成RpcRequest对象,并发送消息
 * 2. 解析服务器返回的数据
 */
@Component
public class RpcClientProxy {

    @Autowired
    NettyRpcClient rpcClient;

    //缓存创建好的代理对象,方便复用
    Map<Class, Object> SERVICE_PROXY = new HashMap<>();

    /**
     * 获取代理对象,参数表示获取谁的代理对象
     */
    public Object getProxy(Class serviceClass) {
        
        //从缓存中查找
        Object proxy = SERVICE_PROXY.get(serviceClass);

        if (proxy == null) {
            //创建代理对象
            //第二参数表示IUserService
            proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(),
                    new Class[]{serviceClass}, new InvocationHandler() {
                        
                        //invoke方法不会被立即调用,只有访问接口的方法时才会调用
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //1.封装请求对象
                            RpcRequest rpcRequest = new RpcRequest();
                            rpcRequest.setRequestId(UUID.randomUUID().toString());
                            
                            //method是接口的方法封装后的对象,getclass得到的就是接口,而服务端的缓存bean的名字就是接口的名字
                            rpcRequest.setClassName(method.getDeclaringClass().getName());
                            rpcRequest.setMethodName(method.getName());
                            rpcRequest.setParameterTypes(method.getParameterTypes());
                            rpcRequest.setParameters(args);

                            try {
                                //2.发送消息,返回值表示服务端返回的数据
                                Object msg = rpcClient.send(JSON.toJSONString(rpcRequest));

                                //3.将服务端的消息转化
                                RpcResponse rpcResponse = JSON.parseObject(msg.toString(), RpcResponse.class);
                                if (rpcResponse.getError() != null) {
                                    throw new RuntimeException(rpcResponse.getError());
                                }
                                if (rpcResponse.getResult() != null) {
                                    return JSON.parseObject(rpcResponse.getResult().toString(),
                                            method.getReturnType());
                                }
                                return null;
                            } catch (Exception e) {
                                e.printStackTrace();
                                throw e;
                            }
                        }
                    });
            
            //将代理对象放入缓存
            SERVICE_PROXY.put(serviceClass, proxy);
            return proxy;
        } else {
            return proxy;
        }
    }
}
  1. 引用代理类
/**
 * 引用代理类,使用在controller的IUserService上
 */
@Target(ElementType.FIELD) //作用于字段
@Retention(RetentionPolicy.RUNTIME) //在运行时可以获取得到
public @interface RpcReference {
}
  1. 用户控制类
/**
 * 用户控制类,远程调用服务端的getUserById方法
 */
@RestController
@RequestMapping("/user")
public class UserController {

    //使用了注解之后,目前还不能使用代理对象,必须经过后续的处理才能将代理对象注入进来
    @RpcReference
    IUserService userService;

    @RequestMapping("/getUserById")
    public User getUserById(int id) {
        //调用服务端方法
        return userService.getById(id);
    }
}
  1. bean的后置增强,将代理对象注入到使用了自定义的 @RpcReference 注解的字段上
    类似于 @Autowired 注解,使用这个注解之后将某个对象注入到这个字段上
/**
 * bean的后置增强
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Autowired
    RpcClientProxy rpcClientProxy;

    /**
     * 自定义注解的注入
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //1.得到bean(u)中的所有字段
        Field[] declaredFields = bean.getClass().getDeclaredFields();
        
        //遍历字段
        for (Field field : declaredFields) {
            
            //2.查找字段中是否包含RpcReference这个注解
            RpcReference annotation = field.getAnnotation(RpcReference.class);
            
            if (annotation != null) {
                
                //3.获取代理对象
                Object proxy = rpcClientProxy.getProxy(field.getType());
                
                try {
                    //4.属性注入,field表示加了RpcReference注解的IUserService对象
                    field.setAccessible(true);
                    field.set(bean, proxy); //userService由代理对象创建
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }
}

流程总结:

哪些框架使用的是RPC_java_04

运行结果:

将服务端先启动,然后启动客户端,进行访问:


哪些框架使用的是RPC_rpc_05