
注: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

name/password -> nacos/nacos


| <dependencyManagement> <dependencies> <!--spring cloud--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.4.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.0.RELEASE</version> <scope>import</scope> <type>pom</type> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>1.5.0.RELEASE</version> </dependency> <!--spring cloud--> ...... 其他依赖 </dependencyManagement> |
| <!-- 向注册中心进行服务注册 --> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <!--1.1.4--> <version>1.1.4</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>1.5.0.RELEASE</version> </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.nacos</groupId> <artifactId>nacos-client</artifactId> <!--1.1.4--> <version>1.1.4</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>1.5.0.RELEASE</version> </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)策略
Spring-cloud-alibaba-feign 搭建| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.1-SNAPSHOT</version> </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<RiskConfig> getRiskConfig(@RequestParam(value="riskCode") String riskCode); } |
服务端web服务和feign提供的接口保持一致
| @RestController @RequestMapping("/riskConfig") @Slf4j public class RiskConfigServer { @Autowired private RiskConfigService riskConfigService;
@RequestMapping(value="/getRiskConfig",method = {RequestMethod.GET,RequestMethod.POST}) @ResponseBody public ServiceResult<RiskConfig> getRiskConfig(HttpServletRequest request,String riskCode){ 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<String> params) { try { ServiceResult<RiskConfig> result = riskConfigFeign.getRiskConfig(params.getData()); 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.nacos</groupId> <artifactId>nacos-client</artifactId><!--1.1.4--> <version>1.1.4</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>1.5.1.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>1.5.1.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.7.1</version> </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<RiskConfig> getRiskConfig(ServiceRequest<String> riskCode){ 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.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>1.5.1.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>2.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> <version>3.0.1</version><!-- 使用nacos动态配置使用 --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>2.2.4.RELEASE</version> </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://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.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> <version>2.2.1-SNAPSHOT</version> </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<String, Object> params = new LinkedMultiValueMap<>(); params.add("riskCode",obj); HttpEntity<MultiValueMap> entity = new HttpEntity<>(params,requestHandler); return restTemplate.postForEntity(targetUrl,entity,ServiceResult.class); } |
Gateway

Web

Provider

Zipkin

页面返回接口 Reseful json数据

我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev
因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实
在Ruby中,以毫秒为单位获取自纪元(1970)以来的当前系统时间的正确方法是什么?我试过了Time.now.to_i,好像不是我想要的结果。我需要结果显示毫秒并且使用long类型,而不是float或double。 最佳答案 (Time.now.to_f*1000).to_iTime.now.to_f显示包含十进制数字的时间。要获得毫秒数,只需将时间乘以1000。 关于ruby-以毫秒为单位获取当前系统时间,我们在StackOverflow上找到一个类似的问题:
关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭8年前。Improvethisquestion我们有以下(以及更多)系统,我们将数据从一个应用推送/拉取到另一个:托管CRM(InsideSales.com)Asterisk电话系统(内部)横幅广告系统(openx,我们托管)潜在客户生成系统(自行开发)电子商务商店(spree,我们托管)工作板(本土)一些工作网站抓取+入站工作提要电子邮件传送系统(如Mailchimp,自主开发)事件管理系统(如eventbrite,自主开发)仪表板系统(大量图表和
我正在尝试找出一种方法来显示来自不在RAILS_ROOT下(在RedHat或Ubuntu环境中)的已安装文件系统的图像。我不想使用符号链接(symboliclink),因为这个应用程序实际上是通过Tomcat部署的,而当我关闭Tomcat时,Tomcat会尝试跟随符号链接(symboliclink)并删除挂载中的所有图像。由于这些文件的数量和大小,将图像放在public/images下也不是一种选择。我查看了send_file,但它只会显示一张图片。我需要在一个格式良好的页面中显示6个请求的图像。由于膨胀,我宁愿不使用Base64编码,但我不知道如何将图像数据与呈现的页面一起传递下去。
我无法运行Spring。这是错误日志。myid-no-MacBook-Pro:myid$spring/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/lib/spring/sid.rb:17:in`fiddle_func':uninitializedconstantSpring::SID::DL(NameError)from/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/li