Spring Cloud Netflix Ribbon核心接口
LoadBalancerClient
主要職責
- 轉化URI:將含應用名稱URI轉化成具體主機+端口形式
- 選擇服務實例:通過負載算法,選擇指定服務中的一臺機器實例
- 請求執行回調:針對選擇後服務實例,執行具體的請求回調操作
默認實現:RibbonLoadBalancerClient
自動裝配源:RibbonAutoConfiguration#loadBalancerClient(...)
負載均衡器上下文
LoadBalancerContext
主要職責
- 轉化URI:將含應用名稱URI轉化成具體主機+端口的形式
- 組件關聯:關聯RetryHandler、ILoadBalancer等
- 記錄服務統計信息:記錄請求響應時間、錯誤數量等
- 默認實現:RibbonLoadBalancerContext
- 自動裝配源:RibbonClientConfiguration#ribbonLoadBalancerContext(...)
負載均衡器
ILoadBalancer
主要職責
- 增加服務器
- 獲取服務器:通過關聯Key獲取、獲取所有服務列表、獲取可用服務器列表
- 服務器狀態:標記服務器宕機
- 默認實現:ZoneAwareLoadBalancer
- 自動裝配源:RibbonClientConfiguration#ribbonLoadBalancer(...)
規則接口
IRule
主要職責
- 選擇服務器:根據負載均衡器以及關聯Key獲取候選的服務器
- 默認實現:ZoneAvoidanceRule
- 自動裝配源:RibbonClientConfiguration#ribbonRule(...)
PING策略
IPing
主要職責
- 活動檢測:根據指定的服務器,檢測其是否活動
- 默認實現:DummyPing
- 自動裝配源:RibbonClientConfiguration#ribbonPing(...)
服務器列表
ServerList
主要職責
- 獲取初始化服務器列表
- 獲取更新服務器列表
- 默認實現:ConfigurationBasedServerList或DiscoveryEnabledNIWSServerList
- 自動裝配源:RibbonClientConfiguration#ribbonPing(...)
我的項目版本信息Maven配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules>
<module>user-api</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-lesson-7</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-cloud-lesson-7</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<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-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
idea多工程配置
pom.xml文件中
然後刪除之前src文件夾,然後new個項目,效果如圖
三個模塊:
- user-api:公用 API
- user-robbon-client:客戶端應用
- user-service-provider:服務端應用
實現 user-robbon-client
配置信息
application.properties:
## 用戶 Ribbon 客戶端應用
spring.application.name = user-ribbon-client
## 服務端口
server.port = 8080
## 提供方服務名稱
provider.service.name = user-service-provider
## 提供方服務主機
provider.service.host = localhost
## 提供方服務端口
provider.service.port = 9090
## 關閉 Eureka Client,顯示地通過配置方式註冊 Ribbon 服務地址
eureka.client.enabled = false
## 定義 user-service-provider Ribbon 的服務器地址
## 爲 RibbonLoadBalancerClient 提供服務列表
user-service-provider.ribbon.listOfServers = \
http://${provider.service.host}:${provider.service.port}
2.編寫客戶端調用
package com.segumentfault.spring.cloud.lesson7.user.ribbon.client.web.controller;
import com.segumentfault.spring.cloud.lesson7.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.ws.rs.GET;
import java.io.IOException;
/**
* 用戶 Ribbon Controller
*
*/
@RestController
public class UserRibbonController {
/**
* 負載均衡器客戶端
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
@Value("${provider.service.name}")
private String providerServiceName;
@GetMapping("")
public String index() throws IOException {
User user = new User();
user.setId(1L);
user.setName("小馬哥");
// 選擇指定的 service Id
ServiceInstance serviceInstance = loadBalancerClient.choose(providerServiceName);
return loadBalancerClient.execute(providerServiceName, serviceInstance, instance -> {
//服務器實例,獲取 主機名(IP) 和 端口
String host = instance.getHost();
int port = instance.getPort();
String url = "http://" + host + ":" + port + "/user/save";
RestTemplate restTemplate = new RestTemplate();
return restTemplate.postForObject(url, user, String.class);
});
}
}
3.編寫引導類
package com.segumentfault.spring.cloud.lesson7.user.ribbon.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
/**
* 引導類
*
*/
@SpringBootApplication
@RibbonClient("user-service-provider") // 指定目標應用名稱
public class UserRibbonClientApplication {
public static void main(String[] args) {
SpringApplication.run(UserRibbonClientApplication.class, args);
}
}
實現user-service-provider
1.配置信息
application.properties
## 用戶服務提供方應用信息
spring.application.name = user-service-provider
## 服務端口
server.port = 9090
## 關閉 Eureka Client,顯示地通過配置方式註冊 Ribbon 服務地址
eureka.client.enabled = false
2.實現UserService
package com.segumentfault.spring.cloud.lesson7.user.service.provider.service;
import com.segumentfault.spring.cloud.lesson7.api.UserService;
import com.segumentfault.spring.cloud.lesson7.domain.User;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 內存實現{@link UserService}
*
*/
@Service
public class InMemoryUserService implements UserService {
private Map<Long, User> repository = new ConcurrentHashMap<>();
@Override
public boolean saveUser(User user) {
return repository.put(user.getId(), user) == null;
}
@Override
public List<User> findAll() {
return new ArrayList(repository.values());
}
}
3.實現Web服務
package com.segumentfault.spring.cloud.lesson7.user.service.web.controller;
import com.segumentfault.spring.cloud.lesson7.api.UserService;
import com.segumentfault.spring.cloud.lesson7.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 用戶服務提供方 Controller
*
*/
@RestController
public class UserServiceProviderController {
@Autowired
private UserService userService;
@PostMapping("/user/save")
public boolean user(@RequestBody User user){
return userService.saveUser(user);
}
}
4.編寫引導類
package com.segumentfault.spring.cloud.lesson7.user.service;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
/**
* 引導類
*
*/
@SpringBootApplication
public class UserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceProviderApplication.class, args);
}
}
下面知識點是拓展源碼練習,只做瞭解
分析調用鏈路
選擇服務器邏輯
LoadBalancerClient(LoadBalancerClient) -> ILoadBalancer(ZoneAwareLoadBalancer) -> IRule (ZoneAvoidanceRule)
自定義實現IRule
擴展AbstractLoadBalancerRule:MyRule
package com.segumentfault.spring.cloud.lesson7.user.ribbon.client.rule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
import java.util.List;
/**
* 自定義{@link IRule} 實現,永遠選擇最後一臺可達服務器
*
*/
public class MyRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
ILoadBalancer loadBalancer = getLoadBalancer();
//獲取所有可達服務器列表
List<Server> servers = loadBalancer.getReachableServers();
if (servers.isEmpty()) {
return null;
}
// 永遠選擇最後一臺可達服務器
Server targetServer = servers.get(servers.size() - 1);
return targetServer;
}
}
將MyRule暴露成Bean
通過ribbonClientConfiguration
學習源碼
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
然後將其暴露成Bean
/**
* 將 {@link MyRule} 暴露成 {@link Bean}
*
* @return {@link MyRule}
*/
@Bean
public IRule myRule() {
return new MyRule();
}
配置化實現組件
通過學習PropertiesFactory
源碼
public PropertiesFactory() {
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
classToProperty.put(ServerList.class, "NIWSServerListClassName");
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}
可知NFLoadBalancerClassName等是可以配置的
實現IPing:MyPing
package com.segumentfault.spring.cloud.lesson7.user.ribbon.client.ping;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.Server;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
/**
* 實現 {@link IPing} 接口:檢查對象 /health 是否正常狀態碼:200
*
*/
public class MyPing implements IPing {
@Override
public boolean isAlive(Server server) {
String host = server.getHost();
int port = server.getPort();
// /health endpoint
// 通過 Spring 組件來實現URL 拼裝
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
builder.scheme("http");
builder.host(host);
builder.port(port);
builder.path("/health");
URI uri = builder.build().toUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity responseEntity = restTemplate.getForEntity(uri, String.class);
// 當響應狀態等於 200 時,返回 true ,否則 false
return HttpStatus.OK.equals(responseEntity.getStatusCode());
}
}
增加配置
application.properties
## 擴展 IPing 實現
user-service-provider.ribbon.NFLoadBalancerPingClassName = \
com.segumentfault.spring.cloud.lesson7.user.ribbon.client.ping.MyPing