寫作時間:2020-02-16
Spring Cloud: Greenwich, Spring Boot: 2.1, JDK: 1.8, IDE: IntelliJ IDEA
說明
Spring Cloud Ribbon 是Spring Cloud Netflix 子項目的核心組件之一,主要給服務間調用及API網關轉發提供負載均衡的功能,本文將對其用法進行詳細介紹。
Ribbon簡介
在微服務架構中,很多服務都會部署多個,其他服務去調用該服務的時候,如何保證負載均衡是個不得不去考慮的問題。負載均衡可以增加系統的可用性和擴展性,當我們使用RestTemplate來調用其他服務時,Ribbon可以很方便的實現負載均衡功能。
1. 創建Eureka Server工程
拷貝來自文章【易實戰】Spring Cloud Greenwich Eureka:服務註冊與發現 的項目EurekaServer。
Run Dashboard
運行完成後訪問地址http://localhost:8001/可以看到Eureka註冊中心的界面
2. 創建UserServiceClient工程
首先創建一個UserServiceClient,用於給Ribbon提供服務調用。
2.1 在pom.xml
中添加依賴
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
2.2 在application.yml
進行配置
server:
port: 8201
spring:
application:
name: user-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/
2.3 添加User
類
package com.zgpeace.cloud.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@AllArgsConstructor
public class User {
private Long id;
private String userName;
private String password;
}
2.4 添加CommonResult
類
package com.zgpeace.cloud.domain;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class CommonResult<T> {
private T data;
private String message;
private Integer code;
public CommonResult(T data, String message, Integer code) {
this.data = data;
this.message = message;
this.code = code;
}
public CommonResult(String message, Integer code) {
this(null, message, code);
}
public CommonResult(T data) {
this(data, "操作成功", 200);
}
}
2.5 添加UserService
接口
package com.zgpeace.cloud.service;
import com.zgpeace.cloud.domain.User;
import java.util.List;
public interface UserService {
void create(User user);
User getUser(Long id);
void update(User user);
void delete(Long id);
User getByUserName(String userName);
List<User> getUserByIds(List<Long> ids);
}
2.6 添加service
實現類UserServiceImpl
package com.zgpeace.cloud.service.impl;
import com.zgpeace.cloud.domain.User;
import com.zgpeace.cloud.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserServiceImpl implements UserService {
private List<User> userList;
@Override
public void create(User user) {
userList.add(user);
}
@Override
public User getUser(Long id) {
List<User> findUserList = userList.stream()
.filter(userItem -> userItem.getId().equals(id))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(findUserList)) {
return findUserList.get(0);
}
return null;
}
@Override
public void update(User user) {
userList.stream().filter(userItem -> userItem.getId().equals(user.getId()))
.forEach(userItem -> {
userItem.setUserName(user.getUserName());
userItem.setPassword(user.getPassword());
});
}
@Override
public void delete(Long id) {
User user = getUser(id);
if (user != null) {
userList.remove(user);
}
}
@Override
public User getByUserName(String userName) {
List<User> findUserList = userList.stream().filter(userItem -> userItem.getUserName().equals(userName))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(findUserList)) {
return findUserList.get(0);
}
return null;
}
@Override
public List<User> getUserByIds(List<Long> ids) {
return userList.stream().filter(userItem -> ids.contains(userItem.getId()))
.collect(Collectors.toList());
}
@PostConstruct
public void initData() {
userList = new ArrayList<>();
userList.add(new User(1L, "zgpeace", "123456"));
userList.add(new User(2L, "John", "123456"));
userList.add(new User(3L, "Gary", "123456"));
}
}
2.7 添加UserController
用於提供調用接口
package com.zgpeace.cloud.controller;
import com.zgpeace.cloud.domain.CommonResult;
import com.zgpeace.cloud.domain.User;
import com.zgpeace.cloud.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@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);
}
}
2. 8 Run Dashboard
運行完成後訪問地址http://localhost:8001/可以看到Eureka註冊中心的界面, 多了一個client,端口爲8201
2.9 訪問數據請求鏈接http://192.168.0.106:8201/user/1
% curl http://192.168.0.106:8201/user/1
{"data":{"id":1,"userName":"zgpeace","password":"123456"},"message":"操作成功","code":200}%
2.10 啓動另一個UserServiceClient
於8202端口,可以通過修改IDEA中的SpringBoot的啓動配置實現
3. 創建一個RibbonClient工程
創建一個RibbonClient模塊來調用UserServiceClient模塊演示負載均衡的服務調用。
3.1 在pom.xml
中添加相關依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
3.2 在application.yml
進行配置
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
3.3 使用@LoadBalanced
註解賦予RestTemplate
負載均衡的能力
可以看出使用Ribbon的負載均衡功能非常簡單,和直接使用RestTemplate沒什麼兩樣,只需給RestTemplate添加一個@LoadBalanced即可。
package com.zgpeace.cloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3.4 拷貝UserServiceClient
中domain package
以及下面的類
com.zgpeace.cloud.domain.CommonResult
com.zgpeace.cloud.domain.User
3.5 添加UserRibbonController
類
注入RestTemplate,使用其調用UserServiceClient中提供的相關接口,這裏對GET和POST調用進行了演示,其他方法調用均可參考。
package com.zgpeace.cloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
4. 負載均衡功能演示
4.1 Run Dashboard
啓動服務如下:
- eureka-server於8001端口;
- UserServiceClient於8201端口;
- 另一個UserServiceClient於8202端口
- RibbonClient於8301端口
此時運行中的服務如下:
4.2 調用接口進行測試:http://172.20.10.3:8301/user/1. 注意:這裏必須要用註冊進來的ip地址,localhost訪問不了
% curl http://172.20.10.3:8301/user/1
{"data":{"id":1,"userName":"zgpeace","password":"123456"},"message":"操作成功","code":200}%
4.3 可以發現運行在8201和8202的UserServiceClient控制檯交替打印如下信息:
5. Ribbon的常用配置
5.1 全局配置
ribbon:
ConnectTimeout: 1000 #服務請求連接超時時間(毫秒)
ReadTimeout: 3000 #服務請求處理超時時間(毫秒)
OkToRetryOnAllOperations: true #對超時請求啓用重試機制
MaxAutoRetriesNextServer: 1 #切換重試實例的最大個數
MaxAutoRetries: 1 # 切換實例後重試最大次數
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改負載均衡算法
5.2 指定服務進行配置
與全局配置的區別就是
Ribbon
節點掛在服務名稱下面,如下是對RibbonClient
調用UserServiceClient
時的單獨配置。
user-service:
ribbon:
ConnectTimeout: 1000 #服務請求連接超時時間(毫秒)
ReadTimeout: 3000 #服務請求處理超時時間(毫秒)
OkToRetryOnAllOperations: true #對超時請求啓用重試機制
MaxAutoRetriesNextServer: 1 #切換重試實例的最大個數
MaxAutoRetries: 1 # 切換實例後重試最大次數
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改負載均衡算法
6. 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://github.com/zgpeace/SpringCloudGreenwich
參考
https://juejin.im/post/5d7f9006f265da03951a260c