天天看點

掌門1對1微服務體系 Solar | 阿裡巴巴 Sentinel 落地實踐

前言

掌門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

進行更深層次的內建。

在生産環境中使用 Sentinel

Solar 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

    對象到

    Skywalking

    Server
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 服務增加授權規則,

    limitApp

    為 A 服務名
[
    {
        "resource": "sentinel-resource",
        "limitApp": "a-service-id",
        "strategy": 0
    }
]           

② 服務的

APPID

當 A 服務發送請求到 B 服務,所攜帶的 A 服務的

APPID

不滿足條件,該請求就會被 B 服務熔斷。

spring.application.strategy.service.sentinel.request.origin.key=n-d-service-app-id           
  • limitApp

    為 A 服務的

    APPID

[
    {
        "resource": "sentinel-resource",
        "limitApp": "a-service-app-id",
        "strategy": 0
    }
]           

③ 服務執行個體所在的

IP

Port

IP

Port

端口不滿足條件,該請求就會被 B 服務熔斷。

spring.application.strategy.service.sentinel.request.origin.key=n-d-service-address           
  • limitApp

    為 A 服務執行個體所在的

    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           
  • limitApp

    為 B 服務的組名
[
    {
        "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           
  • limitApp

    為 B 服務的版本号
[
    {
        "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           
  • limitApp

    為 B 服務的區域值
[
    {
        "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           
  • limitApp

    為 B 服務的子環境值
[
    {
        "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

項目負責人宿何在落地過程中的支援和幫助。