Loading

SpringCloud Alibaba Nacos详解

1. 现有项目注册中心替换alibaba组件Nacos

QQ图片20201109164856

1.springcloud 、springboot、springcloud alibaba版本对应

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

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

注意:

  • 0.9版本之后,毕业版本的groudId 为 com.alibaba.com
  • 0.9版本之前,化器版本groudId 为org.springframework.cloud(不推荐使用)

image-20201029100642198

image-20201029100835653

image-20201029101405686

2. 依赖修改

目前mango系统各组件版本

  • springboot版本为1.5
  • springcloud alibaba使用毕业版版本1.5.0RELEASE ,不要使用孵化器版本
  • nacos service 阿里文档中推荐1.1.1版本,实际上使用最新的稳定版本也可以使用,测试验证使用1.2版本可以正常使用
<!--pom文件依赖新增一下内容,删除其他注册中心的相关的依赖(例如:Eureka) 和其他配置中心的依赖(例如:Springcloud config)-->
<dependencyManagement>
    <!--只是对版本进行管理,不会实际引入jar -->
    <dependencies>
        <dependency>
       <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Edgware.SR6</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>1.5.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
  <!--mango系统替换后需要指定springCloud-common包版本号-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-commons</artifactId>
        <version>1.3.5.RELEASE</version>
    </dependency>
    <!--因为nacos config是通过web获取配置,需要导入springboot-web依赖-->
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--nacos 服务发现-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>spring-cloud-commons</artifactId>
                <groupId>org.springframework.cloud</groupId>
            </exclusion>
            <exclusion>
                <artifactId>fastjson</artifactId>
                <groupId>com.alibaba</groupId>
            </exclusion>
        </exclusions>
    </dependency>
       <!--nacos 配置管理-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>spring-cloud-commons</artifactId>
                <groupId>org.springframework.cloud</groupId>
            </exclusion>
        </exclusions>
    </dependency>
      <!--sentinel依赖,注意网关根据不同的实现方式对应着不同的sentinel依赖-->
    <dependency>
          <groupId>com.alibaba.csp</groupId>
          <artifactId>spring-boot-starter-ahas-sentinel-client</artifactId>
          <version>1.7.2</version>
	</dependency>
    
    <dependency>      
          <groupId>org.springframework.cloud</groupId>       
          <artifactId>spring‐cloud‐starter‐openfeign</artifactId>   
    </dependency>
</dependencies>

网关不同实现方式sentinle对应的依赖:

<!--zuul 1.x网关接入, Mango即是zuul 1.X-->
<dependency>
     <groupId>com.alibaba.csp</groupId>
     <artifactId>spring-cloud-zuul-starter-ahas-sentinel</artifactId>
     <version>1.1.8</version>
</dependency>
<!--springcloudGateway网关接入-->
<dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>spring-cloud-gateway-starter-ahas-sentinel</artifactId>
      <version>1.1.8</version>
</dependency>

3. 配置文件修改

其他详细的配置可参考nacos-配置管理中的介绍

#bootstrap
spring:
    application:
        name: gateway
    cloud:
        sentinel:
            transport:
            	#sentinel 控制面板
                dashboard: 127.0.0.1:8083
        nacos:
            config:
            	# nacos service地址
                server-addr: 127.0.0.1:8848
                namespace: public 
                group: DEFAULT_GROUP
                data-id: gateway.yml
                file-extension: yaml
                refresh:
                    enables: true # 默认为fasle ,设为true,开启动态刷新
#application
server:
	port: 56020 #启动端口 命令行注入  
spring:   
	application:     
		name: gateway
     cloud:     
     	nacos:      
        	discovery:       
        		# nacos service地址
            	server‐addr: 127.0.0.1:8848

4. 代码修改

  1. 启动类新增服务发现注解 @EnableDiscoveryClient @EnableFeignClients
  2. 生产者声明@FeignClient(name = "uaa") ,mango项目中,生产者远程代理在common中,注意修改common的依赖

2. nacos介绍

Nacos是阿里的一个开源产品,它是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案。
nacos文档: https://nacos.io/en-us/docs/what-is-nacos.html

2.1 nacos-配置管理

nacos-config文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config

2.1.1 配置中心的使用场景

微服务架构,系统拆分为一个个服务节点,配置文件也被分割,分散中就包含了很多相同的配置,造成冗余。

配置中心就是将配置从应用里剥离出来, 进行统一管理。

配置中心的服务流程

1. 用户在配置中心更新配置信息。
2. 服务A和服务B及时得到配置更新通知,从配置中心获取配置

2.1.2 常见的配置中心

nacos 读写性能最高,且和springcloud condig相比,nacos带图形化界面配置。

img

2.1.3 nacos 快速部署

  1. 下载地址:https://github.com/alibaba/nacos/releases,下载源码自己编译/下载编译好的安装包

  2. 安装包启动方式:bin目录下执行 sh startup.sh -m standalone

    standalone 代表单机模式运行,非集群模式

    sh startup ‐m cluster 标识集群启动

  3. 启动成功, http://127.0.0.1:8848/nacos可打开nacos控制台,默认账号密码都为 nacos

  4. OPEN API 配置管理测试 :https://nacos.io/en-us/docs/open-api.html

    1. 发布配置: curl -X POST "http://127.0.0.1:8848/nacos/v1/cs/configs dataId=nacos.cfg.dataId&group=test&content=HelloWorld"
    2. 获取配置: curl -X GET "http://127.0.0.1:8848/nacos/v1/cs/configs?dataId=nacos.cfg.dataId&group=test"

2.1.4 nacos 外部mysql数据库支持

单机模式nacos使用的是嵌入式数据库实现数据储存,可以配置mysql作为外部存储(目前只支持使用mysql)。

  1. 新建数据库nacos_config

  2. 执行nacos下conf/nacos-mysql.sql 文件

  3. 修改nacos下conf/application.properties,增加支持mysql数据源配置,增加MySQL数据源账号密码

  4. 增加后记得重启一下nacos

    spring.datasource.platform=mysql  
    db.num=1
    db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true 
    db.user=
    db.password=
    

2.1.5 nacos 配置管理应用

对于nacos配置管理,通过namespace、group、Data ID 可以定位到一个配置集(也可以理解为一个配置文件)。

  • namespace:设计是 nacos 基于此做多环境以及多租户(多个用户共同使用nacos)数据(配置和服务)隔离的。
    一个租户情况下:有多套不同环境,可以使用同一套nacos集群,通过建多个不同的nacespace来做配置/服务的隔离
    多个租户情况下:不同租户分配不同的账号密码,创建自己的namespace,不同租户下namespace互不可见(nacos还没有实现多租户功能)

nacos 服务隔离,应该是同一namespace下才可以调用其他服务,注册中心虽然是一个,但是注册表不同

服务获取配置集需要配置的内容:

  1. nacos server 地址 ,必须配置
  2. namespace:可以不指定,默认是public,若指定填写的是namespace的id而不是名称,id在nacos管理页面查看
  3. group:可以不指定,默认是DEFAUT_GROUP
  4. dataId:必须指定

nacos配置管理提供了配置clone,历史版本、回滚、订阅者查询等核心管理能力,通过图形化界面很好操作。

监听查询
Nacos提供配置订阅者即监听者查询能力,同时提供客户端当前配置的MD5校验值,以便帮助用户更好的检查配 置变更是否推送到 Client 端。


2.1.6. nacos登录管理:

除了使用默认的nacos账号密码,可以通过直接在数据库中增加账号密码,密码为BCrypt加密方法
也可以在nacos service中管理登录功能,

spring.security.enabled=false management.security=false security.basic.enabled=false nacos.security.ignore.urls=/** 

2.1.7 nacos集成分布式系统

通过 Spring cloud原生方式快捷的获取配置内容:

1. 在bootstrap.yml基础配置

Spring:	
	cloud:
        nacos:
            config:
            	enabled: true # 可不指定,默认true,false时关闭了nacos-config配置
                server-addr: 127.0.0.1:8848 #必须指定
                namespace: c67e4a97‐a698‐4d6d‐9bb1‐cfac5f5b51c4 # 可不指定,默认为public
                ext-configs[0]: #配置单个配置集的时候,可省略
                  	group: DEFAULT_GROUP  # 可不指定,默认为DEFAULT_GROUP
                 	file-extension: yaml  # 默认是properties
                	data-id: service.yaml # 可不指定,默认是${spring.application.name}和文件拓展名组合
                						# 指定data-id时,需要带上文件拓展名,yaml和yml都可以,和file-extension两者不干扰,也可以配置多个data-id,优先级根据ex-config[n]中n的数字大小决定,数字越大,优先级越高
                	refresh:
                    	enables: true # 默认为false,设为fasle 关闭动态刷新
                 ext‐config[1]:       
                  # Data Id 不在默认的组,不支持动态刷新 
                 	data‐id: ext‐config‐common02.properties  # 可以配置公用的配置               
                 	group: GLOBALE_GROUP                      

在加载配置文件时,不仅会加载${spring.application.name}.${file-extension:properties}为前缀的基础配置

还会加载${spring.application}0 - ${profile}.${file-extension:properties}的基础信息

${spring.profiles.active} 当通过配置文件来指定时必须放在 bootstrap.properties 文件中。但是实际项目中,时通过启动参数-Dspring.profiles.active=<profile>来切换不同的配置

通过修改namespace、group、dataId可以获取指定的配置集,还可以修改profile.active来指定配置集

2. 自定义共享DataId配置

spring:   
 	cloud:   
  		nacos:   
     		config:      
     			# 配置分享dataId必须带上文件后缀名,refreable-dataids也是
     			# shared-dataids 中group为默认的DEFAULT_GROUP ,若所配的data-id不在默认组中获取不到配置集
     			# 支持多个DataId配置,逗号隔开,优先级按照配置出现的先后顺序,即后面的优先级要高于前面。
    			shared‐dataids: ext‐config‐common01.properties,ext‐config‐common02.properties 
    		 		# 指定动态刷新的 配置集
    		 		refreshable‐dataids: ext‐config‐common01.properties 

3. 配置的优先级

Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置。

  • A: 通过 spring.cloud.nacos.config.shared-configs[n].data-id 支持多个共享 Data Id 的配置
  • B: 通过 spring.cloud.nacos.config.extension-configs[n].data-id 的方式支持多个扩展 Data Id 的配置
  • C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置

当三种方式共同使用时,他们的一个优先级关系是:A < B < C

4. 实时获取最新配置

// 注入配置文件上下文
@Autowired 
private ConfigurableApplicationContext applicationContext;  

@GetMapping(value = "/configs") 
public String getConfigs(){   
    // 实时获取配置
	return applicationContext.getEnvironment().getProperty("common.name"); 
}

2.1.8 nacos集群部署

  1. 安装三个以上nacos

  2. 在所有nacos conf目录下将cluster.conf.example文件改为cluster.conf,将每行配置成 ip:port。(请配置3个或3个以上节点)

    # ip:port  
    127.0.0.1:8848 
    127.0.0.1:8849 
    127.0.0.1:8850 
    
    
  3. 单机的话还需修改conf下application.properties中server.port ,防止端口冲突,如果服务器有多个ip也要指定具体的ip地址,如:nacos.inetutils.ip-address=127.0.0.1

  4. 客户端配置,spring.cloud.nacos.config.server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850

    关闭其中的注册中心,其他注册注册中心会被成功选举。

2.2服务发现

2.2.1 常见服务发现中心:

从长远来看Nacos在以后的版本会 支持SpringCLoud+Kubernetes的组合,填补 2 者的鸿沟,在两套体系下可以采用同一套服务发现和配置管理的解 决方案,这将大大的简化使用和维护的成本。另外,Nacos 计划实现 Service Mesh,也是未来微服务发展的趋 势。

image-20201030093733986

2.2.2 代码修改

增加依赖,增加服务发现注解, 如1-4,省略

2.2.3 服务管理

  1. 可以通过nacos service 可视化界面对服务进行管理, 可以看到已经注册的服务,且可以进行服务上线下线管理,通过配置实例的权重,修改实例接收的流量,权重为0时,不接受流量,和下线效果一样
  2. 也可以在服务详情页面,进行元数据的修改,key-value的结构

2.2.4 nacos disvocery 配置项信息详情

配置项 Key 默认值 说明
服务端地址 spring.cloud.nacos.discovery.server-addr Nacos Server 启动监听的ip地址和端口
服务名 spring.cloud.nacos.discovery.service ${spring.application.name} 给当前的服务命名
服务分组 spring.cloud.nacos.discovery.group DEFAULT_GROUP 设置服务所处的分组
权重 spring.cloud.nacos.discovery.weight 1 取值范围 1 到 100,数值越大,权重越大
网卡名 spring.cloud.nacos.discovery.network-interface 当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址
注册的IP地址 spring.cloud.nacos.discovery.ip 优先级最高
注册的端口 spring.cloud.nacos.discovery.port -1 默认情况下不用配置,会自动探测
命名空间 spring.cloud.nacos.discovery.namespace 常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
AccessKey spring.cloud.nacos.discovery.access-key 当要上阿里云时,阿里云上面的一个云账号名
SecretKey spring.cloud.nacos.discovery.secret-key 当要上阿里云时,阿里云上面的一个云账号密码
Metadata spring.cloud.nacos.discovery.metadata 使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息
日志文件名 spring.cloud.nacos.discovery.log-name
集群 spring.cloud.nacos.discovery.cluster-name DEFAULT 配置成Nacos集群名称
接入点 spring.cloud.nacos.discovery.enpoint UTF-8 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址
是否集成Ribbon ribbon.nacos.enabled true 一般都设置成true即可
是否开启Nacos Watch spring.cloud.nacos.discovery.watch.enabled true 可以设置成false来关闭 watch

3. sentinel

image-20201116143639964

流控制组件,包括流控制,并发限制,电路中断和自适应系统保护,以确保微服务的可靠性。

sentinel包括两个部分:

  1. 核心库:不依赖任何框架
  2. 控制台:springboot应用,可直接运行,负责管理推送规则、监控、集群限流分配,机器发现等.

sentinel功能:

  1. 流控
  2. 断路和并发
    1. 最大并发限制
      过限制并发线程的数量(即信号隔离)来减少不稳定资源的影响,而不是使用线程池。(hystrix使用线程池来实现隔离,可做对比//TODO
      当资源的响应时间变长时,线程将开始被占用。当线程数累积到一定数量时,新的传入请求将被拒绝。反之亦然,当资源恢复并变得稳定时,占用的线程也将被释放,新请求将被接受。
    2. 断路
      根据不稳定资源的响应时间降级不稳定资源保证可靠性。当资源的响应时间太大时,将在指定的时间窗口中拒绝所有对该资源的访问。

3.1 定义资源

能通过sentinel Api定义的diamond,就是资源

可以是:服务,方法,某一段代码

3.1.1 Api方式定义资源

文档:https://github.com/alibaba/Sentinel/wiki/如何使用

// 举例
@GetMapping(value="/echo")
public String echo(){
	// 定义资源
	try(Entry entry = Sphu.entry("echo")){
	 //定义被保护的逻辑
	 	return "i am from port"+port;
	}catch(BlockException e){
		retrun "当前访问被控制了";
	}
}

3.1.2 使用注解定义资源

sentinle提供了@SentinelResource注解的方式定义资源,更推荐使用。

主要属性 作用
value 资源名称,必需项(不能为空)
blockHandler 在原方法被限流/降级/系统保护的时候调用,可选项必须与原方法必须处于同一个类中、访问类型必须为public、返回类型需要与原方法相匹配、参数类型需要和原方法相匹配。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  1. 定义配置类

    @Configuration
    public class SentinelAspectConfiguration {
    
        @Bean
        public SentinelResourceAspect sentinelResourceAspect() {
            return new SentinelResourceAspect();
        }
    }
    
  2. 使用注解定义资源

    @GetMapping(value = "/echo")
    @SentinelResource(value = "echo",blockHandler = "echoBlockHandler")
    public String echo() {
    
        //调用规则
        this.initFlowRules();
        //定义被保护的逻辑
        return "i am from port " + port;
    }
    // 1. 必须处于同一个类,2. 访问类型必须为public,3. 返回值类型必须与原方法一致,4. 参数与类型必须与原方法相同,BlockException是特例
    public String echoBlockHandler(BlockException e){
        //处理被控制的逻辑
        return "当前访问被控制了";
    }
    
    private void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //关联域名
        rule.setResource("echo");
        //设定限流阈值类型
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //设置限流阈值
        rule.setCount(2);
        //设置流控针对的调用来源,default为不区分来源
        rule.setLimitApp("default");
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
    

3.2 定义规则

规则就是保护服务稳定的准则,可以是流控规则、服务熔断降级规则、系统保护规则、来源访问控制规则、热点参数规则

sentinle的所有规则都在内存中动态查询修改,规则可以实时进行调整。

定义规则有两种途径:1.sentinel Api 2. sentinel控制台

3.2.1 Api规则定义实现

![1584606721445](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1584606721445.png?lastModify=1604835718)

// 举例
private void initFolwRules(){
	// flowRule 流控规则,可以定义多个流控规则
	List<FlowRule> rules = new ArrayList<>();
	FlowRule rule = new FlowRule();
	//关联域名、资源名称
	rule.setResource("echo");
	//设定限流阀值类型
  rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    //设置限流阈值
    rule.setCount(2);
    //设置流控针对的调用来源,default为不区分来源
    rule.setLimitApp("default");
    rules.add(rule);
   
    FlowRuleManager.loadRules(rules);
}

3.2.2 资源和规则结合

        return "当前访问被控制了";
@GetMapping(value = "/echo")
public String echo() {

    //调用规则
    this.initFlowRules();

    //定义资源
    try (Entry entry = SphU.entry("echo")){
        //定义被保护的逻辑
        return "i am from port " + port;
    } catch (BlockException e) {
        //处理被控制的逻辑
        return "当前访问被控制了";
    }
}

private void initFlowRules() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    //关联域名
    rule.setResource("echo");
    //设定限流阈值类型
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    //设置限流阈值
    rule.setCount(2);
    //设置流控针对的调用来源,default为不区分来源
    rule.setLimitApp("default");
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

3.3 sentinle控制台使用

访问:https://github.com/alibaba/Sentinel/releases。进行对应版本下载,可下载jar包,也可下载exe文件

3.3.1 客户端接入控制台

客户端添加控制台配置后重启服务,若未开启自动心跳, 则服务资源后调用后,才能在控制台看到具体服务。sentinel是延迟加载的。

project:
	name: 服务名
// application.yml中添加配置
sentinel:
	dashboard:
		server: sentinel控制台地址
		

3.4 sentinel各规则详解

3.4.1流控- QPS(每秒查询率)流量控制

sentinel实现流控的原理是监控应用流量的 QPS并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

3.4.1.1 不针对来源进行流控

针对来源选项值当为default,代表不针对来源。即对所有微服务都生效。

3.4.1.2 针对特定来源流控

对于一个系统而言,不同的请求来源,很有可能会设置不同的规则。

例如对于商品搜索结果页面,针对PC端指定一个规则,针对小程序端指定一个规则。

  1. 添加依赖

  2. 声明配置bean

    @Component
    public class MyRequestOriginParser implements RequestOriginParser {
        @Override
        public String parseOrigin(HttpServletRequest httpServletRequest) {
            // 从请求路径或者请求头中获取请求来源信息
            // 此处以请求头为例
            String origin = httpServletRequest.getParameter("origin");
            if (StringUtils.isBlank(origin)) {
                throw new IllegalArgumentException("origin must be exist");
            }
            return origin;
        }
    }
    
  3. 添加配置类

    @Configuration
    @ConditionalOnWebApplication(
        type = ConditionalOnWebApplication.Type.SERVLET
    )
    @ConditionalOnClass({CommonFilter.class})
    @ConditionalOnProperty(
        name = {"spring.cloud.sentinel.enabled"},
        matchIfMissing = true
    )
    //@EnableConfigurationProperties({SentinelProperties.class})
    // 从sentinel源码中copy出来
    public class SentinelWebAutoConfiguration {
        //private static final Logger log = LoggerFactory.getLogger(com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration.class);
        //    @Autowired
        //    private SentinelProperties properties;
        @Autowired
        private Optional<UrlCleaner> urlCleanerOptional;
        @Autowired
        private Optional<UrlBlockHandler> urlBlockHandlerOptional;
        @Autowired
        private Optional<RequestOriginParser> requestOriginParserOptional;
    
        public SentinelWebAutoConfiguration() {
        }
    
        @PostConstruct
        public void init() {
            this.urlBlockHandlerOptional.ifPresent(WebCallbackManager::setUrlBlockHandler);
            this.urlCleanerOptional.ifPresent(WebCallbackManager::setUrlCleaner);
            this.requestOriginParserOptional.ifPresent(WebCallbackManager::setRequestOriginParser);
        }
    
        @Bean
        @ConditionalOnProperty(
            name = {"spring.cloud.sentinel.filter.enabled"},
            matchIfMissing = true
        )
        public FilterRegistrationBean sentinelFilter() {
            FilterRegistrationBean<Filter> registration = new FilterRegistrationBean();
            //        com.alibaba.cloud.sentinel.SentinelProperties.Filter filterConfig = this.properties.getFilter();
            //        if (filterConfig.getUrlPatterns() == null || filterConfig.getUrlPatterns().isEmpty()) {
            //            List<String> defaultPatterns = new ArrayList();
            //            defaultPatterns.add("/*");
            //            filterConfig.setUrlPatterns(defaultPatterns);
            //        }
    
            //        registration.addUrlPatterns((String[])filterConfig.getUrlPatterns().toArray(new String[0]));
            Filter filter = new CommonFilter();
            registration.setFilter(filter);
            registration.setOrder(-2147483648);
            //registration.setOrder(filterConfig.getOrder());
            registration.addInitParameter("HTTP_METHOD_SPECIFY", String.valueOf(false));
            //log.info("[Sentinel Starter] register Sentinel CommonFilter with urlPatterns: {}.", filterConfig.getUrlPatterns());
            return registration;
        }
    }
    
    
    1. 在控制台增加流控规则,指定来源

      ![1585061229263](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1585061229263.png?lastModify=1604838986)

    2. 访问资源增加origin=pc,才会被限流,不是pc时可以任意访问。例如:http://localhost:8083/info?origin=pc

3.4.2 流控-并发线程控制

并发线程数限流用于保护业务线程数不被耗尽。

例如,当下游服务出现响应时间延长,则会导致上游服务吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。

为了避免这种情况,业内比较常见的解决方案是线程隔离(hystrix)。给不同的业务逻辑分配不同的线程池。但是这种方案会造成大量的线程上下文切换,严重影响效率。

sentinle实现原理:统计请求上下文线程数、超出阈值,新的请求被拒绝,效果当前与信号隔离。

// 测试,在控制台增加并发线程控制规则,可以发现有一部分请求会被隔离掉,这是因为超过了线程的阈值。
public class MyThread implements Runnable {
    @Override
    public void run() {
        RestTemplate restTemplate = new RestTemplate();
        String forObject = restTemplate.getForObject("http://localhost:8083/info", String.class);
        System.out.println(forObject);
    }
}

public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        //线程池
        ExecutorService pool = Executors.newCachedThreadPool();
        for(int i=0;i<100;i++){
            MyThread myThread = new MyThread();
            pool.execute(myThread);
        }
    }
}

3.4.3 流控-流控模式、流控效果

  1. QPS关联模式-快速失败:场景为两个方法会产生性能上相互影响,如读方法和写方法。若写方法优先级高,在定义读方法流控规则时,将关联资源定为写方法,当写方法操作超过阈值,会限制读方法。
  2. QPS链路模式快速失败:链路即从指定入口访问的资源达到阈值则限流

流控效果:

  1. 快速失败:达到限流规则,不允许进行访问

  2. warmUp:冷启动/预热,对流量的增加是逐步增加的,适用于处理突发性流量。threshold (阈值)/coldFactor(冷加载因子,默认为3)=最初的阈值,接着最初的阈值*预热时长来最终实现限流。

  3. 排队等待/匀速器:让请求匀速通过,适用处理间隔性突发的流量。

    原理:如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过;

    否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);

    若预期的通过时间超出最大排队时长,则直接拒接这个请求。

    ![1585067276895](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1585067276895.png?lastModify=1604840075)

3.4.4 熔断降级

避免某个接口异常,造成请求堆积,导致系统雪崩。

1. 熔断降级策略

3.4.4.1 熔断策略-平均响应时间:rt

1s 内持续进入 5 个请求,对应时刻的平均响应时间超过阈值count,以 ms 为单位),那么会在下一个时间窗口DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断

3.4.4.2 熔断策略-异常比例

当资源的每秒请求量 >= 5,并且每秒异常总数通过量的比值超过阈值每秒异常总数/通过量 > 阈值)之后,资源进入降级状态,即在下一个时间窗口,对这个方法的调用自动熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%

3.4.4.3 熔断策略-异常数

当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

3.4.5 热点参数限流

热点就是经常被访问的数据,也叫热数据。

热点参数限流会统计访问时所带的参数,根据定义的规则进行参数级别的限流。也是一种流量控制。

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

举例:对id为1的参数进行限流,那么在访问时,生效的级别只会在id为1时,当id为其他值时不会进行限流控制。

![1584713089706](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1584713089706.png?lastModify=1604846149)

3.4.5.1 热点参数位限流

![1584714823721](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1584714823721.png?lastModify=1604846213)

上述配置声明:对于hot资源通过QPS限流模式对访问路径上第一个参数,每秒只能访问一次。

3.4.5.1 热点参数值限流

![1584715354860](file://E:/Sentinel%E4%BD%BF%E7%94%A8%E5%AE%9E%E8%B7%B5/%E8%AE%B2%E4%B9%89/assets/1584715354860.png?lastModify=1604846338)

上述内容声明:对于hot资源通过QPS限流模式对访问路径上第一个参数,每秒只能访问一次。但 当参数值2,每秒能访问两次。当参数值3,每秒能访问10000次。

3.4.6 系统自适应限流

文档:https://github.com/alibaba/Sentinel/wiki/系统自适应限流

3.4.6.1 系统保护简介

sentinel从 应用的load、cpu利用率、总体平均rt、入口QPS和并发线程几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡。

系统保护目的:1. 保证系统不会拖垮 2.系统稳定的前提下,保证系统的吞吐量

以往的系统保护思路:根据硬指标,即load来做系统过载的保护,当系统负载高于阈值,就会禁止或减少流量的进入,load好转即恢复流量的进入。

以往设计思路的弊端

  1. load是一个结果,根据load 的情况来调节流量的通过率会有延迟性
  2. 恢复慢:当下游应用异常导致RT过高,从而load达到了一个很高的点,当下游应用恢复,这是load应然很高,通过率依然还在受限制

sentinel系统保护的思路

  1. 根据系统能处理的请求允许进来的请求做平衡,而不是通过一个间接的指标(load)做限流。
  2. 在sentinel系统保护的做法中,load是作用启动自适应保护因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。

3.4.6.2 系统保护-规则

系统保护规则是从应用级别的入口流量做控制的,并且仅对入口流量生效(进入应用的流量EntryType.IN,如web服务或Dubbo服务端接收的请求)。

从单台机器的load、CPU利用率、平均RT、入口QPS、并发线程数等几个维度监控应用指标。

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护

3.4.6.3 系统保护原理

3.5 动态规则、规则持久化

​ Sentinel默认会把规则信息保存到内存中,当服务重启之后,规则就会丢失。可以将规存储在文件、数据库或者配置中心当中。

3.5.1 规则推送模式

  • pull(拉)模式:客户端定期轮询拉取规则,规则持久化,但实时性不能保证,拉取过于频繁还有性能问题。
  • push(推)模式:规则中心统计推送,客户端通过注册监听器的方式监听变化,可以使用Nacos、zookeeper等配置中心。这种方式有更好的实时性和以执行的保证。推荐使用

4. 负载

文档:https://juejin.im/post/6844903608371118094

在衡量服务器的性能时,经常会涉及到几个指标,load(机器负载)、cpu、mem、qps、rt等。每个指标都有其独特的意义,很多时候在线上出现问题时,往往会伴随着某些指标的异常。

负载(load)是linux机器的一个重要指标,直观了反应了机器当前的状态。

在UNIX系统中,系统负载是对当前CPU工作量的度量,被定义为特定时间间隔内运行队列中的平均线程数。load average 表示机器一段时间内的平均load。这个值越低越好。负载过高会导致机器无法处理其他请求及操作,甚至导致死机。Linux的负载高,主要是由于CPU使用、内存使用、IO消耗三部分构成。任意一项使用过多,都将导致服务器负载的急剧攀升。

load值代表的是对应时间内的jobs的平均数量,比如load1就表示过去1分钟内的jobs数量的平均值。

4.1 查看机器负载

在Linux机器上,有多个命令都可以查看机器的负载信息。其中包括uptimetopw等。

  • uptime:能够打印系统总共运行了多长时间和系统的平均负载。
➜  ~ uptime
13:29  up 23:41, 3 users, load averages: 1.74 1.87 1.97
现在时间、系统已经运行了多长时间、目前有多少登陆用户、系统在过去的1分钟、5分钟和15分钟内的平均负载。
  • w:能够打印当前时间,系统启动到现在的时间,登录用户的数目,系统在最近1分钟、5分钟和15分钟的平均负载。然后是每个用户的各项数据,项目显示顺序如下:登录帐号、终端名称、远 程主机名、登录时间、空闲时间、JCPU、PCPU、当前正在运行进程的命令行]。

  • top:是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。

4.2 机器正常负载范围

没有一个明确的指标。

可以根据自己机器的实际情况,建立一个指标的基线(如近一个月的平均值),只要日常的load在基线上下范围内不太大都可以接收,如果差距太多可能就要人为介入检查了。

阮一峰对此的建议为:
当系统负荷持续大于0.7,你必须开始调查了,问题出在哪里,防止情况恶化。
当系统负荷持续大于1.0,你必须动手寻找解决办法,把这个值降下来。
当系统负荷达到5.0,就表明你的系统有很严重的问题,长时间没有响应,或者接近死机了。你不应该让系统达到这个值。

注:这里的指标是但cpu的,如果是多喝系统,需乘上cpu的数量

4.3 如何降低负荷

CPU使用、内存使用、IO消耗都可能导致负载高。如果是软件问题,有可能由于Java中的某些线程被长时间占用、大量内存持续占用等导致。建议从以下几个方面排查代码问题:

  1. 是否有内存泄露导致频繁GC
  2. 是否有死锁发生
  3. 是否有大字段的读写
  4. 会不会是数据库操作导致的,排查SQL语句问题
  5. 死循环

这里还有个建议,如果发现线上机器Load飙高,可以考虑先把堆栈内存dump下来后,进行重启,暂时解决问题,然后再考虑回滚和排查问题。

4.4 排查思路

1、使用uptime查看当前load,发现load飙高。

➜  ~ uptime
13:29  up 23:41, 3 users, load averages: 10 10 10
复制代码

2、使用top命令,查看占用CPU较高的进程ID。

➜  ~ top

PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
1893 admin     20   0 7127m 2.6g  38m S 181.7 32.6  10:20.26 java
复制代码

发现PID为1893的进程占用CPU 181%。而且是一个Java进程,基本断定是软件问题。

3、使用 top命令,查看具体是哪个线程占用率较高

➜  ~ top -Hp 1893
PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
4519 admin     20   0 7127m 2.6g  38m R 18.6 32.6   0:40.11 java
复制代码

4、使用printf命令查看这个线程的16进制

➜  ~ printf %x 4519
11a7
复制代码

5、使用jstack命令查看当前线程正在执行的方法。(Java命令学习系列(二)——Jstack)

➜  ~ jstack 1893 |grep -A 200 11a7
"thread-5" #500 daemon prio=10 os_prio=0 tid=0x00007f632314a800 nid=0x11a2 runnable [0x000000005442a000]
java.lang.Thread.State: RUNNABLE
at sun.misc.URLClassPath$Loader.findResource(URLClassPath.java:684)
at sun.misc.URLClassPath.findResource(URLClassPath.java:188)
at java.net.URLClassLoader$2.run(URLClassLoader.java:569)
at java.net.URLClassLoader$2.run(URLClassLoader.java:567)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findResource(URLClassLoader.java:566)
at org.hibernate.validator.internal.xml.ValidationXmlParser.getInputStreamForPath(ValidationXmlParser.java:248)
at com.hollis.test.util.BeanValidator.validate(BeanValidator.java:30)
复制代码

从上面的线程的栈日志中,可以发现,当前占用CPU较高的线程正在执行我代码的com.hollis.test.util.BeanValidator.validate(BeanValidator.java:30)类。那么就可以去排查这个类是否用法有问题了。

6、还可以使用jstat(Java命令学习系列(四)——jstat)来查看GC情况,看看是否有频繁FGC,然后再使用jmap(Java命令学习系列(三)——Jmap)来dump内存,查看是否存在内存泄露。

posted @ 2021-03-26 15:09  半瓶牛奶🥛  阅读(1021)  评论(0编辑  收藏  举报