接着之前的RPC实现:https://www.cnblogs.com/wuzhenzhao/p/9962250.html RPC框架的简单实现,基于这个小程序,在我学习完Zookeeper之后如何将注册中心与RPC调用结合起来。直接进入正题

  我这边用到的 curator 客户端工具的依赖是:版本太高不兼容的话会报异常

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.5.0</version>
</dependency>

  承接上文,我们之前在服务发布的时候是直接写死一个地址发布的,现在需要结合注册中心 ,那么我们势必要在发布服务的时候在指定的 zookeeper服务上面注册我们的节点,基于 zookeeper 临时节点的特性,一旦服务 down机,那么这个节点也会消失,同时会有事件触发,添加一个注册服务:

服务端:

public interface IRegisterCenter {

    /**
* 注册服务名称和服务地址
* @param serviceName
* @param serviceAddress
*/
void register(String serviceName,String serviceAddress);
}

  实现:注册服务的实现就是往zookeeper 的指点根节点下插入一个临时节点,如果根节点不存在则先创建,由于 curator 做了很好的实现,这里先用他来做实现。

public class RegisterCenterImpl implements IRegisterCenter {

    //zk链接地址
public final static String CONNNECTION_STR = "192.168.1.101:2181"; //注册根节点
public final static String ZK_REGISTER_PATH = "/registrys"; private CuratorFramework curatorFramework; {// 这段代码无非是连接服务器,自己看着写在哪里把
curatorFramework = CuratorFrameworkFactory.builder().
connectString(CONNNECTION_STR). //
sessionTimeoutMs().
retryPolicy(new ExponentialBackoffRetry(,
)).build();
curatorFramework.start();
} @Override
public void register(String serviceName, String serviceAddress) {
//注册相应的服务
String servicePath = ZK_REGISTER_PATH + "/" + serviceName; try {
//判断 /registrys/product-service是否存在,不存在则创建
if (curatorFramework.checkExists().forPath(servicePath) == null) {
curatorFramework.create().creatingParentsIfNeeded().
withMode(CreateMode.PERSISTENT).forPath(servicePath, "".getBytes());
}
// 组装节点地址
String addressPath = servicePath + "/" + serviceAddress;
String rsNode = curatorFramework.create().withMode(CreateMode.EPHEMERAL).
forPath(addressPath, "".getBytes());
System.out.println("服务注册成功:" + rsNode); } catch (Exception e) {
e.printStackTrace();
}
}
}

  有了注册服务我们需要进入服务发布的类里面进行一些修改,由于服务的地址及端口都注册到注册中心上了,我们需要增加注册中心属性,便已获取相关信息。

public class RpcServer {
//创建一个线程池
private static final ExecutorService executorService= Executors.newCachedThreadPool(); private IRegisterCenter registerCenter; //注册中心
private String serviceAddress; //服务发布地址 // 存放服务名称和服务对象之间的关系
Map<String,Object> handlerMap=new HashMap<>(); public RpcServer(IRegisterCenter registerCenter, String serviceAddress) {
this.registerCenter = registerCenter;
this.serviceAddress = serviceAddress;
} /**
* 绑定服务名称和服务对象
* @param services
*/
public void bind(Object... services){
for(Object service:services){// 这里为了获取对应服务的类名,我们这里定义了一个注解来实现 代码请看下面
RpcAnnotation annotation=service.getClass().getAnnotation(RpcAnnotation.class);
String serviceName=annotation.value().getName();
handlerMap.put(serviceName,service);//绑定服务接口名称对应的服务
}
} public void publisher(){
ServerSocket serverSocket=null;
try{
String[] addrs=serviceAddress.split(":");//这个时候服务的ip port 都是从这个注册地址上获取
serverSocket=new ServerSocket(Integer.parseInt(addrs[])); //启动一个服务监听
// handlerMap 可能存放多个发布服务,我这里演示的是一个
for(String interfaceName:handlerMap.keySet()){
registerCenter.register(interfaceName,serviceAddress);
System.out.println("注册服务成功:"+interfaceName+"->"+serviceAddress);
} while(true){ //循环监听
Socket socket=serverSocket.accept(); //监听服务
//通过线程池去处理请求
executorService.execute(new ProcessorHandler(socket,handlerMap));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
}
}

  我们可以采用注解的方式把这个服务注册上去,注解类:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcAnnotation {
/**
* 对外发布的服务的接口地址
* @return
*/
Class<?> value();
// 暂时没用 可以处理版本
String version() default "";
}

  然后再对应需要发布的服务类实现上加上注解:

public interface HelloService {
String sayHello(String msg);
}
@RpcAnnotation(HelloService.class)
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String msg) {
return " RPC Hello, " + msg;
}
}

  上面监听服务请求的处理也发生了改变,之前传入的是单独的service 现在需要把绑定的 handlerMap 带过去。 接下来看 ProcessorHandler :

public class ProcessorHandler implements Runnable {
private Socket socket; Map<String, Object> handlerMap;// 现在需要从map里获取需要发布的绑定服务 public ProcessorHandler(Socket socket, Map<String, Object> handlerMap) {
this.socket = socket;
this.handlerMap = handlerMap;
} @Override
public void run() {
//处理请求
ObjectInputStream inputStream = null;
try {
//获取客户端的输入流
inputStream = new ObjectInputStream(socket.getInputStream());
//反序列化远程传输的对象RpcRequest
RpcRequest request = (RpcRequest) inputStream.readObject();
Object result = invoke(request); //通过反射去调用本地的方法 //通过输出流讲结果输出给客户端
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(result);
outputStream.flush();
inputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} private Object invoke(RpcRequest request) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
//以下均为反射操作,目的是通过反射调用服务
Object[] args = request.getParameters();
Class<?>[] types = new Class[args.length];
for (int i = ; i < args.length; i++) {
types[i] = args[i].getClass();
}
//从handlerMap中,根据客户端请求的地址,去拿到响应的服务,通过反射发起调用
Object service = handlerMap.get(request.getClassName());
Method method = service.getClass().getMethod(request.getMethodName(), types);
return method.invoke(service, args);
}
}

  然后发布服务:

public static void main(String[] args) throws IOException {
HelloService helloService=new HelloServiceImpl();
IRegisterCenter registerCenter=new RegisterCenterImpl(); RpcServer rpcServer=new RpcServer(registerCenter,"127.0.0.1:8080");
rpcServer.bind(helloService);
rpcServer.publisher();
System.in.read();
}

  输出:

服务注册成功:/registrys/com.wuzz.demo.rpc.server.HelloService/127.0.0.1:
注册服务成功:com.wuzz.demo.rpc.server.HelloService->127.0.0.1:

  对应的zk服务器上也创建了对应的节点:

客户端:

  对于客户端需要从远程调用服务,现在是需要从注册中心先去获取该服务类锁对应的再注册中心注册的服务地址及端口,再去发起请求,并且对指定到 路径进行监听,那么客户端需要定义一个服务的发现服务:

public interface IServiceDiscovery {
/**
* 根据请求的服务地址,获得对应的调用地址
* @param serviceName
* @return
*/
String discover(String serviceName);
}

  实现:

public class ServiceDiscoveryImpl implements IServiceDiscovery {
// 从指定节点下获取的子节点列表
List<String> repos = new ArrayList<>();
// 服务器连接地址,就是服务端的 ZkConfig.CONNNECTION_STR
private String address; private CuratorFramework curatorFramework; //注册根节点
public final static String ZK_REGISTER_PATH = "/registrys"; // 为了方便测试,这里直接再构造里面启动连接
public ServiceDiscoveryImpl(String address) {
this.address = address; curatorFramework = CuratorFrameworkFactory.builder().
connectString(address).
sessionTimeoutMs().
retryPolicy(new ExponentialBackoffRetry(,
)).build();
curatorFramework.start();
} @Override // 本质就是获取指定服务节点下的子节点
public String discover(String serviceName) {
String path = ZK_REGISTER_PATH + "/" + serviceName;
try {
repos = curatorFramework.getChildren().forPath(path); } catch (Exception e) {
throw new RuntimeException("获取子节点异常:" + e);
}
//动态发现服务节点的变化
registerWatcher(path); //简单负载均衡机制
if (repos == null || repos.size() == ) {
return null;
}
if (repos.size() == ) {
return repos.get();
} int len = repos.size();
Random random = new Random();
return repos.get(random.nextInt(len));//返回调用的服务地址
} // 这里是之前提到的用 curator 客户端进行时间注册的操作
private void registerWatcher(final String path) {
PathChildrenCache childrenCache = new PathChildrenCache
(curatorFramework, path, true); PathChildrenCacheListener pathChildrenCacheListener = new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
repos = curatorFramework.getChildren().forPath(path);
}
};
childrenCache.getListenable().addListener(pathChildrenCacheListener);
try {
childrenCache.start();
} catch (Exception e) {
throw new RuntimeException("注册PatchChild Watcher 异常" + e);
}
}
}

  接下去由于客户端是通过动态代理去获取远程对象,由于原来参数为 IP Port ,现在需要通过注册中心去拿:

public class RpcClientProxy {

    // 服务发现
private IServiceDiscovery serviceDiscovery; public RpcClientProxy(IServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
} /**
* 创建客户端的远程代理。通过远程代理进行访问
* @param interfaceCls
* @param <T>
* @return
*/
public <T> T clientProxy(final Class<T> interfaceCls){
//使用到了动态代理。
return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(),
new Class[]{interfaceCls},new RemoteInvocationHandler(serviceDiscovery));
}
}

  对应的RemoteInvocationHandler:

public class RemoteInvocationHandler implements InvocationHandler {
private IServiceDiscovery serviceDiscovery; public RemoteInvocationHandler(IServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//组装请求
RpcRequest request = new RpcRequest();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameters(args); String serviceAddress = serviceDiscovery.discover(request.getClassName()); //根据接口名称得到对应的服务地址
//通过tcp传输协议进行传输
TCPTransport tcpTransport = new TCPTransport(serviceAddress);
//发送请求
return tcpTransport.send(request);
}
}

  由于 IP Port 都是来自注册中心的一个服务节点下的子节点的信息,而zookeeper服务器上存储的是该服务的全路径名称,在其之下的直接点才是真的IP/PORT的信息,所以我们通过这个request.getClassName()获取服务的地址,这里的传输方法也要进行修改:仅仅是修改了 IP/PORT的来源。

public class TCPTransport {
private String serviceAddress; public TCPTransport(String serviceAddress) {
this.serviceAddress=serviceAddress;
} //创建一个socket连接
private Socket newSocket(){
System.out.println("创建一个新的连接");
Socket socket;
try{
String[] arrs=serviceAddress.split(":");
socket=new Socket(arrs[],Integer.parseInt(arrs[]));
return socket;
}catch (Exception e){
throw new RuntimeException("连接建立失败");
}
} public Object send(RpcRequest request){
Socket socket=null;
try {
socket = newSocket();
//获取输出流,将客户端需要调用的远程方法参数request发送给
ObjectOutputStream outputStream=new ObjectOutputStream
(socket.getOutputStream());
outputStream.writeObject(request);
outputStream.flush();
//获取输入流,得到服务端的返回结果
ObjectInputStream inputStream=new ObjectInputStream
(socket.getInputStream());
Object result=inputStream.readObject();
inputStream.close();
outputStream.close();
return result; }catch (Exception e ){
throw new RuntimeException("发起远程调用异常:",e);
}finally {
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

  启动客户端:

public static void main(String[] args) throws InterruptedException {
IServiceDiscovery serviceDiscovery = new
ServiceDiscoveryImpl("192.168.1.101:2181"); RpcClientProxy rpcClientProxy = new RpcClientProxy(serviceDiscovery); HelloService hello = rpcClientProxy.clientProxy(HelloService.class);
System.out.println(hello.sayHello("wuzz"));
}

  输出:

创建一个新的连接
RPC Hello , wuzz

  这样就完成了注册中心的简单结合,这里还可以通过注解里的 version实现版本的管理,以及如果想要实现负载的效果,由于客户端已经实现了负载的简单实现,只需要启动两个服务端,向zk注册,然后启动客户端去调用即可。

RPC与Zookeeper注册中心的简单实现的更多相关文章

  1. Spring Cloud 系列之 ZooKeeper 注册中心

    什么是注册中心 服务注册中心是服务实现服务化管理的核心组件,类似于目录服务的作用,主要用来存储服务信息,譬如提供者 url 串.路由信息等.服务注册中心是微服务架构中最基础的设施之一. 注册中心可以说 ...

  2. dubbo实战之三:使用Zookeeper注册中心

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. dubbo连接zookeeper注册中心因为断网导致线程无限等待问题【转】

    最近维护的系统切换了网络环境,由联通换成了电信网络,因为某些过滤规则导致系统连不上zookeeper服务器(应用系统机器在深圳,网络为电信线路,zookeeper服务器在北京,网络为联通线路),因为我 ...

  4. Dubbo框架应用之(三)--Zookeeper注册中心、管理控制台的安装及讲解

    我是在linux下使用dubbo-2.3.3以上版本的zookeeper注册中心客户端.Zookeeper是Apache Hadoop的子项目,强度相对较好,建议生产环境使用该注册中心.Dubbo未对 ...

  5. springcloud之服务注册与发现(zookeeper注册中心)-Finchley.SR2版

    新年第一篇博文,接着和大家分享springcloud相关内容:本次主要内容是使用cloud结合zookeeper作为注册中心来搭建服务调用,前面几篇文章有涉及到另外的eureka作为注册中心,有兴趣的 ...

  6. Zookeeper注册中心的搭建

    一.Zookeeper的介绍 Zookeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用 ...

  7. Zookeeper 注册中心

    一.Zookeeper的介绍 Zookeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用 ...

  8. Dubbo(六):zookeeper注册中心的应用

    Dubbo中有一个非常本质和重要的功能,那就是服务的自动注册与发现,而这个功能是通过注册中心来实现的.而dubbo中考虑了外部许多的注册组件的实现,zk,redis,etcd,consul,eurek ...

  9. 微服务架构 | 3.3 Apache Zookeeper 注册中心

    @ 目录 前言 1. Zookeeper 基础知识 1.1 Zookeeper 是什么 1.2 Zookeeper 的数据结构 1.3 Watcher 机制 1.4 常见应用场景分析 1.5 Zook ...

随机推荐

  1. mysql数据库的基本操作:创建数据库、查看数据库、修改数据库、删除数据库

    本节相关: 创建数据库 查看数据库 修改数据库 删除数据库 首发时间:2018-02-13 20:47 修改: 2018-04-07:考虑到规范化,将所有语法中“关键字”变成大写;以及因为整理“mys ...

  2. Eclipse启动时发生An internal error occurred duri ng: "Initializing Java Tooling ----网上的坑爹的一个方法

    补充一下: 上面的方法不行. 我的个人解决方法 出现这种问题的原因,我的是eclipse换了,工作目录还是用之前的那个 把build Automatically的钩去掉 假设我们是用之前的worksp ...

  3. [20190101]块内重整.txt

    [20190101]块内重整.txt --//我不知道用什么术语表达这样的情况,我仅仅一次开会对方这么讲,我现在也照用这个术语.--//当dml插入数据到数据块时,预留一定的空间(pctfree的百分 ...

  4. [20181109]12c sqlplus rowprefetch参数5

    [20181109]12c sqlplus rowprefetch参数5.txt --//这几天一直在探究设置sqlplus参数rowprefetch与arraysize的关系,有必要做一些总结以及一 ...

  5. C#-委托(十七)

    概述 委托(Delegate) 是存有对某个方法的引用的一种引用类型变量 委托特别用于实现事件和回调方法.所有的委托都派生自 System.Delegate 类 委托是一个类,么它就可以被定义在任何地 ...

  6. JS学习之路之JavaScript match() 方法

    match() 方法,在字符串内找到相应的值并返回这些值,()内匹配字符串或者正则表达式. 该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置. d ...

  7. JavaSE: Java 5 新特性

    Java5新特性 1.Java 语言 1.1 Generics 1.2 foreach 1.3 自动拆箱装箱 1.4 enum 1.5 可变参数 varargs 1.6 static import 1 ...

  8. PATH_SEPARATOR

    PATH_SEPARATOR是一个常量,在Linux系统中是一个" : "号,Windows上是一个";"号.所以编写程序时最好用常量 PATH_SEPARAT ...

  9. Vue学习之路5-v-model指令

    1. 指令释义 v-model在表单控件或者组件上创建双向绑定,本质上是负责监听用户的输入事件(onchange,onkeyup,onkeydown等,具体是哪个,还请查阅官方底层实现文档)以更新数据 ...

  10. Node爬虫之初体验

    记得之前就听说过爬虫,个人初步理解就是从网页中抓取一些有用的数据,存储到本地,今天就当是小牛试刀,拿来溜溜...... 实现需求: 抓取课程数据,输入url后并在浏览器端以一定的数据格式显示出来(如下 ...