搭建一个微服务系统,有两个服务,Client和Server,Server有三个实例A、B、C,我让Client调用Server,Loadbalancer负载分担默认采用轮询机制,当Server-A/B/C响应都正常时,会轮流负载分担到三个实例上。而当我把其中的两个实例Server-A和Server-B设置为处理超时后,问题出现了。
当使用spring cloud loadbalancer的重试策略时,调用会遇到失败的情况。
当使用feign的重试策略时,调用不会失败。
下面就详细介绍这两种情况。
我用的是Spring Cloud框架,以下组合:Nacos + OpenFeign + Loadbalancer + Hystrix,Spring Cloud版本号是:2021.0.4,Spring Boot版本号:2.6.11,Nacos版本号:2021.0.1.0,Hystrix版本号:2.2.10.RELEASE。
1、Client 的 pom.yml 文件部分配置如下:
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.6.11</spring-boot.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
<com.alibaba.cloud.version>2021.0.1.0</com.alibaba.cloud.version>
<spring-cloud-openfeign.version>3.1.4</spring-cloud-openfeign.version>
<openfeign.feign-httpclient>11.8</openfeign.feign-httpclient>
<spring-cloud-loadbalancer.version>3.1.4</spring-cloud-loadbalancer.version>
<spring-cloud-hystrix.version>2.2.10.RELEASE</spring-cloud-hystrix.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<!-- 不使用Ribbon进行客户端负载均衡,而使用loadbalancer -->
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
<version>${com.alibaba.cloud.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>${spring-cloud-hystrix.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>${openfeign.feign-httpclient}</version>
</dependency>
<!--> 注意,当使用Spring Cloud Loadbalancer的重试策略时,必须增加对spring-retry的依赖 <-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.3</version>
</dependency>
注意:
在包含nacos时,需要排除ribbon,采用loadbalancer。
同时,当需要使用Spring Cloud Loadbalancer的重试策略时,必须增加对spring-retry的依赖,否则在调用失败后不会重试。
2、Server 的 pom.xml 文件,不需要包含feign、loadbalancer、hystrix,只需要包含nacos。
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.6.11</spring-boot.version>
<spring-cloud.version>2021.0.4</spring-cloud.version>
<com.alibaba.cloud.version>2021.0.1.0</com.alibaba.cloud.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<!-- 不使用Ribbon进行客户端负载均衡,而使用loadbalancer -->
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
<version>${com.alibaba.cloud.version}</version>
</dependency>
我们分别选择Loadbalancer和Feign的重试策略,进行试验。
1、Client yml配置:
############### 服务端口号 ###############
server:
port: 60002
spring:
application:
######### 服务名称 #########
name: Client-1
cloud:
inetutils:
# 优先选择这个前缀的IP进行注册
preferred-networks: 172.26.57
######### nacos注册中心 #########
nacos:
discovery:
# nacos注册中心的地址
server-addr: 172.26.57.84:8848
heart-beat-interval: 2000 # 该实例在客户端上报心跳的间隔时间(毫秒)
heart-beat-timeout: 7000 # 该实例在不发送心跳后,从健康到不健康的时间(毫秒)
ip-delete-timeout: 15000 # 该实例在不发送心跳后,被 nacos下掉该实例的时间(毫秒)
######### 负载分担 #########
loadbalancer:
enabled: true
health-check:
refetch-instances: true
refetch-instances-interval: 5s
repeat-health-check: false
retry:
# 该参数用来开启或关闭重试机制,默认是开启
enabled: true
# 对当前实例重试的次数,默认值: 0
max-retries-on-same-service-instance: 0
# 切换实例进行重试的次数,默认值: 1
max-retries-on-next-service-instance: 2
# 对所有的操作请求都进行重试
retry-on-all-operations: true
circuitbreaker:
hystrix:
enabled: true
####################### Feign配置 ##########################
feign:
client:
config:
default:
# 两端建立连接的请求超时时间,默认10000ms
connectTimeout: 2000
# 读取超时时间,默认60000ms,建立连接后从服务端读取到可用资源所用的时间
readTimeout: 3000
# 调用日志打印等级,需要同步将Feign调用类的日志等级设置为Debug才生效
loggerLevel: BASIC
httpclient:
# 为true时表示程序使用Apache的httpclient作为HTTP请求框架。默认值: true
enabled: true
# 默认值: 2000
#connectionTimeout: 2000
circuitbreaker:
enabled: true
####################### Hystrix配置 ##########################
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
# 熔断超时时间
# Hystrix的超时时间需要大于Ribbon的超时时间,否则 Hystrix命令超时后,该命令直接熔断,重试机制就没有意义了
# hystrix超时 >= (MaxAutoRetries + 1) * (ribbon ConnectTimeout + ribbon ReadTimeout)
timeoutInMilliseconds: 10000
重要配置说明:
spring.cloud.loadbalancer.retry.enabled=true 表示使能Loadbalancer的重试策略
max-retries-on-next-service-instance=2 表示调用第一个实例失败后,切换实例重试2次
feign.client:.config.default.readTimeout=3000 表示Feign调用其它服务如果超过3秒钟未返回,则视为调用超时
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000 表示调用其它服务超过10秒未返回则打开熔断开关
2、Server yml配置:
############### 服务端口号 ###############
server:
port: 60005
spring:
application:
######### 服务名称 #########
name: Server-1
main:
######### nacos注册中心 #########
cloud:
inetutils:
# 优先选择这个前缀的IP进行注册
preferred-networks: 172.26.57
nacos:
discovery:
# nacos注册中心的地址
server-addr: 172.26.57.84:8848
heart-beat-interval: 2000 # 该实例在客户端上报心跳的间隔时间(毫秒)
heart-beat-timeout: 7000 # 该实例在不发送心跳后,从健康到不健康的时间(毫秒)
ip-delete-timeout: 15000 # 该实例在不发送心跳后,被 nacos下掉该实例的时间(毫秒)
1、Client代码:
/**
Client1Application启动类
*/
@SpringBootApplication
@EnableDiscoveryClient /** 向注册中心注册 */
@EnableFeignClients /** 使能Feign调用功能 */
@EnableScheduling /** 使能Schedule功能 */
public class Client1Application {
public static void main(String[] args) {
SpringApplication.run(Client1Application.class, args);
}
}
/**
Feign调用类,调用Server服务
*/
@Primary
@FeignClient(name = "Server-1", fallback = HelloRpcHystrix.class)
public interface HelloRPC {
@RequestMapping(value = "/server/hello")
ResponseEntity<String> hello();
}
/**
熔断类,当调用其它服务超出Hystrix配置的超时时间(timeoutInMilliseconds)后,调用该方法进行返回。
*/
@Component
public class HelloRpcHystrix implements HelloRPC {
@Override
public ResponseEntity<String> hello() {
return new ResponseEntity<>("调用失败,短路处理!!!", HttpStatus.REQUEST_TIMEOUT);
}
}
/**
应用类,循环调用Server服务
*/
@Component
@Slf4j
public class Requester {
@Autowired
private HelloRPC helloRPC;
@Scheduled(initialDelay = 2000, fixedDelay = 20000)
public void request() {
log.info("[info] 发出hello请求!");
long time = System.currentTimeMillis();
ResponseEntity<String> responseEntity = helloRPC.hello();
log.info("hello请求结果: {} 耗时: {}ms", responseEntity.getBody(), System.currentTimeMillis() - time);
}
}
2、Server代码:
/**
Server1Application启动类
*/
@SpringBootApplication
@EnableDiscoveryClient
public class Server1Application {
public static void main(String[] args) {
SpringApplication.run(Server1Application.class, args);
}
}
/**
Server提供的接口服务类,Client调用该接口
*/
@Slf4j
@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping("/server")
public class HelloServer {
@Value("${addr.client.ip}") /** yml配置文件中需要配置该变量,配置的是该实例部署设备的ip */
public String ip;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public ResponseEntity<String> response() {
log.info("收到请求!");
log.info("回复请求,IP: {}", ip);
return new ResponseEntity<>(String.format("回复IP: %s", ip), HttpStatus.OK);
}
}
将Client部署在ip为172.26.57.7的设备上;
将Server分别部署在ip为172.26.57.9,172.26.57.10,172.26.57.19的三台设备上。启动四个设备上的服务。
1、当Server所有实例均能正常返回时,通过日志可以看到Client的请求采用轮询的机制负载分担到Server的三个实例上。 
2.、修改172.26.57.9和172.26.57.10设备上的Server代码,使其在返回请求时,休眠10秒钟再返回。代码修改如下:
@Slf4j
@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping("/server")
public class HelloServer {
@Value("${addr.client.ip}") /** yml配置文件中需要配置该变量,配置的是该实例部署设备的ip */
public String ip;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public ResponseEntity<String> response() {
log.info("收到请求!");
/** 休眠10秒钟,再返回请求结果,此时时间超过了Client设置的feign的readTimeout时间(3秒) */
sleep(10000);
log.info("回复请求,IP: {}", ip);
return new ResponseEntity<>(String.format("回复IP: %s", ip), HttpStatus.OK);
}
private void sleep(int mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
结果如下所示:

结论:
上图可看出,我们配置的超时时间为3秒,熔断后返回的时间是9秒,说明在熔断之前,执行了3次调用,这也跟配置保持了一致,配置是切换实例调用2次,加上首次调用,一共就是3次。
Server一共只有三个实例,其中两个实例会超时10秒再返回,还有一个实例是好的,由此我们可以推断出,切换实例后,再调用2次,并不是调用剩下未调用的实例。
通过查看其它两个实例的日志,发现切换实例后,第一次调用的是B(超时10返回),第二次又调用回了A(超时10秒返回),A是首次调用的实例。
所以,三次调用的顺序是:A->B->A,并没有调到正常返回的实例C。
1、Client yml配置:
主要修改点为,将spring.cloud.loadbalancer.retry.enabled设置为false,同时删除retry下的其它配置。
其它配置保持不变
spring:
cloud:
loadbalancer:
enabled: true
retry:
# 该参数用来开启或关闭重试机制,默认是开启
enabled: false
# # 对当前实例重试的次数,默认值: 0
# max-retries-on-same-service-instance: 0
# # 切换实例进行重试的次数,默认值: 1
# max-retries-on-next-service-instance: 2
# # 对所有的操作请求都进行重试
# retry-on-all-operations: true
2、Server yml配置:
保持不变
1、Client代码:
增加FeignConfig类,配置Feign的重试策略,重试次数为3次(包括首次调用),如下代码所示。
@Configuration
public class FeignConfig {
/**
* 请求失败后的重试配置
* */
@Bean
public Retryer feignRetryer() {
/** 重试间隔100ms,最大重试间隔时间为1秒,重试次数为3次 */
return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1L), 3);
}
}
2、Server代码:
保持不变
将Client部署在ip为172.26.57.7的设备上;
将Server分别部署在ip为172.26.57.9,172.26.57.10,172.26.57.19的三台设备上。启动四个设备上的服务。
1、当Server所有实例均能正常返回时,通过日志可以看到Client的请求采用轮询的机制负载分担到Server的三个实例上。

2.、修改172.26.57.9和172.26.57.10设备上的Server代码,使其在返回请求时,休眠10秒钟再返回。
结果如下图所示:
经过重试两次后(即一共调用了三次),调用到了正常返回的实例C。
说明Feign的重试策略与Loadbalancer不一样,它在重试时会排除之前调用失败的实例。

Feign的调用失败重试策略优于Spring Cloud Loadbalancer的重试策略,尽量采用Feign的重试策略。在配置时显式关闭Loadbalancer重试策略,如下所示:
spring:
cloud:
loadbalancer:
retry:
enabled: false # 该参数用来开启或关闭重试机制,默认是开启
我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我需要一些关于TDD概念的帮助。假设我有以下代码defexecute(command)casecommandwhen"c"create_new_characterwhen"i"display_inventoryendenddefcreate_new_character#dostufftocreatenewcharacterenddefdisplay_inventory#dostufftodisplayinventoryend现在我不确定要为什么编写单元测试。如果我为execute方法编写单元测试,那不是几乎涵盖了我对create_new_character和display_invent
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
如何找到调用此方法的位置?defto_xml(options={})binding.pryoptions=options.to_hifoptions&&options.respond_to?(:to_h)serializable_hash(options).to_xml(options)end 最佳答案 键入caller。这将返回当前调用堆栈。文档:Kernel#caller.例子[0]%rspecspec10/16|===================================================62=====