Spring Cloud 快速上手之 Ribbon 負載均衡
簡介java
Spring Cloud Ribbon是基於HTTP和TCP的客戶端負載工具,它是基於Netflix Ribbon實現的。經過Spring Cloud的封裝,能夠輕鬆地將面向服務的REST 模板請求,自動轉換成客戶端負載均衡服務調用。提供雲端負載均衡,有多種負載均衡策略可供選擇,可配合服務發現和斷路器使用。mysql
準備工做Ribbon基本配置負載均衡源碼分析負載均衡策略單獨使用飢餓加載REFERENCES獲取更多web
手機用戶請
橫屏
獲取最佳閱讀體驗,REFERENCES
中是本文參考的連接,如須要連接和更多資源,能夠掃碼加入『知識星球』(文末)獲取長期知識分享服務。spring
準備工做
開發環境sql
Greenwich.SR5json
Spring Boot 2.1.5微信
MySQL 5.7架構
JDK 1.8app
依賴管理負載均衡
<!--負載均衡 Ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
須要注意的是,在spring-cloud-starter-netflix-eureka-client
默認集成了spring-cloud-starter-netflix-ribbon
,所以能夠不引入。
Ribbon
基本配置
RestTemlate 配置
@Configuration
public class RpcConfig {
@Bean
//添加此註解後,能夠直接經過 服務 ID 進行接口調用,而無需輸入IP 和端口信息
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
RestController
@RestController
public class UserController {
@Autowired
private IUserService userService;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return userService.findById(id);
}
@GetMapping("/instance/{instanceId}")
public String instance(@PathVariable String instanceId) {
ServiceInstance choose = loadBalancerClient.choose(instanceId);
HashMap<String, String> instanceInfo = new HashMap<>();
return JSON.toJSONString(choose,true);
}
}
UserServiceImpl
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private RestTemplate restTemplate;
@Override
public User findById(Long id) {
return this.restTemplate.getForObject("http://ms-provider-user-v2/" + id, User.class);
}
}
接口測試
http://localhost:8012/instance/ms-consumer-user-v2-ribbon
GET http://localhost:8012/instance/ms-consumer-user-v2-ribbon
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 1792
Date: Tue, 05 May 2020 06:10:12 GMT
{
"host": "192.168.0.100",
"instanceId": "192.168.0.100:8012",
"metadata": {
"management.port": "8012"
},
"port": 8012,
"secure": false,
"server": {
"alive": true,
"host": "192.168.0.100",
"hostPort": "192.168.0.100:8012",
"id": "192.168.0.100:8012",
"instanceInfo": {
"actionType": "ADDED",
"appName": "MS-CONSUMER-USER-V2-RIBBON",
"coordinatingDiscoveryServer": false,
"countryId": 1,
"dataCenterInfo": {
"name": "MyOwn"
},
"dirty": false,
"healthCheckUrl": "http://192.168.0.100:8012/actuator/health",
"healthCheckUrls": [
"http://192.168.0.100:8012/actuator/health"
],
"homePageUrl": "http://192.168.0.100:8012/",
"hostName": "192.168.0.100",
"iPAddr": "192.168.0.100",
"id": "192.168.0.100:ms-consumer-user-v2-ribbon:8012",
"instanceId": "192.168.0.100:ms-consumer-user-v2-ribbon:8012",
"lastDirtyTimestamp": 1588658754217,
"lastUpdatedTimestamp": 1588658754758,
"leaseInfo": {
"durationInSecs": 90,
"evictionTimestamp": 0,
"registrationTimestamp": 1588658754758,
"renewalIntervalInSecs": 30,
"renewalTimestamp": 1588659024755,
"serviceUpTimestamp": 1588658754254
},
"metadata": {
"$ref": "$.metadata"
},
"overriddenStatus": "UNKNOWN",
"port": 8012,
"sID": "na",
"securePort": 443,
"secureVipAddress": "ms-consumer-user-v2-ribbon",
"status": "UP",
"statusPageUrl": "http://192.168.0.100:8012/actuator/info",
"vIPAddress": "ms-consumer-user-v2-ribbon",
"version": "unknown"
},
"metaInfo": {
"appName": "MS-CONSUMER-USER-V2-RIBBON",
"instanceId": "192.168.0.100:ms-consumer-user-v2-ribbon:8012",
"serviceIdForDiscovery": "ms-consumer-user-v2-ribbon"
},
"port": 8012,
"readyToServe": true,
"zone": "defaultZone"
},
"serviceId": "ms-consumer-user-v2-ribbon",
"uri": "http://192.168.0.100:8012"
}
Response code: 200; Time: 100ms; Content length: 1792 bytes
http://localhost:8012/user/16
GET http://localhost:8012/user/16
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 05 May 2020 06:54:25 GMT
{
"id": 16,
"account": "account5",
"userName": "x_user_5",
"age": 20
}
Response code: 200; Time: 27ms; Content length: 61 bytes
負載均衡
配置服務提供者多實例
兩個實例的啓動參數分別爲--spring.profiles.active=ribbon1
和
--spring.profiles.active=ribbon2
server:
port: 8011
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db_yier?characterEncoding=UTF-8&rewriteBatchedStatements=true
username: root
password: Abc123++
driver-class-name: com.mysql.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.dialect.MySQL5Dialect
application:
name: ms-provider-user-v2
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
instance:
prefer-ip-address: true
info:
app:
name: @project.artifactId@
encoding: @project.build.sourceEncoding@
java:
source: @java.version@
target: @java.version@
---
spring:
profiles: ribbon1
server:
port: 8013
---
spring:
profiles: ribbon2
server:
port: 8014
接口測試
http://localhost:8012/instance/ms-provider-user-v2
//第一次調用
GET http://localhost:8012/instance/ms-provider-user-v2
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 1729
Date: Tue, 05 May 2020 07:39:49 GMT
{
"host": "192.168.0.100",
"instanceId": "192.168.0.100:8014",
"metadata": {
"management.port": "8014"
},
"port": 8014,
"secure": false,
"server": {
"alive": true,
"host": "192.168.0.100",
"hostPort": "192.168.0.100:8014",
"id": "192.168.0.100:8014",
"instanceInfo": {
"actionType": "ADDED",
"appName": "MS-PROVIDER-USER-V2",
"coordinatingDiscoveryServer": false,
"countryId": 1,
"dataCenterInfo": {
"name": "MyOwn"
},
"dirty": false,
"healthCheckUrl": "http://192.168.0.100:8014/actuator/health",
"healthCheckUrls": [
"http://192.168.0.100:8014/actuator/health"
],
"homePageUrl": "http://192.168.0.100:8014/",
"hostName": "192.168.0.100",
"iPAddr": "192.168.0.100",
"id": "192.168.0.100:ms-provider-user-v2:8014",
"instanceId": "192.168.0.100:ms-provider-user-v2:8014",
"lastDirtyTimestamp": 1588663883990,
"lastUpdatedTimestamp": 1588663884538,
"leaseInfo": {
"durationInSecs": 90,
"evictionTimestamp": 0,
"registrationTimestamp": 1588663884538,
"renewalIntervalInSecs": 30,
"renewalTimestamp": 1588664154534,
"serviceUpTimestamp": 1588663884034
},
"metadata": {
"$ref": "$.metadata"
},
"overriddenStatus": "UNKNOWN",
"port": 8014,
"sID": "na",
"securePort": 443,
"secureVipAddress": "ms-provider-user-v2",
"status": "UP",
"statusPageUrl": "http://192.168.0.100:8014/actuator/info",
"vIPAddress": "ms-provider-user-v2",
"version": "unknown"
},
"metaInfo": {
"appName": "MS-PROVIDER-USER-V2",
"instanceId": "192.168.0.100:ms-provider-user-v2:8014",
"serviceIdForDiscovery": "ms-provider-user-v2"
},
"port": 8014,
"readyToServe": true,
"zone": "defaultZone"
},
"serviceId": "ms-provider-user-v2",
"uri": "http://192.168.0.100:8014"
}
Response code: 200; Time: 12ms; Content length: 1729 bytes
//第二次調用
GET http://localhost:8012/instance/ms-provider-user-v2
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 1729
Date: Tue, 05 May 2020 07:40:26 GMT
{
"host": "192.168.0.100",
"instanceId": "192.168.0.100:8013",
"metadata": {
"management.port": "8013"
},
"port": 8013,
"secure": false,
"server": {
"alive": true,
"host": "192.168.0.100",
"hostPort": "192.168.0.100:8013",
"id": "192.168.0.100:8013",
"instanceInfo": {
"actionType": "ADDED",
"appName": "MS-PROVIDER-USER-V2",
"coordinatingDiscoveryServer": false,
"countryId": 1,
"dataCenterInfo": {
"name": "MyOwn"
},
"dirty": false,
"healthCheckUrl": "http://192.168.0.100:8013/actuator/health",
"healthCheckUrls": [
"http://192.168.0.100:8013/actuator/health"
],
"homePageUrl": "http://192.168.0.100:8013/",
"hostName": "192.168.0.100",
"iPAddr": "192.168.0.100",
"id": "192.168.0.100:ms-provider-user-v2:8013",
"instanceId": "192.168.0.100:ms-provider-user-v2:8013",
"lastDirtyTimestamp": 1588663874416,
"lastUpdatedTimestamp": 1588663874966,
"leaseInfo": {
"durationInSecs": 90,
"evictionTimestamp": 0,
"registrationTimestamp": 1588663874966,
"renewalIntervalInSecs": 30,
"renewalTimestamp": 1588664144962,
"serviceUpTimestamp": 1588663874460
},
"metadata": {
"$ref": "$.metadata"
},
"overriddenStatus": "UNKNOWN",
"port": 8013,
"sID": "na",
"securePort": 443,
"secureVipAddress": "ms-provider-user-v2",
"status": "UP",
"statusPageUrl": "http://192.168.0.100:8013/actuator/info",
"vIPAddress": "ms-provider-user-v2",
"version": "unknown"
},
"metaInfo": {
"appName": "MS-PROVIDER-USER-V2",
"instanceId": "192.168.0.100:ms-provider-user-v2:8013",
"serviceIdForDiscovery": "ms-provider-user-v2"
},
"port": 8013,
"readyToServe": true,
"zone": "defaultZone"
},
"serviceId": "ms-provider-user-v2",
"uri": "http://192.168.0.100:8013"
}
Response code: 200; Time: 12ms; Content length: 1729 bytes
關注"instanceId": "192.168.0.100:ms-provider-user-v2:8013",
和 "instanceId": "192.168.0.100:ms-provider-user-v2:8014",
能夠發現實現了負載均衡,兩次請求被均勻的分配到2個ms-provider-user-v2
服務實例上。
源碼分析
LoadBalancerInterceptor
LoadBalancerInterceptor
是註解@LoadBalanced
的關聯實現類。
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
* @author William Tran
*/
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
在LoadBalancerAutoConfiguration
中,會對RestTemplate
進行加強處理:
//傳入攔截器
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
//加工RestTemplate
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
而在 Spring 容器注入單例 Bean 的時候,會在DefaultListableBeanFactory
中調用以下一段代碼:
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
負載均衡策略
改變負載均衡策略,配置形式,或者註解形式均可以(IRule)
配置文件
ms-provider-user-v2:
ribbon:
# 配置隨機策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
代碼配置
/**
* 配置均衡負載策略
* @return
*/
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
# 測試 LoadBalancerClient 返回的 實例 ms-provider-user-v2 的信息
GET http://localhost:8012/instance/ms-provider-user-v2
Accept: application/json
經過返回的結果測試,能夠驗證是隨機的,而非默認的輪詢選擇機制。
單獨使用
依賴配置
刪除spring-cloud-starter-netflix-eureka-client
,並引入配置:
<!--負載均衡 Ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
配置
server:
port: 8012
spring:
application:
name: ms-consumer-user-v2-ribbon-single
#ms-provider-user-v2:
# ribbon:
# # 配置隨機策略
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
# 單獨使用,不使用 Eureka
ms-provider-user-v2:
ribbon:
listOfServers: localhost:8013,localhost:8014
測試
GET http://localhost:8012/instance/ms-provider-user-v2
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 390
Date: Tue, 05 May 2020 08:27:49 GMT
{
"host": "localhost",
"instanceId": "localhost:8013",
"metadata": {},
"port": 8013,
"secure": false,
"server": {
"alive": true,
"host": "localhost",
"hostPort": "localhost:8013",
"id": "localhost:8013",
"metaInfo": {
"instanceId": "localhost:8013"
},
"port": 8013,
"readyToServe": true,
"zone": "UNKNOWN"
},
"serviceId": "ms-provider-user-v2",
"uri": "http://localhost:8013"
}
Response code: 200; Time: 507ms; Content length: 390 bytes
結果輪詢返回的端口爲 8013 或 8014。
飢餓加載
默認狀況下Ribbon是懶加載的。當服務起動好以後,第一次請求是很是慢的,第二次以後就快不少。其解決方式:開啓飢餓加載。
ribbon:
eager-load:
# 開啓飢餓加載
enabled: true
# 爲哪些服務的名稱開啓飢餓加載,多個用逗號分隔
clients: server-1,server-2,server-3
Spring Cloud 會爲每一個名稱的 Ribbon Client 維護一個子應用程序的上下文,默認是懶加載的,配置飢餓加載後,能夠在啓動時就加載對應子應用程序的上下文,從而提升首次請求的訪問速度。
REFERENCES
Spring Cloud 中文索引
Spring Cloud Alibaba之負載均衡組件 - Ribbon詳解(三)
獲取更多
掃碼關注
架構探險之道
,回覆"源碼",獲取本文相關源碼
掃碼加入知識星球,獲取更多珍貴筆記、視頻、電子書的等資源。
本文分享自微信公衆號 - 架構探險之道(zacsnz1314)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。