今天分享的是关于微服务 eureka集群 + 微服务生产者集群 + feign服务调用 的一个坑(报异常com.netflix.client.ClientException: Load balancer does not have available server for client: CLOUD-PROVIDER-PAYMENT)。
1.环境
先说一下我的环境。
大概如下图所示:
各个模块的端口号:
两台eureka分别在7001、7002端口。
两台服务提供者分别在8001、8002端口。
服务消费者在80端口。
2.核心代码
这里说明一下,所有代码都是用来学习spring cloud相关组件的demo级代码,没有加一些健壮性的判断,学会spring cloud组件即可。
服务提供者
核心配置:
server:
port: 8001
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver #mysql驱动包
url: jdbc:mysql://localhost:3306/study_springcloud?useUnicode=true&characterEncoding-utr-8&useSSL=false
username: root
password: root
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: cn.skywalker.springcloud.entity #所有Entity别名类所在包
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka # 集群版
instance:
instance-id: payment8001
prefer-ip-address: true
controller
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort; // 从配置文件中获取端口号
// 根据主键获取Payment
@GetMapping("/selectOne/{id}")
public R selectOne(@PathVariable("id") Long id) {
return R.ok().data(this.paymentService.queryById(id)).addMessage("server.port = " + serverPort);
}
}
Feign Client的接口代码
@FeignClient(value = "CLOUD-PROVIDER-PAYMENT")
@RequestMapping("/payment")
public interface PaymentClient {
//通过主键查询单条数据
@GetMapping("/selectOne/{id}")
public R selectOne(@PathVariable("id") Long id);
}
服务消费者
核心配置:
server:
port: 80
spring:
application:
name: cloud-consumer-order
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
fetch-registry: false
service-url:
#defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka # 集群版
controller
@RestController
@RequestMapping("/consumer")
@Slf4j
public class OrderController {
@Resource
private PaymentClient paymentClient;
@GetMapping("/payment/selectOne/{id}")
public R selectOne(@PathVariable("id") Long id) {
return R.ok().data(paymentClient.selectOne(id));
}
//----------------这是一条华丽的分割线----------------
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/payment/discovery")
public R discovery() {
return R.ok().data(discoveryClient);
}
}
3.坑
我们满心欢喜的写完了代码,然后把集群环境启动了起来,没有报错,很开心,然后我们打开了eureka的控制台,一切都正常,依然很开心。。
再然后我们输入了url测试接口,快乐就定格在了敲击enter键的那一瞬间。。。
oh,amazing…
我去度娘看了看,大多数的答案都是添加一行配置:
ribbon:
eureka:
enabled: true
但是,我发现人家的默认值本来就是true(我的eureka版本是2.2.1.RELEASSE)。
这就引发了我的思考,找不到可用的服务?eureka上面明明全部的服务都注册上去了。
我就在服务消费者
的controller
里加上了discovery client的方法。
进行调用后,结果如下:
eureka中明明全都有啊,为什么服务列表services
是个空的呢。。。
这个问题困扰了我一个小时,不过我已经有一种感觉,答案已经快要揭晓了
。
4.解决
终于,在我无意中找到了一个关键的地方:
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。
#单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: false
service-url:
#defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka # 集群版
关键的地方我已经加上了注释,相信各位也都看懂了吧。
没错,就是eureka.client.fetch-registry
这个配置项,在服务提供者是集群的环境下(即同一个服务名下有多台服务器提供服务),必须设置为true
才可以配合ribbon进行负载均衡。
他本身的作用就是从eureka中抓取已有的注册信息。
我们可以思考一下,如果不从eureka中获取注册的服务实例信息,ribbon怎么帮我们负载均衡?
ribbon:你**一台服务实例都没,还让我用各种算法去给你负载均衡?做梦!直接送你一句:
com.netflix.client.ClientException: Load balancer does not have available server for client: CLOUD-PROVIDER-PAYMENT
说到这里,问题已经解决了。。我们只需要把eureka.client.fetch-registry
改为true
就搞定了。
可以看到,已经可以正常访问到我们的接口,并且有ribbon默认实现的轮询策略。
nice,打完收工。