- 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,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
单元化环境:
- 申请要素:版本时间、项目名称、UnitID、应用名、类型(申请\回收)
- 部署(docker、k8s、jenkins):机器IP、dubbo.properties、unit.properties(包含GroupID)
- dubbo服务注册:
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
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
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版本