當前位置:
首頁 > 知識 > dubbo源碼分析之服務治理

dubbo源碼分析之服務治理

在之前的 dubbo 源碼分析中我們分析了 dubbo 的服務暴露。provider 把需要暴露的服務地址信息註冊到註冊中心(比如:zookeeper),然後把通過 java nio 框架 netty 以 socket 的方式把遠程服務暴露給 consumer 調用,並且訂閱註解中心,當註冊中心發生變化的時候 Inovke 調用就會改變。當 consumer 需要引用服務的時候通過 javassist 創建代理對象,獲取到代理對象 InvokerInvocationHandler,而它組合了一個 MockClusterInvoker。dubbo 通過這個對象進行服務治理,也就是之前分析的集群容錯源碼的分析。我們再來看一下集群容錯的架構圖:

dubbo源碼分析之服務治理

dubbo 不僅提供了 dubbo monitor 來監控服務指數,還提供了一個管理控制台用於服務的治理。

1、dubbo admin 安裝

安裝

wget https://archive.apache.org/dist/tomcat/tomcat-6/v6.0.35/bin/apache-tomcat-6.0.35.tar.gz
tar zxvf apache-tomcat-6.0.35.tar.gz
cd apache-tomcat-6.0.35
rm -rf webapps/ROOT
git clone https://github.com/dubbo/dubbo-ops.git /var/tmp/dubbo-ops
pushd /var/tmp/dubbo-ops
mvn clean package
popd
unzip /var/tmp/dubbo-ops/dubbo-admin/target/dubbo-admin-2.0.0.war -d webapps/ROOT
1
2
3
4
5
6
7
8
9
10
11

配置 dubbo.properties 把 dubbo 註解中心地址配置成真正項目裡面的註冊中心

vi webapps/ROOT/WEB-INF/dubbo.properties
#dubbo.properties
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
1
2
3
4
5
6
7

運維:

# 啟動:
./bin/startup.sh
# 停止:
./bin/shutdown.sh
1
2
3
4

然後可以通過: http://127.0.0.1:8080/ 進行訪問。

2、dubbo admin

dubbo admin 主要包含以下幾個頁面。

搜索頁面

當你需要管理 Dubbo 的服務時,首先要搜索到這個服務,然後打開它的管理頁面

dubbo源碼分析之服務治理

服務提供者頁面

dubbo源碼分析之服務治理

服務消費者頁面

dubbo源碼分析之服務治理

服務應用頁面

dubbo源碼分析之服務治理

添加路由規則頁面

dubbo源碼分析之服務治理

添加動態配置頁面

dubbo源碼分析之服務治理

通過這些頁面可以查詢所有的服務提供者、服務消費者、服務的應用以及動態的添加路由規則或者添加動態的配置。當然也包含服務的降級、服務訪問權重的調節以及負載均衡的調節。

3、服務治理原理

dubbo admin 使用 Spring MVC 來自頁面展示的。我們先來看一下dubbo admin 中的配置文件 dubbo.properties裡面配置了 dubbo 的註冊中心地址以及 dubbo admin 的用戶名與密碼。


dubbo.properties

dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
1
2
3

通過配置的暴露服務的註冊中心地址,就可以從註冊中心獲取提供者、消費者、路由信息、權重等信息。

下面我們再來看一下 dubbo-admin 裡面的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
<property name="ignoreResourceNotFound" value="true"/>
<property name="locations">
<list>
<value>/WEB-INF/dubbo.properties</value>
<value>file://${user.home}/dubbo.properties</value>
</list>
</property>
</bean>
<dubbo:application name="dubbo-admin"/>
<dubbo:registry client="curator" address="${dubbo.registry.address}" check="false" file="false"/>
<dubbo:reference id="registryService" interface="com.alibaba.dubbo.registry.RegistryService" check="false"/>
<bean id="configService" class="com.alibaba.dubbo.governance.service.impl.ConfigServiceImpl"/>
<bean id="consumerService" class="com.alibaba.dubbo.governance.service.impl.ConsumerServiceImpl"/>
<bean id="overrideService" class="com.alibaba.dubbo.governance.service.impl.OverrideServiceImpl"/>
<bean id="ownerService" class="com.alibaba.dubbo.governance.service.impl.OwnerServiceImpl"/>
<bean id="providerService" class="com.alibaba.dubbo.governance.service.impl.ProviderServiceImpl"/>
<bean id="routeService" class="com.alibaba.dubbo.governance.service.impl.RouteServiceImpl"/>
<bean id="userService" class="com.alibaba.dubbo.governance.service.impl.UserServiceImpl">
<property name="rootPassword" value="${dubbo.admin.root.password}"/>
<property name="guestPassword" value="${dubbo.admin.guest.password}"/>
</bean>
<bean id="governanceCache" class="com.alibaba.dubbo.governance.sync.RegistryServerSync"/>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

3.1 RegistryService

這個其實就是 dubbo admin 進行服務治理的對象,當在頁面更新了服務信息的時候後台會通過這個對象進行服務信息的更新:

# 取消註冊舊的服務信息
registryService.unregister(oldOverride);
# 註冊新的服務信息
registryService.register(newOverride);
1
2
3
4

在這裡它會引用 dubbo.properties 裡面配置的註冊中心,然後引用 RegistryService 這個服務。這個是不是和 consumer 引用遠程服務的配置是一樣的。但是我們可以使用 Zookeeper 數據查看工具 ZooInspector 查看 zookeeper 節點上面的數據。

dubbo源碼分析之服務治理

可以看到在 zookeeper 裡面並沒有暴露遠程服務 RegistryService 。然後我們來看打斷點的跟蹤到 RegistryProtocol#refer的時候:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
return doRefer(cluster, registry, type, url);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

發現 dubbo admin 再進行服務引用的時候會先根據配置 URL 獲取一個 Registry 而 Registry 又是繼承於 RegistryService 介面。然後再選擇遠程服務之前會判斷這個服務是不是 RegistryService 。如果是就會根據已經獲取到的 Registry 創建一個本地 Invoke,然後由這個本地 Invoke 創建 ZookeeperRegistry 的代理對象。(這個比較坑,我糾結了很久才發現 :( )。

3.2 RegistryServerSync

這個對象實現了 Spring 框架的 InitializingBean,所以在這個 bean 初始化的時候就會訂閱以下 URL:

private static final URL SUBSCRIBE = new URL(Constants.ADMIN_PROTOCOL, NetUtils.getLocalHost(), 0, "",
Constants.INTERFACE_KEY, Constants.ANY_VALUE,
Constants.GROUP_KEY, Constants.ANY_VALUE,
Constants.VERSION_KEY, Constants.ANY_VALUE,
Constants.CLASSIFIER_KEY, Constants.ANY_VALUE,
Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY + ","
+ Constants.CONSUMERS_CATEGORY + ","
+ Constants.ROUTERS_CATEGORY + ","
+ Constants.CONFIGURATORS_CATEGORY,
Constants.ENABLED_KEY, Constants.ANY_VALUE,
Constants.CHECK_KEY, String.valueOf(false));
1
2
3
4
5
6
7
8
9
10
11

因為它的 interface 設置的是 Constants.ANY_VALUE 也就是 * ,所以會訂閱所有的服務。具體 zookeeper 註冊中心的訂閱服務可以參考 ZookeeperRegistry#doSubscribe 方法。訂閱了所以服務並且會獲取到所有服務的服務信息緩存到 RegistryServerSync#registryCache 屬性中。RegistryServerSync 還實現了NotifyListener,所以在註冊中心訂閱的時候還把它本身傳進進去了。當註冊中心的所有服務信息發生變更的時候就會調用 RegistryServerSync#notify 更新RegistryServerSync#registryCache 緩存信息。這樣就可以對服務進行治理。

3.3 原理分析

dubbo admin 通過 Spring mvc 來展示註冊中心保存的服務地址信息:提供者、消費者、路由信息、權重等信息。通過 com.alibaba.dubboadmin.web.mvc.BaseController 的繼承類來修改註冊中心中的服務信息:

dubbo源碼分析之服務治理

這些 controller 主要是通過 AbstractService 的繼承類來修改註冊中心裏面的註冊信息。

public class AbstractService {
protected static final Logger logger = LoggerFactory.getLogger(AbstractService.class);
@Autowired
protected RegistryService registryService;
@Autowired
private RegistryServerSync sync;
public ConcurrentMap<String, ConcurrentMap<String, Map<Long, URL>>> getRegistryCache() {
return sync.getRegistryCache();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

它有一個 RegistryService 是 ZookeeperRegistry 的代理對象,而且還有 RegistryServerSync 這個註冊中心的服務信息緩存(當服務地址信息發生變更時還可以動態更新緩存)。這樣就起到了服務治理的效果。以下都是可以動態修改信息的實現類:

dubbo源碼分析之服務治理

4、服務治理應用

通過 dubbo admin 頁面不僅僅可以展示服務提供者、消費者、路由信息、權重等信息。還可以修改服務提供者,消息者的屬性來達到服務治理的目的。下面我們就來看一下有哪一些應用:

4.1 服務降級

可以通過dubbo admin 來實現服務降級功能,服務降級其實是臨時屏蔽某個出錯的非關鍵服務,並定義降級後的返回策略。有兩種方式:

mock=force:return+null 表示消費方對該服務的方法調用都直接返回 null 值,
不發起遠程調用。用來屏蔽不重要服務不可用時對調用方的影響。
還可以改為 mock=fail:return+null 表示消費方對該服務的方法調用在失敗後,再返回 null 值,
不拋異常。用來容忍不重要服務不穩定時對調用方的影響。
1
2
3
4
5

它的實現其實就是在集群調用的入口: MockClusterInvoker#invoke:

public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
//no mock
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);
} else {
//fail-mock
try {
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

這段代碼的邏輯分為 3 種情況:

  • no mock:正常情況,從註冊中心經過集群、目錄服務、路由服務、負載均衡選擇一個合適的 Invoke 來進行調用。
  • force:direct mock:屏蔽,它不進行遠程調用,直接返回一個之前設置的值.
  • fail-mock:容錯,容錯的其實就是調用失敗後,返回一個設置的值

4.2 灰度發布

灰度發布(又名金絲雀發布)是指在黑與白之間,能夠平滑過渡的一種發布方式。在其上可以進行A/B testing,即讓一部分用戶繼續用產品特性A,一部分用戶開始用產品特性B,如果用戶對B沒有什麼反對意見,那麼逐步擴大範圍,把所有用戶都遷移到B上面來。灰度發布可以保證整體系統的穩定,在初始灰度的時候就可以發現、調整問題,以保證其影響度。

比如我們需要在兩台機器(192.168.100.38、192.168.48.32)上暴露服務,可以通過以下方式來進行灰度發布:

  • 發布192.168.48.32,切斷192.168.48.32訪問流量,然後進行服務的發布。
  • 192.168.48.32發布成功後,恢復 192.168.48.32的流量,
  • 切斷192.168.100.38,繼續發布 192.168.100.38

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

Python項目中跟蹤系統導入Zipkin
spring+mybatis 實現多數據源切換

TAG:程序員小新人學習 |