微服務(wù)實(shí)踐目錄,可以參見(jiàn)連接浴鸿。
背景
之前翻譯過(guò)一篇文章《[翻譯]功能切換(又稱功能標(biāo)志)》井氢。在這篇文章中介紹了各種需要特性開(kāi)關(guān)的點(diǎn),并且以Nodejs的例子展示了特性開(kāi)關(guān)的一些細(xì)節(jié)赚楚。基于這片文章這里討論一下在Java上有哪些點(diǎn)可能會(huì)用到特性開(kāi)關(guān)骗卜,并進(jìn)行特性開(kāi)關(guān)技術(shù)的具體討論宠页。
特性開(kāi)關(guān)的特性
在特性開(kāi)關(guān)最通用的用法中有解決功能沖突、藍(lán)綠發(fā)布寇仓、新特性驗(yàn)證(卡方檢驗(yàn))等功能外举户,特性開(kāi)關(guān)還可以完成以下的幾個(gè)功能:
優(yōu)雅降級(jí)
對(duì)于在較大壓力到達(dá)系統(tǒng)中之后,可以騰空一些不重要業(yè)務(wù)的資源消耗遍烦。讓這部分資源頂?shù)礁又匾臉I(yè)務(wù)中去俭嘁。可以總結(jié)為:保護(hù)高業(yè)務(wù)價(jià)值的請(qǐng)求服猪,并動(dòng)態(tài)丟棄其他請(qǐng)求供填。例如一個(gè)電商系統(tǒng)最主要的就是商品展示拐云、購(gòu)買(mǎi)流程,而客服服務(wù)近她、物流等是較為次要的系統(tǒng)叉瘩。就可以拋棄次要系統(tǒng)專(zhuān)注于主要系統(tǒng)的流程保證。
這里其實(shí)也是互聯(lián)網(wǎng)中的一個(gè)概念:服務(wù)分級(jí)粘捎∞泵澹可以把一個(gè)互聯(lián)網(wǎng)服務(wù)的平臺(tái)拆分成多個(gè)層次。核心業(yè)務(wù)服務(wù)攒磨、周邊業(yè)務(wù)服務(wù)泳桦、次要業(yè)務(wù)服務(wù)這樣就可以針對(duì)不同的服務(wù)制定不同的保障策略。斷路器
使用專(zhuān)用策略和自定義規(guī)則實(shí)施斷路器模式娩缰,從而可以主動(dòng)關(guān)閉不可用的功能灸撰。
特性開(kāi)關(guān)的層次
整個(gè)特性開(kāi)關(guān)可以針對(duì)不同的層次進(jìn)行管理以滿足特性要求。對(duì)于從客戶端到服務(wù)端的方式可以定義為:設(shè)備->客戶端->用戶/用戶群->自定義策略->全站這幾種層次漆羔。每個(gè)層次上可以實(shí)現(xiàn)不同類(lèi)型的功能開(kāi)關(guān)功能梧奢。
- 設(shè)備層
對(duì)于廣泛認(rèn)知的同一個(gè)APP蘋(píng)果版和安卓班功能不同的問(wèn)題可以很好的體現(xiàn)出來(lái)。 - 客戶端
現(xiàn)在比較流行XXX極速版演痒。比如京東和京東極速版亲轨,抖音和抖音極速版。 - 用戶/用戶群
之前微信的新功能發(fā)布都是需要申請(qǐng)才可以進(jìn)行新功能體驗(yàn)的鸟顺。這一個(gè)層面就是對(duì)于不同的用戶進(jìn)行用戶的A/B測(cè)試惦蚊。 - 自定義策略
按照地域(中國(guó)版,美國(guó)版)讯嫂,按照語(yǔ)言(中文版蹦锋,英文版),按照國(guó)家法律(敏感字審查等)等等都可以進(jìn)行不同的可行開(kāi)關(guān) - 全站
對(duì)于未開(kāi)發(fā)完成的欧芽,但是已經(jīng)合入到線上分支莉掂。進(jìn)行線上驗(yàn)證的功能是很有必要做全站屏蔽的。
技術(shù)解決方案對(duì)比
現(xiàn)階段有很多框架千扔、工具庫(kù)可以滿足特性快關(guān)的需求憎妙,這里就對(duì)這些特性開(kāi)關(guān)的實(shí)現(xiàn)進(jìn)行一些對(duì)比方便在技術(shù)選型中進(jìn)行使用。
- 功能對(duì)比
框架 | 位置 | 控制臺(tái) | 返回能力 | 說(shuō)明 |
---|---|---|---|---|
FF4J | 皆可 | 有 | 不控制 | 侵入性較大 |
Togglz | 皆可 | 有 | 不控制 | 侵入性較大 |
piranha | 皆可 | 無(wú) | 不控制 | Uber開(kāi)源的特性開(kāi)關(guān) |
fitchy | 皆可 | 無(wú) | 可以控制 | 現(xiàn)階段只支持簡(jiǎn)單的特性開(kāi)關(guān)功能曲楚。 |
flip | 皆可 | 無(wú) | 多年前的代碼厘唾。例子居然是jsp的 |
從功能對(duì)比上來(lái)看的化只有FF4J和Togglz是處于可用狀態(tài)的。其他的幾乎都處于不可使用狀態(tài)龙誊。但是這兩個(gè)可用的還是屬于侵入性較大的工具庫(kù)抚垃,因?yàn)樗麄兌夹枰约簩?xiě)if...else才可以實(shí)現(xiàn)特性開(kāi)關(guān)的功能。
從上面的功能對(duì)比中可以看到只有兩個(gè)框架是可用的FF4J和Togglz。而針對(duì)這兩個(gè)框架進(jìn)行對(duì)比FF4J有739star鹤树、開(kāi)發(fā)團(tuán)隊(duì)56人铣焊、最后提交代碼8天前,Togglz有586star魂迄、開(kāi)發(fā)團(tuán)隊(duì)58人粗截、最后提交代碼是2個(gè)月前。最新版的jar包是在Togglz是2018年7月發(fā)布捣炬,F(xiàn)F4J是在2020年5月發(fā)布熊昌。
FF4J和Togglz的文檔和issue處理進(jìn)度來(lái)說(shuō),F(xiàn)F4J略勝一籌湿酸。從更多功能考慮FF4J還可以支撐審計(jì)婿屹、策略開(kāi)關(guān)、權(quán)限開(kāi)關(guān)推溃、監(jiān)控等昂利。從功能和文檔完備度來(lái)說(shuō)FF4J比較好一點(diǎn)。
-
代碼對(duì)比
除了以上問(wèn)題后铁坎,兩種框架的代碼的例子都是侵入行非常強(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
}
-
對(duì)性能影響
在配置與使用特性開(kāi)關(guān)的過(guò)程中如果對(duì)系統(tǒng)的性能和穩(wěn)定產(chǎn)生影響就需要關(guān)注引入的特性開(kāi)關(guān)的所造成的可用性問(wèn)題了。分析FF4J的開(kāi)關(guān)的代碼:
FF4j.check(String featureID)
上面的方法用來(lái)檢查特性開(kāi)關(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;
}
從這里以及其他衍生的方法中檢查幾乎沒(méi)有需要進(jìn)行計(jì)算扩所,遠(yuǎn)程通信的內(nèi)容。所以朴乖,可以認(rèn)為它對(duì)于業(yè)務(wù)代碼的影響幾乎很小祖屏。
具體使用
真正開(kāi)始使用是就會(huì)遇到各種個(gè)樣的問(wèn)題,對(duì)于FF4J來(lái)說(shuō)也是如此的买羞。這里先說(shuō)明FF4J的兩種使用方式袁勺。
對(duì)于系統(tǒng)特性開(kāi)關(guān)來(lái)說(shuō)最主要的是對(duì)開(kāi)關(guān)的動(dòng)態(tài)配置管理工作。這部分管理工作FF4J有兩種方式進(jìn)行支撐:web畜普,cli期丰。對(duì)于cli來(lái)說(shuō)只能連接本地的服務(wù)中的開(kāi)關(guān),對(duì)于web來(lái)說(shuō)可以控制多個(gè)服務(wù)中的開(kāi)關(guān)吃挑。所以選擇web方式對(duì)于稍大一點(diǎn)系統(tǒng)是必要的钝荡。而web中有兩種方式:
方式1對(duì)于在SpringBoot上來(lái)說(shuō)是不可用狀態(tài),因?yàn)闆](méi)有辦法將embedded web劃到一個(gè)獨(dú)立的contextpath中儒鹿。因?yàn)槭褂梅?wù)端渲染thymeleaf時(shí)沒(méi)有辦法給ff4j和業(yè)務(wù)等配置獨(dú)立的contextpath化撕,導(dǎo)致使用thymeleaf渲染的頁(yè)面無(wú)法加載几晤。方式2控制的服務(wù)可以更多更完善约炎。所以使用方式2是比選項(xiàng),在進(jìn)行方式2的配置過(guò)程中需要加入:
<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的話會(huì)報(bào)錯(cuò),所以在FF4j官方文檔的例子中不配置thymeleaf是啟動(dòng)不起來(lái)的圾浅。
然后使用FF4J的注解會(huì)發(fā)現(xiàn)有很多的問(wèn)題侨舆。所以自定義注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeatureFlippingAnnotation {
}
再定義注解處理類(lèi)拯勉,進(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。這些也可以在其他的特性開(kāi)關(guān)庫(kù)中實(shí)現(xiàn)轩性,不過(guò)因?yàn)楹芏嗵匦蚤_(kāi)關(guān)庫(kù)未意識(shí)到這些應(yīng)用點(diǎn)而放棄掉声登。這其實(shí)從某個(gè)方面來(lái)說(shuō)就是:
思維決定認(rèn)識(shí),認(rèn)識(shí)決定高度揣苏,高度決定人生
參考:
微服務(wù)版本分支管理與特性開(kāi)關(guān)
特性開(kāi)關(guān)框架選型之FF4J vs Togglz
SpringAOP整合Togglz悯嗓!你的周末健身時(shí)光不再被打擾!P恫臁脯厨!
ff4j 特性開(kāi)關(guān)功能開(kāi)發(fā)的一些實(shí)踐理論
FF4J: Feature Toggling for Spring/Spring Boot Applications