注:springboot,spring-cloud,spring-cloud-alibaba相关jar有冲突,需要注意版本;
系统核心组件及架包 |
系统版本 |
Nacos中间件 |
1.1.4 |
Sentinel中间件 |
1.8.4 |
Zipkin-server 中间件 |
2.23.19 |
org.springframework.boot |
2.2.4.RELEASE |
spring-cloud-dependencies |
Hoxton.SR1 |
spring-cloud-alibaba-dependencies |
2.2.0.RELEASE |
spring-cloud-starter-alibaba-nacos-discovery |
1.5.0.RELEASE |
spring-cloud-starter-alibaba-nacos-config |
2.2.0.RELEASE |
nacos-client |
1.1.4 |
spring-cloud-starter-alibaba-sentinel |
1.5.1.RELEASE |
sentinel-datasource-nacos |
1.7.1 |
sentinel-core |
1.7.1 |
spring-cloud-starter-gateway |
2.2.1.RELEASE |
spring-cloud-starter-bootstrap |
3.0.1 |
spring-boot-starter-webflux |
2.2.4.RELEASE |
spring-cloud-starter-zipkin |
2.2.1.RELEASE |
spring-cloud-starter-openfeign |
2.2.1.RELEASE |
同理创建子项目:
ddky-risk-generate:用于生成JDBC相关基础代码
ddky-risk-core : 用于基础对象,工具Util核心jar
ddky-risk-feign: 用于web服务调用封装
ddky-risk-products: 用于提供提供者服务
ddky-risk-web:用于提供消费者服务
ddky-risk-gateway: 用于网关转发服务
下载地址:https://github.com/alibaba/nacos/releases/tag/1.1.4
(版本兼容问题)
Windows 单机启动
cd D:\fills-tools\spring-cloud\nacos\bin
执行命令,单集群模式
startup.cmd -m standalone
http://localhost:8848/nacos
name/password -> nacos/nacos
<dependencyManagement> <dependencies>
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.2.4.RELEASEversion> <type>pomtype> <scope>importscope> dependency> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-dependenciesartifactId> <version>Hoxton.SR1version> <type>pomtype> <scope>importscope> dependency> <dependency> <groupId>com.alibaba.cloudgroupId> <artifactId>spring-cloud-alibaba-dependenciesartifactId> <version>2.2.0.RELEASEversion> <scope>importscope> <type>pomtype> dependency> <dependency> <groupId>com.alibaba.cloudgroupId> <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId> <version>1.5.0.RELEASEversion> dependency>
...... 其他依赖 </dependencyManagement> |
<dependency> <groupId>com.alibaba.nacosgroupId> <artifactId>nacos-clientartifactId> <version>1.1.4version> dependency> <dependency> <groupId>com.alibaba.cloudgroupId> <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId> <version>1.5.0.RELEASEversion> dependency>
|
spring: application: name: ddky-risk-cloud-provider cloud: nacos: discovery: server-addr: 127.0.0.1:8848 |
@RestController @RequestMapping("/riskConfig") @Slf4j public class RiskConfigServer { @Autowired private RiskConfigService riskConfigService; @RequestMapping(value="/getRiskConfig",method = {RequestMethod.GET,RequestMethod.POST}) public RiskConfig getRiskConfig(String riskCode){ log.info("请求参数:{}",riskCode); RiskConfig res = riskConfigService.queryRiskConfig(riskCode); // *service 服务 log.info("响应结果:{}",JsonTools.writeValueAsString(res)); return res; } } |
<dependency> <groupId>com.alibaba.nacosgroupId> <artifactId>nacos-clientartifactId> <version>1.1.4version> dependency> <dependency> <groupId>com.alibaba.cloudgroupId> <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId> <version>1.5.0.RELEASEversion> dependency>
|
spring: application: name: ddky-risk-cloud-consumer cloud: nacos: discovery: server-addr: 127.0.0.1:8848 |
RestTemplate 手动实现访问服务端服务,只做了解
ServiceInstanceLoadBalancer 手动实现负载均衡,只做了解
实战应用场景参考:Spring-cloud-alibaba-feign 搭建
@RestController @RequestMapping("riskConfig") public class RiskConfigController extends BaseController { @Autowired protected ServiceInstanceLoadBalancer serviceInstanceLoadBalancer; @GetMapping(value = "/getRiskConfig", produces = {"application/json;charset=UTF-8"}) @ResponseBody public ServiceResult> getRiskConfig(String riskCode) { try { ServiceResult result = new ServiceResult<>(); //serviceInstance = loadBalancerClient.choose("ddky-risk-cloud-provider"); ServiceInstance serviceInstance = serviceInstanceLoadBalancer.getServiceInstance(); String targetUrl = serviceInstance.getUri() + "/riskConfig/getRiskConfig"; log.info("获取服务端请求地址"+targetUrl); LinkedMultiValueMap<String, Object> params = new LinkedMultiValueMap<String,Object>(); params.add("riskCode",riskCode); Map<String,Object> objectMap = new HashMap<>(); objectMap.put("riskCode",riskCode); RiskConfig config = restTemplate.postForObject(targetUrl,params,RiskConfig.class,objectMap); result.setResult(config); return result; }catch (Exception e){ log.error("保存异常",e); return new ServiceResult<>(ResultCodeEnum.failure.getIndex(),e.getMessage()); } } } |
ServiceInstanceLoadBalancer 自定义负载均衡也可以使用springcloud的LoadBalancerClient负载均衡客户端
只做了解
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.stereotype.Component; import java.net.InetAddress; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; /** * nacos 手动实现服务治理负责均衡 随机,轮询,Hash */ @Component public class ServiceInstanceLoadBalancer { //nacos 提供者服务名称 @Value("${server.cloud.name}") private String providerServer; //负载均衡策略,该配置可改成动态方式 @Value("${server.instance.type:0}") private String instanceType; // 0-随机,1-轮询,2-Hash //随机 private static final String RANDOM = "0"; //轮询 //private static final String POLL = "1"; //Hash private static final String HASH = "2"; //获取随机数 private static final Random random = new Random(); //nacos注册服务客户端 @Autowired protected DiscoveryClient discoveryClient; //CAS 原子类 private AtomicInteger poll = new AtomicInteger(0); //获取服务提供者 public ServiceInstance getServiceInstance(){ List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances(providerServer); ServiceInstance serviceInstance =null; try { if (RANDOM.equals(instanceType)) {//随机 serviceInstance = serviceInstanceList.get(random.nextInt(serviceInstanceList.size())); } else if (HASH.equals(instanceType)) {//IP Hash serviceInstance = serviceInstanceList.get(getIpHash() % serviceInstanceList.size()); } else {//轮询,默认 serviceInstance = serviceInstanceList.get(poll.getAndIncrement() % serviceInstanceList.size()); } }catch (Exception e){ } return serviceInstance; } //获取IP Hash private int getIpHash(){ try { return InetAddress.getLocalHost().getHostAddress().hashCode(); } catch (Exception e) { return 0; } } } |
String providerServer : nacos 服务端提供者服务名称
String instanceType :负载均衡策略,该配置可改成动态方式(disconf,apollo,redis)
Random random :随机数轮询
AtomicInteger poll:CAS 原子类 用于轮询自增使用
DiscoveryClient discoveryClient: nacos 提供者客户端获取所有服务
public ServiceInstance getServiceInstance()
根据instanceType策略(0-随机,1-轮询,2-Hash)获取服务端server
private int getIpHash()
获取本地IP地址转为HashCode(int),用于Hash方式获取服务端server
instanceType (0-随机)策略
instanceType (1-轮询)策略
instanceType (2-Hash)策略
<dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-openfeignartifactId> <version>2.2.1-SNAPSHOTversion> dependency> |
注:Feign 配置可以忽略,可以针对线上环境具体配置
feignName:FeginClient的名称
connectTimeout : 建立链接的超时时长
readTimeout : 读取超时时长
loggerLevel: Fegin的日志级别
errorDecoder :Feign的错误解码器
retryer : 配置重试
requestInterceptors : 添加请求拦截器
decode404 : 配置熔断不处理404异常
feign: client: config: feignName: ##定义FeginClient的名称 connectTimeout: 5000 # 链路链接超时时间 readTimeout: 5000 # 读取超时时间 # 配置Feign的日志级别,相当于代码配置方式中的Logger loggerLevel: full |
value、name:作用一样,配置nacos服务名称,用于服务发现
url:用于配置指定服务的地址,相当于直接请求这个服务,不经过Ribbon的服务选择
path:定义当前FeignClient访问接口时统一前缀,例接口地址是/riskConfig/getRiskConfig, 定义了前缀是riskConfig, 同Spring的RequestMapping
contextId:唯一标识,不想将所有的调用接口都定义在一个类中,手动指定不同的contextId解决冲突
decode404:请求报404错误时,值为true时执行decoder解码,否则抛出异常
configuration:配置Feign配置类,在配置类中可以自定义Feign的Encoder、Decoder、LogLevel、Contract等
fallback:容错的处理类,也就是回退逻辑,fallback的类必须实现Feign Client的接口,无法知道熔断的异常信息。
fallbackFactory:容错的处理类,可以知道熔断的异常信息
package com.ddky.risk.cloud.feign; import com.ddky.risk.cloud.domain.RiskConfig; import com.ddky.risk.cloud.response.ServiceResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(value="ddky-risk-cloud-provider",path ="/riskConfig") public interface RiskConfigFeign { @RequestMapping("/getRiskConfig") public ServiceResult } |
服务端web服务和feign提供的接口保持一致
@RestController @RequestMapping("/riskConfig") @Slf4j public class RiskConfigServer { @Autowired private RiskConfigService riskConfigService; @RequestMapping(value="/getRiskConfig",method = {RequestMethod.GET,RequestMethod.POST}) @ResponseBody public ServiceResult log.info("请求参数:{}",riskCode); ServiceResult result = new ServiceResult(); RiskConfig res = riskConfigService.queryRiskConfig(riskCode); log.info("响应结果:{}",JsonTools.writeValueAsString(res)); result.setResult(res); return result; } } |
@Slf4j @RestController @RequestMapping("riskConfig") public class RiskConfigController extends BaseController {
@Autowired private RiskConfigFeign riskConfigFeign; @GetMapping(value = "/getRiskConfigFeign", produces = {"application/json;charset=UTF-8"}) @ResponseBody public ServiceResult> getRiskConfigFeign(ServiceRequest try { ServiceResult return result; }catch (Exception e){ log.error("保存异常",e); return new ServiceResult<>(ResultCodeEnum.failure.getIndex(),e.getMessage()); } } } |
Gateway
Web
Products
Zipkin
下载地址:https://github.com/alibaba/Sentinel/releases
启动脚本
java -jar D:/fills-tools/spring-cloud/sentinel-dashboard-1.8.1.jar --server.port=8881
或者:java -jar ./sentinel-dashboard-1.8.1.jar
用户名/密码 sentinel/sentinel
<dependency> <groupId>com.alibaba.nacosgroupId> <artifactId>nacos-clientartifactId> <version>1.1.4version> dependency> <dependency> <groupId>com.alibaba.cloudgroupId> <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId> <version>1.5.1.RELEASEversion> dependency> <dependency> <groupId>com.alibaba.cloudgroupId> <artifactId>spring-cloud-starter-alibaba-sentinelartifactId> <version>1.5.1.RELEASEversion> dependency> <dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-datasource-nacosartifactId> <version>1.7.1version> dependency> <dependency> <groupId>com.alibaba.cspgroupId> <artifactId>sentinel-coreartifactId> <version>1.7.1version> dependency> |
#添加sentinel依赖后 暴露/actuator/sentinel management: endpoints: web: exposure: include: '*' |
http://localhost:8882/actuator/sentinel
spring: application: name: ddky-risk-cloud-provider cloud: nacos: discovery: server-addr: 127.0.0.1:8848 sentinel: # sentinel 接入配置 eager: true transport: dashboard: 127.0.0.1:8080 #指定sentinel控制台的地址 port: 8719 clientIp: 127.0.0.1 datasource: #sentinel使用nacos接入持久化配置 flow: nacos: serverAddr: 127.0.0.1:8848 dataId: ddky-risk-cloud-provider-flow groupId: DEFAULT_GROUP dataType: json ruleType: flow |
data为泛型,用于封装自定义sentinel处理器,入参统一
package com.ddky.risk.cloud.request; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; @JsonIgnoreProperties(ignoreUnknown = true) public class ServiceRequest <T> implements Serializable { private static final long serialVersionUID = -1L; private T data; public ServiceRequest(T data) { this.data = data; } public ServiceRequest() { } public T getData() { return data; } public void setData(T data) { this.data = data; } } |
result为泛型,用于封装自定义sentinel处理器,出参统一
package com.ddky.risk.cloud.response; import com.ddky.risk.cloud.enums.ResultCodeEnum; import com.ddky.risk.cloud.exception.BusinessException; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; /** * 通用返回值 */ @JsonIgnoreProperties(ignoreUnknown = true) public class ServiceResult<T> implements Serializable {
private static final long serialVersionUID = -394529804715984961L; private Integer code; private String msg; private T result; public ServiceResult() { this(ResultCodeEnum.Success); } public ServiceResult(ResultCodeEnum resultCode) { this.code = resultCode.getIndex(); this.msg = resultCode.getMessage(); } public ServiceResult(Integer code, String msg) { this.code = code; this.msg = msg; } public ServiceResult(BusinessException e) { this.code = e.getCode(); this.msg = e.getMessage(); } ... public Integer getCode() { return this.code; } public void setCode(Integer code) { this.code = code; } ... } |
封装自定义处理器是因为sentinel 需要方法入参(参数最后多了一个异常)和出参与原方法保值一致才能拦截到sentinel阻断异常见:SentienlException类
package com.ddky.risk.cloud.exception; 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.ddky.risk.cloud.enums.ResultCodeEnum; import com.ddky.risk.cloud.request.ServiceRequest; import com.ddky.risk.cloud.response.ServiceResult; // 自定义sentinel处理器 public class SentienlException { /** * 系统异常时,捕获并返回错误 * @param data * @param e * @return */ public static ServiceResult errorException(ServiceRequest data,Throwable e){ return new ServiceResult(ResultCodeEnum.failure.getIndex(),e.getClass().getName()+":"+e.getMessage()); } /** * sentinel 限流异常时,封装并返回错误 * @param data * @param e * @return */ public static ServiceResult blockException(ServiceRequest data,BlockException e){ if (e instanceof FlowException) {//限制异常 return new ServiceResult(ResultCodeEnum.flow_error); } else if (e instanceof ParamFlowException) {//热点参数异常 return new ServiceResult(ResultCodeEnum.hot_error); } else if (e instanceof DegradeException) {//降级异常 return new ServiceResult(ResultCodeEnum.degrade_error); } else if (e instanceof AuthorityException) {//受权异常 return new ServiceResult(ResultCodeEnum.auth_error); } //提供安全异常 return new ServiceResult(ResultCodeEnum.system_block_error); } } |
SentinelResource使用说明:
Fallback:系统异常时回调方法
FallbackClass:指定系统异常处理类,不配是默认当前类下
BlockHandler:系统阻断时处理方法
BlockHandlerClass:指定系统阻断处理类,不配是默认当前类下
package com.ddky.risk.cloud.webserver; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.ddky.risk.cloud.domain.RiskConfig; import com.ddky.risk.cloud.exception.SentienlException; import com.ddky.risk.cloud.request.ServiceRequest; import com.ddky.risk.cloud.response.ServiceResult; import com.ddky.risk.cloud.service.nacos.RiskConfigService; import com.ddky.risk.cloud.util.JsonTools; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; /** * @author ysf * @version 1.0 * @description: TODO * @date 2022/2/14 10:07 */ @RestController @RequestMapping("/riskConfig") @Slf4j public class RiskConfigServer { @Autowired private RiskConfigService riskConfigService; @RequestMapping(value="/getRiskConfig",method = {RequestMethod.GET,RequestMethod.POST}) @ResponseBody @SentinelResource(value="/getRiskConfig",blockHandler = "blockException",blockHandlerClass = {SentienlException.class},fallback = "errorException",fallbackClass = {SentienlException.class}) public ServiceResult log.info("请求参数:{}",riskCode); ServiceResult result = new ServiceResult(); RiskConfig res = riskConfigService.queryRiskConfig(riskCode.getData()); log.info("响应结果:{}",JsonTools.writeValueAsString(res)); result.setResult(res); return result; } } |
正常结果:
阻断结果:
异常结果
配置数据实体对象参考:com.alibaba.cloud.sentinel.datasource.RuleType
Sentinel切面执行器:com.alibaba.csp.sentinel.annotation.aspectj
.SentinelResourceAspect
FLOW("flow", FlowRule.class), DEGRADE("degrade", DegradeRule.class), PARAM_FLOW("param-flow", ParamFlowRule.class), SYSTEM("system", SystemRule.class), AUTHORITY("authority", AuthorityRule.class), GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule"), GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition"); |
Resource:资源名是限流规则的作用对象
LimitApp:流控针对的调用来源,若为 default 则不区分调用来源
Grade:限流阈值类型,QPS 模式(1)或并发线程数模式(0)
Count:限流阈值
Strategy:调用关系限流策略:直接-0、链路-1、关联-2
ControlBehavior:流量控制效果(直接拒绝-0、Warm Up-1、匀速排队-2)
ClusterMode:是否集群限流 是-true、否-false
[{ "resource": "/getRiskConfig", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false }] |
resource:资源名,
grade:熔断策略0-慢比例调用,1-异常比例,2-异常数,
count:grade=0,毫秒数;=1,比列阀值[0.0,1.0];=2,异常数,
slowRatioThreshold:grade=0,比例阈值[0.0,1.0],
timeWindow":熔断时长秒,
minRequestAmount:最小请求数,
statIntervalMs:统计时长(ms)
[{ "resource": "/getRiskConfig", "grade": "0", "count": "1", "slowRatioThreshold": "0.1", "timeWindow": "1", "minRequestAmount": "1", "statIntervalMs": "1000" }] |
resource:资源名,
paramIdx:热点参数的索引,
count:单机阈值,
durationInSec:统计窗口时长(单位为 秒),
clusterMode":是否集群,
paramFlowItemList.classType:参数数据类型:int,double,byte,float,long,char,java.lang.String
paramFlowItemList.object:参数值
paramFlowItemList.count:限流阈值
注:该场景用于接口验证参数是基础数据,入参是引用对象不可用,所以不适用本次封装的请求对象
[{ "resource": "/getRiskConfig", "paramIdx": "0", "count": "1", "durationInSec": "1", "clusterMode": false, "paramFlowItemList":[{ "classType": "java.lang.String", "object": "10001", "count":"1" }] }] |
grade:0-LOAD,1-RT,2-线程数,3-入口QPS,4-CPU使用率
highestSystemLoad:LOAD
avgRt:RT
maxThread:线程数
qps:入口 QPS
highestCpuUsage:CPU 使用率
注:entryType = EntryType.IN 切面配置内部资源调用才会触发系统保护规则
@SentinelResource(value="/getRiskConfig",entryType= EntryType.IN,blockHandler = "blockException",blockHandlerClass = {SentienlException.class},fallback = "errorException",fallbackClass = {SentienlException.class})
[{ "highestCpuUsage:CPU":1 }] |
resource:资源名
limitApp:流控应用,多个调用方名称用半角英文逗号(,)分隔
strategy:授权类型0-白名单,1-黑名单
[{ "resource":"/getRiskConfig", "limitApp":"", "strategy":0 }] |
<dependency> <groupId>com.alibaba.cloudgroupId> <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId> <version>1.5.1.RELEASEversion> dependency> <dependency> <groupId>com.alibaba.cloudgroupId> <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId> <version>2.2.0.RELEASEversion> dependency> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-gatewayartifactId> <version>2.2.1.RELEASEversion> dependency> <dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-bootstrapartifactId> <version>3.0.1version> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webfluxartifactId> <version>2.2.4.RELEASEversion> dependency> |
注:该方式是写死在application.yml配置,每次需要新增修改,需要重新发布应用,只做了解不做使用
server: port: 8888 logging: config: classpath:logback.xml level: org: springframework: boot: autoconfigure: ERROR # 日志不打印条件评估报告 spring: application: name: ddky-risk-cloud-getway # 应用名称 cloud: nacos: discovery: server-addr: localhost:8848 gateway: discovery: locator: enabled: true routes: - id: ddky-risk-cloud-consumer #路由标识符,区别于其他 Route uri: lb://ddky-risk-cloud-consumer #路由指向的目的地 uri,即客户端请求最终被转发到的微服务 predicates: #断言,用于条件判断,只有断言都返回真,才会真正的执行路由 - Path=/riskConfig/** |
注:该方式是需要通过application.yml和bootstrap.yml (gateway nacos 需要使用),每次只要在nacos配置中心新增修改,保存后即可实时刷新并应用
application.yml
server: port: 8888 logging: config: classpath:logback.xml level: org: springframework: boot: autoconfigure: ERROR # 日志不打印条件评估报告 spring: application: name: ddky-risk-cloud-getway # 应用名称 |
bootstrap.yml
spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: #nacos config 配置 server-addr: 127.0.0.1:8848 file-extension: yaml #文件内容及格式 group: DEFAULT_GROUP prefix: ddky-risk-cloud-getway #nacos 动态配置前缀 |
nacos 配置中心
配置内容 spring.cloud.gateway.routes List集合
-id:# 路由唯一标识符
uri:# 路由目标地址,通过gateway转发到改地址 http原链接转发 或 lb://nacos 负载均衡
predicates:#断言 用于条件判断 List集合
- Path=/xxx/**
为一个独立子集,可以同时配置多个
例如:http://localhost:8888/riskConfig/index
注:gateway转发调转页面样式错乱
解决方案一:
需要在原页面指定具体css,js,image等静态资源的具体路径
例如: <script src="${path}/static/js/other/select/dynamic-column.js" type="text/javascript">script> 改为 <script src="http://localhost:8081/static/js/other/select/dynamic-column.js" type="text/javascript">script> |
解决方案二:
新增静态资源转发配置:ddky-risk-cloud-consumer-static
spring: cloud: gateway: discovery: locator: enabled: true routes: - id: ddky-risk-cloud-consumer #路由标识符,区别于其他 Route uri: lb://ddky-risk-cloud-consumer #路由指向的目的地 uri,即客户端请求最终被转发到的微服务 predicates: #断言,用于条件判断,只有断言都返回真,才会真正的执行路由 - Path=/riskConfig/** - id: ddky-risk-cloud-consumer-static #路由标识符,区别于其他 Route uri: lb://ddky-risk-cloud-consumer #路由指向的目的地 uri,即客户端请求最终被转发到的微服务 predicates: #断言,用于条件判断,只有断言都返回真,才会真正的执行路由 - Path=/static/** |
解决后效果:
以JSON对象返回结果集,建议统一入参和出参数据封装,避免接口不统一,影响阅读及风格不统一开发耗时
用于存储全链路调用链信息
https://zipkin.io/
https://repo1.maven.org/maven2/io/zipkin/zipkin-server/2.23.19/zipkin-server-2.23.19-exec.jar
java -jar zipkin-server-2.23.19-exec.jar
java -jar zipkin-server-2.23.19-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3310 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=root
数据库脚本
CREATE TABLE IF NOT EXISTS zipkin_spans ( `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` BIGINT NOT NULL, `id` BIGINT NOT NULL, `name` VARCHAR(255) NOT NULL, `remote_service_name` VARCHAR(255), `parent_id` BIGINT, `debug` BIT(1), `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL', `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query', PRIMARY KEY (`trace_id_high`, `trace_id`, `id`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds'; ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames'; ALTER TABLE zipkin_spans ADD INDEX(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames'; ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range'; CREATE TABLE IF NOT EXISTS zipkin_annotations ( `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id', `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id', `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1', `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB', `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation', `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp', `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address', `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null' ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds'; ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames'; ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values'; ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job'; CREATE TABLE IF NOT EXISTS zipkin_dependencies ( `day` DATE NOT NULL, `parent` VARCHAR(255) NOT NULL, `child` VARCHAR(255) NOT NULL, `call_count` BIGINT, `error_count` BIGINT, PRIMARY KEY (`day`, `parent`, `child`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci; |
注:spring-cloud-starter-zipkin jar包含(spring-cloud-starter-sleuth
,spring-cloud-sleuth-zipkin)
<dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-zipkinartifactId> <version>2.2.1-SNAPSHOTversion> dependency> |
spring: sleuth: web: client: enabled: true #开启链路采集 sampler: probability: 1.0 #默认采集数据量 1-百分百,0.1-百分十 zipkin: base-url: http://127.0.0.1:9411/ #制定链路日志收集地址 |
Headers 封装
x-b3-spanid:一个工作单元(rpc 调用)的唯一标识
x-b3-parentspanid:当前工作单元的上一个工作单元,Root Span(请求链路的第一个工作单元)的值为空
x-b3-traceid:一条请求链条(trace) 的唯一标识
x-b3-sampled:是否被抽样为输出的标志,1 为需要被输出,0 为不需要被输出
brave.servlet.TracingFilter#doFilter 实现TraceContext,Span创建并封装Headers实现trace透传
依赖于openfeign框架实现,忽略 请参考 Spring-cloud-alibaba-feign 搭建 模块
只做了解
HttpHeader封装trace信息:
从上一个web的request的Headers 获取X-B3-TraceId,X-B3-SpanId,X-B3-ParentSpanId,X-B3-Sampled
如果数据为空,从request.getAttribute("brave.propagation.TraceContext")对象信息
代码实现:
public ResponseEntity doPathUrl(HttpServletRequest request,String targetUrl,Object obj){ //设置header信息 Map map = MDC.getCopyOfContextMap();//获取trace信息 HttpHeaders requestHandler = new HttpHeaders(); String traceId = map.get("traceId")+""; String parentSpanId = map.get("spanId")+""; String spanId = HexCodec.toLowerHex(nextId()); String sampled = "1"; requestHandler.add("X-B3-TraceId", traceId); requestHandler.add("X-B3-SpanId",spanId); requestHandler.add("X-B3-ParentSpanId", parentSpanId); requestHandler.add("X-B3-Sampled", sampled); MultiValueMap params.add("riskCode",obj); HttpEntity return restTemplate.postForEntity(targetUrl,entity,ServiceResult.class); } |
Gateway
Web
Provider
Zipkin
页面返回接口 Reseful json数据