DUBBO服务环境隔离

1,267 阅读8分钟

  • Service层(服务接口层):与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现
  • Config层(配置层):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类
  • 服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory
  • 服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。
  • 集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。
  • 监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
  • 远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
  • 信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
  • 网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
  • 数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。


服务提供者暴露一个服务:


Invoker是实体域,是Dubbo的核心模型,其他模型都向他靠拢或者转换为它,它代表一个可执行体,可以向它发起Invoke调用,它有可能是一个本地实现或者一个远程的实现或者一个集群的实现。

Protocol层:封装RPC调用,以Invocation和Result为中心,扩展接口为Protocol,Invoker和Exporter。Protocol是服务域,是Invoker暴露和引用的主功能入口,负责Invoker的生命周期管理。


服务消费者消费一个服务:





Directory代表多个Invoker,可以把它看成List<Invoker>,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。

Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。

Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离。

LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选。

集群容错:

Dubbo提供了多种容错方案,缺省为failover重试。

集群容错模式:

  • Failover Cluster
失败自动切换,当出现失败,重试其它服务器。(缺省)

通常用于读操作,但重试会带来更长延迟

可通过retries="2"来设置重试次数(不含第一次)。

  • Failfast Cluster

快速失败,只发起一次调用,失败立即报错。

通常用于非幂等性的写操作,比如新增记录。

  • Failsafe Cluster

失败安全,出现异常时,直接忽略。

通常用于写入审计日志等操作。

  • Failback Cluster

失败自动恢复,后台记录失败请求,定时重发

通常用于消息通知操作。

  • Forking Cluster

并行调用多个服务器,只要一个成功即返回。

通常用于实时性要求较高的读操作,但需要浪费更多服务资源。

可通过forks="2"来设置最大并行数。

  • Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错

通常用于通知所有提供者更新缓存或日志等本地资源信息。

重试次数配置如:(failover集群模式生效)

负载均衡:

  • Random LoadBalance

随机,按权重设置随机概率。

在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

  • RoundRobin LoadBalance

轮循,按公约后的权重设置轮循比率。

存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

  • LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

  • ConsistentHash LoadBalance

一致性Hash,相同参数的请求总是发到同一提供者。

当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。


单元化环境:

  1. 申请要素:版本时间、项目名称、UnitID、应用名、类型(申请\回收)
  2. 部署(docker、k8s、jenkins):机器IP、dubbo.properties、unit.properties(包含GroupID)
  3. dubbo服务注册:
dubbo.properties:

dubbo.registry.address=unitzookeeper://zkurl

dubbo.registry.protocol=unitzookeeper

dubbo.provider.cluster=unitrouter

group.properties:

unit_enabled=true

unit_zookeeper=zkurl

unit_name=123456

public class UnitConfig {
    
    private static final Logger logger = LoggerFactory.getLogger(UnitConfig.class);
    private static String groupName = Constants.DEFAULT_GROUP_NAME;
    private static String zookeeper = null;
    
    static {
        init();
    }

    public static boolean isEnabled() {
        return enabled;
    }

    public static void setEnabled(boolean enabled) {
        UnitConfig.enabled = enabled;
    }

    public static String getUnitName() {
        if (testFlag) {
            init();
        }
        return groupName;
    }

    public static void setUnitName(String groupName) {
        UnitConfig.groupName = groupName;
    }

    public static String getZookeeper() {
        return zookeeper;
    }

    public static void setZookeeper(String zookeeper) {
        UnitConfig.zookeeper = zookeeper;
    }

    private static void init() {
        Properties properties = loadProperties();
        loadEnabled(properties);
        loadZkAddress(properties);
        loadUnitName(properties);
    }
    
    private static Properties loadProperties() {
        Properties properties = new Properties();
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(Constants.DEFAULT_GROUP_CONFIG_FILE_PATH);
            properties.load(fis);
        } catch (FileNotFoundException e) {
            logger.warn(Constants.DEFAULT_GROUP_CONFIG_FILE_PATH + " can not be found!");
        } catch (Exception e) {
            logger.warn("Exception occurred while loading " + Constants.DEFAULT_GROUP_CONFIG_FILE_PATH + ".", e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (Exception e) {
                    logger.error("Exception occurred while closing FileInputStream.", e);
                }
            }
        }
        return properties;
    }

    private static void loadEnabled(Properties properties) {
        if (properties.getProperty(Constants.GROUP_ENABLED_KEY) != null) {
            String enabledStr = properties.getProperty(Constants.GROUP_ENABLED_KEY).trim();
            enabled = "true".equalsIgnoreCase(enabledStr);
        }
    }

    private static void loadZkAddress(Properties properties) {
        if (properties.getProperty(Constants.GROUP_ZK_KEY) != null) {
            zookeeper = properties.getProperty(Constants.GROUP_ZK_KEY).trim();
            logger.info("Loading ZK address[{}] from properties file.", zookeeper);
        }
    }

  
    private static void loadUnitName(Properties properties) {
        loadUnitNameFromProperties(properties);
        loadUnitNameFromSystemProperty();
        loadUnitNameFromZK();
        logger.info("The final UnitName is {}", groupName);
    }

   

    private static void loadUnitNameFromProperties(Properties properties) {
        if (properties.getProperty(Constants.GROUP_NAME_KEY) != null) {
            groupName = properties.getProperty(Constants.GROUP_NAME_KEY).trim();
            logger.info("Loading groupName[{}] from properties file.", groupName);
        }
    }

    private static void loadUnitNameFromSystemProperty() {
        if (System.getProperty(Constants.GROUP_NAME_KEY) != null) {
            groupName = System.getProperty(Constants.GROUP_NAME_KEY).trim();
            logger.info("Loading groupName[{}] from System Property.", groupName);
        }
    }

    private static void loadUnitNameFromZK() {
        if (zookeeper == null) {
            return;
        }
        String localAddress = AddressUtil.getSingleLocalAddress();
        if (localAddress == null) {
            logger.warn("Can't obtain local address!");
            return;
        }

        try {
            ZkClient client = ZkUtil.getZkClient(zookeeper);
            if (client.exists(GROUP_ZK_MAPPINGS_PREFIX + "/" + localAddress)) {
                List<String> children =
                        client.getChildren(GROUP_ZK_MAPPINGS_PREFIX + "/" + localAddress);
                if (children != null && children.size() > 0) {
                    groupName = children.get(0);
                    logger.info("Find {} group name(s) in ZK. Using {}.", children.size(), groupName);
                } else {
                    logger.warn("Can't obtain group name for {}", localAddress);
                }
            }
        } catch (Exception e) {
            logger.error("Exception occurred while filling UnitName with ZK.", e);
        }
    }
}

unit.jar:

META-INF/dubbo目录下

  • com.alibaba.dubbo.rpc.Filter
consumerUnitFilter=filter.UnitConsumerFilter 

providerUnitFilter=filter.UnitProviderFilter

@Activate(group = com.alibaba.dubbo.common.Constants.PROVIDER_SIDE, order = -10000)
public class UnitProviderFilter implements Filter {
    private static Logger logger = LoggerFactory.getLogger(UnitProviderFilter.class);

    public UnitProviderFilter() {
        logger.info("Creat UnitProviderFilter, GROUP=" + UnitConfig.getUnitName());
    }


    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        if (!UnitConfig.isEnabled()) {
            return invoker.invoke(invocation);
        }

        String routeUnit = RpcContext.getContext().getAttachment(Constants.RPC_CONTEXT_UNIT_KEY);
        logger.debug("Get group {} from RpcContext.", routeUnit);
        UnitHolder.setUnit(routeUnit);
        try {
            return invoker.invoke(invocation);
        } finally {
            UnitHolder.clear();
        }
    }
}

@Activate(group = com.alibaba.dubbo.common.Constants.CONSUMER_SIDE, order = -10000)
public class UnitConsumerFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(UnitConsumerFilter.class);

    public UnitConsumerFilter() {
        logger.info("Creat UnitConsumerFilter, GROUP=" + UnitConfig.getGroupName());
    }


    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        if (UnitConfig.isEnabled()) {
            String routeUnit = UnitHolder.getUnitDefault();
            logger.debug("Set group {} into Dubbo Context.", routeUnit );
            RpcContext.getContext().setAttachment(Constants.RPC_CONTEXT_UNIT_KEY, routeUnit );
            invocation.getAttachments().put(Constants.RPC_CONTEXT_UNIT_KEY, routeUnit );
        }
        return invoker.invoke(invocation);
    }
}

public class UnitHolder {
    private static final Logger log = LoggerFactory.getLogger(UnitHolder.class);
    private static ThreadLocal<String> unitGroup = new ThreadLocal<String>();
    
    public static void setUnit(String group) {
        if (StringUtils.isNotEmpty(group)) {
            unitGroup.set(group.trim());
        } else {
            unitGroup.remove();
        }
    }
    
    public static String getUnit() {
        return unitGroup.get();
    }
    
    public static String getUnitWithDefault() {
        String group = getUnit();
        if (group == null) {
            group = UnitConfig.getUnitName();
        }
        return group;
    }

    public static void clear() {
        unitGroup.remove();
    }
}

  • com.alibaba.dubbo.rpc.cluster.Cluster

unitrouter=cluster.UnitCluster
public class UnitCluster implements Cluster {

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new UnitClusterInvoker<T>(directory);
    }
}
public class UnitClusterInvoker<T> extends FailoverClusterInvoker<T> {
    
    private final static Logger logger = LoggerFactory.getLogger(UnitClusterInvoker.class);

    
    public UnitClusterInvoker(Directory<T> directory) {
        super(directory);
    }


    @Override
    protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
        List<Invoker<T>> invokers = super.list(invocation);

        if (!UnitConfig.isEnabled()) {
            return invokers;
        }

        String routeUnit = getRouteUnit();
        Map<String, List<Invoker<T>>> map = getAllInvokersWithUnit(invokers);

        List<Invoker<T>> invokerList = map.get(routeUnit);
        if (invokerList == null) {
            logger.debug("Can't find the wanted group {}, using {}.", routeUnit, Constants.DEFAULT_GROUP_NAME);
            invokerList = map.get(Constants.DEFAULT_GROUP_NAME);
        } else {
            logger.debug("Find {} invokers for group {}.", invokerList.size(), routeUnit);
        }

        if (invokerList == null) {
            logger.warn("Failed to find proper invokers, maybe {} group is not well configured!",
                    Constants.DEFAULT_GROUP_NAME);
            if (UnitConfig.isAlwaysReturnInvokers()) {
                invokerList = invokers;
            } else {
                invokerList = Collections.emptyList();
            }
        }
        if (logger.isDebugEnabled()) {
            String ipList = "";
            for (Invoker<T> i : invokerList) {
                ipList += i.getUrl().getIp() + " ";
            }
            logger.debug("Return {} nodes for {}. They are {}.", invokerList.size(), invocation, ipList);
        }
        return Collections.unmodifiableList(invokerList);
    }

    private String getRouteUnit() {
        String routeUnit = UnitHolder.getUnit();
        if (StringUtils.isBlank(routeUnit)) {
            routeUnit = UnitConfig.getUnitName(); // Always return a name, if not set, use DEFAULT.
            logger.debug("Unit NOT found in Context. Setting GROUP to {}.", routeUnit);
        } else {
            logger.debug("Find Unit {} from Context.", routeUnit);
        }
        return routeUnit;
    }

    private Map<String, List<Invoker<T>>> getAllInvokersWithUnit(List<Invoker<T>> invokers) {
        Map<String, List<Invoker<T>>> map = new HashMap<String, List<Invoker<T>>>();
        for (Invoker<T> invoker : invokers) {
            String groupKey = invoker.getUrl().getParameter(Constants.URL_GROUP_KEY, Constants.DEFAULT_GROUP_NAME);
            List<Invoker<T>> invokerList = map.get(groupKey);
            if (invokerList == null) {
                invokerList = new ArrayList<Invoker<T>>();
                map.put(groupKey, invokerList);
            }
            invokerList.add(invoker);
        }
        logger.debug("There are {} groups and {} providers.", map.size(), invokers.size());
        return map;
    }
}
  • com.alibaba.dubbo.registry.RegistryFactory

unitzookeeper=registry.unitZookeeperRegistryFactory

public class UnitZookeeperRegistryFactory extends AbstractRegistryFactory {
    private static final Logger logger = LoggerFactory.getLogger(UnitZookeeperRegistryFactory.class);

    private ZookeeperTransporter zookeeperTransporter;

    public UnitZookeeperRegistryFactory() {
        logger.info("Creating UnitZookeeperRegistryFactory, GROUP=" + UnitConfig.getUnitName());
    }

    public Registry createRegistry(URL url) {
        return new UnitZookeeperRegistry(url, zookeeperTransporter);
    }

    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
        this.zookeeperTransporter = zookeeperTransporter;
    }
}
public class UnitZookeeperRegistry extends ZookeeperRegistry {

    public UnitZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        super(url, zookeeperTransporter);
    }

    @Override
    protected void doRegister(URL url) {
        super.doRegister(appendRouteUnit(url));
    }

    @Override
    protected void doUnregister(URL url) {
        super.doUnregister(appendRouteUnit(url));
    }

    protected URL appendRouteUnit(URL url) {
        if (!UnitConfig.isEnabled()) {
            return url;
        }

        String routeUnit = UnitConfig.getUnitName();
        if (StringUtils.isBlank(routeUnit)) {
            routeUnit = Constants.DEFAULT_GROUP_NAME;
        }
        if (StringUtils.isNotEmpty(routeUnit)) {
            url = url.addParameter(Constants.URL_GROUP_KEY, routeUnit);
        }
        return url;
    }
}


Dubbo测试平台:

1.TestCase库对应一个或多个Facade版本

2.执行测试用例,选择对应的Docker环境和Facade版本