如需转载,请根据 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 许可,附上本文作者及链接。
本文作者: 执笔成念
作者昵称: zbcn
本文链接: https://1363653611.github.io/zbcn.github.io/2021/01/03/springcloud-03Ribbon/
Spring-cloud-Ribbon
Spring Cloud Ribbon 是Spring Cloud Netflix 子项目的核心组件之一,主要给服务间调用及API网关转发提供负载均衡的功能,本文将对其用法进行详细介绍。
Ribbon 简介
在微服务架构中,很多服务都会部署多个,其他服务去调用该服务的时候,如何保证负载均衡是个不得不去考虑的问题。负载均衡可以增加系统的可用性和扩展性,当我们使用RestTemplate来调用其他服务时,Ribbon可以很方便的实现负载均衡功能。
RestTemplate 使用
RestTemplate是一个HTTP客户端,使用它我们可以方便的调用HTTP接口,支持GET、POST、PUT、DELETE等方法。
GET 请求
1 | <T> T getForObject(String url, Class<T> responseType, Object... uriVariables); |
2 | |
3 | <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables); |
4 | |
5 | <T> T getForObject(URI url, Class<T> responseType); |
6 | |
7 | <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables); |
8 | |
9 | <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables); |
10 | |
11 | <T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType); |
getForObject() 方法
返回对象为响应体中数据转化成的对象,举例如下:
1 | "/{id}") ( |
2 | public CommonResult getUser(@PathVariable Long id) { |
3 | return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id); |
4 | } |
getForEntity() 方法
返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等,举例如下:
1 | "/getEntityByUsername") ( |
2 | public CommonResult getEntityByUsername(@RequestParam String username) { |
3 | ResponseEntity<CommonResult> entity = restTemplate.getForEntity(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username); |
4 | if (entity.getStatusCode().is2xxSuccessful()) { |
5 | return entity.getBody(); |
6 | } else { |
7 | return new CommonResult("操作失败", 500); |
8 | } |
9 | } |
POST 请求
1 | <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables); |
2 | |
3 | <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables); |
4 | |
5 | <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType); |
6 | |
7 | <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables); |
8 | |
9 | <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables); |
10 | |
11 | <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType); |
PostForObject() 方法
1 | "/create") ( |
2 | public CommonResult create(@RequestBody User user) { |
3 | return restTemplate.postForObject(userServiceUrl + "/user/create", user, CommonResult.class); |
4 | } |
postForEntity() 方法
1 | "/create") ( |
2 | public CommonResult create(@RequestBody User user) { |
3 | return restTemplate.postForEntity(userServiceUrl + "/user/create", user, CommonResult.class).getBody(); |
4 | } |
PUT 请求
1 | void put(String url, @Nullable Object request, Object... uriVariables); |
2 | |
3 | void put(String url, @Nullable Object request, Map<String, ?> uriVariables); |
4 | |
5 | void put(URI url, @Nullable Object request); |
实例
1 | "/update") ( |
2 | public CommonResult update(@RequestBody User user) { |
3 | restTemplate.put(userServiceUrl + "/user/update", user); |
4 | return new CommonResult("操作成功",200); |
5 | } |
DELETE 请求
1 | void delete(String url, Object... uriVariables); |
2 | |
3 | void delete(String url, Map<String, ?> uriVariables); |
4 | |
5 | void delete(URI url); |
实例
1 | "/delete/{id}") ( |
2 | public CommonResult delete(@PathVariable Long id) { |
3 | restTemplate.delete(userServiceUrl + "/user/delete/{1}", null, id); |
4 | return new CommonResult("操作成功",200); |
5 | } |
创建 user-service 业务模块
首先我们创建一个user-service,用于给Ribbon提供服务调用。
添加 pom.xml 依赖
1 | <dependency> |
2 | <groupId>org.springframework.cloud</groupId> |
3 | <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> |
4 | </dependency> |
5 | <dependency> |
6 | <groupId>org.springframework.boot</groupId> |
7 | <artifactId>spring-boot-starter-web</artifactId> |
8 | </dependency> |
在application.yml 进行配置
配置端口号和注册中心地址:
1 | server: |
2 | port: 8201 |
3 | spring: |
4 | application: |
5 | name: user-service |
6 | eureka: |
7 | client: |
8 | register-with-eureka: true |
9 | fetch-registry: true |
10 | service-url: |
11 | defaultZone: http://localhost:8001/eureka/ |
添加UserController 提供调用接口
Crud 业务接口
1 |
|
2 | "/user") ( |
3 | public class UserController { |
4 | private Logger log = LoggerFactory.getLogger(this.getClass()); |
5 | |
6 | |
7 | private UserService userService; |
8 | |
9 | "/create") ( |
10 | public ResponseResult create(@RequestBody User user) { |
11 | userService.create(user); |
12 | return ResponseResult.success("操作成功"); |
13 | } |
14 | |
15 | "/{id}") ( |
16 | public ResponseResult<User> getUser(@PathVariable Long id) { |
17 | User user = userService.getUser(id); |
18 | log.info("根据id获取用户信息,用户名称为:{}",user.getUserName()); |
19 | return ResponseResult.success(user); |
20 | } |
21 | |
22 | "/getUserByIds") ( |
23 | public ResponseResult<List<User>> getUserByIds( List<Long> ids) { |
24 | List<User> userList= userService.getUserByIds(ids); |
25 | log.info("根据ids获取用户信息,用户列表为:{}",userList); |
26 | return ResponseResult.success(userList); |
27 | } |
28 | |
29 | "/getByUsername") ( |
30 | public ResponseResult<User> getByUsername(@RequestParam String username) { |
31 | User user = userService.getByUsername(username); |
32 | return ResponseResult.success(user); |
33 | } |
34 | |
35 | "/update") ( |
36 | public ResponseResult update(@RequestBody User user) { |
37 | userService.update(user); |
38 | return ResponseResult.success("操作成功"); |
39 | } |
40 | |
41 | "/delete/{id}") ( |
42 | public ResponseResult delete(@PathVariable Long id) { |
43 | userService.delete(id); |
44 | return ResponseResult.success("操作成功"); |
45 | } |
46 | } |
创建Ribbon-service
这里我们创建一个ribbon-service模块来调用user-service模块演示负载均衡的服务调用。
在pom.xml 中添加 依赖
1 | <dependency> |
2 | <groupId>org.springframework.boot</groupId> |
3 | <artifactId>spring-boot-starter-web</artifactId> |
4 | </dependency> |
5 | <dependency> |
6 | <groupId>org.springframework.cloud</groupId> |
7 | <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> |
8 | </dependency> |
9 | <dependency> |
10 | <groupId>org.springframework.cloud</groupId> |
11 | <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> |
12 | </dependency> |
在 application.yml 中添加配置
主要是配置了端口、注册中心地址及user-service的调用路径。
1 | server: |
2 | port: 8301 |
3 | spring: |
4 | application: |
5 | name: ribbon-service |
6 | eureka: |
7 | client: |
8 | register-with-eureka: true |
9 | fetch-registry: true |
10 | service-url: |
11 | defaultZone: http://localhost:8001/eureka/ |
12 | service-url: |
13 | user-service: http://user-service |
使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
可以看出使用Ribbon的负载均衡功能非常简单,和直接使用RestTemplate没什么两样,只需给RestTemplate添加一个@LoadBalanced即可。
1 |
|
2 | public class RibbonConfig { |
3 | |
4 | |
5 | |
6 | public RestTemplate restTemplate(){ |
7 | return new RestTemplate(); |
8 | } |
9 | } |
添加UserRibbonController类
注入RestTemplate,使用其调用user-service中提供的相关接口,这里对GET和POST调用进行了演示,其他方法调用均可参考。
1 |
|
2 | "/user") ( |
3 | public class UserRibbonController { |
4 | |
5 | |
6 | private RestTemplate restTemplate; |
7 | |
8 | "${service-url.user-service}") ( |
9 | private String userServiceUrl; |
10 | |
11 | "/{id}") ( |
12 | public ResponseResult getUser(@PathVariable Long id) { |
13 | return restTemplate.getForObject(userServiceUrl + "/user/{1}", ResponseResult.class, id); |
14 | } |
15 | |
16 | "/getByUsername") ( |
17 | public ResponseResult getByUsername(@RequestParam String username) { |
18 | return restTemplate.getForObject(userServiceUrl + "/user/getByUsername?username={1}", ResponseResult.class, username); |
19 | } |
20 | |
21 | "/getEntityByUsername") ( |
22 | public ResponseResult getEntityByUsername(@RequestParam String username) { |
23 | ResponseEntity<ResponseResult> entity = restTemplate.getForEntity(userServiceUrl + "/user/getByUsername?username={1}", ResponseResult.class, username); |
24 | if (entity.getStatusCode().is2xxSuccessful()) { |
25 | return entity.getBody(); |
26 | } else { |
27 | return ResponseResult.fail(ApiStatus.INTERNAL_SERVER_ERROR.getCode(),ApiStatus.INTERNAL_SERVER_ERROR.getValue(),"操作失败"); |
28 | } |
29 | } |
30 | |
31 | "/create") ( |
32 | public ResponseResult create(@RequestBody Map user) { |
33 | return restTemplate.postForObject(userServiceUrl + "/user/create", user, ResponseResult.class); |
34 | } |
35 | |
36 | "/update") ( |
37 | public ResponseResult update(@RequestBody Map user) { |
38 | return restTemplate.postForObject(userServiceUrl + "/user/update", user, ResponseResult.class); |
39 | } |
40 | |
41 | "/delete/{id}") ( |
42 | public ResponseResult delete(@PathVariable Long id) { |
43 | return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}", null, ResponseResult.class, id); |
44 | } |
45 | |
46 | } |
负载均衡功能演示
- 启动eureka-server于8001端口;
- 启动user-service于8201端口;
- 启动另一个user-service于8202端口,可以通过修改IDEA中的SpringBoot的启动配置实现:
此时运行中的服务
调用接口进行测试:http://localhost:8301/user/1
服务调用打印信息
Ribbon的常用配置
全局配置
1 | ribbon: |
2 | ConnectTimeout: 1000 #服务请求连接超时时间(毫秒) |
3 | ReadTimeout: 3000 #服务请求处理超时时间(毫秒) |
4 | OkToRetryOnAllOperations: true #对超时请求启用重试机制 |
5 | MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数 |
6 | MaxAutoRetries: 1 # 切换实例后重试最大次数 |
7 | NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法 |
指定服务进行配置
与全局配置的区别就是ribbon节点挂在服务名称下面,如下是对ribbon-service调用user-service时的单独配置。
1 | user-service: |
2 | ribbon: |
3 | ConnectTimeout: 1000 #服务请求连接超时时间(毫秒) |
4 | ReadTimeout: 3000 #服务请求处理超时时间(毫秒) |
5 | OkToRetryOnAllOperations: true #对超时请求启用重试机制 |
6 | MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数 |
7 | MaxAutoRetries: 1 # 切换实例后重试最大次数 |
8 | NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法 |
Ribbon的负载均衡策略
所谓的负载均衡策略,就是当A服务调用B服务时,此时B服务有多个实例,这时A服务以何种方式来选择调用的B实例,ribbon可以选择以下几种负载均衡策略。
- com.netflix.loadbalancer.RandomRule:从提供服务的实例中以随机的方式;
- com.netflix.loadbalancer.RoundRobinRule:以线性轮询的方式,就是维护一个计数器,从提供服务的实例中按顺序选取,第一次选第一个,第二次选第二个,以此类推,到最后一个以后再从头来过;
- com.netflix.loadbalancer.RetryRule:在RoundRobinRule的基础上添加重试机制,即在指定的重试时间内,反复使用线性轮询策略来选择可用实例;
- com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择;
- com.netflix.loadbalancer.BestAvailableRule:选择并发较小的实例;
- com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
- com.netflix.loadbalancer.ZoneAwareLoadBalancer:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例。
用到的模块
1 | ZBCN-SERVER |
2 | ├── zbcn-register/eureka-server -- eureka注册中心 |
3 | ├── zbcn-business/user-service -- 提供User对象CRUD接口的服务 |
4 | └── zbcn-common/ribbon-server -- ribbon服务调用测试服务 |