微服務(wù)實踐目錄,可以參見連接。
背景
之前翻譯過一篇文章《[翻譯]功能切換(又稱功能標(biāo)志)》。在這篇文章中介紹了各種需要特性開關(guān)的點,并且以Nodejs的例子展示了特性開關(guān)的一些細(xì)節(jié)。基于這片文章這里討論一下在Java上有哪些點可能會用到特性開關(guān),并進(jìn)行特性開關(guān)技術(shù)的具體討論。
特性開關(guān)的特性
在特性開關(guān)最通用的用法中有解決功能沖突、藍(lán)綠發(fā)布、新特性驗證(卡方檢驗)等功能外,特性開關(guān)還可以完成以下的幾個功能:
優(yōu)雅降級
對于在較大壓力到達(dá)系統(tǒng)中之后,可以騰空一些不重要業(yè)務(wù)的資源消耗。讓這部分資源頂?shù)礁又匾臉I(yè)務(wù)中去。可以總結(jié)為:保護(hù)高業(yè)務(wù)價值的請求,并動態(tài)丟棄其他請求。例如一個電商系統(tǒng)最主要的就是商品展示、購買流程,而客服服務(wù)、物流等是較為次要的系統(tǒng)。就可以拋棄次要系統(tǒng)專注于主要系統(tǒng)的流程保證。
這里其實也是互聯(lián)網(wǎng)中的一個概念:服務(wù)分級。可以把一個互聯(lián)網(wǎng)服務(wù)的平臺拆分成多個層次。核心業(yè)務(wù)服務(wù)、周邊業(yè)務(wù)服務(wù)、次要業(yè)務(wù)服務(wù)這樣就可以針對不同的服務(wù)制定不同的保障策略。斷路器
使用專用策略和自定義規(guī)則實施斷路器模式,從而可以主動關(guān)閉不可用的功能。
特性開關(guān)的層次
整個特性開關(guān)可以針對不同的層次進(jìn)行管理以滿足特性要求。對于從客戶端到服務(wù)端的方式可以定義為:設(shè)備->客戶端->用戶/用戶群->自定義策略->全站這幾種層次。每個層次上可以實現(xiàn)不同類型的功能開關(guān)功能。
- 設(shè)備層
對于廣泛認(rèn)知的同一個APP蘋果版和安卓班功能不同的問題可以很好的體現(xiàn)出來。 - 客戶端
現(xiàn)在比較流行XXX極速版。比如京東和京東極速版,抖音和抖音極速版。 - 用戶/用戶群
之前微信的新功能發(fā)布都是需要申請才可以進(jìn)行新功能體驗的。這一個層面就是對于不同的用戶進(jìn)行用戶的A/B測試。 - 自定義策略
按照地域(中國版,美國版),按照語言(中文版,英文版),按照國家法律(敏感字審查等)等等都可以進(jìn)行不同的可行開關(guān) - 全站
對于未開發(fā)完成的,但是已經(jīng)合入到線上分支。進(jìn)行線上驗證的功能是很有必要做全站屏蔽的。
技術(shù)解決方案對比
現(xiàn)階段有很多框架、工具庫可以滿足特性快關(guān)的需求,這里就對這些特性開關(guān)的實現(xiàn)進(jìn)行一些對比方便在技術(shù)選型中進(jìn)行使用。
- 功能對比
框架 | 位置 | 控制臺 | 返回能力 | 說明 |
---|---|---|---|---|
FF4J | 皆可 | 有 | 不控制 | 侵入性較大 |
Togglz | 皆可 | 有 | 不控制 | 侵入性較大 |
piranha | 皆可 | 無 | 不控制 | Uber開源的特性開關(guān) |
fitchy | 皆可 | 無 | 可以控制 | 現(xiàn)階段只支持簡單的特性開關(guān)功能。 |
flip | 皆可 | 無 | 多年前的代碼。例子居然是jsp的 |
從功能對比上來看的化只有FF4J和Togglz是處于可用狀態(tài)的。其他的幾乎都處于不可使用狀態(tài)。但是這兩個可用的還是屬于侵入性較大的工具庫,因為他們都需要自己寫if...else才可以實現(xiàn)特性開關(guān)的功能。
從上面的功能對比中可以看到只有兩個框架是可用的FF4J和Togglz。而針對這兩個框架進(jìn)行對比FF4J有739star、開發(fā)團(tuán)隊56人、最后提交代碼8天前,Togglz有586star、開發(fā)團(tuán)隊58人、最后提交代碼是2個月前。最新版的jar包是在Togglz是2018年7月發(fā)布,F(xiàn)F4J是在2020年5月發(fā)布。
FF4J和Togglz的文檔和issue處理進(jìn)度來說,F(xiàn)F4J略勝一籌。從更多功能考慮FF4J還可以支撐審計、策略開關(guān)、權(quán)限開關(guān)、監(jiān)控等。從功能和文檔完備度來說FF4J比較好一點。
-
代碼對比
除了以上問題后,兩種框架的代碼的例子都是侵入行非常強(qiáng)的代碼。
FF4J:
if (ff4j.check(FEATURE_ADMIN_ONLY)) {
htmlPage.append("<li>THIS LINE IS SHOWN ONLY FOR PEOPLE WITH ROLE <b>ADMIN</b></li>");
}
Togglz:
if (MyFeatures.HOT_NEW_FEATURE.isActive()) {
// do cool new stuff here
}
-
對性能影響
在配置與使用特性開關(guān)的過程中如果對系統(tǒng)的性能和穩(wěn)定產(chǎn)生影響就需要關(guān)注引入的特性開關(guān)的所造成的可用性問題了。分析FF4J的開關(guān)的代碼:
FF4j.check(String featureID)
上面的方法用來檢查特性開關(guān)的檢查。它其中的代碼為:
/**
* Elegant way to ask for flipping.
*
* @param featureID
* feature unique identifier.
* @param executionContext
* current execution context
* @return current feature status
*/
public boolean check(String featureID, FlippingExecutionContext executionContext) {
Feature fp = getFeature(featureID);
boolean flipped = fp.isEnable();
// If authorization manager provided, apply security filter
if (flipped && getAuthorizationsManager() != null) {
flipped = isAllowed(fp);
}
// If custom strategy has been defined, delegate flipping to
if (flipped && fp.getFlippingStrategy() != null) {
flipped = fp.getFlippingStrategy().evaluate(featureID, getFeatureStore(), executionContext);
}
// Update current context
flippingExecutionContext.set(executionContext);
// Any access is logged into audit system
publishCheck(featureID, flipped);
return flipped;
}
從這里以及其他衍生的方法中檢查幾乎沒有需要進(jìn)行計算,遠(yuǎn)程通信的內(nèi)容。所以,可以認(rèn)為它對于業(yè)務(wù)代碼的影響幾乎很小。
具體使用
真正開始使用是就會遇到各種個樣的問題,對于FF4J來說也是如此的。這里先說明FF4J的兩種使用方式。
對于系統(tǒng)特性開關(guān)來說最主要的是對開關(guān)的動態(tài)配置管理工作。這部分管理工作FF4J有兩種方式進(jìn)行支撐:web,cli。對于cli來說只能連接本地的服務(wù)中的開關(guān),對于web來說可以控制多個服務(wù)中的開關(guān)。所以選擇web方式對于稍大一點系統(tǒng)是必要的。而web中有兩種方式:
方式1對于在SpringBoot上來說是不可用狀態(tài),因為沒有辦法將embedded web劃到一個獨立的contextpath中。因為使用服務(wù)端渲染thymeleaf時沒有辦法給ff4j和業(yè)務(wù)等配置獨立的contextpath,導(dǎo)致使用thymeleaf渲染的頁面無法加載。方式2控制的服務(wù)可以更多更完善。所以使用方式2是比選項,在進(jìn)行方式2的配置過程中需要加入:
<ff4j.version>1.8.6</ff4j.version>
<dependency>
<groupId>org.ff4j</groupId>
<artifactId>ff4j-spring-boot-starter</artifactId>
<version>${ff4j.version}</version>
</dependency>
<dependency>
<groupId>org.ff4j</groupId>
<artifactId>ff4j-web</artifactId>
<version>${ff4j.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
</dependency>
如果不配置thymeleaf的話會報錯,所以在FF4j官方文檔的例子中不配置thymeleaf是啟動不起來的。
然后使用FF4J的注解會發(fā)現(xiàn)有很多的問題。所以自定義注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeatureFlippingAnnotation {
}
再定義注解處理類,進(jìn)行特性的管理工作。
@Aspect
@Component
@Slf4j
@ConditionalOnBean(FF4j.class)
public class FeatureFlippingAspect {
@Autowired
public FF4j ff4j;
//切面
@Around("@annotation(cn.eduplus.uc.common.flipping.FeatureFlippingAnnotation)")
public Object before(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("FeatureFlippingAspect before aspect");
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Method method = ms.getMethod();
String className = method.getDeclaringClass().getSimpleName();
String ffName = className + "." + method.getName();
log.info("FeatureFlippingAspect before aspect. ffName = " + ffName);
if (ff4j.check(ffName)) {
return joinPoint.proceed();
} else {
log.info("方法規(guī)則式攔截," + method.getName());
return null;
}
}
}
使用它的方式:
public class foo {
@FeatureFlippingAnnotation
void bar(){
System.out.println('bar");
}
}
總結(jié)
FF4J的功能還以應(yīng)用于Avoid Feature Branching,Blue/Green Deployments,Canary Release,Dark Launch,Graceful degradation,Thin client application,Business Toggle,A/B Testing,Circuit Breaker。這些也可以在其他的特性開關(guān)庫中實現(xiàn),不過因為很多特性開關(guān)庫未意識到這些應(yīng)用點而放棄掉。這其實從某個方面來說就是:
思維決定認(rèn)識,認(rèn)識決定高度,高度決定人生
參考:
微服務(wù)版本分支管理與特性開關(guān)
特性開關(guān)框架選型之FF4J vs Togglz
SpringAOP整合Togglz!你的周末健身時光不再被打擾!!!
ff4j 特性開關(guān)功能開發(fā)的一些實踐理論
FF4J: Feature Toggling for Spring/Spring Boot Applications