Ribbon 简介
Spring Cloud Ribbon 是 Spring Cloud Netflix 子项目的核心组件之一,主要给服务间调用及 API 网关转发提供负载均衡的功能,本文将对其用法进行详细介绍。
在微服务架构中,很多服务都会部署多个,其他服务去调用该服务的时候,如何保证负载均衡是个不得不去考虑的问题。负载均衡可以增加系统的可用性和扩展性,当我们使用RestTemplate来调用其他服务时,Ribbon 可以很方便的实现负载均衡功能。
RestTemplate 的使用
RestTemplate 是一个 HTTP 客户端,使用它我们可以方便的调用 HTTP 接口,支持 GET、POST、PUT、DELETE 等方法。
GET 请求方法 1 2 3 4 5 6 7 8 9 10 11 <T> T getForObject (String url, Class<T> responseType, Object... uriVariables) ; <T> T getForObject (String url, Class<T> responseType, Map<String, ?> uriVariables) ; <T> T getForObject (URI url, Class<T> responseType) ; <T> ResponseEntity<T> getForEntity (String url, Class<T> responseType, Object... uriVariables) ; <T> ResponseEntity<T> getForEntity (String url, Class<T> responseType, Map<String, ?> uriVariables) ; <T> ResponseEntity<T> getForEntity (URI var1, Class<T> responseType) ;
getForObject 方法
返回对象为响应体中数据转化成的对象,举例如下:
1 2 3 4 @GetMapping ("/{id}" )public CommonResult getUser (@PathVariable Long id) { return restTemplate.getForObject(userServiceUrl + "/user/{1}" , CommonResult.class , id ) ; }
getForEntity 方法
返回对象为 ResponseEntity 对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等,举例如下:
1 2 3 4 5 6 7 8 9 @GetMapping ("/getEntityByUsername" )public CommonResult getEntityByUsername (@RequestParam String username) { ResponseEntity<CommonResult> entity = restTemplate.getForEntity(userServiceUrl + "/user/getByUsername?username={1}" , CommonResult.class , username ) ; if (entity.getStatusCode().is2xxSuccessful()) { return entity.getBody(); } else { return new CommonResult("操作失败" , 500 ); } }
POST 请求方法 1 2 3 4 5 6 7 8 9 10 11 <T> T postForObject (String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) ; <T> T postForObject (String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) ; <T> T postForObject (URI url, @Nullable Object request, Class<T> responseType) ; <T> ResponseEntity<T> postForEntity (String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) ; <T> ResponseEntity<T> postForEntity (String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) ; <T> ResponseEntity<T> postForEntity (URI url, @Nullable Object request, Class<T> responseType) ;
postForObject 示例
1 2 3 4 @PostMapping ("/create" )public CommonResult create (@RequestBody User user) { return restTemplate.postForObject(userServiceUrl + "/user/create" , user, CommonResult.class ) ; }
postForEntity 示例
1 2 3 4 @PostMapping ("/create" )public CommonResult create (@RequestBody User user) { return restTemplate.postForEntity(userServiceUrl + "/user/create" , user, CommonResult.class ).getBody () ; }
PUT 请求方法 1 2 3 4 5 void put (String url, @Nullable Object request, Object... uriVariables) ;void put (String url, @Nullable Object request, Map<String, ?> uriVariables) ;void put (URI url, @Nullable Object request) ;
PUT 请求示例
1 2 3 4 5 @PutMapping ("/update" )public CommonResult update (@RequestBody User user) { restTemplate.put(userServiceUrl + "/user/update" , user); return new CommonResult("操作成功" ,200 ); }
DELETE 请求方法 1 2 3 4 5 void delete (String url, Object... uriVariables) ;void delete (String url, Map<String, ?> uriVariables) ;void delete (URI url) ;
DELETE 请求示例
1 2 3 4 5 @DeleteMapping ("/delete/{id}" )public CommonResult delete (@PathVariable Long id) { restTemplate.delete(userServiceUrl + "/user/delete/{1}" , null , id); return new CommonResult("操作成功" ,200 ); }
创建服务提供者
首先我们创建一个 user-service,用于给 Ribbon 提供服务调用。
引入依赖 1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
相关配置
主要是配置了端口和注册中心地址。
1 2 3 4 5 6 7 8 9 10 11 server: port: 8201 spring: application: name: user-service eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8001/eureka/
创建 Controller UserController 类定义了对 User 对象常见的 CRUD 接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @RestController @RequestMapping ("/user" )public class UserController { private Logger LOGGER = LoggerFactory.getLogger(this .getClass()); @Autowired private UserService userService; @PostMapping ("/create" ) public CommonResult create (@RequestBody User user) { userService.create(user); return new CommonResult("操作成功" , 200 ); } @GetMapping ("/{id}" ) public CommonResult<User> getUser (@PathVariable Long id) { User user = userService.getUser(id); LOGGER.info("根据id获取用户信息,用户名称为:{}" ,user.getUsername()); return new CommonResult<>(user); } @GetMapping ("/getUserByIds" ) public CommonResult<List<User>> getUserByIds(@RequestParam List<Long> ids) { List<User> userList= userService.getUserByIds(ids); LOGGER.info("根据ids获取用户信息,用户列表为:{}" ,userList); return new CommonResult<>(userList); } @GetMapping ("/getByUsername" ) public CommonResult<User> getByUsername (@RequestParam String username) { User user = userService.getByUsername(username); return new CommonResult<>(user); } @PostMapping ("/update" ) public CommonResult update (@RequestBody User user) { userService.update(user); return new CommonResult("操作成功" , 200 ); } @PostMapping ("/delete/{id}" ) public CommonResult delete (@PathVariable Long id) { userService.delete(id); return new CommonResult("操作成功" , 200 ); } }
创建服务消费者 这里我们创建一个 ribbon-service 模块来调用 user-service 模块演示负载均衡的服务调用。
引入依赖 1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-ribbon</artifactId > </dependency >
相关配置 主要是配置了端口、注册中心地址及 user-service 的调用路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 8301 spring: application: name: ribbon-service eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8001/eureka/ service-url: user-service: http://user-service
使用 @LoadBalanced
注解赋予 RestTemplate 负载均衡的能力。使用 Ribbon 的负载均衡功能非常简单,和直接使用 RestTemplate 没什么两样,只需给 RestTemplate 添加一个 @LoadBalanced
即可。
1 2 3 4 5 6 7 8 9 @Configuration public class RibbonConfig { @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate(); } }
创建 Controller 注入 RestTemplate,使用其调用 user-service 中提供的相关接口,这里对 GET 和 POST 调用进行了演示,其他方法调用均可参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @RestController @RequestMapping ("/user" )public class UserRibbonController { @Autowired private RestTemplate restTemplate; @Value ("${service-url.user-service}" ) private String userServiceUrl; @GetMapping ("/{id}" ) public CommonResult getUser (@PathVariable Long id) { return restTemplate.getForObject(userServiceUrl + "/user/{1}" , CommonResult.class , id ) ; } @GetMapping ("/getByUsername" ) public CommonResult getByUsername (@RequestParam String username) { return restTemplate.getForObject(userServiceUrl + "/user/getByUsername?username={1}" , CommonResult.class , username ) ; } @GetMapping ("/getEntityByUsername" ) public CommonResult getEntityByUsername (@RequestParam String username) { ResponseEntity<CommonResult> entity = restTemplate.getForEntity(userServiceUrl + "/user/getByUsername?username={1}" , CommonResult.class , username ) ; if (entity.getStatusCode().is2xxSuccessful()) { return entity.getBody(); } else { return new CommonResult("操作失败" , 500 ); } } @PostMapping ("/create" ) public CommonResult create (@RequestBody User user) { return restTemplate.postForObject(userServiceUrl + "/user/create" , user, CommonResult.class ) ; } @PostMapping ("/update" ) public CommonResult update (@RequestBody User user) { return restTemplate.postForObject(userServiceUrl + "/user/update" , user, CommonResult.class ) ; } @PostMapping ("/delete/{id}" ) public CommonResult delete (@PathVariable Long id) { return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}" , null , CommonResult.class , id ) ; } }
负载均衡功能演示
启动 eureka-server 于 8001 端口
启动 user-service 于 8201 端口
启动另一个 user-service 于 8202 端口,可以通过修改 IDEA 中的 Spring Boot 的启动配置实现
此时运行中的服务如下
调用接口进行测试:http://localhost:8301/user/1
可以发现运行在 8201 和 8202 的 user-service 控制台交替打印如下信息
Ribbon的常用配置
#### 全局配置
1 2 3 4 5 6 7 ribbon: ConnectTimeout: 1000 ReadTimeout: 3000 OkToRetryOnAllOperations: true MaxAutoRetriesNextServer: 1 MaxAutoRetries: 1 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
指定服务进行配置 与全局配置的区别就是ribbon节点挂在服务名称下面,如下是对ribbon-service调用user-service时的单独配置。
1 2 3 4 5 6 7 8 user-service: ribbon: ConnectTimeout: 1000 ReadTimeout: 3000 OkToRetryOnAllOperations: true MaxAutoRetriesNextServer: 1 MaxAutoRetries: 1 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
:采用双重过滤,同时过滤不是同一区域的实例和故障实例,选择并发较小的实例。
更多干货请移步:https://antoniopeng.com
如果你喜欢这个博客或发现它对你有用,欢迎你点击右下角 “OPEN CHAT” 进行评论。也欢迎你分享这个博客,让更多的人参与进来。如果在博客中的内容侵犯了您的版权,请联系博主删除它们。谢谢你!