本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1. 使用zookeeper注册中心替换map
在上一篇文章中,用到了RemoteRegistryMap替换了zookeeper实现服务的缓存,此方式的缺点是需要通过文件来共享map,如果服务提供者接口列表发生变化,那么也不能及时修改,那么我们怎么使用zookeeper注册中心实现服务注册和发现功能?
zookeeper 本身是一个文件系统,可以在将服务的url相关信息挂载到指定节点目录下,然后通过watch机制监听服务提供者接口服务是否发生变化,如果有变化,那么watcher将会收到zookeeper的通知。
2. 整合curator
curator是对zookeeper client的二次封装,在提供客户端实例上提供了更好的便利, 主要包含framework(核心)、client(提供客户端支持)、recipes(集群管理, leader选举、分布式锁)这三个模块。
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.1.0</version>
</dependency>
<!-- curator 客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.1.0</version>
</dependency>
<!-- leader选举、分布式锁-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
添加上述依赖后,我们就通过curator创建一个zookeeper client, windows环境下可以换为localhost, 在静态块中初始化连接,可以在启动时加载。
static CuratorFramework client;
static {
client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new RetryNTimes(3, 1000));
client.start();
}
主要包含2个方法,registry()和get()方法。
注: 在用get()方法时,我们可以利用缓存思想,通过判断接口的Class名(全限定名) 是否在map里,如果在那么从map取,如果不在那么再从zookeeper注册中心拉取,然后缓存到map里。
package com.example.dubbo.registry;
import com.alibaba.fastjson.JSONObject;
import com.example.dubbo.framework.URL;
import com.example.dubbo.provider.api.UserInterface;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author bingbing
* @date 2021/4/30 0030 18:39
* 注册接口名,服务地址列表
*/
public class ZookeeperRegistry {
// 在静态块里初始化客户端
static CuratorFramework client;
private static String dubboServicePath = "/dubbo/service/";
static {
client = CuratorFrameworkFactory.newClient("localhost:2181", new RetryNTimes(3, 1000));
client.start();
}
// 用map做本地缓存
private static Map<String, List<URL>> listMap = new HashMap<>();
// 使用临时节点挂载URL相关信息
public static void registry(String interfaceName, URL url) {
// 1. 将url信息挂载到zookeeper目录下
try {
String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(String.format(dubboServicePath + "%s/%s", interfaceName, JSONObject.toJSONString(url)));
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
public static List<URL> get(String interfaceName) {
// 1. 如果本地map里有,那么就从map中取数据
if (listMap.containsKey(interfaceName)) {
// 监听机制
// watch();
return listMap.get(interfaceName);
}
List<URL> urlList = new ArrayList<>();
// 2. 如果本地map没有,那么从zookeeper注册中心拿数据
try {
List<String> strs = client.getChildren().forPath(String.format(dubboServicePath + "%s", interfaceName));
for (String s : strs) {
// 反序列化url对象
urlList.add(JSONObject.parseObject(s, URL.class));
}
} catch (Exception e) {
e.printStackTrace();
}
// 3. 将zookeeper里的信息缓存到本地map里
listMap.put(interfaceName, urlList);
return urlList;
}
public static void main(String[] args) {
System.out.println(client);
// 1. 注册
String interfaceName = "UserService";
List<URL> urlList = Arrays.asList(new URL("localhost", 8080), new URL("localhost", 9000));
for (URL url : urlList) {
ZookeeperRegistry.registry(interfaceName, url);
}
List<URL> urlRes = ZookeeperRegistry.get(interfaceName);
System.out.println(urlRes);
}
}
每次从zookeeper获取出来的数据需要先反序列化一下,然后再返回,因为在存入zookeeper前用的是Json格式的数据
接下来将zookeeper接入!
修改消费者
将RemoteRegistry更换为zookeeperRegistry
package com.example.dubbo.framework;
import com.example.dubbo.protocol.http.HttpClient;
import com.example.dubbo.provider.LocalRegistry;
import com.example.dubbo.registry.RemoteRegistry;
import com.example.dubbo.registry.ZookeeperRegistry;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Random;
/**
* @author bingbing
* @date 2021/4/29 0029 10:46
*/
public class ProxyFactory<T> {
public static <T> T getProxy(final Class interfaceclass) {
return (T) Proxy.newProxyInstance(interfaceclass.getClassLoader(), new Class[]{interfaceclass}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 可以自己定义mock, 通过Mock实现服务降级
// 2. 获取服务地址列表, 从zookeeper里拉取到新的服务列表
// List<URL> lists = RemoteRegistry.get(interfaceclass.getName());
List<URL> lists = ZookeeperRegistry.get(interfaceclass.getName());
// 3. 负载均衡策略选择一个url进行使用。
URL url = LoadBalance.random(lists);
// 3. 发送http 请求
HttpClient client = new HttpClient();
Invocation invocation = new Invocation(interfaceclass.getName(), method.getName(), objects, method.getParameterTypes());
Object obj = client.send(url, invocation);
return obj;
}
});
}
}
修改服务提供者
同样在启动时,用ZookeeperRegistry 替换RemoteRegistry
package com.example.dubbo.provider;
import com.example.dubbo.framework.URL;
import com.example.dubbo.protocol.http.HttpServer;
import com.example.dubbo.provider.api.UserInterface;
import com.example.dubbo.provider.impl.UserInterfaceImpl;
import com.example.dubbo.registry.RemoteRegistry;
import com.example.dubbo.registry.ZookeeperRegistry;
/**
* @author bingbing
* @date 2021/4/29 0029 10:18
*/
public class ProviderApplication {
public static void main(String[] args) {
// 1. 指定容器启动地址和端口
URL url = new URL("127.0.0.1", 9090);
// 2. 本地注册
LocalRegistry.regist(UserInterface.class.getName(), UserInterfaceImpl.class);
// 3. 远程注册
// RemoteRegistry.registry(UserInterface.class.getName(), url);
ZookeeperRegistry.registry(UserInterface.class.getName(), url);
// 4. 启动容器,处理请求
HttpServer server = new HttpServer();
server.start(url);
}
}
重写启动服务后,观察调用结果:
至此,zookeeper整合成功!
3. 提供watcher机制
在每次从本地map中获取数据时,先从看一下Zookeeper有没有给我消费者这边发消息,如果有的话,那么我就先更新Map,然后再从本地map中返回。
package com.example.dubbo.registry;
import com.alibaba.fastjson.JSONObject;
import com.example.dubbo.framework.URL;
import com.example.dubbo.provider.api.UserInterface;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.Watcher;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author bingbing
* @date 2021/4/30 0030 18:39
* 注册接口名,服务地址列表
*/
public class ZookeeperRegistry {
// 在静态块里初始化客户端
static CuratorFramework client;
private static String dubboServicePath = "/dubbo/service/";
static {
client = CuratorFrameworkFactory.newClient("localhost:2181", new RetryNTimes(3, 1000));
client.start();
}
// 用map做本地缓存
private static Map<String, List<URL>> listMap = new HashMap<>();
// 使用临时节点挂载URL相关信息
public static void registry(String interfaceName, URL url) {
// 1. 将url信息挂载到zookeeper目录下
try {
String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(String.format(dubboServicePath + "%s/%s", interfaceName, JSONObject.toJSONString(url)));
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 通过watch机制监听zookeeper
*
* @return
*/
public static void watch() {
//将interfaceName的服务地址列表更新zookeeper
AtomicReference<Boolean> watchFlag= new AtomicReference<>(false);
Watcher watcher = (WatchedEvent -> {
watchFlag.set(true);
System.out.println("监听到事件!" + WatchedEvent);
});
if (watchFlag.get()==false) {
return;
}
// 1.获取到所有的接口节点,刷新本地map
try {
List<String> strs = client.getChildren().forPath(dubboServicePath);
for (String s : strs) {
// 先获取到类Class
String userInterfaceName = JSONObject.parseObject(s, UserInterface.class).getClass().getName();
List<URL> urlList = new ArrayList<>();
if (listMap.containsKey(userInterfaceName)) {
List<String> urlStrs = client.getChildren().forPath(dubboServicePath + userInterfaceName);
for (String urlStr : urlStrs) {
urlList.add(JSONObject.parseObject(urlStr, URL.class));
}
//更新map
listMap.put(userInterfaceName, urlList);
}
}
} catch (Exception e) {
}
}
public static List<URL> get(String interfaceName) {
// 1. 如果本地map里有,那么就从map中取数据
if (listMap.containsKey(interfaceName)) {
// 监听机制
watch();
return listMap.get(interfaceName);
}
List<URL> urlList = new ArrayList<>();
// 2. 如果本地map没有,那么从zookeeper注册中心拿数据
try {
List<String> strs = client.getChildren().forPath(String.format(dubboServicePath + "%s", interfaceName));
for (String s : strs) {
// 反序列化url对象
urlList.add(JSONObject.parseObject(s, URL.class));
}
} catch (Exception e) {
e.printStackTrace();
}
// 3. 将zookeeper里的信息缓存到本地map里
listMap.put(interfaceName, urlList);
return urlList;
}
public static void main(String[] args) {
System.out.println(client);
// 1. 注册
String interfaceName = "UserService";
List<URL> urlList = Arrays.asList(new URL("localhost", 8080), new URL("localhost", 9000));
for (URL url : urlList) {
ZookeeperRegistry.registry(interfaceName, url);
}
List<URL> urlRes = ZookeeperRegistry.get(interfaceName);
System.out.println(urlRes);
}
}
\