Spring Cloud(三):服务提供与调用 Eureka【Finchley 版】
上一篇文章我们介绍了 Eureka 服务注册中心的搭建,这篇文章介绍一下如何使用 Eureka 服务注册中心,搭建一个简单的服务端注册服务,客户端去调用服务使用的案例。
案例中有三个角色:服务注册中心、服务提供者、服务消费者,其中服务注册中心就是我们上一篇的 Eureka 单节点启动既可。
流程如下:
- 启动注册中心
- 服务提供者生产服务并注册到服务中心中
- 消费者从服务中心中获取服务并执行
服务提供者
我们假设服务提供者有一个 hello()
方法,可以根据传入的参数,提供输出 “hello xxx +当前时间” 的服务。
POM 包配置
创建一个基本的 Spring Boot 应用,命名为eureka-producer
,在 pom.xml 中添加如下配置:
1 |
|
配置文件
application.yml 配置如下
1 |
|
通过spring.application.name
属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。eureka.client.serviceUrl.defaultZone
属性对应服务注册中心的配置内容,指定服务注册中心的位置。为了在本机上测试区分服务提供方和服务注册中心,使用server.port
属性设置不同的端口。
启动类
保持默认生成的即可, Finchley.RC1 这个版本的 Spring Cloud 已经无需添加@EnableDiscoveryClient
注解了。(那么如果我引入了相关的 jar 包又想禁用服务注册与发现怎么办?设置eureka.client.enabled=false
)
@EnableDiscoveryClient
is no longer required. You can put aDiscoveryClient
implementation on the classpath to cause the Spring Boot application to register with the service discovery server.
Spring Cloud - @EnableDiscoveryClient
1 |
|
Controller
提供 hello 服务
1 |
|
启动工程后,就可以在注册中心Eureka的页面看到 EUERKA-PRODUCER 服务。
我们模拟一个请求试一下 Producer 能否正常工作
http://localhost:8000/hello/?name=yibo
1 |
|
OK, 直接访问时没有问题的,到此服务提供者配置就完成了。
服务消费者
创建服务消费者根据使用 API 的不同,大致分为三种方式。虽然大家在实际使用中用的应该都是 Feign,但是这里还是把这三种都介绍一下吧,如果你只关心 Feign,可以直接跳到最后。
三种方式均使用同一配置文件,不再单独说明了
1 |
|
使用 LoadBalancerClient
从LoadBalancerClient
接口的命名中,我们就知道这是一个负载均衡客户端的抽象定义,下面我们就看看如何使用 Spring Cloud 提供的负载均衡器客户端接口来实现服务的消费。
POM 包配置
我们先来创建一个服务消费者工程,命名为:eureka-consumer
。pom.xml 同 Producer 的,不再赘述。
启动类
初始化RestTemplate
,用来发起 REST 请求。
1 |
|
Controller
创建一个接口用来消费 eureka-producer 提供的接口:
1 |
|
可以看到这里,我们注入了LoadBalancerClient
和RestTemplate
,并在hello
方法中,先通过loadBalancerClient
的choose
方法来负载均衡的选出一个eureka-producer
的服务实例,这个服务实例的基本信息存储在ServiceInstance
中,然后通过这些对象中的信息拼接出访问服务调用者的/hello/
接口的详细地址,最后再利用RestTemplate
对象实现对服务提供者接口的调用。
另外,为了在调用时能从返回结果上与服务提供者有个区分,在这里我简单处理了一下,name+="!"
,即服务调用者的 response 中会比服务提供者的多一个感叹号(!)。
访问 http://localhost:9000/hello/?name=yibo 以验证是否调用成功
1 |
|
Spring Cloud Ribbon
之前已经介绍过 Ribbon 了,它是一个基于 HTTP 和 TCP 的客户端负载均衡器。它可以通过在客户端中配置 ribbonServerList 来设置服务端列表去轮询访问以达到均衡负载的作用。
当 Ribbon 与 Eureka 联合使用时,ribbonServerList 会被 DiscoveryEnabledNIWSServerList 重写,扩展成从 Eureka 注册中心中获取服务实例列表。同时它也会用 NIWSDiscoveryPing 来取代 IPing,它将职责委托给 Eureka 来确定服务端是否已经启动。
POM 包配置
将之前的 eureka-consumer 工程复制一份,并命名为 eureka-consumer-ribbon。
pom.xml 文件还用之前的就行。至于 spring-cloud-starter-ribbon,因为我使用的 Spring Cloud 版本是 Finchley.RC1,spring-cloud-starter-netflix-eureka-client 里边已经包含了 spring-cloud-starter-netflix-ribbon 了。
1 |
|
启动类
修改应用主类,为RestTemplate
添加@LoadBalanced
注解
1 |
|
Controller
修改 controller,去掉LoadBalancerClient
,并修改相应的方法,直接用 RestTemplate
发起请求
1 |
|
可能你已经注意到了,这里直接用服务名eureka-producer
取代了之前的具体的host:port
。那么这样的请求为什么可以调用成功呢?因为 Spring Cloud Ribbon 有一个拦截器,它能够在这里进行实际调用的时候,自动的去选取服务实例,并将这里的服务名替换成实际要请求的 IP 地址和端口,从而完成服务接口的调用。
访问 http://localhost:9001/hello/?name=yibo 以验证是否调用成功
1 |
|
也可以通过启动多个 eureka-producer 服务来观察其负载均衡的效果。
Spring Cloud Feign
在实际工作中,我们基本上都是使用 Feign 来完成调用的。我们通过一个例子来展现 Feign 如何方便的声明对 eureka-producer 服务的定义和调用。
POM 包配置
创建一个基本的 Spring Boot 应用,命名为eureka-producer-feign
,在 pom.xml 中添加如下配置:
1 |
|
启动类
在启动类上加上@EnableFeignClients
1 |
|
Feign 调用实现
创建一个 Feign 的客户端接口定义。使用@FeignClient
注解来指定这个接口所要调用的服务名称,接口中定义的各个函数使用 Spring MVC 的注解就可以来绑定服务提供方的 REST 接口,比如下面就是绑定 eureka-producer 服务的/hello/
接口的例子:
1 |
|
此类中的方法和远程服务中 Contoller 中的方法名和参数需保持一致。
这里有几个坑,后边有详细说明。
Controller
修改 Controller,将 HelloRemote 注入到 controller 层,像普通方法一样去调用即可
1 |
|
通过 Spring Cloud Feign 来实现服务调用的方式非常简单,通过@FeignClient
定义的接口来统一的声明我们需要依赖的微服务接口。而在具体使用的时候就跟调用本地方法一点的进行调用即可。由于 Feign 是基于 Ribbon 实现的,所以它自带了客户端负载均衡功能,也可以通过 Ribbon 的 IRule 进行策略扩展。另外,Feign 还整合的 Hystrix 来实现服务的容错保护,这个在后边会详细讲。(在 Finchley.RC1 版本中,Feign 的 Hystrix 默认是关闭的。参考 Spring Cloud OpenFeign 和 Disable HystrixCommands For FeignClients By Default)。
在我的 IDEA 里,这里会有错误提示,如下
这个其实不用管,运行的时候会被正确注入。如果嫌这个提示烦,可以在HelloRemote
这个接口上边加@Component
注解。
访问 http://localhost:9002/hello/yibo 以验证是否调用成功
1 |
|
踩坑记录
问题一:not have available server
1 |
|
这个问题刚开始困扰了我好长时间,最后发现原来是因为我没加入 eureka-client 这个依赖,只加了 spring-boot-starter-web 和 spring-cloud-starter-openfeign。只有后两者的话,启动的时候其实是不会有任何异常被抛出的,
但是如果细心地查看了启动 log 的话,其中有这么一条可以看出实际上确实是没有获取到任何服务的
1 |
|
所以,要想使用 Feign,至少需要以下三个依赖
- spring-boot-starter-web
- spring-cloud-starter-openfeign
- spring-cloud-starter-netflix-eureka-client
问题二:Request method ‘POST’ not supported
1 |
|
HelloRemote
中的代码是这样的,出现上边的异常
1 |
|
改成这样,还是同样的异常
1 |
|
再改,这次 OK 了
1 |
|
这个问题挺奇葩的的,不加@RequestParam
就变成了 POST 请求,不论我是用@GetMapping
还是method = RequestMethod.GET
,也是无语了。
至于怎么想到的加了个@RequestParam(value = "name")
,说来话长。一开始我没加 eureka-client 依赖,也没加@RequestParam
注解,一启动就报异常:
1 |
|
所以就加了@RequestParam
,算是歪打正着吧。
至于为什么出现这个 GET 变 POST 的情况,个人猜测应该是当参数没有被@RequestParam
注解修饰时,会自动被当做 request body 来处理。只要有 body,就会被 Feign 认为是 POST 请求,所以整个服务是被当作带有 request parameter 和 body 的 POST 请求发送出去的。
负载均衡
以上三种方式都能实现负载均衡,都是以轮询访问的方式实现的。这个以大家常用的 Feign 的方式做一个测试。
以上面 eureka-producer 为例子修改,将其中的 controller 改动如下:
1 |
|
打包启动
1 |
|
访问 http://localhost:9002/hello/yibo 进行测试。在不断的测试下去会发现两种结果交替出现
1 |
|
这说明两个服务中心自动提供了服务均衡负载的功能。如果我们将服务提供者的数量在提高为 N 个,测试结果一样,请求会自动轮询到每个服务端来处理。
相关阅读
Spring Cloud(一):服务治理技术概览
Spring Cloud(二):服务注册与发现 Eureka
Spring Cloud(三):服务提供与调用 Eureka
Spring Cloud(四):服务容错保护 Hystrix
Spring Cloud(五):Hystrix 监控面板
Spring Cloud(六):Hystrix 监控数据聚合 Turbine
Spring Cloud(七):配置中心(Git 版与动态刷新)
Spring Cloud(八):配置中心(服务化与高可用)
Spring Cloud(九):配置中心(消息总线)
Spring Cloud(十):服务网关 Zuul(路由)
Spring Cloud(十一):服务网关 Zuul(过滤器)
Spring Cloud(十二):分布式链路跟踪(Sleuth 与 Zipkin)
示例代码:GitHub
参考
Spring Cloud 构建微服务架构:服务消费(基础)【Dalston 版】
Spring Cloud 构建微服务架构:服务消费(Ribbon)【Dalston 版】
Spring Cloud 构建微服务架构:服务消费(Feign)【Dalston 版】
springcloud(三):服务提供与调用
Spring Cloud OpenFeign
Disable HystrixCommands For FeignClients By Default
Feign 使用 Hystrix 无效原因及解决方法
spring cloud-Feign 使用中遇到的问题总结