前言
掌門1對1精耕線上教育領域,近幾年業務得到了快速發展,但同時也遭遇了“成長的煩惱”。随着微服務數量不斷增加,流量進一步暴增,硬體資源有點不堪重負,那麼,如何實作更好的限流熔斷降級等流量防護措施,這個課題就擺在了掌門人的面前。由于
Spring Cloud
體系已經演進到第二代,第一代的
Hystrix
限流熔斷降級元件已經不大适合現在的業務邏輯和規模,同時它目前被
Spring Cloud
官方置于維護模式,将不再向前發展。
如何選擇一個更好的限流熔斷降級元件?經過對
Alibaba Sentinel
、
Resilience4j
Hystrix
等開源元件做了深入的調研和比較,最終標明
Alibaba Sentinel
做微服務體系
Solar
中的限流熔斷降級必選元件。
Sentinel 簡介
阿裡巴巴中間件部門開發的新一代以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個次元保護服務的穩定性的分布式系統的流量防衛兵。它承接了阿裡巴巴近10年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、消息削峰填谷、叢集流量控制、實時熔斷下遊不可用應用等。
它具有非常豐富的開源生态:
它和
Hystrix
相比,有如下差異:
摘自官網
Sentinel Roadmap關于
Sentinel
如何使用,它的技術實作原理怎樣等,官方文檔或者民間部落格、公衆号文章等可以提供非常詳盡且有價值的材料,這些不在本文的讨論範圍内,就不一一贅述。筆者嘗試結合掌門1對1現有的技術棧以及中間件一體化的戰略,并着眼于強大的
Spring Cloud Alibaba
技術生态圈展開闡釋。
Sentinel 深度內建 Apollo
Sentinel
官方在
sentinel-datasource-apollo
子產品中已經對
Apollo
做了一些擴充,主要實作了
Sentinel
規則的讀取和訂閱邏輯。這些并不夠,我們需要對
Apollo
進行更深層次的內建。
在生産環境中使用 SentinelSolar SDK 環境初始化
定制
EnvironmentPostProcessor
類,實作如下:
-
的項目名稱從Sentinel Dashboard
Apollo
的次元進行展示AppId
- 根據環境
值讀取相應的配置檔案,并通路對應環境的env
域名Sentinel Dashboard
在生産環境部署若幹台Sentinel Dashboard
執行個體,阿裡雲ECS
做負載均衡,實作對叢集的水準擴充SLB
public class SentinelClientEnvironmentPostProcessor implements EnvironmentPostProcessor {
private final ResourceLoader resourceLoader = new DefaultResourceLoader();
private static final String DEFAULT_CLASSPATH_LOCATION = "classpath:/META-INF/app.properties";
private static final String DEFAULT_LOCATION = "/META-INF/app.properties";
private static final String DEFAULT_LOG_LOCATION = "/opt/logs/";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
try {
Resource appResource = resourceLoader.getResource(DEFAULT_CLASSPATH_LOCATION);
if (!appResource.exists()) {
appResource = resourceLoader.getResource(DEFAULT_LOCATION);
}
Properties appProperties = new Properties();
appProperties.load(new InputStreamReader(appResource.getInputStream()));
String appId = appProperties.getProperty("app.id");
System.setProperty("project.name", appId);
System.setProperty("csp.sentinel.log.dir", DEFAULT_LOG_LOCATION + appId);
Properties properties = new Properties();
String path = isOSWindows() ? "C:/opt/settings/server.properties" : "/opt/settings/server.properties";
File file = new File(path);
if (file.exists() && file.canRead()) {
FileInputStream fis = new FileInputStream(file);
if (fis != null) {
try {
properties.load(new InputStreamReader(fis, Charset.defaultCharset()));
} finally {
fis.close();
}
}
}
String idc = properties.getProperty("idc");
String location;
String env = System.getProperty("env");
if (StringUtils.isEmpty(idc)) {
if (!isBlank(env)) {
env = env.trim().toLowerCase();
} else {
env = System.getenv("ENV");
if (!isBlank(env)) {
env = env.trim().toLowerCase();
} else {
env = properties.getProperty("env");
if (!isBlank(env)) {
env = env.trim();
} else {
env = Env.FAT.getEnv();
}
}
}
location = "classpath:/META-INF/sentinel-" + env + ".properties";
} else {
location = "classpath:/META-INF/sentinel-" + idc + ".properties";
}
Resource serverResource = resourceLoader.getResource(location);
properties.load(new InputStreamReader(serverResource.getInputStream()));
for (String key : properties.stringPropertyNames()) {
System.setProperty(key, properties.getProperty(key));
}
System.setProperty(CommonConstant.SENTINEL_VERSION_NAME, CommonConstant.SENTINEL_VERSION_VALUE);
} catch (Exception e) {
LOG.error(e.getMessage());
}
}
private boolean isBlank(String str) {
return Strings.nullToEmpty(str).trim().isEmpty();
}
private boolean isOSWindows() {
String osName = System.getProperty("os.name");
return !isBlank(osName) && osName.startsWith("Windows");
}
}
把
SentinelClientEnvironmentPostProcessor
類放置
\resources\META-INF\spring.factories
檔案中,内容為
org.springframework.boot.env.EnvironmentPostProcessor=\
com.zhangmen.solar.component.sentinel.common.context.SentinelClientEnvironmentPostProcessor
在
\resources\META-INF
目錄下,定制環境配置檔案,檔案名格式為
sentinel-{環境号}.properties
。下文以
dev
環境和
flow
流控配置(其它規則配置,請自行參考
Spring Cloud Alibaba Sentinel
的相關資料)為樣例。
sentinel-dev.properties
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8080
spring.cloud.sentinel.datasource.ds.apollo.namespaceName=application
spring.cloud.sentinel.datasource.ds.apollo.flowRulesKey=sentinel.flowRules
spring.cloud.sentinel.datasource.ds.apollo.ruleType=flow
...
Sentinel Dashboard 持久化改造
原生的
Sentinel Dashboard
在建立完規則後,規則内容儲存在服務的記憶體中,當服務重新開機後所有的規則内容都會消失。是以,在生産部署時需要考慮配置持久化,并且使用
Apollo
動态規則的感覺能力。
① 向外暴露 Sentinel 規則的 Restful 接口
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {
@Autowired
@Qualifier("apolloFlowRuleProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("apolloFlowRulePublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
....
}
② 實作 Sentinel Apollo 規則提供
@Component("apolloFlowRuleProvider")
public class ApolloFlowRuleProvider extends BaseApolloRuleProvider<FlowRuleEntity> {
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
List<FlowRuleEntity> flowRuleEntityList = super.getRules(appName);
if (!CollectionUtils.isEmpty(flowRuleEntityList)) {
List<FlowRuleEntity> flowRuleEntities = JSONArray.parseArray(flowRuleEntityList.toString(), FlowRuleEntity.class);
long id = 1;
for (FlowRuleEntity entity : flowRuleEntities) {
entity.setId(id++);
entity.getClusterConfig().setFlowId(entity.getId());
}
return flowRuleEntities;
} else {
return null;
}
}
@Override
protected String getDataId() {
return ApolloConfigUtil.getFlowDataId();
}
}
③ 實作 Sentinel Apollo 規則訂閱
@Component("apolloFlowRulePublisher")
public class ApolloFlowRulePublisher extends BaseApolloRulePublisher<List<FlowRuleEntity>> {
@Override
public void publish(String app, String operator, List<FlowRuleEntity> rules) throws Exception {
if (!CollectionUtils.isEmpty(rules)) {
for (int i = 0; i < rules.size(); i++) {
rules.get(i).setId((long) (i + 1));
rules.get(i).setApp(null);
rules.get(i).setGmtModified(null);
rules.get(i).setGmtCreate(null);
rules.get(i).setIp(null);
rules.get(i).setPort(null);
rules.get(i).getClusterConfig().setFlowId((long) (i + 1));
}
} else {
rules = null;
}
super.publish(app, operator, rules);
}
@Override
protected String getDataId() {
return ApolloConfigUtil.getFlowDataId();
}
}
上述代碼實作了對
Apollo
配置讀寫操作。熟悉
Apollo
的同學應該知道,這些操作需要基于
Apollo OpenApi
來操作;動态感覺能力的邏輯已經由
sentinel-datasource-apollo
子產品實作。
Sentinel 內建 Skywalking
由于掌門1對1微服務技術棧落地的比較早,鑒于曆史的局限性(當時沒有更先進的技術可供選擇),除了 Hystrix 比較古老以外,另一個技術棧的痛點是全鍊路監控中間件的改造也提上議事日程,CAT 作為開源界老牌作品,為公司底層全鍊路監控提供強有力的保障,但随着技術的演進,它逐漸已經不适合公司的未來發展方向,經過對比,最終選擇 Skywalking 将作為它的替代者(關于 Skywalking 的技術選型,将在後面掌門1對1微服務體系 Solar 的公衆号系列文章中會一一闡述)。
業務系統要求對限流熔斷降級實作全鍊路實時埋點,并希望在
Skywalking
界面上提供限流熔斷降級埋點的多元度統計。由于
Skywalking
實作了
OpenTracing
标準化協定,那麼以
OpenTracing
為橋梁,通過
Solar
SDK 輸出
Sentinel
埋點到
Skywalking
Server 不失為一個好的技術選擇。下面簡單扼要介紹一下基于
Sentinel InitFunc
的
SPI
機制實作埋點輸出:
Sentinel
将
ProcessorSlot
作為
SPI
接口進行擴充(1.7.2 版本以前
SlotChainBuilder
SPI
),使得
Slot Chain
具備了擴充的能力。您可以自行加入自定義的 slot 并編排 slot 間的順序,進而可以給
Sentinel
添加自定義的功能。
Sentinel 工作主流程抽象 Sentinel ProcessorSlot 埋點輸出
Sentinel
ProcessorSlotEntryCallback
提供
onPass
和
onBlocked
兩個方法,畢竟限流熔斷降級并不是正常的功能,不會發生在大流量上面,是以
onPass
上我們不做任何處理,否則正常的調用去實作攔截,将為産生大量的埋點資料,會讓
Skywalking
Server 承受很大的性能壓力,是以
onBlocked
将是我們關注的重點,它除了輸出
Sentinel
本身的上下文參數之外,也會輸出微服務
Solar
名額參數,主要包括:
- 埋點
名稱,這裡為Span
,在SENTINEL
全鍊路監控界面中,使用者可以非常容易的找到這個埋點Skywalking
- 服務所在的
名,指服務的組
邏輯分組
- 服務類型,包括服務和網關(網關也是一種特殊的服務),
埋點可以支援在服務和網關上的輸出Sentinel
- 服務的
,它為APPID
元件的範疇概念Apollo
- 服務名,它對應為
的配置值spring.application.name
- 服務執行個體所在的
位址和IP
端口Port
- 服務版本号
- 服務所在的區域
- 服務所在的子環境
接下去是
Sentinel
層面的參數,請自行參考
Sentinel
官方文檔和源碼,了解其含義,這裡不做具體講解。
public abstract class SentinelTracerProcessorSlotEntryCallback<S> implements ProcessorSlotEntryCallback<DefaultNode> {
@Override
public void onPass(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, Object... args) throws Exception {
}
@Override
public void onBlocked(BlockException e, Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, Object... args) {
S span = buildSpan();
PluginAdapter pluginAdapter = PluginContextAware.getStaticApplicationContext().getBean(PluginAdapter.class);
outputSpan(span, DiscoveryConstant.SPAN_TAG_PLUGIN_NAME, context.getName());
outputSpan(span, DiscoveryConstant.N_D_SERVICE_GROUP, pluginAdapter.getGroup());
outputSpan(span, DiscoveryConstant.N_D_SERVICE_TYPE, pluginAdapter.getServiceType());
String serviceAppId = pluginAdapter.getServiceAppId();
if (StringUtils.isNotEmpty(serviceAppId)) {
outputSpan(span, DiscoveryConstant.N_D_SERVICE_APP_ID, serviceAppId);
}
outputSpan(span, DiscoveryConstant.N_D_SERVICE_ID, pluginAdapter.getServiceId());
outputSpan(span, DiscoveryConstant.N_D_SERVICE_ADDRESS, pluginAdapter.getHost() + ":" + pluginAdapter.getPort());
outputSpan(span, DiscoveryConstant.N_D_SERVICE_VERSION, pluginAdapter.getVersion());
outputSpan(span, DiscoveryConstant.N_D_SERVICE_REGION, pluginAdapter.getRegion());
outputSpan(span, DiscoveryConstant.N_D_SERVICE_ENVIRONMENT, pluginAdapter.getEnvironment());
outputSpan(span, SentinelStrategyConstant.ORIGIN, context.getOrigin());
outputSpan(span, SentinelStrategyConstant.ASYNC, String.valueOf(context.isAsync()));
outputSpan(span, SentinelStrategyConstant.RESOURCE_NAME, resourceWrapper.getName());
outputSpan(span, SentinelStrategyConstant.RESOURCE_SHOW_NAME, resourceWrapper.getShowName());
outputSpan(span, SentinelStrategyConstant.RESOURCE_TYPE, String.valueOf(resourceWrapper.getResourceType()));
outputSpan(span, SentinelStrategyConstant.ENTRY_TYPE, resourceWrapper.getEntryType().toString());
outputSpan(span, SentinelStrategyConstant.RULE_LIMIT_APP, e.getRuleLimitApp());
if (tracerSentinelRuleOutputEnabled) {
outputSpan(span, SentinelStrategyConstant.RULE, e.getRule().toString());
}
outputSpan(span, SentinelStrategyConstant.CAUSE, e.getClass().getName());
outputSpan(span, SentinelStrategyConstant.BLOCK_EXCEPTION, e.getMessage());
outputSpan(span, SentinelStrategyConstant.COUNT, String.valueOf(count));
if (tracerSentinelArgsOutputEnabled) {
outputSpan(span, SentinelStrategyConstant.ARGS, JSON.toJSONString(args));
}
finishSpan(span);
}
protected abstract S buildSpan();
protected abstract void outputSpan(S span, String key, String value);
protected abstract void finishSpan(S span);
}
整合 OpenTracing & Skywalking
實作
SentinelTracerProcessorSlotEntryCallback
的三個核心方法:
-
- 建立buildSpan
的埋點Skywalking
對象Span
-
- 輸出相關埋點資料的鍵值對到outputSpan
Skywalking
對象中Span
-
- 送出finishSpan
Skywalking
對象到Span
ServerSkywalking
public class SentinelSkywalkingTracerProcessorSlotEntryCallback extends SentinelTracerProcessorSlotEntryCallback<Span> {
private Tracer tracer = new SkywalkingTracer();
@Override
protected Span buildSpan() {
return tracer.buildSpan(SentinelStrategyConstant.SPAN_NAME).startManual();
}
@Override
protected void outputSpan(Span span, String key, String value) {
span.setTag(key, value);
}
@Override
protected void finishSpan(Span span) {
span.finish();
}
}
實作 Sentinel InitFunc SPI 擴充
SPI
的擴充切入類
public class SentinelSkywalkingTracerInitFunc implements InitFunc {
@Override
public void init() throws Exception {
StatisticSlotCallbackRegistry.addEntryCallback(SentinelSkywalkingTracerProcessorSlotEntryCallback.class.getName(), new SentinelSkywalkingTracerProcessorSlotEntryCallback());
}
}
SPI
的擴充切入類放置
\resources\META-INF\services\com.alibaba.csp.sentinel.init.InitFunc
com.nepxion.discovery.plugin.strategy.sentinel.skywalking.monitor.SentinelSkywalkingTracerInitFunc
摘自
Nepxion Discovery 開源社群對于 Sentinel 跟 Opentracing, Skywalking, Jaeger 的內建可參考 https://github.com/Nepxion/Discovery 中的 discovery-plugin-strategy-sentinel-starter-opentracing, discovery-plugin-strategy-sentinel-starter-skywalking 等子產品。
最終在
Skywalking
全鍊路界面上輸出如下:
全鍊路調用鍊中,我們可以看到
solar-service-a
服務的鍊路上輸出了
SENTINEL
埋點,表示
solar-service-a
上發生了
Sentinel
限流熔斷降級事件之一。
點選
SENTINEL
埋點,在呼出的内容看闆上,我們可以看到
solar-service-a
服務發生了限流事件,上面顯示限流的規則和異常資訊以及微服務
Solar
名額等一系列參數。
我們可以點選界面上邊的【熔斷查詢】進行
Sentinel
相關資料的分析和統計
Sentinel 內建 InfluxDB & Grafana
監控資料持久化到 InfluxDB
① Sentinel MetricFetcher 拉取資料
Dashboard
服務端拉取
Sentinel
用戶端(即
Solar
微服務)的監控資料
@Component
public class MetricFetcher {
@Autowired
@Qualifier("influxDBMetricRepository")
private MetricsRepository<MetricEntity> metricStore;
...
}
② InfluxDB 執行個體初始化
@Configuration
public class InfluxDBAutoConfiguration {
@Value("${spring.influx.url}")
private String influxDBUrl;
@Value("${spring.influx.user}")
private String userName;
@Value("${spring.influx.password}")
private String password;
@Value("${spring.influx.database}")
private String database;
@Bean
public InfluxDB influxDB() {
InfluxDB influxDB = null;
try {
influxDB = InfluxDBFactory.connect(influxDBUrl, userName, password);
influxDB.setDatabase(database).enableBatch(100, 1000, TimeUnit.MILLISECONDS);
influxDB.setLogLevel(InfluxDB.LogLevel.NONE);
} catch (Exception e) {
LOG.error(e.getMessage());
}
return influxDB;
}
}
③ Sentinel 資料寫入到 InfluxDB
@Component("influxDBMetricRepository")
public class InfluxDBMetricRepository implements MetricsRepository<MetricEntity> {
@Autowired
private InfluxDB influxDB;
@Override
public void save(MetricEntity metric) {
try {
Point point = createPoint(metric);
influxDB.write(point);
} catch (Exception e) {
LOG.error(e.getMessage());
}
}
@Override
public void saveAll(Iterable<MetricEntity> metrics) {
if (metrics == null) {
return;
}
try {
BatchPoints batchPoints = BatchPoints.builder().build();
metrics.forEach(metric -> {
Point point = createPoint(metric);
batchPoints.point(point);
});
influxDB.write(batchPoints);
} catch (Exception e) {
LOG.error(e.getMessage());
}
}
}
Grafana 界面展現監控資料
Sentinel Limit-App 熔斷擴充
掌門1對1已經實作通過灰階藍綠釋出方式,實作對流量的精确制導和調撥,但為了進一步實施更安全的流量保障,引入了基礎名額和灰階藍綠釋出名額的熔斷,同時也支援業務自定義名額群組合名額的熔斷。
通過對
Sentinel
Limit-App
機制的擴充并定制授權規則,實作微服務
Solar
的熔斷擴充。對于授權規則中涉及到的參數,簡要做如下說明:
-
為resource
注解的@SentinelResource
,也可以是調用的value
路徑值URL
-
如果有多個,可以通過limitApp
分隔。特别注意,下文為了描述簡單,隻以單個為例,
-
表示白名單,符合條件就放行流量;strategy
strategy
表示黑名單,符合條件就限制流量。特别注意,下文為了描述簡單,隻以白名單為例1
基礎名額的熔斷
通過
Http Header
自動攜帶下遊服務的基礎名額進行全鍊路傳遞的方式,對下遊調用實施基礎名額的熔斷。支援如下名額:
① 服務名
當 A 服務發送請求到 B 服務,所攜帶的 A 服務名不滿足條件,該請求就會被 B 服務熔斷。
- B 服務增加配置項
spring.application.strategy.service.sentinel.request.origin.key=n-d-service-id
- B 服務增加授權規則,
為 A 服務名limitApp
[
{
"resource": "sentinel-resource",
"limitApp": "a-service-id",
"strategy": 0
}
]
② 服務的 APPID
APPID
當 A 服務發送請求到 B 服務,所攜帶的 A 服務的
APPID
不滿足條件,該請求就會被 B 服務熔斷。
spring.application.strategy.service.sentinel.request.origin.key=n-d-service-app-id
-
為 A 服務的limitApp
APPID
[
{
"resource": "sentinel-resource",
"limitApp": "a-service-app-id",
"strategy": 0
}
]
③ 服務執行個體所在的 IP
Port
IP
Port
IP
Port
端口不滿足條件,該請求就會被 B 服務熔斷。
spring.application.strategy.service.sentinel.request.origin.key=n-d-service-address
-
為 A 服務執行個體所在的limitApp
IP
Port
[
{
"resource": "sentinel-resource",
"limitApp": "a-ip:a-port",
"strategy": 0
}
]
灰階藍綠釋出名額的熔斷
Http Header
自動攜帶下遊服務的灰階藍綠釋出名額進行全鍊路傳遞的方式,對下遊調用實施灰階藍綠釋出名額的熔斷。支援如下名額:
① 服務所在的 組
名
組
組
名和 B 服務的
組
名不一緻,該請求就會被 B 服務熔斷。
spring.application.strategy.service.sentinel.request.origin.key=n-d-service-group
-
為 B 服務的組名limitApp
[
{
"resource": "sentinel-resource",
"limitApp": "b-group",
"strategy": 0
}
]
② 服務版本号
當 A 服務發送請求到 B 服務,所攜帶的 A 服務的版本号和 B 服務的版本号不一緻,該請求就會被 B 服務熔斷。
spring.application.strategy.service.sentinel.request.origin.key=n-d-service-version
-
為 B 服務的版本号limitApp
[
{
"resource": "sentinel-resource",
"limitApp": "b-version",
"strategy": 0
}
]
③ 服務所在的區域
當 A 服務發送請求到 B 服務,所攜帶的 A 服務的區域值和 B 服務的區域值不一緻,該請求就會被 B 服務熔斷。
spring.application.strategy.service.sentinel.request.origin.key=n-d-service-region
-
為 B 服務的區域值limitApp
[
{
"resource": "sentinel-resource",
"limitApp": "b-region",
"strategy": 0
}
]
④ 服務所在的子環境
當 A 服務發送請求到 B 服務,所攜帶的 A 服務的子環境值和 B 服務的子環境值不一緻,該請求就會被 B 服務熔斷。
spring.application.strategy.service.sentinel.request.origin.key=n-d-service-env
-
為 B 服務的子環境值limitApp
[
{
"resource": "sentinel-resource",
"limitApp": "b-env",
"strategy": 0
}
]
業務自定義名額的熔斷
Http Header
攜帶下遊服務的業務自定義名額進行全鍊路傳遞的方式,對下遊調用實施自定義名額的熔斷。
當 A 服務發送請求到 B 服務,所攜帶的 A 的自定義名額不滿足條件,該請求就會被 B 服務熔斷。例如: A 服務把
userName
Http Header
傳遞給 B 服務,而 B 服務隻接受
userName
zhangsan
的請求,那麼我們可以通過如下方式來解決:
- B 服務通過适配類實作
Sentinel
值的解析Origin
public class MyServiceSentinelRequestOriginAdapter extends AbstractServiceSentinelRequestOriginAdapter {
@Override
public String parseOrigin(HttpServletRequest request) {
return request.getHeader("userName");
}
}
- B 服務的配置類裡通過
方式進行适配類建立@Bean
@Bean
public ServiceSentinelRequestOriginAdapter ServiceSentinelRequestOriginAdapter() {
return new MyServiceSentinelRequestOriginAdapter();
}
-
limitApp
zhangsan
[
{
"resource": "sentinel-resource",
"limitApp": "zhangsan",
"strategy": 0
}
]
假如該方式仍未能滿足業務場景,業務系統希望根據
userName
擷取
userType
,根據使用者類型做統一熔斷,例如,使用者類型為
AUTH_USER
的請求才能放行,其它都熔斷,那麼我們可以把上面的例子修改如下:
- B 服務的适配類更改如下:
public class MyServiceSentinelRequestOriginAdapter extends AbstractServiceSentinelRequestOriginAdapter {
@Override
public String parseOrigin(HttpServletRequest request) {
String userName = request.getHeader("userName");
String userType = getUserTypeByName(userName);
return userType;
}
}
- B 服務的授權規則更改如下:
[
{
"resource": "sentinel-resource",
"limitApp": "AUTH_USER",
"strategy": 0
}
]
組合名額的熔斷
Http Header
攜帶下遊服務的業務自定義名額、基礎名額或者灰階藍綠釋出名額進行全鍊路傳遞的方式,對下遊調用實施組合名額的熔斷,例如,根據傳入的微服務版本号 + 使用者名,組合在一起進行熔斷。下面示例表示為下遊服務版本為
1.0
且
userName
zhangsan
,同時滿足這兩個條件下,所有服務的請求允許被放行,否則被熔斷。
public class MyServiceSentinelRequestOriginAdapter extends AbstractServiceSentinelRequestOriginAdapter {
@Override
public String parseOrigin(HttpServletRequest request) {
String version = request.getHeader(DiscoveryConstant.N_D_SERVICE_VERSION);
String userName = request.getHeader("userName");
return version + "&" + userName;
}
}
[
{
"resource": "sentinel-resource",
"limitApp": "1.0&zhangsan",
"strategy": 0
}
]
Sentinel 網關流控實踐
闡述網關流控實踐的時候,我們使用精确比對的方式對某個服務的請求做限流控制為例;對網關代理的
solar-service-a
服務的接口
/inspector/inspect
做限流控制為例。
API 分組管理
API
管理頁面裡添加
solar-service-a
, 并精确比對串
/inspector/inspect
網關流控規則
在流控規則界面裡配置相關的規則
Skywalking
全鍊路界面上輸出如下(跟
Solar
服務側
Sentinel
埋點相似,不一一闡述了):
Sentinel 叢集限流實踐
我們采用
Sentinel
官方提供的嵌入式
Token Server
解決方案,即服務叢集中選擇一個節點做為
Token Server
,同時該節點也作為
Token Client
響應外部的請求的伺服器。具體實作方式通過
Sentinel
實作預留的
SPI
InitFunc
接口,可以參考官方
sentinel-demo
子產品下面的
sentinel-demo-cluster-embedded
。
public class SentinelApolloTokenClusterInitFunc implements InitFunc {
@Override
public void init() throws Exception {
// Register client dynamic rule data source.
initDynamicFlowRuleProperty();
initDynamicParamRuleProperty();
// Register token client related data source.
// Token client common config:
ClusterClientConfigInitializer.doInit();
// Token client assign config (e.g. target token server) retrieved from assign map:
ClusterClientAssignConfigInitializer.doInit();
// Register token server related data source.
// Register dynamic rule data source supplier for token server:
ClusterRuleSupplierInitializer.doInit();
// Token server transport config extracted from assign map:
ServerTransportConfigInitializer.doInit();
// Init cluster state property for extracting mode from cluster map data source.
ClusterStateInitializer.doInit();
// ServerFlowConfig 配置
ServerFlowConfigInitializer.doInit();
}
}
SPI
\resources\META-INF\services\com.alibaba.csp.sentinel.init.InitFunc
com.zhangmen.solar.sentinel.SentinelApolloTokenClusterInitFunc
作者介紹
任浩軍,掌門基礎架構部研發經理。曾就職于平安銀行、萬達、惠普,曾負責平安銀行平台架構部
PaaS
平台基礎服務架構研發。10 多年開源經曆,
Github
ID:@HaojunRen,
Nepxion
開源社群創始人,
Nacos
Group Member,
Spring Cloud Alibaba
&
Nacos
Sentinel
OpenTracing
Committer。
張彬彬,掌門基礎架構部架構師。主要負責公司微服務架構以及開源項目的開發和實踐,開源項目愛好者,多年網際網路開發經驗。
非常感謝阿裡巴巴
Sentinel
項目負責人宿何在落地過程中的支援和幫助。