我们的服务都需要部署多实例的,然后实现负载均衡
负载均衡的两种方式
- 服务器端的负载均衡
- 客户端侧负载均衡
服务器端负载均衡
在单体架构时代,我们为一个应用部署多个实例,然后用nginx做反向代理。当有请求过来的时候,先通过nginx,nginx在通过负载均衡算法,计算一下转发到哪个实例,然后进行转发。由于负载均衡算法是由nginx提供的,而nginx又是部署在服务器端的,所以这种方式又被称为服务器端负载均衡
服务器端负载均衡
客户端侧负载均衡
我们之前的例子是用户中心提供服务,内容中心去调用。当我们将用户中心部署多实例的时候,内容中心就可以通过DiscoveryClient获取到所有的用户中心实例了。如果我们自己在内容中心写一个负载均衡的规则,计算一下请求哪一个实例,然后交给RestTemplate去请求,这样也可以实现负载均衡。这样对于用户中心来说,内容中心就是客户端,负载均衡算法是由客户端提供的,那这样实现的负载均衡就是客户端侧负载均衡
客户端侧负载均衡
手动实现一个客户端侧负载均衡
根据上面的图形案例,手动实现一个客户端侧负载均衡
// 获取所有用户中心实例的请求地址
List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
// 处理每个实例的uri
List<String> uris = instances.stream()
.map(instance -> instance.getUri().toString()+"/users?id={id}")
.collect(Collectors.toList());
//使用随机算法,获取一个下标,根据下边找到一个uri
int index = ThreadLocalRandom.current().nextInt(uris.size());
String uri = uris.get(index);
log.info("请求user服务的地址:"+uri);
// 使用RestTemplate发送请求
UserDTO userDTO = this.restTemplate.getForObject(
uri,
UserDTO.class,
userId
);
使用Ribbon实现负载均衡
1. 整合Ribbon
第一步:添加依赖:
依赖已经添加过了,因为我们已经添加过了nacos依赖,在nacos依赖中存在Ribbon的依赖
第二步:添加注解
注解添加到RestTemplate上面。如下图:
image.png
第三步:添加配置
这个不需要配置
这样就将Ribbon整合好了。
使用的时候将RestTemplate的请求地址改为http://user-center/users/?id={userId}
代码片段如下图:
image.png
2. Ribbon组成
image.png
3. Ribbon内置的负载均衡规则
image.png
默认使用的是ZoneAvoidanceRule规则
如果我们想要更改Ribbon的负载均衡规则,我们只需要写一个配置类就可以了。具体代码如下:
package com.chuxin.contentcenter.config;
import com.netflix.loadbalancer.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @FileName: RibbonConfig
* @Description: Ribbon的配置
* @author: <a href="mailto: muyuanpei@163.com">myp</a>
* @create: 2019-08-13 15:23
* @Copyright: (c) 初心科技有限公司(待创)
*/
@Configuration
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
// 负载均衡规则,改为随机
return new RandomRule();
// 选择一个最小并发请求的server,逐个考察server,如果server被tripped了,则跳过
// return new BestAvailableRule();
//轮训选择,轮询index,选择index对应位置的server
// return new RoundRobinRule();
//根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低
// return new WeightedResponseTimeRule();
}
}
上面我们只是修改了一下Ribbon的负载均衡的策略。我们还可以根据Ribbon组成的那个列表中修改其他的配置,方法都是类似的哦!
4. 细粒度配置自定义
Ribbon支持非常灵活的配置,支持细粒度的配置。实际项目中就是配置他的负载均衡规则,我们上面实现的是细粒度比较低的配置,下面举例说明细粒度高的配置以及实现
例子:如果有三个微服务,微服务A、微服务B、微服务C。当A调用B和C的时候,默认的ZoneAvoidanceRule规则满足不了我们的需求时,我们就可以修改配置。修改为A调用B的时候使用随机的原则,A调用C的时候使用轮询的规则
4.1 使用代码方式配置
首先在启动类所在包的平级下创建一个包,然后在包下面创建一个专门为一个微服务服务的配置类,在配置类上面添加@Configuration
注解,在类里面注入使用的规则,然后在启动类可以扫描到的包下面创建一个对应的RibbonClient的配置,详情如下:
image.png
配置类UserCenterRibbonConfig.java内容如下:
package com.chuxin.ribbonconfig;
import com.netflix.loadbalancer.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @FileName: RibbonConfig
* @Description: Ribbon的配置
* @author: <a href="mailto: muyuanpei@camelotchina.com">myp</a>
* @create: 2019-08-13 15:23
* @Copyright: (c) 初心科技有限公司(待创)
*/
@Configuration
public class UserCenterRibbonConfig {
@Bean
public IRule ribbonRule() {
// 负载均衡规则,改为随机
return new RandomRule();
//默认使用规则
// return new ZoneAvoidanceRule();
// 选择一个最小并发请求的server,逐个考察server,如果server被tripped了,则跳过
// return new BestAvailableRule();
//轮训选择,轮询index,选择index对应位置的server
// return new RoundRobinRule();
//根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低
// return new WeightedResponseTimeRule();
}
@Bean
public IPing ribbonPing() {
//默认的简单实现,标记服务器的活跃性
return new DummyPing();
// return new PingUrl();
//
// return new NoOpPing();
// return new PingConstant();
}
}
配置类UserRibbonConfig.java内容如下:
package com.chuxin.contentcenter.config;
import com.chuxin.ribbonconfig.UserCenterRibbonConfig;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;
/**
* @FileName: UserRibbonConfig
* @Description: user类的Ribbon自定义配置
* @author: <a href="mailto: muyuanpei@camelotchina.com">myp</a>
* @create: 2019-08-15 11:13
*/
@Configuration
//表示这个配置类是为用户中心服务的
@RibbonClient(name = "user-center", configuration = UserCenterRibbonConfig.class)
public class UserRibbonConfig {
}
解释一下为什么非要创建一个和启动类所在包平级的目录下创建包来放Ribbon的配置类,就是因为Spring上下文的问题导致的。具体解析如下:
Spring上下文可以参考另一篇文章【Spring父子上下文解析】
Ribbon的父子上下文重叠会导致什么问题,我们可以参考【官方文档】,内容如下图:
官网提供父子上下文重叠导致的问题
大概意思:Ribbon的配置类一定要有@Configuration
注解,但是不能被@ComponentScan
注解重复扫描,否则就会被所有的RibbonClient共享。
因为我们上面提到的是细粒度的配置,我们要实现的也是细粒度的配置,如果我们将Ribbon的配置类放在@ComponentScan
注解能扫描到的位置,那么我们的微服务A不管是掉微服务B还是微服务C用的都会是同一个规则,也就变成了一个全局的配置
4.2 使用配置文件方式配置
直接在配置文件中,添加如下代码就可以啦
#服务名称
user-center:
ribbon:
#随机
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#默认
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule
#轮询
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
这样就配置好了,使用哪种规则,直接将全路径配置在这里就ok了。
4.3 两种方式的比较
image.png
正常情况下使用属性配置方式就可以了。(SpringBoot支持读取外部配置,就是将jar包和application.yml放在同一个目录下即可。他的优先级要比jar包里面的配置文件优先级高)
4.4 细粒度配置-最佳实践
-
尽量使用属性配置,属性方式实现不了的在考虑用代码配置
-
在同一微服务内 尽量保证单一性 ,比如同一使用属性配置,不要两种方式混用,增加定位代码的复杂性
5. 全局配置
全局配置和上面细粒度配置的相似。之前说过,Ribbon的那个配置类如果放在启动类可以扫描到的包下面,那么就是全局的配置。但是会存在父子上下文重复问题,严重会导致项目启动失败。所以不建议使用。
使用@RibbonClients(defaultConfiguration = xxx.class)
我们还是使用代码配置的方式,只需要改一下启动类能扫描到的那个类就可以,我们改一下,代码如下:
@Configuration
//表示这个配置类是为用户中心服务的
@RibbonClients(defaultConfiguration = UserCenterRibbonConfig.class)
public class UserRibbonConfig {
}
这样就可以实现全局的配置了
6. 支持的配置项
文章的第二点就是Ribbon的组成,表格里面所有项,都是Ribbon支持的配置项。
举例:配置IPing
Java代码方式:就是在UserCenterRibbonConfig .java类里面加上一个bean注入的就可以了,代码如下:
@Bean
public IPing ribbonPing() {
//默认的简单实现,标记服务器的活跃性
return new DummyPing();
// return new PingUrl();
//
// return new NoOpPing();
// return new PingConstant();
}
属性方式:就是按照模板填写响应的配置就可以了
user-center:
ribbon:
NFLoadBalancerPingClassName: com.netflix.loadbalancer.PingUrl
模板如图:
image.png
7. 饥饿加载
默认情况下Ribbon是懒加载的,所以导致我们通过内容中心调用用户中心的第一次请求响应特别慢。我们可以将其配置为饥饿加载,从而解决这个问题。只需要在配置类里面添加两个配置就ok
配置如下:
# 配置饥饿加载
ribbon:
eager-load:
# true 为打开状态
enabled: true
# 细粒度指定哪些具体的服务,多个使用英文逗号分隔
clients: user-center,xxxx,xxxx
网友评论