背景说明

  • 微服务架构与单体或垂直架构本,服务维护的成本高了很多,在研发集成测试环境使用比较随意的情况下,开始恶性循环变得无法稳定使用。
  • 研发与测试如何共享同一套集成测试环境的同时又不会干扰到测试工作的稳定准确?

思路

公司dubbo服务都通过zk进行了注册,dubbo原生提供了消费者选择服务提供者的时候会进行一层路由过滤。
通过增加一条路由,限制测试服的消费者仅能选择部署在测试服的提供者,来规避测试人员测试过程中调用开发人员本地不稳定代码。

方案

实现思路

  1. 通过增加一个zk的监听,获取新加入的节点
  2. 动态对新加入的节点增加路由配置

image.png

优缺点

  1. 优点:对研发和测试人员无感知,研发人员可以尽情使用测试服zk,给研发人员提供开发便利
  2. 缺点:暂无

实现代码(模拟dubbo-admin 2.7中添加路由规则进行编写)

import com.eqxiu.dubboa.model.domain.Route;
import com.eqxiu.dubboa.model.dto.ConditionRouteDTO;
import com.eqxiu.dubboa.model.store.RoutingRule;
import com.eqxiu.dubboa.util.RouteUtils;
import com.eqxiu.dubboa.util.YamlParser;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.zookeeper.ZookeeperRegistry;
import org.apache.dubbo.remoting.zookeeper.ZookeeperClient;
import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
import org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import static com.eqxiu.dubboa.route.Constants.DYNAMIC_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY;
import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY;


public class AutoRouteManager {

    static Logger logger = LoggerFactory.getLogger(AutoRouteManager.class);

    private String zkUrl = "127.0.0.1:2181";

    private String service;

    private String appName;

    private CuratorFramework zkClient;

    private ZookeeperConfiguration configuration;

    private ZookeeperRegistry registry;

    private ZookeeperClient client;


    private String prefix = Constants.CONFIG_KEY;

    private String root;

    //服务路径
    private String path;

    public CuratorFramework getZkClient() {
        String url = zkUrl;
        CuratorFrameworkFactory.Builder zkClientBuilder = CuratorFrameworkFactory.builder()
                .connectString(url)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3));
        zkClient = zkClientBuilder.build();
        zkClient.start();
        return zkClient;
    }

    public AutoRouteManager(CuratorFramework zkClient,String host,Integer port) {
        this.root = "/dubbo";
        this.zkClient = zkClient;
        //初始化zk节点编辑工具类
        configuration = new ZookeeperConfiguration();
        configuration.init(this.zkClient);
        Map<String, String> parameters = new HashMap<>(2);
        parameters.put("interface", "org.apache.dubbo.registry.RegistryService");
        parameters.put("group", "dubbo");
        //初始化url,获取zk注册中心
        URL url = new URL("zookeeper", host, port, "org.apache.dubbo.registry.RegistryService", parameters);
        ZookeeperTransporter zookeeperTransporter = new ZookeeperTransporter() {
            @Override
            public ZookeeperClient connect(URL url) {
                return new CuratorZookeeperClient(url);
            }
        };
        registry = new ZookeeperRegistry(url, zookeeperTransporter);
    }

    /**
     * 针对服务名,项目名创建路由规则
     * 
     * @param service 服务名
     * @param appName 项目名
     * @param conditionUrl 条件路由
     * @return 
     * @author djl
     * @date 2020/7/3 9:51 上午
     */
    public void setRoute(String service ,String appName,String conditionUrl) {
        //入参整理
        ConditionRouteDTO conditionRoute = new ConditionRouteDTO();
        List<String> routeList = new ArrayList<>();
        routeList.add(conditionUrl);
        conditionRoute.setConditions(routeList);
        conditionRoute.setPriority(0);
        conditionRoute.setEnabled(true);
        conditionRoute.setForce(true);
        conditionRoute.setRuntime(false);
        conditionRoute.setApplication(appName);
        conditionRoute.setService(service);
        //参数处理
        String id = ConvertUtil.getIdFromDTO(conditionRoute);
        String path = getPath(id, Constants.CONDITION_ROUTE);
        String existConfig = configuration.getConfig(path);
        RoutingRule existRule = null;
        if (existConfig != null) {
            existRule = YamlParser.loadObject(existConfig, RoutingRule.class);
        }
        existRule = RouteUtils.insertConditionRule(existRule, conditionRoute);
        //register2.7
        configuration.setConfig(path, YamlParser.dumpObject(existRule));
        //register2.6
        if (StringUtils.isNotEmpty(conditionRoute.getService())) {
            for (Route old : convertRouteToOldRoute(conditionRoute)) {
                //route://0.0.0.0/com.djl.test2?category=routers&compatible_config=true&dynamic=false&enabled=true&force=true&name=null&priority=0&router=condition&rule=+%3D%3E+host+%21%3D+172.22.3.91&runtime=false
                URL temp = old.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true);
                create(toUrlPath(temp), temp.getParameter(DYNAMIC_KEY, true));
            }
        }
    }

    // path--->/dubbo/com.djl.test2/routers/route%3A%2F%2F0.0.0.0%2Fcom.djl.test2%3Fcategory%3Drouters%26compatible_config%3Dtrue%26dynamic%3Dfalse%26enabled%3Dtrue%26force%3Dtrue%26name%3Dnull%26priority%3D0%26router%3Dcondition%26rule%3D%2B%253D%253E%2Bhost%2B%2521%253D%2B172.22.3.91%26runtime%3Dfalse
    public void create(String path, boolean ephemeral) {
        if (!ephemeral) {
            if (checkExists(path)) {
                return;
            }
        }
        int i = path.lastIndexOf('/');
        if (i > 0) {
            create(path.substring(0, i), false);
        }
        if (ephemeral) {
//            createEphemeral(path);
        } else {
            createPersistent(path);
        }
    }

    public void createPersistent(String path) {
        try {
            zkClient.create().forPath(path);
        } catch (KeeperException.NodeExistsException e) {
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    public boolean checkExists(String path) {
        try {
            if (zkClient.checkExists().forPath(path) != null) {
                return true;
            }
        } catch (Exception e) {
        }
        return false;
    }

    private String toUrlPath(URL url) {
        return toCategoryPath(url) + PATH_SEPARATOR + URL.encode(url.toFullString());
    }

    private String toCategoryPath(URL url) {
        return toServicePath(url) + PATH_SEPARATOR + url.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
    }

    private String toServicePath(URL url) {
        String name = url.getServiceInterface();
        if (ANY_VALUE.equals(name)) {
            return toRootPath();
        }
        return toRootDir() + URL.encode(name);
    }

    private String toRootPath() {
        return root;
    }

    private String toRootDir() {
        if (root.equals(PATH_SEPARATOR)) {
            return root;
        }
        return root + PATH_SEPARATOR;
    }

    private String getPath(String key, String type) {
        key = key.replace("/", "*");
        if (type.equals(Constants.CONDITION_ROUTE)) {
            return prefix + Constants.PATH_SEPARATOR + key + Constants.CONDITION_RULE_SUFFIX;
        } else {
            return prefix + Constants.PATH_SEPARATOR + key + Constants.TAG_RULE_SUFFIX;
        }
    }

    private List<Route> convertRouteToOldRoute(ConditionRouteDTO route) {
        List<Route> oldList = new LinkedList<Route>();
        for (String condition : route.getConditions()) {
            Route old = new Route();
            old.setService(route.getService());
            old.setEnabled(route.isEnabled());
            old.setForce(route.isForce());
            old.setRuntime(route.isRuntime());
            old.setPriority(route.getPriority());
            String rule = parseCondition(condition);
            old.setRule(rule);
            oldList.add(old);
        }
        return oldList;
    }

    private String parseCondition(String condition) {
        StringBuilder when = new StringBuilder();
        StringBuilder then = new StringBuilder();
        condition = condition.trim();
        if (condition.contains("=>")) {
            String[] array = condition.split("=>", 2);
            String consumer = array[0].trim();
            String provider = array[1].trim();
            if (consumer.length() != 0) {
                if (when.length() != 0) {
                    when.append(" & ").append(consumer);
                } else {
                    when.append(consumer);
                }
            }
            if (provider.length() != 0) {
                if (then.length() != 0) {
                    then.append(" & ").append(provider);
                } else {
                    then.append(provider);
                }
            }
        }
        return (when.append(" => ").append(then)).toString();
    }
}

JlDang
34 声望3 粉丝