【RPC】一步一步实现基于netty+zookeeper的RPC框架(二)

上一篇实现了服务注册发现和基本的字符串通信功能,这一篇则是实现我们平常使用RPC框架的使用类来调用的功能。

实现consumer端通过接口类来调用远程服务,主要核心在于使用动态代理和反射,这里就一步一步来实现。

这里贴出github代码地址,想直接看代码的可以直接下载运行:https://github.com/whiteBX/wrpc

首先来看consumer端代码,RPCConsumer完整代码如下:

public class RPCConsumer {

    /**
     * url处理器
     */
    private UrlHolder   urlHolder   = new UrlHolder();
    /**
     * netty客户端
     */
    private NettyClient nettyClient = new NettyClient();

    /**
     * 远程调用
     *
     * @param appCode
     * @param param
     * @return
     */
    public String call(String appCode, String param) {
        try {
            // 从zookeeper获取服务地址
            String serverIp = urlHolder.getUrl(appCode);
            if (serverIp == null) {
                System.out.println("远程调用错误:当前无服务提供者");
                return "connect error";
            }
            // 连接netty,请求并接收响应
            RpcClientNettyHandler clientHandler = new RpcClientNettyHandler();
            clientHandler.setParam(param);
            nettyClient.initClient(serverIp, clientHandler);
            String result = clientHandler.process();
            System.out.println(MessageFormat.format("调用服务器:{0},请求参数:{1},响应参数:{2}", serverIp, param, result));
            return result;
        } catch (Exception e) {
            System.out.println("远程服务调用失败:" + e);
            return "error";
        }
    }

    /**
     * 获取代理类
     * @param clazz
     * @param appCode
     * @return
     */
    public <T> T getBean(final Class<T> clazz, final String appCode) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String param = JSON.toJSONString(args[0]);
                String beanMessage = MessageFormat.format(ConsumerConstant.BEAN_STRING, clazz.getName(),
                    method.getName(), method.getParameterTypes()[0].getName());
                return JSON.parseObject(call(appCode, beanMessage.concat(param)), method.getReturnType());
            }
        });
    }
}

这里getBean方法主要是通过动态代理来获取代理类,传入的就是我们自己的Service的接口类,其中绑定了InvocationHandler来在调用接口类的方法时执行对应的远程调用操作,包括远程调用包装和返回值解析.

接下来看provider相关类,在RPCProvider类中新增服务注册方法,将我们自己的Service注册进系统缓存:

public class RPCProvider {
    /**
     * netty客户端
     */
    private static NettyClient nettyClient = new NettyClient();
    /**
     * zookeeper客户端
     */
    private static ZKClient    zkClient    = new ZKClient();

    public void registry(String server, int port) {
        // 开启netty监听客户端连接
        nettyClient.startServer(port);
        // 创建zk连接并创建临时节点
        ZooKeeper zooKeeper = zkClient.newConnection(ProviderConstant.ZK_CONNECTION_STRING,
            ProviderConstant.ZK_SESSION_TIME_OUT);
        String serverIp = server + CommonConstant.COMMOA + port;
        zkClient.createEphemeralNode(zooKeeper, ProviderConstant.APP_CODE, serverIp.getBytes());
    }

    /**
     * 注册服务提供者
     * @param clazz
     * @param obj
     */
    public void provide(Class<?> clazz, Object obj) {
        ProviderBeanHolder.regist(clazz.getName(), obj);
    }
}

其中的ProviderBeanHolder类代码如下,主要负责缓存服务注册信息:

 public class ProviderBeanHolder {

    /**
     * bean注册缓存
     */
    private static Map<String, Object> providerList = new HashMap<String, Object>();

    /**
     * 注册
     * @param clazzName
     * @param obj
     */
    public static void regist(String clazzName, Object obj) {
        providerList.put(clazzName, obj);
        System.out.println("注册provider:" + clazzName);
    }

    /**
     * 获取
     * @param clazzName
     * @return
     */
    public static Object getBean(String clazzName) {
        return providerList.get(clazzName);
    }
}

接下来来看RpcServerNettyHandler类,这个类负责接收客户端请求并处理,这里通过反射来调用服务注册的方法。

public class RpcServerNettyHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("服务端收到请求:" + msg);
        try {
            // 解析出 类名+方法名+请求参数类型(方法签名)
            String[] splitParam = msg.toString().split(ProviderConstant.DOLLAR_SPLIT);
            String[] beanMessage = splitParam[0].split(ProviderConstant.SHARP_SPLIT);
            // 获取注册的服务
            Object object = ProviderBeanHolder.getBean(beanMessage[0]);
            if (object == null) {
                System.out.println("服务类未注册:" + beanMessage[0]);
            }
            // 通过反射调用服务
            Class paramType = Class.forName(beanMessage[2]);
            Method method = object.getClass().getDeclaredMethod(beanMessage[1], paramType);
            Object response = method.invoke(object, JSON.parseObject(splitParam[1], paramType));
            // 请求响应
            ctx.writeAndFlush(JSON.toJSONString(response));
        } catch (Exception e) {
            System.out.println("服务异常");
        }
    }
}

这里主要就是用到了反射相关的知识,实现了服务的调用和响应,到这里相关的代码就实现了,下面来写个service测试一下:

public class HelloRequest {

    private int    seq;

    private String content;

    // 省略getter setter
}
public class HelloResponse {

    private int code;

    private String message;

    // 省略getter setter
}
public interface HelloService {

    HelloResponse hello(HelloRequest request);
}
public class HelloServiceImpl implements HelloService {

    public HelloResponse hello(HelloRequest request) {
        System.out.println("HelloService收到请求,序列号:" + request.getSeq());
        HelloResponse response = new HelloResponse();
        response.setCode(200);
        response.setMessage("success:" + request.getSeq());
        return response;
    }
}

测试代码如下:

    // ProviderTest相关代码
    public static void main(String[] args) throws InterruptedException {
        RPCProvider provider = new RPCProvider();
        provider.registry("127.0.0.1", 8091);
        provider.registry("127.0.0.1", 8092);
        provider.registry("127.0.0.1", 8093);
        provider.registry("127.0.0.1", 8094);
        provider.registry("127.0.0.1", 8095);
        provider.provide(HelloService.class, new HelloServiceImpl());

        Thread.sleep(Long.MAX_VALUE);
    }
    // ConsumerTest相关代码
    public static void main(String[] args) {
        RPCConsumer consumer = new RPCConsumer();
        HelloService helloService = consumer.getBean(HelloService.class, ConsumerConstant.APP_CODE);
        int i = 0;
        while (true) {
            HelloRequest request = new HelloRequest();
            request.setSeq(i++);
            HelloResponse helloResponse = helloService.hello(request);
            System.out.println("客户端收到响应:" + JSON.toJSONString(helloResponse));
            Thread.sleep(Long.MAX_VALUE);
        }
    }

执行结果如下:

// 服务端日志
zookeeper连接成功
临时节点创建成功:/registry/100000/0000000025
zookeeper连接成功
临时节点创建成功:/registry/100000/0000000026
zookeeper连接成功
临时节点创建成功:/registry/100000/0000000027
zookeeper连接成功
临时节点创建成功:/registry/100000/0000000028
zookeeper连接成功
临时节点创建成功:/registry/100000/0000000029
注册provider:org.white.wrpc.hello.service.HelloService
服务端收到请求:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":0}
服务端收到请求,序列号:0
服务端收到请求:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":1}
服务端收到请求,序列号:1
服务端收到请求:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":2}
服务端收到请求,序列号:2

// 客户端日志
zookeeper连接成功
调用服务器:127.0.0.1,8094,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":0},响应参数:{"code":200,"message":"success:0"}
客户端收到响应:{"code":200,"message":"success:0"}
调用服务器:127.0.0.1,8094,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":1},响应参数:{"code":200,"message":"success:1"}
客户端收到响应:{"code":200,"message":"success:1"}
调用服务器:127.0.0.1,8092,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":2},响应参数:{"code":200,"message":"success:2"}
客户端收到响应:{"code":200,"message":"success:2"}

此时修改Provider类里的端口,重新启动4个服务,可以看到客户端有如下日志:

zookeeper连接成功
zookeeper连接成功
zookeeper连接成功
zookeeper连接成功
调用服务器:127.0.0.1,8095,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":29},响应参数:{"code":200,"message":"success:29"}
客户端收到响应:{"code":200,"message":"success:29"}
调用服务器:127.0.0.1,8091,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":30},响应参数:{"code":200,"message":"success:30"}
客户端收到响应:{"code":200,"message":"success:30"}
调用服务器:127.0.0.1,8097,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":31},响应参数:{"code":200,"message":"success:31"}
客户端收到响应:{"code":200,"message":"success:31"}
调用服务器:127.0.0.1,8092,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":32},响应参数:{"code":200,"message":"success:32"}
客户端收到响应:{"code":200,"message":"success:32"}
调用服务器:127.0.0.1,8099,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":33},响应参数:{"code":200,"message":"success:33"}
客户端收到响应:{"code":200,"message":"success:33"}
调用服务器:127.0.0.1,8096,请求参数:org.white.wrpc.hello.service.HelloService#hello#org.white.wrpc.hello.model.request.HelloRequest${"seq":34},响应参数:{"code":200,"message":"success:34"}

可以看到,新增了服务,客户端什么都不用改,就能连接到新的服务,此时关闭新启的服务,会发现客户端会访问剩余的服务,不会出现任何问题。

到这里就实现了RPC框架的自己注册bean并通过接口调用.

后续将一步一步实现负载均衡/调用链路Trace记录/限流等功能,欢迎持续关注!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值