文章目录
一、 Ribbon简介
Ribbon 是一个基于Http和TCP的客服端负载均衡工具,它是基于Netflix Ribbon实现的。它不像spring cloud服务注册中心、配置中心、API网关那样独立部署,但是它几乎存在于每个spring cloud 微服务中。包括feign提供的声明式服务调用也是基于该Ribbon实现的。ribbon默认提供很多种负载均衡算法,例如 轮询、随机 等等。甚至包含自定义的负载均衡算法。Ribbon可以用于解决并提供微服务的负载均衡的问题。
二、 使用Ribbon开发微服务
在Spring Cloud中,使用Ribbon技术开发Eureka Client组件还是非常方便的。我们在开发过程中,不需要像Dubbo那样关注服务的角色。无论是Provider还是Consumer都是一个微服务客户端,只是在编码层面上,服务消费者代码的开发相对比较麻烦。我们通过简单案例测试一下Spring Cloud中的微服务开发过程。
因为现在的Eureka Server部署在Linux中,并已为Linux定义了新的主机域名,需要先修改开发测试环境中的hosts文件。windows中的hosts文件位于:C:\windows\system32\dirvers\etc\hosts。新增内容如下:(IP根据具体情况配置)
192.168.14.128 eureka1
192.168.14.129 eureka2
1 创建springcloud工程 和 commons子模块
1.1 POM依赖
<?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>
<packaging>pom</packaging>
<modules>
<module>commons</module>
<module>ribbonappservice</module>
<module>ribbonappclient</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<groupId>com.bjsxt</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
1.2 commons 的pom.xml
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>commons</artifactId>
</project>
1.3 commons子模块中user类 (包含set,get等方法)
2 开发服务提供者 - ribbonappservice
2.1 POM依赖
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- 服务提供方项目, 使用Ribbon技术开发 -->
<artifactId>ribbonappservice</artifactId>
<dependencies>
<!-- spring cloud是通过http协议对外发布一个基于REST规则的微服务
通过SpringMVC技术中的Controller对外提供微服务的。
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka客户端依赖,spring cloud中,服务的提供者和消费者都是Eureka客户端。必须依赖此资源 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.2 服务提供者代码
package com.bjsxt.userservice.controller;
import com.bjsxt.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* 提供服务的控制器
*/
@Controller
public class UserController {
@RequestMapping("/user/save")
@ResponseBody
public Map<String, Object> save(User user){
System.out.println("新增用户数据: " + user);
Map<String, Object> result = new HashMap<>();
result.put("code", "200"); // 返回的状态码
result.put("message", "新增用户成功"); // 返回的处理结果消息。
return result;
}
}
2.3 配置文件application.yml
server:
port: 8082
spring:
application:
name: ribbon-app-service
eureka: # Eureka客户端,启动的时候,如果未配置Eureka服务端地址列表,则在localhost:8761注册
client:
service-url: # 配置Eureka服务端地址,注册中心地址,多个地址使用逗号 ',' 分隔。
defaultZone: http://localhost:8761/eureka/
2.4 启动类
package com.bjsxt.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 服务提供方启动类
* 在Spring Cloud低版本中,如果开发的代码是Eureka Client(服务提供者和消费者),
* 那么启动类上需要增加注解
* @EnableEurekaClient - 当前应用是一个Eureka客户端
* @EnableDiscoveryClient - 当前应用需要启动发现机制,就是找到Eureka服务端,并注册发现服务。
*/
@SpringBootApplication
public class RibbonAppServiceApp {
public static void main(String[] args) {
SpringApplication.run(RibbonAppServiceApp.class, args);
}
}
2.5 检查Eureka Server中的服务注册情况(Eureka Server项目使用SpringCloud Netflix Eureka文章中的)
3 开发服务消费者 - ribbonappclient
3.1 POM依赖
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ribbonappclient</artifactId>
<dependencies>
<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>com.bjsxt</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3.2 控制器
package com.bjsxt.userclient.controller;
import com.bjsxt.entity.User;
import com.bjsxt.userclient.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
/**
* 服务消费端
*/
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/user/save")
@ResponseBody
public Map<String, Object> save(User user){
// 调用本地服务代码,本地服务代码远程调用application service服务提供方。
Map<String, Object> result = this.userService.save(user);
System.out.println("远程调用返回的结果:" + result);
return result;
}
}
3.3 服务接口
package com.bjsxt.userclient.service;
import com.bjsxt.entity.User;
import java.util.Map;
public interface UserService {
Map<String, Object> save(User user);
}
3.4 服务实现
package com.bjsxt.userclient.service.impl;
import com.bjsxt.entity.User;
import com.bjsxt.userclient.service.UserService;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
/**
* 是Ribbon技术中的负载均衡客户端对象。其中封装了从Eureka Server上发现的所有的服务地址列表
* 包括服务的名称,IP,端口等。
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 远程方法调用。访问application service,访问的地址是:http://localhost:8080/user/save
* @param user
* @return
*/
@Override
public Map<String, Object> save(User user) {
// 根据服务的名称,获取服务实例。服务名称就是配置文件yml中的spring.application.name
// 服务实例包括,这个名称的所有服务地址和端口。
ServiceInstance instance = this.loadBalancerClient.choose("ribbon-app-service");
// 访问地址拼接
StringBuilder builder = new StringBuilder("");
builder.append("http://").append(instance.getHost())
.append(":").append(instance.getPort()).append("/user/save")
.append("?username=").append(user.getUsername())
.append("&password=").append(user.getPassword())
.append("&remark=").append(user.getRemark());
System.out.println("本地访问地址:" + builder.toString());
// 创建一个Rest访问客户端模板对象。
RestTemplate template = new RestTemplate();
// 约束响应结果类型
ParameterizedTypeReference<Map<String, Object>> responseType =
new ParameterizedTypeReference<Map<String, Object>>() {
};
// 远程访问application service。
ResponseEntity<Map<String, Object>> response =
template.exchange(builder.toString(), HttpMethod.GET,
null, responseType);
Map<String, Object> result = response.getBody();
return result;
}
}
3.5 配置文件application.yml
server:
port: 8081
spring:
application:
name: ribbon-app-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
3.6 启动类
package com.bjsxt.userclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RibbonAppClientApp {
public static void main(String[] args) {
SpringApplication.run(RibbonAppClientApp.class, args);
}
}
三、 集中式与进程内负载均衡区别
业界主流的负载均衡解决方案有:集中式负载均衡和进程内负载均衡。
1 集中式负载均衡
即在客户端和服务端之间使用独立的负载均衡设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务端。
2 进程内负载均衡
将负载均衡逻辑集成到客户端组件中,客户端组件从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务端发起请求。Ribbon就是一个进程内的负载均衡实现。
四、 Ribbon常见的负载均衡策略
Ribbon就属于进程内负载均衡,它只是一个类库,集成于Eureka Client进程,Eureka Client进程通过访问注册中心Eureka Server发现服务列表,发现的服务列表信息是由ribbon来管理的。当访问Application Service的时候,Application Client会通过ribbon来找到合适的Application Service地址信息,并发起远程调用请求。
1 Ribbon中的常用负载均衡简介
1 轮询策略(默认) RoundRobinRule
轮询策略表示每次都顺序取下一个provider,比如一共有5个provider,第1次取第1个,第2次取第2个,第3次取第3个,以此类推
2 权重轮询策略(常用) WeightedResponseTimeRule
1.根据每个provider的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。
2.原理:一开始为轮询策略,并开启一个计时器,每30秒收集一次每个provider的平均响应时间,当信息足够时,给每个provider附上一个权重,并按权重随机选择provider,高权越重的provider会被高概率选中。
3 随机策略(不推荐) RandomRule
从provider列表中随机选择一个provider
4 最少并发数策略(应用在硬件软件环境一致的情况下) BestAvailableRule
选择正在请求中的并发数最小的provider,除非这个provider在熔断中。
5 在“选定的负载均衡策略”基础上进行重试机制 RetryRule
1.“选定的负载均衡策略”这个策略是轮询策略RoundRobinRule
2.该重试策略先设定一个阈值时间段,如果在这个阈值时间段内当选择provider不成功,则一直尝试采用“选定的负载均衡策略:轮询策略”最后选择一个可用的provider
6 可用性敏感策略(一般在同区域内服务集群环境中使用) AvailabilityFilteringRule
过滤性能差的provider,有2种:
第一种:过滤掉在eureka中处于一直连接失败provider
第二种:过滤掉高并发的provider
7 区域敏感性策略(应用在大型的,物理隔离分布式环境中) ZoneAvoidanceRule
1.以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的provider
2.如果这个ip区域内有一个或多个实例不可达或响应变慢,都会降低该ip区域内其他ip被选中的权重。
2 配置负载均衡策略
可以通过修改 ribbonappclient 应用的全局配置文件来改变当前环境中使用的Ribbon负载均衡策略。
server:
port: 8081
spring:
application:
name: ribbon-app-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
ribbon-app-service: # 远程访问这个命名的服务
ribbon: # 底层Ribbon配置
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 就是具体的负载均衡策略类型全名
五、 Ribbon的点对点直连
Ribbon也可以脱离Eureka Server注册中心,通过配置的方式指定要调用的远程服务信息,实现Ribbon点对点直连。修改的配置内容如下:
server:
port: 8081
spring:
application:
name: ribbon-app-client
#eureka:
# client:
# service-url:
# defaultZone: http://localhost:8761/eureka/
ribbon-app-service: # 远程访问这个命名的服务
ribbon: # 底层Ribbon配置
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 就是具体的负载均衡策略类型全名
listOfServers: localhost:8080 # 多个地址用逗号分隔。
ribbon: # 关闭Ribbon自动访问Eureka服务端。
eureka:
enabled: false