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-ribbon-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("ssss");
// 选择指定的 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