日升时奋斗,日落时自省
目录
1、LoadBalancer
LoadBalancer(负载均衡器):一种网络设备或软件机制,用于分发传入的网络流量负载(请求)到多个后端目标服务器上,从而实现系统资源的均衡利用和提高系统的可用性和性能
1.1、负载均衡器分类
负载均衡分为服务器端负载均衡和客户端负载均衡
(1)服务器端负载均衡器指的是存放在服务器端的负载均衡器,例如nginx(常用反向代理)、HAProxy(Rabbitmq的代理)、F5
(2)客户端负载均衡指的是嵌套在客户端的负载均衡器,例如Ribbon(当前已经停止维护了)、Spring Cloud LoadBalancer
1.2、常见负载均衡策略
无论是服务器端负载均衡和客户端负载均衡,他们负载均衡策略都是相同的,负载均衡只是以中国思想
常见策略(7种):
(1)轮询(Round Robin):每个新的请求分发给后端后,依次循环。(最简单的)
(2)随机选择(Random):随机选择一个后端服务器来处理每个新的请求
(3)最少连接(Least Connections):最少连接策略将请求分发给当前连接数最少的后端服务器(这里会有一个计数器,计算后进行连接)
(4)IP哈希(IP Hash):IP哈希策略使用客户端的IP地址来计算哈希值,然后将请求发送到与哈希值对应的后端服务器(IP哈希值%后端服务器个数)
(5)加权轮询(Weight Round Robin):加权轮询策略给每个后端服务器分配一个权重值,按照权重值比例分发请求
(6)加权随机选择(Weighted Random):类似加权轮询,相同权重随机选着
(7)最短响应时间(Least Response Time): 测量每个后端服务器的响应时间,并将请求发送到响应时间最短的服务器
注:7种种更常用的就是轮询、随机、IP哈希、加权轮询(或者加权随机轮询)
隐式关联:在项目中添加Spring Cloud OpenFegin 和注册中心如Nacos之后,在添加Spring Cloud LoadBalancer则会在进行接口调用时直接使用Spring Cloud LoadBalancer(不需要你配置什么,依赖带上就行了,剩下的交给系统,但是友友们也是可以自己进行自定义或者修改负载均衡策略)
2、Java默认负载均衡策略
注:以下源码来源spring-cloud-starter-loadbalancer依赖
Spring Cloud LoadBalancer负载均衡策略是默认的是轮询,这一点可以通过Spring Cloud LoadBalancer的配置LoadBalancerClientConfiguration中发现,部分源码如下
2.1、轮询策略源码
快速按下两次shift可以进行搜索LoadBalancerClientConfiguration类进行搜索,就能找到源码部分,主要就是以下部分,我们进行更改的地方
public LoadBalancerClientConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty("loadbalancer.client.name");
return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
这里算是应用层面(name就是服务名,return就是返回一个服务转发里面)
如何把这个服务进行转发出来点进去RoundRobinLoadBalancer类去看下面的代码
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + this.serviceId);
}
return new EmptyResponse();
} else if (instances.size() == 1) {
return new DefaultResponse((ServiceInstance)instances.get(0));
} else {
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
会有这部分源码,instance就是服务,如果不为空,判断服务个数如果是1就是默认响应服务,如果不是1,那说明有多个 重点解释最后三行代码;
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
position就是一个原子类顶一个定位参数,incrementAndGet()方法就是原子进行加的,&(“与”操作)二级制去掉负数,负数的第一个位置表示0,但是最大值所有位都是1,就是为了“与”后为正数;
ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());
instances也就是实例集合,但是我们只取定位参数取模后下标对应的服务实例作为负载均衡的实例
return new DefaultResponse(instance);
作为默认实例进行返回;
如果需要自己写的话,直接继承就行了
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> RandomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new CustomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
就是把源码复制过来注解bean注入一下就行 (不要加@Configuration注解,我们还会在启动类上加@LoadBalancerClients注解,这两个是不能连用的)
注:LoadBalancerClientFactory.PROPERTY_NAME 参数就是前面对应的字符串
2.2、随机策略源码
随机负载均衡策略的操作其实是和轮询操作源码是一样的,我们这里直接使用注入的方法进行演示
public class RandomLoadBalancerConfig{
@Bean
public ReactorLoadBalancer<ServiceInstance> RandomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
注:不同只有返回类不同,这次返回的RandomLoadBalancer类进行返回,值都是一样的,在开始给友友们一张图就是这几个类之间继承关系,ReactorLoadBalancer类下面有三个实现类,所以这里就是返回一个RandomLoadBalancer就是采用随机负载均衡策略
2.3、设置全局负载均衡器
注:这是我们自己写的一个类,采用Bean注入方式被Spring采取的,所以在启动类的时候要使用一个注解去设置我们写负均衡策略,在启动类上加上注解
@LoadBalancerClients(defaultConfiguration = RandomLoadBalancerConfig.class)
参数:
defaultConfiguration: 默认负载策略类
你需要的负载均衡类作为注解的参数,所有微服务之间都使用的是这一个策略
2.4、设置局部负载均衡器(随机策略)
局部负载均衡器是在接口处设置的 使用@LoadBalancerClient
@Service
@FeignClient("loadbalancer-service")
@LoadBalancerClient(name = "loadbalancer-service",configuration = RandomLoadBalancerConfig.class)
public interface UserService {
@RequestMapping("/user/getname")
public String getName(@RequestParam("id")Integer id);
}
参数:
name:服务名称 针对调用那个服务采用该策略
configuration:配置负载均衡策略类
注:局部设置负载均衡器在某些版本下无效,这种情况友友们尝试设置全局负载均衡器
3、Nacos权重负载均衡器
Nacos中支持两种负载均衡器,一种是权重负载均衡,另一种是第三方CMDB(地域就近访问)标签负载均衡器,我们可以将Spring Cloud LoadBalancer直接配置Nacos的负载均衡器,他默认就是权限负载均衡策略(Nacos控制台可以进行配置)
3.1、Nacos负载均衡策略源码
public class NacosLoadBalancerConfig {
@Autowired
private NacosDiscoveryProperties discoveryProperties; //Nacos配置类
@Bean
public ReactorLoadBalancer<ServiceInstance> NacosLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name
, ServiceInstanceListSupplier.class), name,discoveryProperties);
}
}
注:这里只需要修改返回值既可以,NacosLoadBalancer类多了一个参数就是配置类参数,
同样启动类上加上全局配置负载均衡器
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
4、自定义负载均衡器
实现自定义负载均衡策略需要以下3步
(1)创建自定义负载均衡器
(2)封装自定义负载均衡器
(3)为服务设置自定义负载均衡器
4.1、创建自定义负载均衡器
我们自己定义就是仿照源码去写一个策略 大部分不变的,只有实例选择上有一定的区别实现了一个反应器就是负载均衡实例的ReactorServiceInstanceLoadBalancer类
注:下面实现IP哈希策略
public class CustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);
private final String serviceId; //服务ID
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public CustomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map((serviceInstances) -> {
return this.processInstanceResponse(supplier, serviceInstances);
});
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + this.serviceId);
}
return new EmptyResponse();
} else {
//核心机制;
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attributes.getRequest();
String ipAddress=request.getRemoteAddr();
System.out.println("用户 IP:"+ ipAddress);
int hash=ipAddress.hashCode();
//关键 负载均衡算法
int index = hash%instances.size();
ServiceInstance instance = (ServiceInstance)instances.get(index);
return new DefaultResponse(instance);
}
}
}
这里就是仿照RandomLoadBalancer类来写的,这里其实也只写了核心部分,其他部分都是拷贝过来的;
核心机制:
ServletRequestAttributes attributes =(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
获取访问url的请求参数
HttpServletRequest request=attributes.getRequest();
获取请求参数中的请求所有
int hash=ipAddress.hashCode();
拿到请求的IP地址
int index = hash%instances.size();
采用取模此时就是IP地址取模就是服务实例的一个下标
ServiceInstance instance = (ServiceInstance)instances.get(index);
获取服务实例集中的对应小标的服务实例
return new DefaultResponse(instance);
返回实例就是默认实例
4.2、封装自定义负载均衡器
封装的方法就是采用前面使用的方法,需要的只有返回我们自己写的自定义的类,这里连传递的参数都不需要改变
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> RandomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new CustomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
4.3、设置自定义负载均衡器
@SpringBootApplication
@EnableFeignClients
@LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfig.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
5、缓存
Spring Cloud LoadBalancer在获取实例有两种选择:
(1)及时获取:每次从注册中心得到最新健康的实例,效果好,开销太大
(2)缓存服务列表:每次得到服务列表之后,缓存一段时间,这样既能保证性能,同时也能兼容一定的及时性
而Spring Cloud LoadBalancer中默认开启了缓存服务列表的功能。
Spring Cloud LoadBalancer默认缓存的重要特性有两项:
(1)缓存的过期时间为35s
(2)缓存保存个数为256个
yml配置:
spring:
cloud:
loadbalancer:
cache:
enabled: true
ttl: 10 #缓存存活时间
capacity: 1000 #缓存个数
关闭缓存:
spring:
cloud:
loadbalancer:
cache:
enabled: false
注:官网仍然还有其他的提供的Ribbon和LoadBalancer结合使用的方法,友友们可以执行去看