feat: support spring-retry and feign config refresh and feign eager load support schema (#1651)

pull/1661/head
shedfreewu 2 months ago committed by GitHub
parent 772555b0e0
commit 25bc8120b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -6,3 +6,4 @@
- [feat:upgrade to 2023.0.6.](https://github.com/Tencent/spring-cloud-tencent/pull/1646)
- [feat:Add log output and test scenarios in the FaultToleranceService file.](https://github.com/Tencent/spring-cloud-tencent/pull/1652)
- [fix: fix ConfigChangeListener and unit test](https://github.com/Tencent/spring-cloud-tencent/pull/1656)
- [feat: support spring-retry and feign config refresh and feign eager load support schema](https://github.com/Tencent/spring-cloud-tencent/pull/1651)

@ -68,7 +68,7 @@ public class FeignEagerLoadSmartLifecycle implements SmartLifecycle {
}
String serviceName = URI.create(url).getHost();
LOG.info("[{}] eager-load start", serviceName);
LOG.info("[{}] eager-load start, feign name: {}", serviceName, hardCodedTarget.name());
if (polarisDiscoveryClient != null) {
polarisDiscoveryClient.getInstances(serviceName);
}

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>tsf-example</artifactId>
<groupId>com.tencent.cloud</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>consumer-demo-retry</artifactId>
<dependencies>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,41 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.tsf.demo.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.tsf.annotation.EnableTsf;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableFeignClients // 使用Feign微服务调用时请启用
@EnableTsf
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

@ -0,0 +1,170 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.tsf.demo.consumer.controller;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import com.tencent.cloud.common.util.PolarisCompletableFutureUtils;
import com.tencent.cloud.tsf.demo.consumer.proxy.ProviderDemoService;
import com.tencent.cloud.tsf.demo.consumer.proxy.ProviderService;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.tsf.core.TsfContext;
import org.springframework.tsf.core.entity.Tag;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private ProviderService providerService;
@Autowired
private ProviderDemoService providerDemoService;
@RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET)
public String restProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue) {
if (StringUtils.isNotBlank(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
TsfContext.putTag("operation", "rest");
Map<String, String> mTags = new HashMap<>();
mTags.put("rest-trace-key1", "value1");
mTags.put("rest-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
try {
return restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
}
catch (CallAbortedException callAbortedException) {
return callAbortedException.getMessage();
}
}
@RequestMapping(value = "/echo-slow-rest/{str}", method = RequestMethod.GET)
public String restSlowProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue) {
if (StringUtils.isNotBlank(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
TsfContext.putTag("operation", "rest");
Map<String, String> mTags = new HashMap<>();
mTags.put("rest-trace-key1", "value1");
mTags.put("rest-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
try {
return restTemplate.getForObject("http://provider-demo/echo/slow/" + str, String.class);
}
catch (CallAbortedException callAbortedException) {
return callAbortedException.getMessage();
}
}
@RequestMapping(value = "/echo-rest-async/{str}", method = RequestMethod.GET)
public String restAsync(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue) throws ExecutionException, InterruptedException {
if (StringUtils.isNotBlank(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
TsfContext.putTag("operation", "rest");
Map<String, String> mTags = new HashMap<>();
mTags.put("rest-trace-key1", "value1");
mTags.put("rest-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
CompletableFuture<String> echoFuture = PolarisCompletableFutureUtils.supplyAsync(() -> {
try {
return restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
}
catch (CallAbortedException callAbortedException) {
return callAbortedException.getMessage();
}
});
return echoFuture.get();
}
@RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET)
public String feignProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue) {
if (StringUtils.isNotBlank(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
TsfContext.putTag("operation", "feign");
Map<String, String> mTags = new HashMap<>();
mTags.put("feign-trace-key1", "value1");
mTags.put("feign-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
try {
return providerDemoService.echo(str);
}
catch (CallAbortedException callAbortedException) {
return callAbortedException.getMessage();
}
}
@RequestMapping(value = "/echo-slow-feign/{str}", method = RequestMethod.GET)
public String feignSlowProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue,
@RequestParam(required = false) String delay) {
if (StringUtils.isNotBlank(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
TsfContext.putTag("operation", "feign");
Map<String, String> mTags = new HashMap<>();
mTags.put("feign-trace-key1", "value1");
mTags.put("feign-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
int sleepTime = delay == null ? 1000 : Integer.parseInt(delay);
try {
return providerDemoService.echoSlow(str, sleepTime);
}
catch (CallAbortedException callAbortedException) {
return callAbortedException.getMessage();
}
}
@RequestMapping(value = "/echo-feign-url/{str}", method = RequestMethod.GET)
public String feignUrlProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue) {
if (StringUtils.isNotBlank(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
TsfContext.putTag("operation", "feignUrl");
Map<String, String> mTags = new HashMap<>();
mTags.put("feignUrl-trace-key1", "value1");
mTags.put("feignUrl-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
return providerService.echo(str);
}
}

@ -0,0 +1,54 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.tsf.demo.consumer.controller;
import com.tencent.polaris.api.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.tsf.core.TsfContext;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class SdkBaseTest {
private static final Logger LOG = LoggerFactory.getLogger(SdkBaseTest.class);
@Autowired
private RestTemplate restTemplate;
// 调用一次provider接口
@RequestMapping(value = "/echo-once/{str}", method = RequestMethod.GET)
public String restOnceProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue) {
if (!StringUtils.isEmpty(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
LOG.info("start call echo-once");
String result = restTemplate.getForObject("http://provider-demo/echo/" + str, String.class);
LOG.info("end call echo-once, the result is : " + result);
return result;
}
}

@ -0,0 +1,32 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.tsf.demo.consumer.entity;
/**
* Metadata.
*/
public class CustomMetadata {
private String name;
private String value;
public CustomMetadata(String name, String value) {
this.name = name;
this.value = value;
}
}

@ -0,0 +1,36 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.tsf.demo.consumer.proxy;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "${provider.name:provider-demo}")
public interface ProviderDemoService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
String echo(@PathVariable("str") String str);
@RequestMapping(value = "/echo/error/{str}", method = RequestMethod.GET)
String echoError(@PathVariable("str") String str);
@RequestMapping(value = "/echo/slow/{str}", method = RequestMethod.GET)
String echoSlow(@PathVariable("str") String str, @RequestParam("delay") int delay);
}

@ -0,0 +1,44 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.tsf.demo.consumer.proxy;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* URLFeignClient
* 使provider-ip:provider-port
*/
@FeignClient(name = "provider", url = "http://127.0.0.1:18081", fallback = FeignClientFallback.class)
public interface ProviderService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
String echo(@PathVariable("str") String str);
}
@Component
class FeignClientFallback implements ProviderService {
@Override
public String echo(String str) {
return "tsf-fault-tolerance-" + str;
}
}

@ -0,0 +1,20 @@
server:
port: 18083
spring:
application:
name: consumer-demo
config:
import: optional:polaris
feign:
tsf:
enabled: true
#本地测试时打开
#tsf_namespace_id: default_namespace
logging:
file:
name: /tsf-demo-logs/${spring.application.name}/root.log
level:
root: INFO

@ -67,6 +67,26 @@ public class ConsumerController {
}
}
@RequestMapping(value = "/echo-slow-rest/{str}", method = RequestMethod.GET)
public String restSlowProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue) {
if (StringUtils.isNotBlank(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
TsfContext.putTag("operation", "rest");
Map<String, String> mTags = new HashMap<>();
mTags.put("rest-trace-key1", "value1");
mTags.put("rest-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
try {
return restTemplate.getForObject("http://provider-demo/echo/slow/" + str, String.class);
}
catch (CallAbortedException callAbortedException) {
return callAbortedException.getMessage();
}
}
@RequestMapping(value = "/echo-rest-async/{str}", method = RequestMethod.GET)
public String restAsync(@PathVariable String str,
@ -111,6 +131,28 @@ public class ConsumerController {
}
}
@RequestMapping(value = "/echo-slow-feign/{str}", method = RequestMethod.GET)
public String feignSlowProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,
@RequestParam(required = false) String tagValue,
@RequestParam(required = false) String delay) {
if (StringUtils.isNotBlank(tagName)) {
TsfContext.putTag(tagName, tagValue);
}
TsfContext.putTag("operation", "feign");
Map<String, String> mTags = new HashMap<>();
mTags.put("feign-trace-key1", "value1");
mTags.put("feign-trace-key2", "value2");
TsfContext.putTags(mTags, Tag.ControlFlag.TRANSITIVE);
int sleepTime = delay == null ? 1000 : Integer.parseInt(delay);
try {
return providerDemoService.echoSlow(str, sleepTime);
}
catch (CallAbortedException callAbortedException) {
return callAbortedException.getMessage();
}
}
@RequestMapping(value = "/echo-feign-url/{str}", method = RequestMethod.GET)
public String feignUrlProvider(@PathVariable String str,
@RequestParam(required = false) String tagName,

@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "${provider.name:provider-demo}")
@FeignClient(name = "${provider.name:http://provider-demo}")
public interface ProviderDemoService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
String echo(@PathVariable("str") String str);

@ -15,6 +15,7 @@
<modules>
<module>provider-demo</module>
<module>consumer-demo-retry</module>
<module>consumer-demo</module>
</modules>
</project>

@ -29,7 +29,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@ -52,6 +54,8 @@ public class ProviderController {
@Autowired
private ProviderNameConfig providerNameConfig;
private boolean ifBadGateway = false;
// 获取本机ip
public static String getInet4Address() {
Enumeration<NetworkInterface> nis;
@ -85,6 +89,11 @@ public class ProviderController {
@RequestMapping(value = "/echo/{param}", method = RequestMethod.GET)
public ResponseEntity<String> echo(@PathVariable String param) {
if (ifBadGateway) {
LOG.info("Provider Demo is called wrong.");
return new ResponseEntity<>("failed for call provider demo service. Address: " + getInet4Address(), HttpStatus.BAD_GATEWAY);
}
int status;
String responseBody;
@ -149,4 +158,17 @@ public class ProviderController {
LOG.info("provider-demo -- unit response info: [" + result + "]");
return result;
}
@GetMapping("/setBadGateway")
public String setBadGateway(@RequestParam boolean param) {
this.ifBadGateway = param;
if (param) {
LOG.info("info is set to return HttpStatus.BAD_GATEWAY.");
return "info is set to return HttpStatus.BAD_GATEWAY.";
}
else {
LOG.info("info is set to return HttpStatus.OK.");
return "info is set to return HttpStatus.OK.";
}
}
}

@ -115,12 +115,6 @@ public class RpcEnhancementAutoConfiguration {
return new ExceptionPolarisReporter(properties, polarisSDKContextManager.getConsumerAPI());
}
@Bean
@ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor")
public BlockingLoadBalancerClientBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() {
return new BlockingLoadBalancerClientBeanPostProcessor();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
protected static class RpcEnhancementServletFilterConfig {
@ -195,6 +189,12 @@ public class RpcEnhancementAutoConfiguration {
return new PolarisLoadBalancerRequestTransformer();
}
@Bean
@ConditionalOnClass(name = "org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor")
public BlockingLoadBalancerClientBeanPostProcessor loadBalancerInterceptorBeanPostProcessor() {
return new BlockingLoadBalancerClientBeanPostProcessor();
}
}

@ -1,3 +1,20 @@
/*
* Tencent is pleased to support the open source community by making spring-cloud-tencent available.
*
* Copyright (C) 2021 Tencent. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.cloud.client.loadbalancer;
import java.lang.reflect.Field;

Loading…
Cancel
Save