不停机旧版本,部署新版 本,高比例流量(例如:95%)走旧版本,低比例流量(例如:5%)切换到新版本,通过 监控观察无问题,逐步扩大范围,最终把所有流量都迁移到新版本上。属无损发布。
在发布过程中,我们只需部署服务的灰度版本,流量在调用链路上流转 时,由流经的网关、各个中间件以及各个微服务来识别灰度流量,并动态转发至对应服务的 灰度版本
无论是微服务网关还是微服务本身都需要识别流量,根据治理规则做出动态决策。当服务 版本发生变化时,这个调用链路的转发也会实时改变

通过对服务下所有节点按照标签名和标签值不同进行分组,使得订阅该服务节点信 息的服务消费端可以按需访问该服务的某个分组
k8s – labels.version=gray
在使用Kubernetes Service作为服务发现的业务系统中,服务提供者通过向ApiServer提交 Service资源完成服务暴露,服务消费端监听与该Service资源下关联的Endpoint资源,从 Endpoint资源中获取关联的业务Pod 资源,读取上面的Labels数据并作为该节点的元数据 信息。所以,我们只要在业务应用描述资源Deployment中的Pod模板中为节点添加标签即可。

nacos – `spring.cloud.nacos.discovery.metadata.version=gray
spring:
cloud:
discovery:
server-addr: nacos.localhost.com:8848
metadata:
version: 1.1

https://github.com/Nepxion/Discovery
https://github.com/Nepxion/Discovery/wiki
http://polaris-paas.gitee.io/polaris-sdk/#/
http://nepxion.gitee.io/discovery/#/?id=入门主页

<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
<discovery.version>6.20.0-SNAPSHOT</discovery.version>
...
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery</artifactId>
<version>${discovery.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
discovery-plugin-strategy-starter-gatewaypom.xml
<!-- 1.注册中心插件 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-register-center-starter-nacos</artifactId>
</dependency>
<!-- 2.配置中心插件 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-config-center-starter-nacos</artifactId>
</dependency>
<!-- 3.管理中心插件 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-admin-center-starter</artifactId>
</dependency>
<!-- 4.网关策略编排插件 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-gateway</artifactId>
</dependency>
Gateway 配置
spring:
application:
name: demomall-gateway
strategy:
gateway:
dynamic:
route:
enabled: true #开启网关订阅配置中心的动态路由策略,默认为false
cloud:
discovery:
metadata:
group: discovery-group #组名必须配置
根据实际的灰度发布维度和场景,配置染色方式的元数据
#组名必须要配置,版本、区域、环境和可用区根据具体场景选择其中一种或者几种进行配置
spring.cloud.discovery.metadata.group=discovery-guide-group
spring.cloud.discovery.metadata.version=1.0
spring.cloud.discovery.metadata.region=dev
spring.cloud.discovery.metadata.env=env1
spring.cloud.discovery.metadata.zone=zone1
spring.cloud.discovery.metadata.active=true
discovery-plugin-strategy-starter-servicepom.xml
<!--discovery 1.注册中心插件 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-register-center-starter-nacos</artifactId>
</dependency>
<!--discovery 2.配置中心插件 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-config-center-starter-nacos</artifactId>
</dependency>
<!--discovery 3.管理中心插件 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-admin-center-starter</artifactId>
</dependency>
<!--discovery 4.网关策略编排插件 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-service</artifactId>
</dependency>
配置
spring:
application:
name: demomall-member
cloud:
discovery: #discovery配置,设置流量染色的元数据
metadata:
group: discovery-group #组名必须配置
version: 1.0 #指定版本号
再新启一个配置VM
-Dserver.port=8878
-Dspring.cloud.discovery.metadata.version=1.1
-javaagent:/root/skywalking/skywalking-agent/skywalking-agent.jar
-DSW_AGENT_NAME=demomall-member
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800

在nacos配置中心中增加网关的版本权重灰度发布策略
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version-weight>1.0=90;1.1=10</version-weight>
</strategy>
</rule>
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version-weight>{"discovery-first":"1.0=90;1.1=10", "discovery-second":"1.0=90;1.1=10"}</version-weight>
</strategy>
</rule>
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-release>
<conditions type="gray">
<condition id="gray-condition" version-id="gray-route=5;stable-route=95"/>
</conditions>
<routes>
<route id="gray-route" type="version">{"discovery-first":"1.1", "discovery-second":"1.1"}</route>
<route id="stable-route" type="version">{"discovery-first":"1.0", "discovery-second":"1.0"}</route>
</routes>
</strategy-release>
</rule>
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-release>
<conditions type="gray">
<!-- 灰度路由1,条件expression驱动 -->
<condition id="gray-condition-1" expression="#H['a'] == '1'" version-id="gray-route=10;stable-route=90"/>
<!-- 灰度路由2,条件expression驱动 -->
<condition id="gray-condition-2" expression="#H['a'] == '1' and #H['b'] == '2'" version-id="gray-route=85;stable-route=15"/>
<!-- 兜底路由,无条件expression驱动 -->
<condition id="basic-condition" version-id="gray-route=0;stable-route=100"/>
</conditions>
<routes>
<route id="gray-route" type="version">{"discovery-first":"1.1", "discovery-second":"1.1"}</route>
<route id="stable-route" type="version">{"discovery-first":"1.0", "discovery-second":"1.0"}</route>
</routes>
</strategy-release>
</rule>
Openfeign RequestInterceptor
public interface RequestInterceptor {
void apply(RequestTemplate template);
}
com.nepxion.discovery.plugin.strategy.aop.FeignStrategyInterceptor
public class FeignStrategyInterceptor extends AbstractStrategyInterceptor implements RequestInterceptor {
@Autowired
protected StrategyContextHolder strategyContextHolder;
@Value("${spring.application.strategy.feign.core.header.transmission.enabled:true}")
protected Boolean feignCoreHeaderTransmissionEnabled;
public FeignStrategyInterceptor(String contextRequestHeaders, String businessRequestHeaders) {
super(contextRequestHeaders, businessRequestHeaders);
}
public void apply(RequestTemplate requestTemplate) {
this.interceptInputHeader();
this.applyInnerHeader(requestTemplate);
this.applyOuterHeader(requestTemplate);
this.interceptOutputHeader(requestTemplate);
}
private void applyInnerHeader(RequestTemplate requestTemplate) {
requestTemplate.header("n-d-service-group", new String[]{this.pluginAdapter.getGroup()});
requestTemplate.header("n-d-service-type", new String[]{this.pluginAdapter.getServiceType()});
String serviceAppId = this.pluginAdapter.getServiceAppId();
if (StringUtils.isNotEmpty(serviceAppId)) {
requestTemplate.header("n-d-service-app-id", new String[]{serviceAppId});
}
requestTemplate.header("n-d-service-id", new String[]{this.pluginAdapter.getServiceId()});
requestTemplate.header("n-d-service-address", new String[]{this.pluginAdapter.getHost() + ":" + this.pluginAdapter.getPort()});
String version = this.pluginAdapter.getVersion();
if (StringUtils.isNotEmpty(version) && !StringUtils.equals(version, "default")) {
requestTemplate.header("n-d-service-version", new String[]{version});
}
String region = this.pluginAdapter.getRegion();
if (StringUtils.isNotEmpty(region) && !StringUtils.equals(region, "default")) {
requestTemplate.header("n-d-service-region", new String[]{region});
}
String environment = this.pluginAdapter.getEnvironment();
if (StringUtils.isNotEmpty(environment) && !StringUtils.equals(environment, "default")) {
requestTemplate.header("n-d-service-env", new String[]{environment});
}
String zone = this.pluginAdapter.getZone();
if (StringUtils.isNotEmpty(zone) && !StringUtils.equals(zone, "default")) {
requestTemplate.header("n-d-service-zone", new String[]{zone});
}
}
private void applyOuterHeader(RequestTemplate requestTemplate) {
Enumeration<String> headerNames = this.strategyContextHolder.getHeaderNames();
String routeAddressBlacklist;
if (headerNames != null) {
while(headerNames.hasMoreElements()) {
String headerName = (String)headerNames.nextElement();
routeAddressBlacklist = this.strategyContextHolder.getHeader(headerName);
boolean isHeaderContains = this.isHeaderContainsExcludeInner(headerName.toLowerCase());
if (isHeaderContains) {
if (this.feignCoreHeaderTransmissionEnabled) {
requestTemplate.header(headerName, new String[]{routeAddressBlacklist});
} else {
boolean isCoreHeaderContains = StrategyUtil.isCoreHeaderContains(headerName);
if (!isCoreHeaderContains) {
requestTemplate.header(headerName, new String[]{routeAddressBlacklist});
}
}
}
}
}
if (this.feignCoreHeaderTransmissionEnabled) {
Map<String, Collection<String>> headers = requestTemplate.headers();
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-version"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteVersion();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-version", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-region"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteRegion();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-region", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-env"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteEnvironment();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-env", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-address"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteAddress();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-address", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-version-weight"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteVersionWeight();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-version-weight", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-region-weight"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteRegionWeight();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-region-weight", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-version-prefer"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteVersionPrefer();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-version-prefer", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-version-failover"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteVersionFailover();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-version-failover", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-region-transfer"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteRegionTransfer();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-region-transfer", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-region-failover"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteRegionFailover();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-region-failover", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-env-failover"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteEnvironmentFailover();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-env-failover", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-zone-failover"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteZoneFailover();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-zone-failover", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-address-failover"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteAddressFailover();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-address-failover", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-id-blacklist"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteIdBlacklist();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-id-blacklist", new String[]{routeAddressBlacklist});
}
}
if (CollectionUtils.isEmpty((Collection)headers.get("n-d-address-blacklist"))) {
routeAddressBlacklist = this.strategyContextHolder.getRouteAddressBlacklist();
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
requestTemplate.header("n-d-address-blacklist", new String[]{routeAddressBlacklist});
}
}
}
}
private void interceptOutputHeader(RequestTemplate requestTemplate) {
if (this.interceptDebugEnabled) {
System.out.println("-------- Feign Intercept Output Header Information ---------");
Map<String, Collection<String>> headers = requestTemplate.headers();
Iterator var3 = headers.entrySet().iterator();
while(var3.hasNext()) {
Entry<String, Collection<String>> entry = (Entry)var3.next();
String headerName = (String)entry.getKey();
boolean isHeaderContains = this.isHeaderContains(headerName.toLowerCase());
if (isHeaderContains) {
Collection<String> headerValue = (Collection)entry.getValue();
System.out.println(headerName + "=" + headerValue);
}
}
System.out.println("------------------------------------------------------------");
}
}
protected InterceptorType getInterceptorType() {
return InterceptorType.FEIGN;
}
}
gateway
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
com.nepxion.discovery.plugin.strategy.gateway.filter.AbstractGatewayStrategyRouteFilter
public abstract class AbstractGatewayStrategyRouteFilter implements GatewayStrategyRouteFilter {
@Autowired
protected PluginAdapter pluginAdapter;
@Autowired
protected StrategyWrapper strategyWrapper;
@Autowired(
required = false
)
protected GatewayStrategyMonitor gatewayStrategyMonitor;
@Value("${spring.application.strategy.gateway.header.priority:true}")
protected Boolean gatewayHeaderPriority;
@Value("${spring.application.strategy.gateway.original.header.ignored:true}")
protected Boolean gatewayOriginalHeaderIgnored;
@Value("${spring.application.strategy.gateway.core.header.transmission.enabled:true}")
protected Boolean gatewayCoreHeaderTransmissionEnabled;
@Value("${spring.application.strategy.gateway.route.filter.order:9000}")
protected Integer filterOrder;
public AbstractGatewayStrategyRouteFilter() {
}
public int getOrder() {
return this.filterOrder;
}
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
GatewayStrategyContext.getCurrentContext().setExchange(exchange);
ServerHttpRequest request = exchange.getRequest();
Builder requestBuilder = request.mutate();
this.applyInnerHeader(request, requestBuilder);
this.applyOuterHeader(request, requestBuilder);
if (this.gatewayStrategyMonitor != null) {
this.gatewayStrategyMonitor.monitor(exchange);
}
String path = request.getPath().toString();
if (path.contains("inspector/inspect")) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "endpoint-inspector-inspect", this.pluginAdapter.getPluginInfo((String)null), true);
}
ServerHttpRequest newRequest = requestBuilder.build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
GatewayStrategyContext.getCurrentContext().setExchange(newExchange);
return chain.filter(newExchange);
}
private void applyInnerHeader(ServerHttpRequest request, Builder requestBuilder) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-group", this.pluginAdapter.getGroup(), this.gatewayHeaderPriority);
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-type", this.pluginAdapter.getServiceType(), false);
String serviceAppId = this.pluginAdapter.getServiceAppId();
if (StringUtils.isNotEmpty(serviceAppId)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-app-id", serviceAppId, false);
}
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-id", this.pluginAdapter.getServiceId(), false);
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-address", this.pluginAdapter.getHost() + ":" + this.pluginAdapter.getPort(), false);
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-version", this.pluginAdapter.getVersion(), false);
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-region", this.pluginAdapter.getRegion(), false);
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-env", this.pluginAdapter.getEnvironment(), false);
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-service-zone", this.pluginAdapter.getZone(), false);
}
private void applyOuterHeader(ServerHttpRequest request, Builder requestBuilder) {
String routeEnvironment = this.getRouteEnvironment();
if (StringUtils.isNotEmpty(routeEnvironment)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-env", routeEnvironment, false);
}
if (this.gatewayCoreHeaderTransmissionEnabled) {
Map<String, String> headerMap = this.strategyWrapper.getHeaderMap();
String routeAddress;
String routeVersionWeight;
if (MapUtils.isNotEmpty(headerMap)) {
Iterator var5 = headerMap.entrySet().iterator();
while(var5.hasNext()) {
Entry<String, String> entry = (Entry)var5.next();
routeAddress = (String)entry.getKey();
routeVersionWeight = (String)entry.getValue();
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, routeAddress, routeVersionWeight, this.gatewayHeaderPriority);
}
}
String routeVersion = this.getRouteVersion();
String routeRegion = this.getRouteRegion();
routeAddress = this.getRouteAddress();
routeVersionWeight = this.getRouteVersionWeight();
String routeRegionWeight = this.getRouteRegionWeight();
String routeIdBlacklist = this.getRouteIdBlacklist();
String routeAddressBlacklist = this.getRouteAddressBlacklist();
if (StringUtils.isNotEmpty(routeVersion)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-version", routeVersion, this.gatewayHeaderPriority);
} else {
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-version", this.gatewayHeaderPriority, this.gatewayOriginalHeaderIgnored);
}
if (StringUtils.isNotEmpty(routeRegion)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-region", routeRegion, this.gatewayHeaderPriority);
} else {
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-region", this.gatewayHeaderPriority, this.gatewayOriginalHeaderIgnored);
}
if (StringUtils.isNotEmpty(routeAddress)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-address", routeAddress, this.gatewayHeaderPriority);
} else {
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-address", this.gatewayHeaderPriority, this.gatewayOriginalHeaderIgnored);
}
if (StringUtils.isNotEmpty(routeVersionWeight)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-version-weight", routeVersionWeight, this.gatewayHeaderPriority);
} else {
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-version-weight", this.gatewayHeaderPriority, this.gatewayOriginalHeaderIgnored);
}
if (StringUtils.isNotEmpty(routeRegionWeight)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-region-weight", routeRegionWeight, this.gatewayHeaderPriority);
} else {
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-region-weight", this.gatewayHeaderPriority, this.gatewayOriginalHeaderIgnored);
}
if (StringUtils.isNotEmpty(routeIdBlacklist)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-id-blacklist", routeIdBlacklist, this.gatewayHeaderPriority);
} else {
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-id-blacklist", this.gatewayHeaderPriority, this.gatewayOriginalHeaderIgnored);
}
if (StringUtils.isNotEmpty(routeAddressBlacklist)) {
GatewayStrategyFilterResolver.setHeader(request, requestBuilder, "n-d-address-blacklist", routeAddressBlacklist, this.gatewayHeaderPriority);
} else {
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-address-blacklist", this.gatewayHeaderPriority, this.gatewayOriginalHeaderIgnored);
}
} else {
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-version");
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-region");
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-address");
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-version-weight");
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-region-weight");
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-id-blacklist");
GatewayStrategyFilterResolver.ignoreHeader(requestBuilder, "n-d-address-blacklist");
}
}
public PluginAdapter getPluginAdapter() {
return this.pluginAdapter;
}
}
com.netflix.loadbalancer.IRule
public interface IRule{
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
//TODO 灰度测试
@Autowired
private PluginAdapter pluginAdapter;
@RequestMapping(value = "/gray/{value}", method = RequestMethod.GET)
public String gray(@PathVariable(value = "value") String value) {
value = pluginAdapter.getPluginInfo(value);
value = couponsFeignService.gray(value);
log.info("调用路径:{}", value);
return value;
}
阿里云MSE服务治理产品就是一款基于Java Agent实现的无侵入式企业生产级服务治理产品,您不需要修改任何一行业务代码,即可拥有不限于全链路灰度的治理能力,并且支持近 5年内所有的 Spring Boot、Spring Cloud和Dubbo
https://help.aliyun.com/document_detail/359851.html

无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
有人知道在发布新版本的Ruby和Rails时收到电子邮件的方法吗?他们有邮件列表,RubyonRails有一个推特,但我不想听到那些随之而来的喧嚣,我只想知道什么时候发布新版本,尤其是那些有安全修复的版本。 最佳答案 从therailsblog获取提要.http://weblog.rubyonrails.org/feed/atom.xml 关于ruby-on-rails-如何在发布新的Ruby或Rails版本时收到通知?,我们在StackOverflow上找到一个类似的问题:
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
尝试从我的AngularJS端将数据发布到Rails服务器时出现问题。服务器错误:ActionController::RoutingError(Noroutematches[OPTIONS]"/users"):actionpack(4.1.9)lib/action_dispatch/middleware/debug_exceptions.rb:21:in`call'actionpack(4.1.9)lib/action_dispatch/middleware/show_exceptions.rb:30:in`call'railties(4.1.9)lib/rails/rack/logg
当音乐碰上区块链技术,会擦出怎样的火花?或许周杰伦已经给了我们答案。8月29日下午,B站独家首发周杰伦限定珍藏Demo独家访谈VCR,周杰伦在VCR里分享了《晴天》《青花瓷》《搁浅》《爱在西元前》四首经典歌曲Demo背后的创作故事,并首次公布18年前未发布的神秘作品《纽约地铁》的Demo。在VCR中,方文山和杰威尔音乐提及到“多亏了区块链技术,现在我们可以将这些Demos,变成独一无二具有收藏价值的艺术品,这些Demos可以在薄盒(国内数藏平台)上听到。”如何将音乐与区块链技术相结合,薄盒方面称:“薄盒作为区块链技术服务方,打破传统对于区块链技术只能作为数字收藏的理解。聚焦于区块链技术赋能,在
目录SpringBootStarter是什么?以前传统的做法使用SpringBootStarter之后starter的理念:starter的实现: 创建SpringBootStarter步骤在idea新建一个starter项目、直接执行下一步即可生成项目。 在xml中加入如下配置文件:创建proterties类来保存配置信息创建业务类:创建AutoConfiguration测试如下:SpringBootStarter是什么? SpringBootStarter是在SpringBoot组件中被提出来的一种概念、简化了很多烦琐的配置、通过引入各种SpringBootStarter包可以快速搭建出一
我想上传我在运行时用Ruby生成的数据,就像从block中提供上传数据一样。我找到的所有示例仅展示了如何流式传输必须在请求之前位于磁盘上的文件,但我不想缓冲该文件。除了滚动我自己的套接字连接之外,最好的解决方案是什么?这是一个伪代码示例:post_stream('127.0.0.1','/stream/')do|body|generate_xmldo|segment|body 最佳答案 有效的代码。require'thread'require'net/http'require'base64'require'openssl'class
昨晚看到IDEA官推宣布IntelliJIDEA2023.1正式发布了。简单看了一下,发现这次的新版本包含了许多改进,进一步优化了用户体验,提高了便捷性。至于是否升级最新版本完全是个人意愿,如果觉得新版本没有让自己感兴趣的改进,完全就不用升级,影响不大。软件的版本迭代非常正常,正确看待即可,不持续改进就会慢慢被淘汰!根据官方介绍:IntelliJIDEA2023.1针对新的用户界面进行了大量重构,这些改进都是基于收到的宝贵反馈而实现的。官方还实施了性能增强措施,使得Maven导入更快,并且在打开项目时IDE功能更早地可用。由于后台提交检查,新版本提供了简化的提交流程。IntelliJIDEA
Unity数据可视化图表插件XCharts3.0发布历时8个多月,业余时间,断断续续,XCharts3.0总算发布了。如果要打个满意度,我给3.0版本来个80分。对于代码框架结构设计的调整改动,基本符合预期,甚是满意。相比之前的1.0和2.0版本,我认为3.0才是一个拿得出手给广大开发者使用的版本。1.0发布的时候,很兴奋,从0.1到1.0,也磨了一年,真的等不及想给大家试用了,还特地写过一篇文章以示庆祝。那个时候,1.0虽然还还不够完善,功能也不够丰富,但它是XCharts的开始,没有1.0,也就没有后面的2.0和3.0。后面的2.0发布,做了很多改进和优化,随着版本迭代,慢慢的发现有不少硬
我有一个模型依赖于一个单独的、联合的模型。classMagazine图像是多态的,可以附加到许多对象(页面和文章),而不仅仅是杂志。杂志需要在相关图像发生任何变化时自行更新该杂志还保存了一张自己的截图,可用于宣传:classMagazine现在如果图像发生变化,杂志也需要更新其截图。所以杂志真的需要知道图片什么时候出了问题。所以我们可以天真地直接从封面图片触发屏幕截图更新classImage...但是图片不应该代表杂志做事然而,图片可以用于许多不同的对象,实际上不应该对杂志进行特定的操作,因为这不是图片的责任。该图像也可能附加到页面或文章,并且不需要为它们做各种事情。“正常”的rail