首页 > 其他分享 >sentinel-服务接入原理

sentinel-服务接入原理

时间:2024-08-25 15:25:41浏览次数:12  
标签:接入 spring alibaba 限流 规则 sentinel 原理 com

通过sentinel前世今生介绍,我们知道了sentinel流控主要是依赖sentinel-core,但是我们生产环境往往需要动态更新流控规则所以需要集成nacos、zookeeper、redis、mysql、等中间存储。配置的复杂性和规则的复杂性我们需要可视化的方式对规则进行管理,我们需要集成dashboard。

这些sentinel都为我们提供了解决方案

 

集成这些组件我们需要复杂的配置,sentinel为了方便接入提供的spring-cloud-starter-alibaba-sentinel 自动装配进行快速服务整合

 

服务如何接入

版本选择

https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明

1.pom引入依赖

 

        <!--使用nacos持久化规则-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.8.0</version>
        </dependency>

yml配置

server:
  port: 9000
spring:
  main:
    allow-circular-references: true
  application:
    name: sentinel-nacos-starter-datasource-demo
  cloud:
    nacos:
      discovery.server-addr: 127.0.0.1:8848
    sentinel:
      web-context-unify: false # 关闭context整合 避免链路失效 默认会由context为根链路
      eager: true #启动立即加载规则,而不是懒加载
      transport:
        dashboard: 127.0.0.1:8080 #dashboard地址
        port: 8719 #dashboard通信端口,如果冲突则会自动+1 寻找可用端口
        heartbeat-interval-ms: 5000 #心跳秒数
      datasource:
        flow-rule:
          nacos:
            #流控规则的nacos配置文件 dashbard上报推送的是这个格式
            data-id: ${spring.application.name}-flow-rules
            #流控规则格式 XML OR JSON dashbard配置的json 
            data-type: json
            namespace: 04415d0c-9d8e-4e32-81a7-c0d737bb0063
            group-id: SENTINEL_GROUP
            server-addr: 127.0.0.1:8848
            #规则类型控制规则更新的策略 
            rule-type: flow
        param-flow-rule:
          nacos:
            data-id: ${spring.application.name}-param-rules
            data-type: json
            namespace: 04415d0c-9d8e-4e32-81a7-c0d737bb0063
            group-id: SENTINEL_GROUP
            server-addr: 127.0.0.1:8848
            rule-type: param-flow
        authority-rule:
          nacos:
            data-id: ${spring.application.name}-authority-rules
            data-type: json
            namespace: 04415d0c-9d8e-4e32-81a7-c0d737bb0063
            group-id: SENTINEL_GROUP
            server-addr: 127.0.0.1:8848
            rule-type: authority
        system-rule:
          nacos:
            data-id: ${spring.application.name}-system-rules
            data-type: json
            namespace: 04415d0c-9d8e-4e32-81a7-c0d737bb0063
            group-id: SENTINEL_GROUP
            server-addr: 127.0.0.1:8848
            rule-type: system






 

接入原理

规则是如何实现从nacos自动加载和监听的

com.alibaba.cloud.sentinel.custom.SentinelDataSourceHandler 源码,利用spring 生命周期提供的SmartInitializingSingleton 回调

com.alibaba.cloud.sentinel.custom.SentinelDataSourceHandler#parseBeanDefinition

	public void afterSingletonsInstantiated() {
		//遍历配置文件规则
		sentinelProperties.getDatasource()
				.forEach((dataSourceName, dataSourceProperties) -> {
					try {
						List<String> validFields = dataSourceProperties.getValidField();
						if (validFields.size() != 1) {
							log.error("[Sentinel Starter] DataSource " + dataSourceName
									+ " multi datasource active and won't loaded: "
									+ dataSourceProperties.getValidField());
							return;
						}
						AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties
								.getValidDataSourceProperties();
						abstractDataSourceProperties.setEnv(env);
						abstractDataSourceProperties.preCheck(dataSourceName);
						
						//注册
						registerBean(abstractDataSourceProperties, dataSourceName
								+ "-sentinel-" + validFields.get(0) + "-datasource");
					}
					catch (Exception e) {
						log.error("[Sentinel Starter] DataSource " + dataSourceName
								+ " build error: " + e.getMessage(), e);
					}
				});
	}

 

	private void registerBean(final AbstractDataSourceProperties dataSourceProperties,
			String dataSourceName) {
		//根据配置构建 DataSource 的BeanDefinition
		BeanDefinitionBuilder builder = parseBeanDefinition(dataSourceProperties, dataSourceName);

		//进行初始化
		this.beanFactory.registerBeanDefinition(dataSourceName,
				builder.getBeanDefinition());
		// 获取对应的dataSource
		AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory
				.getBean(dataSourceName);

		// 执行注入
		dataSourceProperties.postRegister(newDataSource);
	}

 

	public void postRegister(AbstractDataSource dataSource) {
		switch (this.getRuleType()) {
		case FLOW:
			FlowRuleManager.register2Property(dataSource.getProperty());
			break;
		case DEGRADE:
			DegradeRuleManager.register2Property(dataSource.getProperty());
			break;
		case PARAM_FLOW:
			ParamFlowRuleManager.register2Property(dataSource.getProperty());
			break;
		case SYSTEM:
			SystemRuleManager.register2Property(dataSource.getProperty());
			break;
		case AUTHORITY:
			AuthorityRuleManager.register2Property(dataSource.getProperty());
			break;
		case GW_FLOW:
			GatewayRuleManager.register2Property(dataSource.getProperty());
			break;
		case GW_API_GROUP:
			GatewayApiDefinitionManager.register2Property(dataSource.getProperty());
			break;
		default:
			break;
		}
	}

spring mvc 资源是如何实现自动注册的

 

com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration 会初始化一个spring 拦截器 并注入容器

	@Autowired
	private SentinelProperties properties;

	@Autowired
	private Optional<UrlCleaner> urlCleanerOptional;

	@Autowired
	private Optional<BlockExceptionHandler> blockExceptionHandlerOptional;

	@Autowired
	private Optional<RequestOriginParser> requestOriginParserOptional; 
/**
	 * 基于下面配置 初始化一个spring mvc拦截器
	 * @param sentinelWebMvcConfig
	 * @return
	 */
	@Bean
	@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",
			matchIfMissing = true)
	public SentinelWebInterceptor sentinelWebInterceptor(
			SentinelWebMvcConfig sentinelWebMvcConfig) {
		return new SentinelWebInterceptor(sentinelWebMvcConfig);
	}

	/**
	 * 拦截器的一些自定义配置,如 自定义sentinel流控处理器
	 * @return
	 */
	@Bean
	@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",
			matchIfMissing = true)
	public SentinelWebMvcConfig sentinelWebMvcConfig() {
		//......
	}

 

 

com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor#preHandle

 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            //根据url获取资源名字
            String resourceName = this.getResourceName(request);
            if (StringUtil.isEmpty(resourceName)) {
                return true;
            } else if (this.increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
                return true;
            } else {
                String origin = this.parseOrigin(request);
                String contextName = this.getContextName(request);
                ContextUtil.enter(contextName, origin);
                Entry entry = SphU.entry(resourceName, 1, EntryType.IN);
                request.setAttribute(this.baseWebMvcConfig.getRequestAttributeName(), entry);
                return true;
            }
        } catch (BlockException var12) {
            BlockException e = var12;

            try {
                //执行自定义的 sentinel流控处理器
                this.handleBlockException(request, response, e);
            } finally {
                ContextUtil.exit();
            }

            return false;
        }
    }

如何自定义spring MVC 流控处理器

从容器获取,我们可以根据这个钩子方法进行统一扩展,比如权限的,指定url的流控降级规则

@Autowired
private Optional<BlockExceptionHandler> blockExceptionHandlerOptional;

 

实现BlockExceptionHandler自定义流控处理器

@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e)
        throws Exception {
        Map<String, Object> resData=new HashMap<>();
        if (e instanceof FlowException) {
            resData.put("status",100);
            resData.put("msg","触发限流规则|"+Thread.currentThread().getId()+"|"+ DateFormatUtils.format(System.currentTimeMillis()
                , "yyyy-MM-dd HH:mm:ss:SSS"));

        } else if (e instanceof DegradeException) {
            resData.put("status",101);
            resData.put("msg","降级了|" +Thread.currentThread().getId()+"|"+ DateFormatUtils.format(System.currentTimeMillis()
                , "yyyy-MM-dd HH:mm:ss:SSS")
);

        } else if (e instanceof ParamFlowException) {
            resData.put("status",102);
            resData.put("msg","热点参数限流"+Thread.currentThread().getId()+"|"+ DateFormatUtils.format(System.currentTimeMillis()
                , "yyyy-MM-dd HH:mm:ss:SSS")
);
        } else if (e instanceof SystemBlockException) {
            resData.put("status",103);
            resData.put("msg","系统规则(负载/...不满足要求"+Thread.currentThread().getId()+"|"+ DateFormatUtils.format(System.currentTimeMillis()
                , "yyyy-MM-dd HH:mm:ss:SSS")
);
        } else if (e instanceof AuthorityException) {
            resData.put("status",104);
            resData.put("msg","授权规则不通过"+Thread.currentThread().getId());
        }
        // http状态码
        httpServletResponse.setStatus(500);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
        httpServletResponse.setContentType("application/json;charset=utf-8");
        new ObjectMapper()
            .writeValue(
                httpServletResponse.getWriter(),
                resData
            );
    }
}

如何整合Fegin

yml配置

feign.sentinel.enabled=true

代码使用

文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

 

源码处

通过扩展 Feign.Builder实现

com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SphU.class, Feign.class })
public class SentinelFeignAutoConfiguration {

	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean
	@ConditionalOnProperty(name = "feign.sentinel.enabled")
	public Feign.Builder feignSentinelBuilder() {
		return SentinelFeign.builder();
	}

}

 

 

 

注解支持

参考文档

https://github.com/alibaba/Sentinel/wiki/注解支持

 

自动装配源码处

com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration#sentinelResourceAspect

 

控制台各个规则使用

流控规则

qps

 

我们最常用的限流规则,限制我们的资源能够接收的每秒最大请求数。防止超过资源接收到自身最大的请求流量。

比如我们抢购功能,压测接口最大峰值qps是1000,我们应该设置每秒超过1000的快速失败,避免大量请求引起的阻塞排队。

 

 

并发线程数

与qps不同的是,是每秒基于线程维度统计,线程数并不等于qps,比如设置5,接口性能是500毫秒。那么在不排队情况,1个线程每秒能执行2个请求,5个线程则是10的qps

下面例子20个线程并发抢占最终成功10个

 

 

 

高级设置流控模式

 

  • 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式

 

  • 关联:统计与当前资源相关的另一个资源,触发阈值时。限流自己

      比如商品详情本身支持2000qps。但是当商品有促销活动时会调用促销资源。促销资源只支持100qps。那么这种时候我们接口也就支持100qp,超过100则会触发等待。则可以通过关联来限制。(关联资源单独设置了流控,则取最小那个)

      或者有下单接口,下单完之后会调用支付接口。当支付接口出现限流,我们可以触发限流下单。

 

  • 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流

     自下而上精细化控制,我们的资源,比如我们查询订单接口。支持200qps,在支付的时候会查询订单。订单详情也会查询订单。这个时候我们的支付优先级肯定更高,则我们设置订单资源关联非支付的则只允许50qps

 

高级设置流控效果

 

  • 快速失败:默认效果,达到阈值则快速失败
  • Warm Up:预热模式,我们接口能够支持2000qps,但是刚启动的时候很多缓存没加载。需要预热一段时间,则缓慢的放流量最终到2000qps,比如一心助手门店维度缓存数据

 

 

热点规则

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

 

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。

场景

抢购活动,针对茅台系列商品 0005,0006.可以通过首页活动广告页进入活动,也可以通过商品详情进入活动页。商品详情这个系列商品会匹配到活动,会走抢购活动的分支,所以根据0005,0006 进行单独的qps设置

 

    @Override
    public void run(ApplicationArguments args) throws Exception {
        initParamFlowRules();
    }

    /**
     * com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot#checkFlow
     * 默认是string.valueof
     * @throws IOException
     */
    public void initParamFlowRules() throws IOException {

        ParamFlowRule rule = new ParamFlowRule("flowRuleParamsString")
            .setParamIdx(0)
            .setCount(20);//总阀20 按各个参数分别统计
        // 针对 String 类型的参数,参数值为李强 单独设置限流 QPS 阈值为 1,而不是全局的阈值 20.
        ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf("李强"))
            .setClassType(String.class.getName())
            .setCount(1);
        rule.setParamFlowItemList(Collections.singletonList(item));

        ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
    }

 

 

授权规则

很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,

若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

 

 

源码

 

  /**
     * 资源应用仅允许appA,appB访问
     * 调用方信息通过 ContextUtil.enter(resourceName, origin) 方法中的 origin 参数传入。
     * ContextUtil.enter(resourceName, origin)
     */
    public void intAuthorityRule(){
        AuthorityRule rule = new AuthorityRule();
        rule.setResource("test");
        rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
        rule.setLimitApp("appA,appB");
        AuthorityRuleManager.loadRules(Collections.singletonList(rule));
    }

 

在starter的拦截器中提供RequestOriginParser 由我们扩展

com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration

public class SentinelWebAutoConfiguration implements WebMvcConfigurer {
	......

	@Autowired
	private Optional<RequestOriginParser> requestOriginParserOptional;
 ......

}

com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor#preHandle

   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            String resourceName = this.getResourceName(request);
            if (StringUtil.isEmpty(resourceName)) {
                return true;
            } else if (this.increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
                return true;
            } else {
                 //获取origin
                String origin = this.parseOrigin(request);
                String contextName = this.getContextName(request);
                ContextUtil.enter(contextName, origin);
                Entry entry = SphU.entry(resourceName, 1, EntryType.IN);
                request.setAttribute(this.baseWebMvcConfig.getRequestAttributeName(), entry);
                return true;
            }
        } catch (BlockException var12) {
            BlockException e = var12;

            try {
                this.handleBlockException(request, response, e);
            } finally {
                ContextUtil.exit();
            }

            return false;
        }
    }

    protected String parseOrigin(HttpServletRequest request) {
        String origin = "";
         //RequestOriginParser
        if (this.baseWebMvcConfig.getOriginParser() != null) {
            origin = this.baseWebMvcConfig.getOriginParser().parseOrigin(request);
            if (StringUtil.isEmpty(origin)) {
                return "";
            }
        }

        return origin;
    }

系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 和线程数四个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统保护规则我的理解是一个兜底策略。比如我们压测都是针对单一接口压测,如果我们压测接口本身是cpu密集型的高并发接口,我们把他压到极致,但是一个服务不仅仅是这一个接口提供能力。当时高并发接口和其他接口同时提供能力。往往还没到我们限流阀值系统负载已经上来了。

所以通过系统规则做一次兜底

 

LOAD:linxu的load指标

RT:平均响应时长

线程数:入口流量的并发线程数

CPU使用率

 

sentinel怎么区分入口流量

通过 

Entry entry = SphU.entry(resourceName, 1, EntryType.IN);控制

com.alibaba.csp.sentinel.EntryType

public enum EntryType {
    IN,
    OUT;

    private EntryType() {
    }
}

sentinle的spring adapter模块在拦截器埋点的就是IN

com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor#preHandle

 

在服务fegin埋点就是OUT

com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler#invoke

 

 

运维相关

规则是否加载成功

参考文档:https://sentinelguard.io/zh-cn/docs/metrics.html

我们如果使用nacos推的方式,容易推失败,比如我遇到过推失败后拉取的是nacos缓存

dashbard源码处:

com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient#executeCommand

URL说明
http://10.4.1.125:8719/getRules?type=degrade 获取熔断规则
http://10.4.1.125:8719/getRules?type=flow
获取限流规则
http://10.4.1.125:8719/getRules?type=system 系统规则
http://10.4.1.125:8719/getRules?type=authority
获取授权规则
http://10.4.1.125:8719/getRules?getParamFlowRules
系统规则
http://10.4.1.125:8721/getParamFlowRules 热点规则

更多端点?

 

 

sentinel日志查看

参考文档:https://sentinelguard.io/zh-cn/docs/logs.html

默认在${user_home}/logs/csp目录下,可通过sp.sentinel.log.dir进行修改

 

标签:接入,spring,alibaba,限流,规则,sentinel,原理,com
From: https://www.cnblogs.com/LQBlog/p/18379000

相关文章

  • sentinel-前世今生
    方便理解sentinel,假如我们自己要实现一套sentinelsentinel前世今生方便理解sentinel,假如我们自己要实现一套sentinel第一阶段一心助手业务服务出现异常,通过监控大盘,发现超过自身服务能够承载的流量,导致请求出现大量排队,服务阻塞,进而导致其他依赖服务出现雪崩效应。通过监控......
  • golang mutex原理
    最近面试遇到问锁的问题,答得不是很好,重新做一下总结梳理 go中的sync包提供了两种锁的类型,分别是互斥锁sync.Mutex和读写锁sync.RWMutex,这两种锁都属于悲观锁饥饿模式与正常模式在下面的内容会经常涉及到一个概念,饥饿模式,这里先简单说一下1.正常模式(非公平锁)正常模式下,所......
  • 现代Web开发中AJAX请求的运作原理
    ajax的请求过程1、新建ajax对象:    IE6不兼容newXMLHttpRequest();    IE6下,ajax对象的兼容方法:        try判断的方法:          varxhr=null;            try{    xhr=newXMLHttpRequest();    }      ......
  • 【计算机组成原理】2.2.3_2 无符号数的加减运算
    2.2.3_2无符号数的加减运算00:00各位同学大家好,在这个视频中我们会探讨无符号数的加减运算用计算机是怎么实现的。在王道书当中重点探讨了有符号数补码的加减运算怎么实现。对于无符号数的加减运算,王道书当中并没有深入的探讨。所以这个视频是对王道书的一个补充。大家可......
  • (javaweb)springboot的底层原理
    目录一.配置优先级二.Bean的管理1.获取bean​编辑​编辑2.bean作用域3.第三方bean三.SpringBoot原理 自动配置原理原理分析:conditional: 自动配置案例:(自定义starter分析)总结一.配置优先级//命令行参数的优先级最高二.Bean的管理1.获取bean注入ioc......
  • 科普文:软件架构Nginx系列之【Nginx 核心架构设计和原理】
    概叙Nginx是什么Nginx(engineX)是一个开源的轻量级的HTTP服务器,能够提供高性能的HTTP和反向代理服务。与传统的Apache服务器相比,在性能上Nginx占用系统资源更小、支持高并发,访问效率更高;在功能上,Nginx不仅作为Web服务软件,还适用于反向代理、负载均衡等场景;在安装配置上,Nginx......
  • OSPF路由原理详解与关键点
    目录一. OSPF简介:二. OSPF原理描述:三.  OSPF的核心内容: 四. OSPF的邻居关系和邻接五.LSA在各区域中传播的支持情况一. OSPF简介:开放式最短路径优先OSPF(OpenShortestPathFirst)是IETF组织开发的一个基于链路状态的内部网关协议(InteriorGatewayProt......
  • 山东大学计算机组成原理实验6七段译码设计(含原理图,引脚分配,实验结果输入输出)
    实验目的熟悉QuartusII的设计流程全过程,学习计数器的设计和硬件测试。掌握原理图的设计方法。实验原理4位计数器连接7段译码,多数码管进行显示控制。实验框图如图6所示。图6 原理图示意图其中,CNT4B采用74161计数器芯片实现,DECL7S采用7448(共阳)设计。实验内容(1)设计工程......
  • MySQL索引底层实现原理
    索引的本质MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。我们知道,数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽可能的快,因此数据库系统的设计者会从查询算法的角度进行优化。最......
  • DDD是软件工程的第一性原理?
    本文书接上回《DDD建模后写代码的正确姿势》,关注公众号(老肖想当外语大佬)获取信息:最新文章更新;DDD框架源码(.NET、Java双平台);加群畅聊,建模分析、技术实现交流;视频和直播在B站。前提本文需要以系列前文的逻辑链条和结论为前提,如果没有阅读过前文的,可以阅读合集《老肖......