一.nacos与apollo/springcloud config
1.nacos集配置中心中心与注册中心与一体,部署相当简单,单机版可以直接用内置数据库存储,也可以配置mysql数据库存储,参考Nacos支持三种部署模式,而apollo需要分别部署configserver,adminserver,portal,mysql脚本,客户端还需要设置环境等参数;
2.nacos也支持多环境集中处理,支持实时刷新及配置变更监听,支持历史回退,权限控制等,项目持续火热;
3.性能上上nacos也是略胜一筹,其它详细对比参考Nacos、Apollo、Spring Cloud Config微服务配置中心对比_延宝小白马的博客-CSDN博客
二/从eureka到nacos nacos官网文档及github demo
1.下载解压1.3.1
2.config/application.properties修改端口,启动sh bin/startup.sh -m standalone (默认使用内置的derby数据库,可使用外置mysql数据库),或docker部署,参数都通过环境变量指定的
docker run -p 8170:8848 --name nacos -d \
-e PREFER_HOST_MODE=hostname \
-e MODE=standalone \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e NACOS_AUTH_ENABLE=true \
-e MYSQL_SERVICE_HOST=4x.xx.xx.xx \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_DB_NAME=nacos \
-e MYSQL_SERVICE_USER=xxxx\
-e MYSQL_SERVICE_PASSWORD=xxxx \
nacos/nacos-server
3.登录http://localhost:8170 默认账号密码:nacos/nacos,可以新建一条配置,用于springcloud项目来读取并测试即时刷新与监听,如项目spring.application.name==xxx,spring.profile.active=dev,使用yaml格式配置,则配置dataid=xxx.dev.yaml;也可为同时配置一个xxx.yaml。namespace(默认public)>group(DEFAULT_GROUP)>data id(一般为配置文件名),项目较多,多环境时可以充分利用namespace,group区分。
4.springcloud项目集成nacos同时将eureka切换为nacos
加入依赖,就是两个jar,一个支持配置中心的一个支持注册中心的,可以单独使用配置中心、注册中心
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
bootstrap.yml 配置配置中心nacos的地址及文件后缀yaml/properties,(需要根据高优先级的bootstarp.yml配置的nacos信息远程读取到配置进行spring初始化)。需要使用配置的类@Value(value = "${test.key1:12345}")注入并在类上加入注解@RefreshScope即可获取nacos的配置并支持即时刷新。有时也需要监听配置更新进行一个业务逻辑,如动态变更日志级别,类似apollo;
spring:
application:
name: payment
cloud:
nacos:
config:
server-addr: 127.0.0.1:8170 #注意不要http://,nacaos使用的是raft协议
file-extension: yaml
namespace: public #默认public,生产环境配个其它的prod,nacos配置管理权限就是基于namepace来设置的
application-dev.yml配置nacos中心中心的url,并将启动类的@EnableEurekaClient改为@EnableDiscoveryClient,并注释掉pom配置的eureka的jar(spring-cloud-starter-netflix-eureka-client)
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8170 #注意不要http://,nacaos使用的是raft协议
监听nacos配置动态修改日志案例
@Component
public class LoggingSystemConfigListener {
private static final String LOGGER_TAG = "logging.level.";
@Autowired
private LoggingSystem loggingSystem;
@NacosConfigListener(dataId = "${nacos.config.data-id}", type = ConfigType.YAML, timeout = 5000)
public void onChange(String newLog) throws Exception {
Map<String, Object> map = new YamlJsonParser().parseMap(newLog);
for (Object t : map.keySet()) {
String key = String.valueOf(t);
// 如果是 logging.level 配置项,则设置其对应的日志级别
if (key.startsWith(LOGGER_TAG)) {
Object val = map.get(key);
String strLevel = val!=null&&val.toString()!=""?val.toString():"info";
LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());
loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level);
}
}
}
}
nacos配置分享>>>
spring:
application:
name: message
profiles:
active: @package.environment@
cloud:
nacos:
config:
server-addr: @package.nacos.addr@ #注意不要http://,nacaos使用的是raft协议
file-extension: yml #后缀
namespace: @package.environment@ #配置命名空间,默认public
shared-configs:
- data-id: common.yml #配置所有工程共享的配置,注意里面的配置默认不能动态刷新,需要时可配置自动刷新
refresh: true
password: @package.nacos.username@ #nacos 1.3.X才支持
username: @package.nacos.password@
context-path: /nacos
group: DEFAULT_GROUP #使用默认组.如果使用namespace区分项目,则可以考虑group区分环境如DEV_GROUP
discovery:
password: @package.nacos.username@
username: @package.nacos.password@
server-addr: @package.nacos.addr@ #注意不要http://,nacaos使用的是raft协议
namespace: @package.environment@ #服务命名空间,默认public
集群方案
参考官方,集群部署一般3节点,bootstarp.yml配置文件配置server-addr改为nginx监听的地址,由nignx转发到真正的nacos实例。注意每个nacos 都需要修改cluster.conf配置3个实例地址,每个实例不能使用内置的derby数据库,需要集中到同一个mysql库。
三/从hystrix到sentinel 文档地址
1.sentinel较hystrix更轻量级高性能,除了支持滑动时间窗口熔断降级,还支持接口基于QPS的限流,支持黑白名单,支持基于系统load,cpu使用率等全局降级,这些都是可以动态修改的,并且还有完美的中文界面。
2.下载安装sentinel 1.8.0 (dashboard)
java -Dserver.port=8101 -Dcsp.sentinel.dashboard.server=localhost:8101 -Dproject.name=sentinel-dashboard -Dauth.username=sentinel -Dauth.password=sentinel -Dlogging.file=/var/sentinel.log -jar sentinel-dashboard-1.8.0.jar
3.docker安装
docker run --name sentinel -d -p 8101:8858 -p 8719:8719 -d bladex/sentinel-dashboard
4.谷歌浏览器访问localhost:8101 账号密码sentinel/sentinel
5.在springcloud项目加入sentinel依赖,maven中央长裤找的,跟springboot大版本一致
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
6.配置sentinel连接地址等
springcloud:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8170
sentinel:
transport:
dashboard: 127.0.0.1:8101
enabled: true
测试:dashboard新增流控/降级规则,资源名写uri即可
7.流控降级
- 直接模式:QPS/并发线程数(直接设置流控阈值0,1进行测试)
- 关联模式:下单/create-order关联支付/pay接口,支付接口触发流控使上游的下单接口也被流控(jmeter/postman/ab压测/pay接口后,再访问下单接口)
- 链路模式:这个是大伙避而不谈的一种模式,确实比较麻烦,参考3-3服务容错sentinel-1流控规则_hzp-CSDN博客
- 快速失败: 直接失败,抛出异常
- Warm Up:当系统长期处于一个低负载的情况下,或服务刚刚启动,这种资源没有加载完成时。warm up冷启动+预热模式,开始的阈值是最大QPS阈值的1/3,然后慢慢增长,在配置时间内达到我们配置的阈值上限,达到保护系统的作用。高峰期后,可能会发生类卸载等,故该机制会重置QPS阈值,准备迎接下一波冲击
- 排队等待:匀速+排队,会给触发阈值的请求一个排队机会,排队超时才抛出异常
触发时抛出异常Blocked by Sentinel (flow limiting),http状态码429 too manay request, 一般需要自定义流控降级blockHandler,注意降级方法出入参方法修饰符
8.熔断降级
熔断降级原理与效果基本同hystrix,阈值触发时,同流控抛出异常Blocked by Sentinel (flow limiting),http状态码429 too manay request, 一般需要自定义流控降级blockHandler,注意降级方法出入参方法修饰符
设置RT(response time)超过1ms超时,统计滑动时间窗口1s内,达到2个请求并且触发RT比例超过50%即会进入熔断,3s后进入半开,放过一个请求试探,通过则关闭熔断器,否则继续熔断,3s后再次进入半开。。。
注意:低版本sentinel没有半开状态,(sentinel异常统计逻辑在全局异常处理之后)被全局异常处理捕获的异常将不会被计入统计
9.热点规则
热点规则是一种基于方法参数配置的一种降级手段,带@SentinelResource注解的请求中有userId这个参数的请求进行流控,其参数例外项还可以配置如userId值为1时设定特别的限流值 。
@GetMapping("/test3")
@SentinelResource(value = "test3",
blockHandler = "exceptionHandler",
blockHandlerClass = SentinelHandler.class,
fallback = "fallbackHandler",
fallbackClass = SentinelHandler.class)
public ResultEntity test3(@RequestParam(required = false) String userId, @RequestParam(required = false) String entpId) {
int aa = 1 / 0;
return ResultEntity.success();
}
package com.construn.vehicle.payment.controller;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.construn.vehicle.common.base.entity.ResultEntity;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
@Slf4j
public class SentinelHandler {
/**
* 熔断降级处理方法参数可以最后多一个 BlockException,其余入参出参类型与原函数一致,方法public static.
* @param a
* @param b
* @param e
* @return
*/
public static ResultEntity exceptionHandler(String a,String b , BlockException e) {
log.error("调用SentinelExceptionUtil中的exceptionHandler方法->{}",e.getMessage());
return ResultEntity.fail("9999","降级处理");
}
/**
* 流控规则检验在前,如果触发流控规则,则不会进入@SentinelResource方法,也就不会进入fallbackHandler
* @param a
* @param b
* @param e
* @return
*/
public static ResultEntity fallbackHandler( String a,String b,Throwable e ) {
log.error("调用SentinelExceptionUtil中的fallbackHandler方法->{}",e.getMessage() );
return ResultEntity.fail("9999","异常处理");
}
}
几个注意点:
- 建议使用@SentinelResource注解的value值配置各种规则(流控,降级规则资源名可以是path或@SentinelResource的value值),如果根据path配置,触发规则只会抛出异常不会进入SentinelHandler进行处理,如果没有blockHandler没有定义则抛出5XX异常。
- 如果程序中抛出了runtime异常则不会计入流控统计,如果程序正常触发阈值则抛出com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException (区别其它流控,这里抛出500异常,而不是429异常——Blocked by Sentinel (flow limiting))。
- 如果配置有降级处理方法,如@SentinelResource(value = "test3", blockHandler = "exceptionHandler",blockHandlerClass = SentinelExceptionUtil.class,fallback = "fallbackHandler",fallbackClass = SentinelExceptionUtil.class) ,但同时如果系统有全局异常处理将com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException提前给捕获了,sentinel则认为未触发流控规则,故不会进入降级方法。可以考虑不捕获ParamFlowException异常或改为网关进行捕获
- 这里的blockHandler不仅仅适用于热点规则,也适用于流控规则,熔断降级规则(如果像上例定义了fallbackHandler,即把异常处理了,那么sentinel感知不到异常,不会触发熔断降级规则,固不会进入blockHandler)。
10.系统规则
可以从LOAD /RT /线程数 /入口 QPS /CPU 使用率 等方面保护系统,触发阈值时抛出异常Blocked by Sentinel (flow limiting),http状态码429 too manay request。系统规则无法像接口一样自定义blockHandler,但可以让程序跳到一个更友好的页面,或返回更友好的json
11. 每个接口单独配置@SentinelResource指定blockHandler,callback比较麻烦,可如下全局sentinel异常处理。其实全局异常处理也不一定好。个人赶脚最好的处理方式是:微服务内部feign调用,调用方自己写fallback;如果是网关调用微服务,则网关统一处理异常
参考springcloud gataway整合sentinel,nacos。
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义sentinel异常返回信息
*/
@Component
public class CustomerBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
SentinelErrorMsg sentinelErrorMsg = new SentinelErrorMsg();
if (e instanceof FlowException) {
sentinelErrorMsg.setMsg("接口限流了");
sentinelErrorMsg.setStatus(101);
} else if (e instanceof DegradeException) {
sentinelErrorMsg.setMsg("服务降级了");
sentinelErrorMsg.setStatus(102);
} else if (e instanceof ParamFlowException) {
sentinelErrorMsg.setMsg("热点参数限流了");
sentinelErrorMsg.setStatus(103);
} else if (e instanceof SystemBlockException) {
sentinelErrorMsg.setMsg("系统规则(负载/...不满足要求)");
sentinelErrorMsg.setStatus(104);
} else if (e instanceof AuthorityException) {
sentinelErrorMsg.setMsg("授权规则不通过");
sentinelErrorMsg.setStatus(105);
}
// http状态码
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
httpServletResponse.setContentType("application/json;charset=utf-8");
// spring mvc自带的json操作工具,叫jackson
new ObjectMapper()
.writeValue(
httpServletResponse.getWriter(),
sentinelErrorMsg
);
}
@PostConstruct
public void init() {
new CustomerBlockHandler();
}
@Data
static class SentinelErrorMsg {
private Integer status;
private String msg;
}
}
12.配置持久化存储方案
单纯使用sentinel,规则无法进行持久化,在关闭、重启工程后,所配置的规则会丢失。需要自己进行额外的持久化操作,可以持久化到Nacos或是本地文件
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8118
enabled: true
datasource: #持久化配置指定nacos
ds1:
nacos:
server-addr: @package.nacos.addr@
dataId: ${spring.application.name}
groupId: SENTINEL_GROUP
namespace: test #这里没使用默认的PUBLIC
data-type: json
rule-type: flow
username: nacos #开启了权限验证则需要配置账户密码
password: nacos
比较麻烦的是需要在nacos配,不能将sentinel dashboard的所有配置copy到nacos??
可以F12 在/rules这个接口的 data内容(一个json array)copy到nacos 配置内容里
这样重启微服务即可从nacos读取到sentinel规则,但是sentinel dashboard还是没有流控规则,这时需要访问test3这个资源时sentinel dashboard才可再次看到。在sentinel修改规则立即生效但不会同步到nacos,如果从nacos修改即可持久化并立即生效