由于之前网关采用的是dubbo的rest协议,但使用一段时间发现速度有些慢,而且总感觉不如直接使用泛化调用来的爽,所以打算研究一下dubbo的泛化调用,在此记录一下,参照了很多大神的思路,水平有限,欢迎指正。
dubbo泛化调用的原理就不细讲了,网上有很多文章,底层基于netty做数据传输,进行rpc调用,netty是个重要的基础,有兴趣的同学可以研究一下,包括它的编码,解码,序列化等等,对理解dubbo框架有很好的帮助的,废话少说,我记录一下我的改造过程中涉及到的几个方面:
1.zkClient,用于监听zk的变化,这里我使用redis作为缓存,把zk的变化记录了下来,有变化时,实时更新缓存,保证时效性。
在初始化的时候,获取zk的地址
@PostConstruct
public void init() {
rootPath = zkConfiguration.getRootPath();
zkServers = zkConfiguration.getServer();
zkClient = new ZkClient(zkServers, 5000);
if (!rootPath.startsWith(SLASH)) {
rootPath = SLASH + rootPath;
}
if (loadBalancerService == null) {
loadBalancerService = new RandomLoadBalanceImpl();
}
runaway(zkClient, rootPath);
}
监听zk的变化,放入redis
private void runaway(final ZkClient zkClient, final String path) {
zkClient.unsubscribeAll();
ConcurrentHashMap> newHosts = new ConcurrentHashMap>();
zkClient.subscribeChildChanges(path, new IZkChildListener() {
public void handleChildChange(String parentPath, List currentChilds) throws Exception {
/*
* System.out.println(parentPath +
* " 's child changed, currentChilds:" + currentChilds);
*/
logger.info("{}'s child changed, currentChilds:{}", parentPath, currentChilds);
// 一级节点的子节点发生变化
runaway(zkClient, path); // once more
}
});
List firstGeneration = zkClient.getChildren(path); // 二级节点
if (firstGeneration != null && firstGeneration.size() > 0) {
for (String child : firstGeneration) {
String firstNextPath = path + "/" + child;
zkClient.subscribeChildChanges(firstNextPath, new IZkChildListener() {
public void handleChildChange(String parentPath, List currentChilds) throws Exception {
/*
* System.out.println(parentPath +
* " 's child changed, currentChilds:" + currentChilds);
*/
logger.info("{}'s child changed, currentChilds:{}", parentPath, currentChilds);
// 2级节点的子节点发生
runaway(zkClient, path);
}
});
List secondGeneration = zkClient.getChildren(firstNextPath); // 三级子节点
if (secondGeneration != null && secondGeneration.size() > 0) {
for (String secondChild : secondGeneration) {
if (secondChild.startsWith(PROVIDERS)) {
String secondNextPath = firstNextPath + "/" + secondChild;
zkClient.subscribeChildChanges(secondNextPath, new IZkChildListener() {
public void handleChildChange(String parentPath, List currentChilds)
throws Exception {
/*
* System.out .println(parentPath +
* " 's child changed, currentChilds:" +
* currentChilds);
*/
logger.info("{}'s child changed, currentChilds:{}", parentPath, currentChilds);
// 3级节点的子节点发生
runaway(zkClient, path);
}
});
List thirdGeneration = zkClient.getChildren(secondNextPath);// 4级子节点
if (thirdGeneration != null && thirdGeneration.size() > 0) {
for (String thirdChild : thirdGeneration) {
if (thirdChild.startsWith(DUBBO)) {
try {
initDubboService(URLDecoder.decode(thirdChild, CommonCodeConstants.MDF_CHARSET_UTF_8));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
}
}
}
}
}
synchronized (this) {
hosts.clear();
hosts.putAll(newHosts);
}
}
截取的地址,不知道是否还有更好的方式,欢迎大家指导
public void initDubboService(String url) {
String[] urls = url.split("\\?");
String key = urls[0].substring(urls[0].lastIndexOf("/")+1);
String path = urls[1];
String[] paths = path.split("\\&");
String serviceName = "";
for (int i=0;i
2.下面就是泛化调用的客户端了,这个ReferenceConfig实例比较重需要缓存一下
public class DubboCallbackUtil {
private static Logger logger = LogManager.getLogger(DubboCallbackUtil.class);
// 应用的信息
private static ApplicationConfig application = new ApplicationConfig();
// 注册中心缓存
private static Map registryConfigCache = new ConcurrentHashMap<>();
// ReferenceConfig缓存
private static Map referenceCache = new ConcurrentHashMap<>();
static {
application.setName("consumer-test");
}
/**
* 注册中心信息
*
*/
private static RegistryConfig getRegistryConfig(String address, String group, String version) {
String key = address + "-" + group + "-" + version;
RegistryConfig registryConfig = registryConfigCache.get(key);
if (null == registryConfig) {
registryConfig = new RegistryConfig();
if (StringUtils.isNotEmpty(address)) {
registryConfig.setAddress(address);
}
if (StringUtils.isNotEmpty(version)) {
registryConfig.setVersion(version);
}
if (StringUtils.isNotEmpty(group)) {
registryConfig.setGroup(group);
}
registryConfigCache.put(key, registryConfig);
}
return registryConfig;
}
private static ReferenceConfig getReferenceConfig(String interfaceName, String address,
String group, String version) {
String referenceKey = interfaceName;
ReferenceConfig referenceConfig = referenceCache.get(referenceKey);
if (null == referenceConfig) {
referenceConfig = new ReferenceConfig<>();
referenceConfig.setApplication(application);
referenceConfig.setRegistry(getRegistryConfig(address, group, version));
referenceConfig.setInterface(interfaceName);
if (StringUtils.isNotEmpty(version)) {
referenceConfig.setVersion(version);
}
referenceConfig.setGeneric(true);
referenceCache.put(referenceKey, referenceConfig);
}
return referenceConfig;
}
public static Object invoke(String interfaceName, String methodName, Object[] paramObject, String address, String version) {
ReferenceConfig reference = getReferenceConfig(interfaceName, address, null, version);
if (null != reference) {
GenericService genericService = (GenericService) reference.get();
if (genericService == null) {
return null;
}
Object resultParam = genericService.$invoke(methodName,new String[]{new String().getClass().getName(),new String().getClass().getName()}, paramObject);
return resultParam;
}
return null;
}
}
3.调用的时候由于业务需求和兼容之前前端接口,往后端传了两个参数,这个可以随意,下面就是调用了
JSONObject jsonObject = JSON.parseObject(reqBody);
Token token = new Token();
String retContent = (String)DubboCallbackUtil.invoke((String)redisClient.getValue("dubbo_"+jsonObject.get("Id")),(String)jsonObject.get("method"),new String[]{jsonObject.toJSONString(),JSON.toJSONString(token,SerializerFeature.WriteMapNullValue)},"zookeeper://"+zkServer,"");
其中业务接口是监听zk变化存在缓存中的,method前端传过来的,剩下的就是json的参数了,先写到这里,后面还有服务端如何解析这些参数,因为后端接受的都是对象,需要通过过滤器来重新转换json的字符串为对应的类型,这个就需要个人发挥啦!