-
如果你是運維負責人,是否會經常發現,你掌管的測試環境中的服務註冊中心,被一些不負責的開發人員把他本地開發環境註冊上來,造成測試人員測試失敗。你希望可以把本地開發環境註冊給遮蔽掉,不讓註冊。
-
如果你是運維負責人,生產環境的某個微服務叢集下的某個實體,暫時出了問題,但又不希望它下線。你希望可以把該實體給遮蔽掉,暫時不讓它被呼叫。
-
如果你是業務負責人,鑒於業務服務的快速迭代性,微服務叢集下的實體釋出不同的版本。你希望根據版本管理策略進行路由,提供給下游微服務區別呼叫,例如訪問控制快速基於版本的不同而切換,例如在不同的版本之間進行流量調撥。
-
如果你是業務負責人,希望灰度釋出功能可以基於業務場景特色定製,例如根據使用者手機號進行不同伺服器的路由。
-
如果你是DBA負責人,希望灰度釋出功能可以基於資料庫切換上。
-
如果你是測試負責人,希望對微服務做A/B測試,那麼透過動態改變版本達到該目的。
-
我們可以在閘道器上根據不同的Token查詢到不同的使用者,把請求路由到指定的伺服器。
-
我們可以在服務上根據不同的業務引數,例如手機號或者身份證號,把請求路由到指定的伺服器。
-
具有極大的靈活性——支援在任何環節做過濾控制和灰度釋出。
-
具有極小的限制性——只要開啟了服務註冊發現,程式入口加了@EnableDiscoveryClient。
-
具有極強的可用性——當遠端配置中心全部掛了,可以透過Rest方式進行灰度釋出。
-
基於黑/白名單的IP地址過濾機制禁止對相應的微服務進行註冊。
-
基於最大註冊數的限制微服務註冊。一旦微服務叢集下註冊的實體數目已經達到上限,將禁止後續的微服務進行註冊。
-
基於黑/白名單的IP地址過濾機制禁止對相應的微服務被髮現。
-
基於版本號配對,透過對消費端和提供端可訪問版本對應關係的配置,在服務發現和負載均衡層面,進行多版本訪問控制。
-
基於版本權重配對,透過對消費端和提供端版本權重(流量)對應關係的配置,在服務發現和負載均衡層面,進行多版本流量調撥訪問控制。
-
透過版本的動態改變,實現切換灰度釋出。
-
透過版本訪問規則的改變,實現切換灰度釋出。
-
透過版本權重規則的改變,實現平滑灰度釋出。
-
對接遠端配置中心,整合Nacos和Redis,非同步接受遠端配置中心主動推送規則資訊,動態改變微服務的規則。
-
結合Spring Boot Actuator,非同步接受Rest主動推送規則資訊,動態改變微服務的規則,支援同步和非同步推送兩種方式。
-
結合Spring Boot Actuator,動態改變微服務的版本,支援同步和非同步推送兩種方式。
-
在服務註冊層面的控制中,一旦禁止註冊的條件觸發,主動推送非同步事件,以便使用者訂閱。
-
使用者可以對服務註冊發現核心事件進行監聽,實現透過擴充套件,使用者自定義和程式設計灰度路由策略。
-
使用者可以實現跟業務有關的路由策略,根據業務引數的不同,負載均衡到不同的伺服器。
-
使用者可以根據內建的版本路由策略+自定義策略,隨心所欲的達到需要的路由功能。
-
在application.properties或者application.yml中,必須為微服務定義一個版本號(version),必須為微服務自定義一個組名(group)或者應用名(application)。
-
使用者只需要關註相關規則推送。可以採用如下方式之一:
-
透過遠端配置中心推送規則
-
透過控制檯介面推送規則
-
透過客戶端工具(例如Postman)推送
<register>
<blacklist>
<service service-name="a" filter-value="172.16"/>
blacklist>
<whitelist>
<service service-name=“a” filter-value=“10.10”/>
whitelist>
register>
<register>
<count>
<service service-name="a" filter-value="500"/>
count>
register>
<discovery>
<blacklist>
<service service-name="a" filter-value="172.16"/>
blacklist>
<whitelist>
<service service-name=“a” filter-value=“10.10”/>
whitelist>
discovery>
<discovery>
<version>
<service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.0" provider-version-value="1.0"/>
<service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.1" provider-version-value="1.1"/>
version>
discovery>
<discovery>
<weight>
<service consumer-service-name="a" provider-service-name="b" provider-weight-value="1.0=90;1.1=10"/>
weight>
discovery>
<customization>
<service service-name="discovery-springcloud-example-b" key="database" value="prod"/>
customization>
-
閘道器不需要配置版本
-
閘道器->服務A(V1.0),閘道器配給服務A(V1.0)的100%權重(流量)
-
上線服務A(V1.1)
-
在閘道器層調撥10%權重(流量)給A(V1.1),給A(V1.0)的權重(流量)減少到90%
-
透過觀測確認灰度有效,把A(V1.0)的權重(流量)全部切換到A(V1.1)
-
下線服務A(V1.0),灰度成功
-
假設當前生產環境,呼叫路徑為閘道器(V1.0)->服務A(V1.0)
-
運維將釋出新的生產環境,部署新服務叢集,服務A(V1.1)
-
由於閘道器(1.0)並未指向服務A(V1.1),所以它們是不能被呼叫的
-
新增用作灰度釋出的閘道器(V1.1),指向服務A(V1.1)
-
灰度閘道器(V1.1)釋出到服務註冊發現中心,但禁止被服務發現,閘道器外的呼叫進來無法負載均衡到閘道器(V1.1)上
-
在灰度閘道器(V1.1)->服務A(V1.1)這條呼叫路徑做灰度測試
-
灰度測試成功後,把閘道器(V1.0)指向服務A(V1.1)
-
下線服務A(V1.0),灰度成功
-
灰度閘道器(V1.1)可以不用下線,留作下次版本上線再次灰度釋出
-
如果您對新服務比較自信,可以更簡化,可以不用灰度閘道器和灰度測試,當服務A(V1.1)上線後,原有閘道器直接指向服務A(V1.1),然後下線服務A(V1.0)
-
基於服務的程式設計灰度路由,實現DiscoveryEnabledExtension,透過RequestContextHolder(獲取來自閘道器的Header引數)和ServiceStrategyContext(獲取來自RPC方式的方法引數)獲取業務背景關係引數,進行路由自定義
-
基於Zuul的程式設計灰度路由,實現DiscoveryEnabledExtension,透過Zuul自帶的RequestContext(獲取來自閘道器的Header引數)獲取業務背景關係引數,進行路由自定義
-
基於Spring Cloud Api Gateway的程式設計灰度路由,實現DiscoveryEnabledExtension,透過GatewayStrategyContext(獲取來自閘道器的Header引數)獲取業務背景關係引數,進行路由自定義
@Bean
public MyDiscoveryEnabledExtension myDiscoveryEnabledExtension() {
return new MyDiscoveryEnabledExtension();
}
public class MyDiscoveryEnabledExtension implements DiscoveryEnabledExtension {
private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledExtension.class);
@Override
public boolean apply(Server server, Map<String, String> metadata) {
// 對Rest呼叫傳來的Header引數(例如Token)做策略
return applyFromHeader(server, metadata);
}
// 根據Rest呼叫傳來的Header引數(例如Token),選取執行呼叫請求的服務實體
private boolean applyFromHeader(Server server, Map<String, String> metadata) {
RequestContext context = RequestContext.getCurrentContext();
String token = context.getRequest().getHeader("token");
// String value = context.getRequest().getParameter("value");
String serviceId = server.getMetaInfo().getAppName().toLowerCase();
LOG.info("Zuul端負載均衡使用者定製觸發:serviceId={}, host={}, metadata={}, context={}", serviceId, server.toString(), metadata, context);
String filterToken = "abc";
if (StringUtils.isNotEmpty(token) && token.contains(filterToken)) {
LOG.info("過濾條件:當Token含有'{}'的時候,不能被Ribbon負載均衡到", filterToken);
return false;
}
return true;
}
}
public class MyDiscoveryEnabledExtension implements DiscoveryEnabledExtension {
private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledExtension.class);
@Override
public boolean apply(Server server, Map metadata) {
// 對RPC呼叫傳來的方法引數做策略
return applyFromMethd(server, metadata);
}
@SuppressWarnings("unchecked")
private boolean applyFromMethd(Server server, Map<String, String> metadata) {
ServiceStrategyContext context = ServiceStrategyContext.getCurrentContext();
Map<String, Object> attributes = context.getAttributes();
String serviceId = server.getMetaInfo().getAppName().toLowerCase();
String version = metadata.get(DiscoveryConstant.VERSION);
LOG.info("Serivice端負載均衡使用者定製觸發:serviceId={}, host={}, metadata={}, context={}", serviceId, server.toString(), metadata, context);
String filterServiceId = "discovery-springcloud-example-b";
String filterVersion = "1.0";
String filterBusinessValue = "abc";
if (StringUtils.equals(serviceId, filterServiceId) && StringUtils.equals(version, filterVersion)) {
if (attributes.containsKey(ServiceStrategyConstant.PARAMETER_MAP)) {
Map<String, Object> parameterMap = (Map<String, Object>) attributes.get(ServiceStrategyConstant.PARAMETER_MAP);
String value = parameterMap.get("value").toString();
if (StringUtils.isNotEmpty(value) && value.contains(filterBusinessValue)) {
LOG.info("過濾條件:當serviceId={} && version={} && 業務引數含有'{}'的時候,不能被Ribbon負載均衡到", filterServiceId, filterVersion, filterBusinessValue);
return false;
}
}
}
return true;
}
}
@EventBus
public class MySubscriber {
@Autowired
private PluginAdapter pluginAdapter;
@Subscribe
public void onCustomization(CustomizationEvent customizationEvent) {
CustomizationEntity customizationEntity = customizationEvent.getCustomizationEntity();
String serviceId = pluginAdapter.getServiceId();
if (customizationEntity != null) {
Map<String, Map<String, String>> customizationMap = customizationEntity.getCustomizationMap();
Map<String, String> customizationParameter = customizationMap.get(serviceId);
// 根據customizationParameter的引數動態切換資料源
} else {
// 根據customizationParameter的引數動態切換資料源
}
}
@Subscribe
public void onRegisterFailure(RegisterFailureEvent registerFailureEvent) {
System.out.println("========== 註冊失敗, eventType=" + registerFailureEvent.getEventType() + ", eventDescription=" + registerFailureEvent.getEventDescription() + ", serviceId=" + registerFailureEvent.getServiceId() + ", host=" + registerFailureEvent.getHost() + ", port=" + registerFailureEvent.getPort());
}
}
public class MyRegisterListener extends AbstractRegisterListener {
@Override
public void onRegister(Registration registration) {
}
@Override
public void onDeregister(Registration registration) {
}
@Override
public void onSetStatus(Registration registration, String status) {
}
@Override
public void onClose() {
}
}
public class MyDiscoveryListener extends AbstractDiscoveryListener {
public void onGetInstances(String serviceId, List
}
public void onGetServices(List
}
}
public class MyLoadBalanceListener extends AbstractLoadBalanceListener {
@Override
public void onGetServers(String serviceId, List extends Server> servers) {
}
}
public class MySubscriber {
public void onRegisterFailure(RegisterFailureEvent registerFailureEvent) {
}
}