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