1、Ribbon介绍
Ribbon是Netflix公司开源的一个负载均衡的项目,它是一个基于 HTTP、TCP的客户端负载均衡器。
2、负载均衡
负载均衡是微服务架构中必须使用的技术,通过负载均衡来实现系统的高可用、集群扩容等功能。负载均衡可通过硬件设备及软件来实现,硬件比如:F5、Array等,软件比如:LVS、Nginx等。负载均衡的架构图如下:
用户请求先到达负载均衡器(也相当于一个服务),负载均衡器根据负载均衡算法将请求转发到微服务。负载均衡算法有:轮循、随机、加权轮循、加权随机、地址哈希等方法,负载均衡器维护一份服务列表,根据负载均衡算法将请求转发到相应的微服务上,所以负载均衡可以为微服务集群分担请求,降低系统的压力。
3、客户端负载均衡
上图是服务端负载均衡,客户端负载均衡与服务端负载均衡的区别在于客户端要维护一份服务列表,Ribbon从Eureka Server获取服务列表,Ribbon根据负载均衡算法直接请求到具体的微服务,中间省去了负载均衡服务。
Ribbon负载均衡的流程图如下:
- 在消费微服务中使用Ribbon实现负载均衡,Ribbon先从EurekaServer中获取服务列表。
- Ribbon根据负载均衡的算法去调用微服务。
4、Ribbon测试
Spring Cloud引入Ribbon配合 restTemplate 实现客户端负载均衡。Java中远程调用的技术有很多,如:webservice、socket、rmi、Apache HttpClient、OkHttp等,互联网项目使用基于http的客户端较多,这里就使用okHttp了。我直接复制一个以前的工程了,重命名为test-ribbon,然后src/main/java目录清空。
(1) pom依赖
我这里是以前工程的依赖,有的其实用不到,就不管了,我全贴出来了:
<dependencies>
<!-- spring-data-jpa依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<!-- orm依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.11.RELEASE</version>
</dependency>
<!-- mongodb依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<!-- 依赖的model模块 -->
<dependency>
<groupId>com.ycz</groupId>
<artifactId>ycz-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 依赖api模块 -->
<dependency>
<groupId>com.ycz</groupId>
<artifactId>ycz-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 依赖的web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
<!-- druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!-- mybatis与spring整合包 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!-- http客户端 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<!-- eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.2.RELEASE</version>
<exclusions>
<!-- 排除掉jackson-core包,否则会冲突 -->
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<!-- 排除到gson包 -->
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 测试包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.1.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
主要是okhttp包、web包、eureka-client包。
(2)yml配置
只贴出比较重要的部分:
## eureka配置
eureka:
## 客户端
client:
## 服务注册开关,需要打开
register-with-eureka: true
## 服务发现开关,需要打开
fetch-registry: true
## 向两个注册中心中注册
service-url:
defaultZone: http://localhost:20000/eureka/
## 实例地址
instance:
## 将自己的IP地址注册到服务注册中心
prefer-ip-address: true
ip-address: 127.0.0.1
## 指定注册后实例的ID,这里用工程名:端口号
instance-id: ${spring.application.name}:${server.port}
# Ribbon配置
ribbon:
MaxAutoRetries: 5
MaxAutoRetriesNextServe: 3
OkToRetryOnAllOperations: false
ConnectTimeout: 500000
ReadTimeout: 600000
参数说明:
- MaxAutoRetries: 最大连接次数,在Eureka中可以找到服务,但是连接不上时会重试连接。
- MaxAutoRetriesNextServe:切换实例的重试次数。
- OkToRetryOnAllOperations: 对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作,没有实现幂等的情况下是很危险的,所以设置为false。
- ConnectTimeout: 请求连接的超时时间。
- ReadTimeout:请求处理的超时时间。
(3)启动类
@SpringBootApplication
//包扫描
@EntityScan(basePackages = {"com.ycz.domain.person"})
@EnableDiscoveryClient
public class RibbonTestApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonTestApplication.class, args);
}
//注册RestTemplate,并且开启负载均衡
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
微服务需要注册到服务中心,注意@LoadBalanced注解,使用restTemplate发送http请求时会开启负载均衡,默认的是采取轮循的算法来发送请求,待会儿会有介绍。
(4)测试类
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestRibbon {
@Autowired
RestTemplate restTemplate;
//测试ribbon负载均衡
@Test
public void testRibbonBanlanced() {
//微服务实例ID
String serviceId = "TEST-SWAGGER";
for(int i=1;i<6;i++) {
//使用RestTemplate模板发请求
ResponseEntity<Object> responseEntity = restTemplate.
getForEntity("http://"+serviceId+"/person/list/湖北武汉", Object.class);
//获取响应体
Object obj = responseEntity.getBody();
System.out.println(obj);
}
}
}
这里我犯了一个很低级的错误,开始的时候没加@RunWith(SpringRunner.class)这个注解,所以RestTemplate是注入不进来的,一直报空指针。
(5)准备两个注册的微服务
我这里直接用以前的工程,直接修改yml里面,设置动态的端口参数,第一个配置如下:
server:
port: ${PORT:10000}
在Run Configurations里面配置PORT值,如下:
端口是10000,第二个配置如下:
端口是10001。
(6)测试
进入到RibbonLoadBalancerClient这个类里面,需要打一个断点来测试发送请求时的负载均衡,如下:
在server这里打断点。然后启动eureka服务中心,上面工程两个配置下分别启动,然后启动这个test-ribbon的工程,注意,到这里已经启动了4个微服务工程。先看看eureka中心注册的服务:
3个微服务注册进来了,OK。
再对上面的测试方法testRibbonBanlanced进行debug,第一次循环如下:
可以看到,第一次循环将请求发给了10000端口,控制台也打印出请求成功后返回的数据。然后看第二次循环:
第二次循环,将请求发送给了10001端口,并且控制台也打出来了查询结果。第三次循环会继续将请求发给10000端口,然后第四次发给10001端口,第五次发给10000端口。可以发现其实RestTemplate是将请求负载均衡的发给指定名称下的每一个微服务的,并且采用的是轮循策略。
5、总结
通过使用RestTemplate + Ribbon,然后配合Eureka来远程调用微服务,能够很好的理解什么是客户端的负载均衡,并且对轮循策略有了一个简单的认识。