在我现在的工作中,经常就会跨部门调用接口,用到的就是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等等,作为刚刚入坑的小白,还需要慢慢进步与学习才能继续分享。
如果有不恰当的地方,请各位多多指正!