Spring Cloud Netflix Ribbon简介
Ribbon 是一个基于Http和TCP的客服端负载均衡工具,它是基于Netflix Ribbon实现的。它不像spring cloud服务注册中心、配置中心、API网关那样独立部署,但是它几乎存在于每个spring cloud 微服务中。包括feign提供的声明式服务调用也是基于该Ribbon实现的。ribbon默认提供很多种负载均衡算法,例如 轮询、随机 等等。甚至包含自定义的负载均衡算法。Ribbon可以用于解决并提供微服务的负载均衡的问题。
使用 Ribbon开发微服务项目
- 创建Spring Cloud项目
- 配置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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<groupId>com.wangpx</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>
- 创建公共资源model
package com.wangpx.pojo;
import java.io.Serializable;
import java.util.Objects;
/**
* @program: springCloud
* @description:
* @author: wangpx
* @create: 2020-02-06 14:52
*/
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private String remark;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", remark='" + remark + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) &&
Objects.equals(username, user.username) &&
Objects.equals(password, user.password) &&
Objects.equals(remark, user.remark);
}
@Override
public int hashCode() {
return Objects.hash(id, username, password, remark);
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public User() {
}
public User(Integer id, String username, String password, String remark) {
this.id = id;
this.username = username;
this.password = password;
this.remark = remark;
}
}
- 创建Application Service服务提供者model
- 配置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.wangpx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>applicationService</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.wangpx</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
- 服务提供者代码
package com.wangpx.controller;
import com.wangpx.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @program: springCloud
* @description:
* @author: wangpx
* @create: 2020-02-06 14:54
*/
@RestController
public class UserController {
@RequestMapping("/user/save")
public Map<String,Object> save(User user){
System.out.println("新增用户数据:"+user);
Map<String,Object> map=new HashMap<>();
map.put("code",200);
map.put("message","用户新增成功");
return map;
}
}
- 启动类
package com.wangpx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @program: springCloud
* @description:
* @author: wangpx
* @create: 2020-02-06 14:58
*/
@SpringBootApplication
@EnableEurekaClient
public class RibbonAppServiceApp {
public static void main(String[] args) {
SpringApplication.run(RibbonAppServiceApp.class,args);
}
}
- yml配置文件
server:
port: 8080
spring:
application:
name: ribbon-app-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
- 创建Application Client服务消费者model
- 配置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.wangpx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>applicationClient</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.wangpx</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
- 服务接口
package com.wangpx.client.service;
import com.wangpx.pojo.User;
import java.util.Map;
public interface UserService {
Map<String,Object> save(User user);
}
- 服务接口的实现类
package com.wangpx.client.service.impl;
import com.wangpx.client.service.UserService;
import com.wangpx.pojo.User;
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;
/**
* @program: springCloud
* @description:
* @author: wangpx
* @create: 2020-02-06 15:05
*/
@Service
public class UserServiceImpl implements UserService {
/**
* LoadBalancerClient 是封装了Ribbon技术中的负载均衡客户端对象
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
@Override
public Map<String, Object> save(User user) {
//ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance instance = 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 restTemplate = new RestTemplate();
//约束响应结果类型
ParameterizedTypeReference<Map<String,Object>> reference
= new ParameterizedTypeReference<Map<String,Object>>(){};
//远程访问application service
ResponseEntity<Map<String, Object>> exchange =
restTemplate.exchange(builder.toString(), HttpMethod.GET, null, reference);
Map<String, Object> body = exchange.getBody();
return body;
}
}
- 控制器代码
package com.wangpx.client.controller;
import com.wangpx.client.service.UserService;
import com.wangpx.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @program: springCloud
* @description: 服务消费端
* @author: wangpx
* @create: 2020-02-06 15:03
*/
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/user/save")
public Map<String,Object> save(User user){
Map<String, Object> save = userService.save(user);
System.out.println("远程调用返回的结果:"+save);
return save;
}
}
- 配置yml配置文件
server:
port: 8082
spring:
application:
name: ribbon-app-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
- 启动类
package com.wangpx.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @program: springCloud
* @description:
* @author: wangpx
* @create: 2020-02-06 16:51
*/
@SpringBootApplication
@EnableEurekaClient
public class RibbonClientApp {
public static void main(String[] args) {
SpringApplication.run(RibbonClientApp.class,args);
}
}
- 测试
先启动eureka,然后再启动用户微服务,启动两个端口8080和8081,修改application.yml便可以。最后启动ribbon-client。 - 启动成功后,访问页面,输入url地址:http://localhost:8761/
Ribbon常见的负载均衡策略
1.使用负载均衡带来的好处
- 当集群里的 1台或多台服务器宕机时,剩余没有宕机的服务器可以保证服务的正常使用。
- 使用了更多的机器保证了机器的良性使用,不会由于某一高峰时刻导致系统cpu急剧上升
- 使用了更多的机器保证了机器的良性使用,不会由于某一高峰时刻导致系统cpu急剧上升
- Ribbon就属于进程内负载均衡,它只是一个类库,集成于Eureka Client进程,Eureka Client进程通过访问注册中心Eureka Server发现服务列表,发现的服务列表信息是由ribbon来管理的。当访问Application Service的时候,Application Client会通过ribbon来找到合适的Application Service地址信息,并发起远程调用请求。
id | 策略名称 | 策略对应的 类名 | 实现原理 |
---|---|---|---|
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.“选定的负载均衡策略”这个策略是轮询策略RoundRobinRule2.该重试策略先设定一个阈值时间段,如果在这个阈值时间段内当选择provider不成功,则一直尝试采用“选定的负载均衡策略:轮询策略”最后选择一个可用的provider |
6 | 可用性敏感策略(一般在同区域内服务集群环境中使用) | AvailabilityFilteringRule | 过滤性能差的provider,有2种:第一种:过滤掉在eureka中处于一直连接失败provider第二种:过滤掉高并发的provider |
7 | 区域敏感性策略(应用在大型的,物理隔离分布式环境中) | ZoneAvoidanceRule | 1.以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的provider2.如果这个ip区域内有一个或多个实例不可达或响应变慢,都会降低该ip区域内其他ip被选中的权重。 |
- 配置负载均衡策略
server:
port: 8082
spring:
application:
name: ribbon-app-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 设置负载均衡策略。application-service为设置负载均衡的服务名称
application-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule