接着之前的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. Spotlight on Mysql在Windows平台下的安装及使用简介

    Spotlight on Mysql在Windows平台下的安装及使用简介   by:授客 QQ:1033553122 1.   测试环境 Win7 64位 mysql-connector-odbc- ...

  2. JNI NDK (AndroidStudio+CMake )实现C C++调用Java代码流程

    JNI/NDK Java调用C/C++前言  通过第三篇文章讲解在实际的开发过程中Java层调用C/C++层的处理流程.其实我们在很大的业务里也需要C/C+ +层去调用Java层,这两层之间的相互调用 ...

  3. fetch数据请求的封装

    export default class HttpUtils { static get(url){ return new Promise((resolve,reject)=>{ fetch(ur ...

  4. filter帅选

    var ages = [32, 33, 16, 40]; ages= ages.filter(function checkAdult(obj) {//obj表示数组中的每个元素 return obj ...

  5. 转载:使用redis+flask维护动态代理池

    githu源码地址:https://github.com/Germey/ProxyPool更好的代理池维护:https://github.com/Python3WebSpider/ProxyPool ...

  6. git 入门教程之分支管理

    背景 什么是分支?简单地说,分支就是两个相对独立的时间线,正常情况下,独立的时间线永远不会有交集,彼此不知道对方的存在,只有特定情况下,两条时间线才会相遇,因为相遇,所以相知,因为相知,所以改变! 正 ...

  7. Deep learning深度学习的十大开源框架

    Google开源了TensorFlow(GitHub),此举在深度学习领域影响巨大,因为Google在人工智能领域的研发成绩斐然,有着雄厚的人才储备,而且Google自己的Gmail和搜索引擎都在使用 ...

  8. django数据查询之聚合查询和分组查询

    <1> aggregate(*args,**kwargs): 通过对QuerySet进行计算,返回一个聚合值的字典.aggregate()中每一个参数都指定一个包含在字典中的返回值.即在查 ...

  9. 创建属于其他Session的进程

    创建其他Session(User)的进程需要拿到对应Session的Token作为CreateProcessAsUser的参数来启动进程. 修改有System权限的Token的TokenId为其他Se ...

  10. GitHub-分支管理01

    参考博文:廖雪峰Git教程 1. 分支说明 分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不 ...