rmi+zookeeper模拟dubbo远程调用功能(基于springboot)

在我现在的工作中,经常就会跨部门调用接口,用到的就是HttpClient与dubbo,网上对于dubbo的介绍有很多,我就不摘抄定义了,据我的理解就一句话:具有服务治理、远程调用的一个分布式SOA框架。
但是,在工作中肯定是以使用为主,为了更深入的了解dubbo,所以,我就自己使用rmi和zookeeper模拟的一下dubbo的远程服务功能。在此处,为了搭建环境方便,我就使用了springboot,以下我就列出关键步骤。

1.搭建架构
因为要模拟dubbo的远程调用框架,所以provider和consumer当然必不可少(maven项目),但是除此之外,还需要一个interface的项目,用于定义远程调用的接口,分别在provider和consumer中进行引用。

2.接口示例
在interface项目定义的接口中,每一个接口都需要继承自java.rmi.Remote,以用于支持java的RMI协议
public interface UserService extends Remote{
public String getUsername() throws RemoteException;
}

3.接口示例
在provider中自然就是实现接口,并且实现类要继承java.rmi.server.UnicastRemoteObject类,否则将无法进行远程调用(此处无需再实现java.io.Serializable接口,在UnicastRemoteObject已经实现)。
public class UserServiceImpl extends UnicastRemoteObject implements UserService {
private static final long serialVersionUID = -4173905978568025L;
public UserServiceImpl() throws RemoteException {
super();
}
@Override
public String getUsername() throws RemoteException{
return “this is provider userService”;
}
}

4.RMI工具类
@Component
public class RMIUtils {
public static String getIp() {
InetAddress addr;
String ip = “”;
try {
addr = InetAddress.getLocalHost();
ip = addr.getHostAddress().toString();
} catch (UnknownHostException e) {
e.printStackTrace();
}
return ip;
}

public void bindService(String serviceName, T t) {
    String rmiPort = env.getProperty("rmi.port"); //将注册的服务接口配置在application.yml中
    String ip = RMIUtils.getIp();
    try {
            LocateRegistry.createRegistry(Integer.parseInt(rmiPort)); 
            String nameBind = "rmi://" + ip + ":" + rmiPort + "/" + serviceName;
            Naming.bind(nameBind, t);
            System.out.println("注册服务:" + nameBind);
       } catch (RemoteException e) {
            e.printStackTrace();
       } catch (MalformedURLException e) {
            e.printStackTrace();
       } catch (AlreadyBoundException e) {
            e.printStackTrace();
       }
	}

}

5.将provider注册的服务绑定到当前主机的某个接口上
在配置文件中添加
@Bean
public UserService userService(RMIUtils rmiUtils, Environment env) {
UserService userService = null;
try {
userService = new UserServiceImpl();
rmiUtils.bindService(“UserService”, userService);
} catch (RemoteException e) {
e.printStackTrace();
}
return userService;
}

此时,服务已经注册到本地的接口上了(接口可以在application.yml中进行配置)

6.consumer调用注册的服务
在配置文件中添加远程服务的注入
@Bean
public UserService userService() {
UserService userService = null;
try {
userService = (UserService)Naming.lookup(“rmi://127.0.0.1:12312/UserService”); // 此处的ip未必是127.0.0.1
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
return userService;
}

同时在需要调用的逻辑注入
@Autowired
private ConsumerService consumerService;
@ResponseBody
@RequestMapping(value="/getUsername", method=RequestMethod.GET)
public String getUsername() {
System.out.println(“getUsername”);
return consumerService.printUsername();
}

此时,我们熟悉的RMI调用就已经实现,但是请注意,在服务端ip不确定的时候,ip地址是无法确定的,而且,也不可能每一次都去代码中改,而是需要程序自己去发现服务,然后去调用。这就有必要去引入注册机。

7.引入zookeeper与工具类(因为只是示例,简单的增删改查即可)

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

// zookeeper工具类
public class ZKUtils {
     private CuratorFramework client;
     public ZKUtils(Environment env) {
           // 定义zk服务器的ip和port,多个节点的话用","分隔
           String connectString = env.getProperty("zookeeper.url");
           // retryPolicy是连接zk过程中重连策略,两个参数分别代表:两次重连的等待时间和最大重试次数
           RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 100);
           // 创建CuratorFramework实例,创建完成即代表连接zk成功
           client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
           // 调用start方法打开连接
           client.start();
     }
 
 public void closeClient() {
       if(client != null && client.getState() != CuratorFrameworkState.STOPPED) {
            client.close();
       }
 }
 
 /**
  * @description 创建一个zookeeper节点
  * @param path 路径
  * @param json 节点名
  * @throws Exception
  */
 public void createNode(String path, String data) throws Exception {
       try {
            // 传入路径和节点名调用zkclient自身的create方法
            // 注意此处创建的是临时节点,在provider断开之后就消失也就是没有服务(可以优化为临时节点序列)
            client.create()
                       .creatingParentContainersIfNeeded()
                       .withMode(CreateMode.EPHEMERAL) 
                       .forPath(path, data.getBytes());
       } catch (Exception e) {
            e.printStackTrace();
            throw e;
       }
 }

 /**
  * @description 查询zookeeper路径的节点
  * @param path 要查询节点的路径
  * @return attrJson 查询到的节点名
  * @throws Exception
  */
 public String queryNode(String path) {
       // 定义查询到的节点
       String data = null;
       try {
            // 调用zkclient的getData()方法
            byte[] byteNode = client.getData().forPath(path);
            data = new String(byteNode);
       } catch (Exception e) {
            e.printStackTrace();
            data = null;
       }
       return data;
 }
 
 /**
  * @description 修改zookeeper路径的节点值
  * @param path 要修改节点值的路径
  * @param json 要修改成的内容
  * @throws Exception
  */
 public void editNode(String path, String data) throws Exception {
       try {
            // 调用zkclient的setData()方法
            client.setData().forPath(path, data.getBytes());
       } catch (Exception e) {
            e.printStackTrace();
            throw e;
       }
 }
 
 /**
  * @description 删除zookeeper路径的节点
  * @param path 要删除节点的路径
  * @throws Exception
  */
 public void deteleNode(String path) throws Exception {
       try {
            // 调用zkclient的delete()方法
            client.delete()
                       .deletingChildrenIfNeeded()
                       .forPath(path);
       } catch (Exception e) {
            e.printStackTrace();
            throw e;
       }
 }

}

同时在application.yml中注入工具类

@Bean
 public ZKUtils zkUtils(Environment env) {
       ZKUtils zkUtils = new ZKUtils(env);
       return zkUtils;
 }

8.注册服务的时候将服务的信息存入到zookeeper中

public void bindService(String serviceName, T t) {
    String rmiPort = env.getProperty("rmi.port");
    String ip = RMIUtils.getIp();
 
    try {
            LocateRegistry.createRegistry(Integer.parseInt(rmiPort)); 
            String nameBind = "rmi://" + ip + ":" + rmiPort + "/" + serviceName;
            Naming.bind(nameBind, t);
	zkUtils.createNode("/" + serviceName, nameBind);
            System.out.println("注册服务:" + nameBind);
       } catch (RemoteException e) {
            e.printStackTrace();
       } catch (MalformedURLException e) {
            e.printStackTrace();
       } catch (AlreadyBoundException e) {
            e.printStackTrace();
       }
}

9.在cosumer中只需要知道自己需要的服务名称,就可以知道自己想要的服务在哪个服务器的哪个ip了
String userServiceUrl = zkUtils.queryNode("/UserService");
userService = (UserService)Naming.lookup(userServiceUrl);

当然,这里只是列出了关键的步骤,并不是完整的代码,不过按照这些步骤,就可以实现rmi+zookeeper的远程调用。
在这个示例中,zookeeper起到的作用是管理服务的作用,原理上很简单,但是真正想要做成一个完善的框架是不容易的,更何况还有其他很多的功能,如dubbo中的监视器、服务治理、负载均衡等等功能。
最后,还想提到的一点就是,RMI协议有一点弊端就是会被防护墙拦截,而dubbo的实现,支持着很多协议,如Hessian、HTTP、Redis、WebService等等,作为刚刚入坑的小白,还需要慢慢进步与学习才能继续分享。
如果有不恰当的地方,请各位多多指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值