zookeeper的使用及Demo级RPC通信

本文介绍了如何使用Spring Boot结合Zookeeper实现服务注册与客户端的动态服务调用,包括服务端的bean配置、客户端的代理调用和zookeeper的接入,适合理解RPC通信和微服务架构的读者。
摘要由CSDN通过智能技术生成

目录

Demo级RPC通信

服务端

客户端

zookeeper的使用

服务端发布服务

客户端调用服务

源码地址

client:https://gitee.com/pengzhang_master/rpc-zookeeper-client.git

server:https://gitee.com/pengzhang_master/rpc-zookeeper-server.git

Demo级RPC通信

这是基于netty的Demo级RPC通信小项目,为后面zookeeper的使用做铺垫。

服务端

创建并初始化spring容器

public class Bootstrap {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        ((AnnotationConfigApplicationContext) context).start();
    }
}

通过配置类添加bean

@Configuration
@ComponentScan(basePackages = "com.server")
public class SpringConfig {

    @Bean(name="gpRpcServer")
    public GpRpcServer gpRpcServer(){
        return new GpRpcServer(8080);
    }
}

传入8080端口,供后续暴露端口使用。GpRpcServer类实现接口ApplicationContextAware和InitializingBean,重写方法setApplicationContext(ApplicationContext applicationContext)和afterPropertiesSet()

setApplicationContext(ApplicationContext applicationContext)获取spring容器的上下文,从而获取所有加了@RpcService注解的类的对应的bean,从而将接口和服务放入缓存

afterPropertiesSet()在初始化GpRpcServer的bean的时候通过netty暴露服务端口

public class GpRpcServer implements ApplicationContextAware,InitializingBean {

    private Map<String,Object> handlerMap=new HashMap();

    private int port;

    private IRegistryCenter registryCenter=new RegistryCenterWithZk();

    public GpRpcServer(int port) {
        this.port = port;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        //接收客户端的链接
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        //处理已经被接收的链接
        EventLoopGroup workerGroup=new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap=new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).
                    childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().
                                    addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null))).
                                    addLast(new ObjectEncoder()).
                                    addLast(new ProcessorHandler(handlerMap));
                        }
                    });
            serverBootstrap.bind(port).sync();
        }finally {
           /* workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();*/
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //拿到所有加了@RpcService注解的类的对应的bean
        Map<String,Object> serviceBeanMap=applicationContext.getBeansWithAnnotation(RpcService.class);
        if(!serviceBeanMap.isEmpty()){
            for(Object servcieBean:serviceBeanMap.values()){
                //迭代bean,拿到注解
                RpcService rpcService=servcieBean.getClass().getAnnotation((RpcService.class));
                String serviceName=rpcService.value().getName();//拿到接口类定义
                String version=rpcService.version(); //拿到版本号
                if(!StringUtils.isEmpty(version)){
                    serviceName+="-"+version;
                }
                //放入缓存
                handlerMap.put(serviceName,servcieBean);
                //服务注册
                registryCenter.registry(serviceName,getAddress()+":"+port);
            }
        }
    }
    private static String getAddress(){
        InetAddress inetAddress=null;
        try {
            inetAddress=InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return inetAddress.getHostAddress();// 获得本机的ip地址
    }
}

ProcessorHandler继承SimpleChannelInboundHandler<RpcRequest>,重写channelRead0(ChannelHandlerContext channelHandlerContext, RpcRequest rpcRequest),rpcRequest是netty接受的数据。

channelHandlerContext.writeAndFlush写出数据,ChannelHandlerContext代表了一个ChannelHandler和ChannelPipeline之间的关系,ChannelHandlerContext创建于ChannelHandler被载入到ChannelPipeline的时候,ChannelHandlerContext主要功能是管理在同一ChannelPipeline中各个ChannelHandler的交互。

invoke中通过接受的消息拿到客户端请求参数、类和方法名反射调用,获取调用结果。

public class ProcessorHandler extends SimpleChannelInboundHandler<RpcRequest> {

    private Map<String,Object> handlerMap;


    public ProcessorHandler(Map<String,Object> handlerMap) {
        this.handlerMap = handlerMap;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcRequest rpcRequest) throws Exception {
        Object result=invoke(rpcRequest);
        channelHandlerContext.writeAndFlush(result).addListener(ChannelFutureListener.CLOSE);

    }

    private Object invoke(RpcRequest request) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //反射调用
        String serviceName=request.getClassName();
        String version=request.getVersion();
        //增加版本号的判断
        if(!StringUtils.isEmpty(version)){
            serviceName+="-"+version;
        }

        Object service=handlerMap.get(serviceName);
        if(service==null){
            throw new RuntimeException("service not found:"+serviceName);
        }
        Object[] args=request.getParameters(); //拿到客户端请求的参数
        Method method=null;
        Class clazz=Class.forName(request.getClassName()); //跟去请求的类进行加载
        method=clazz.getMethod(request.getMethodName(),request.getParamTypes()); //sayHello, saveUser找到这个类中的方法
        Object result=method.invoke(service,args);//HelloServiceImpl 进行反射调用
        return result;
    }
}

客户端

创建并初始化spring容器,在上下文中拿到代理对象并进行调用。

public class App {
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext context = new
                AnnotationConfigApplicationContext(SpringConfig.class);
        RpcProxyClient rpcProxyClient = context.getBean(RpcProxyClient.class);

        IHelloService iHelloService = rpcProxyClient.clientProxy
                (IHelloService.class, "v2.0");
        for (int i = 0; i < 100; i++) {
            Thread.sleep(2000);
            System.out.println(iHelloService.sayHello(1.0));
        }
    }
}

通过配置类添加bean

@Configuration
public class SpringConfig {

    @Bean(name="rpcPRoxyClient")
    public RpcProxyClient proxyClient(){
        return new RpcProxyClient();
    }
}

RpcProxyClient中的clientProxy返回代理对象

public class RpcProxyClient {

    private IServiceDiscovery serviceDiscovery=new ServiceDiscoveryWithZk();

    public <T> T clientProxy(final Class<T> interfaceCls, String version){

        return (T)Proxy.newProxyInstance(interfaceCls.getClassLoader(),
                new Class<?>[]{interfaceCls},new RemoteInvocationHandler(serviceDiscovery,version));
    }
}

在RemoteInvocationHandler.invoke中包装请求数据rpcRequest,包括执行方法和参数。再根据服务名加版本号从zookeeper中获取服务地址serviceAddress。根据服务地址发送数据包,并接受返回数据。

public class RemoteInvocationHandler implements InvocationHandler {

    private IServiceDiscovery serviceDiscovery;
    private String version;

    public RemoteInvocationHandler(IServiceDiscovery serviceDiscovery,String version) {
        this.serviceDiscovery=serviceDiscovery;
        this.version=version;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //请求数据的包装
        RpcRequest rpcRequest=new RpcRequest();
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParamTypes(method.getParameterTypes());
        rpcRequest.setParameters(args);
        rpcRequest.setVersion(version);
        String serviceName=rpcRequest.getClassName();
        if(!StringUtils.isEmpty(version)){
            serviceName=serviceName+"-"+version;
        }
        String serviceAddress=serviceDiscovery.discovery(serviceName);
        //远程通信
        RpcNetTransport netTransport=new RpcNetTransport(serviceAddress);
        Object result=netTransport.send(rpcRequest);

        return result;
    }
}

RpcNetTransport.send中利用netty将数据包发送到对应的服务。

public class RpcNetTransport extends SimpleChannelInboundHandler<Object>  {

    private String serviceAddress;

    public RpcNetTransport(String serviceAddress) {
        this.serviceAddress = serviceAddress;
    }

    private final Object lock=new Object();
    private Object result;


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        this.result=o;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常:");
        ctx.close();
    }

    public Object send(RpcRequest request){
        NioEventLoopGroup eventLoogGroup=new NioEventLoopGroup();
        Bootstrap bootstrap=new Bootstrap();
        bootstrap.group(eventLoogGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                socketChannel.pipeline().
                        addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null))).
                        addLast(new ObjectEncoder()).
                        addLast(RpcNetTransport.this);
            }
        }).option(ChannelOption.TCP_NODELAY,true);
        try {
            String urls[]=serviceAddress.split(":");
            ChannelFuture future=bootstrap.connect(urls[0],Integer.parseInt(urls[1])).sync();
            future.channel().writeAndFlush(request).sync();

            if(request!=null){
                future.channel().closeFuture().sync();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            eventLoogGroup.shutdownGracefully();
        }
        return result;
    }
}

zookeeper的使用

服务端发布服务

在服务启动时,初始化zookeeper的连接。

public class RegistryCenterWithZk implements IRegistryCenter{

    CuratorFramework curatorFramework =null;

    {
        //初始化zookeeper的连接, 会话超时时间是5s,衰减重试
        curatorFramework = CuratorFrameworkFactory.builder().
                connectString(ZkConfig.CONNECTION_STR).sessionTimeoutMs(5000).
                retryPolicy(new ExponentialBackoffRetry(1000, 3)).
                namespace("registry")
                .build();
        curatorFramework.start();
    }
、、、、、

在初始化GpRpcServer的bean的时候,在setApplicationContext中获取spring上下文拿到所有加了RpcService注解的bean,并将接口定义和对应的bean放入缓存,然后将服务注册到zookeeper。

@Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //拿到所有加了@RpcService注解的类的对应的bean
        Map<String,Object> serviceBeanMap=applicationContext.getBeansWithAnnotation(RpcService.class);
        if(!serviceBeanMap.isEmpty()){
            for(Object servcieBean:serviceBeanMap.values()){
                //迭代bean,拿到注解
                RpcService rpcService=servcieBean.getClass().getAnnotation((RpcService.class));
                String serviceName=rpcService.value().getName();//拿到接口类定义
                String version=rpcService.version(); //拿到版本号
                if(!StringUtils.isEmpty(version)){
                    serviceName+="-"+version;
                }
                //放入缓存
                handlerMap.put(serviceName,servcieBean);
                //服务注册
                registryCenter.registry(serviceName,getAddress()+":"+port);
            }
        }
    }

在将服务注册到zookeeper时,将服务名和ip、端口号获取并传入。将服务名创建为持久化节点,将ip:port创建为临时节点。

@Override
    public void registry(String serviceName, String serviceAddress) {
        String servicePath="/"+serviceName;
        try {
            //判断节点是否存在
            if(curatorFramework.checkExists().forPath(servicePath)==null){
                //创建持久化节点
                curatorFramework.create().creatingParentsIfNeeded().
                        withMode(CreateMode.PERSISTENT).forPath(servicePath);
            }
            //serviceAddress: ip:port
            String addressPath=servicePath+"/"+serviceAddress;
            //创建临时节点
            curatorFramework.create().withMode(CreateMode.EPHEMERAL).forPath(addressPath);
            System.out.println("服务注册成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

客户端调用服务

在spring上下文中获取RpcProxyClient的bean,调用rpcProxyClient.clientProxy(IHelloService.class, "v2.0")获取代理对象。并调用sayHello方法。

在调用sayHello时实际调用的是invoke,在invoke中通过传入的serviceDiscovery调用discovery(String serviceName)获取服务地址,并完成远程通信,实际上就是将类名、方法名、参数封装后发给服务端,在服务端通过以上数据进行反射调用。

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //请求数据的包装
        RpcRequest rpcRequest=new RpcRequest();
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParamTypes(method.getParameterTypes());
        rpcRequest.setParameters(args);
        rpcRequest.setVersion(version);
        String serviceName=rpcRequest.getClassName();
        if(!StringUtils.isEmpty(version)){
            serviceName=serviceName+"-"+version;
        }
        String serviceAddress=serviceDiscovery.discovery(serviceName);
        //远程通信
        RpcNetTransport netTransport=new RpcNetTransport(serviceAddress);
        Object result=netTransport.send(rpcRequest);

        return result;
    }
public Object send(RpcRequest request){
        NioEventLoopGroup eventLoogGroup=new NioEventLoopGroup();
        Bootstrap bootstrap=new Bootstrap();
        bootstrap.group(eventLoogGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                socketChannel.pipeline().
                        addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null))).
                        addLast(new ObjectEncoder()).
                        addLast(RpcNetTransport.this);
            }
        }).option(ChannelOption.TCP_NODELAY,true);
        try {
            String urls[]=serviceAddress.split(":");
            ChannelFuture future=bootstrap.connect(urls[0],Integer.parseInt(urls[1])).sync();
            future.channel().writeAndFlush(request).sync();

            if(request!=null){
                future.channel().closeFuture().sync();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            eventLoogGroup.shutdownGracefully();
        }
        return result;
    }

在ServiceDiscoveryWithZk中初始化zookeeper的连接。在discovery中完成服务地址的查找和根据已知的服务地址进行负载均衡,以及监听zookeeper上注册的服务节点,若感知到节点发生变化,则刷新缓存中的服务地址。

@Override
    public String discovery(String serviceName) {
        //完成了服务地址的查找(服务地址被删除)
        String path="/"+serviceName; //registry/com.server.HelloService
        if(serviceRepos.isEmpty()) {
            try {
                serviceRepos = curatorFramework.getChildren().forPath(path);
                registryWatch(path);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //针对已有的地址做负载均衡
        LoadBalanceStrategy loadBalanceStrategy=new RandomLoadBalance();
        return loadBalanceStrategy.selectHost(serviceRepos);
    }
private void registryWatch(final String path) throws Exception {
        PathChildrenCache nodeCache=new PathChildrenCache(curatorFramework,path,true);
        PathChildrenCacheListener nodeCacheListener= (curatorFramework1, pathChildrenCacheEvent) -> {
            System.out.println("客户端收到节点变更的事件");
            serviceRepos=curatorFramework1.getChildren().forPath(path);// 再次更新本地的缓存地址
        };
        nodeCache.getListenable().addListener(nodeCacheListener);
        nodeCache.start();

    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值