前戏
在分布式微服务调用的时候我们会想到一个工具叫Ribbon,关于这个Ribbon的具体实现负载均衡的原理可以参考本人这篇文章 《Ribbon原理-RestTemplate使用@LoadBalanced负载均衡源码详解》, 本篇内容主要讲自己如何写一个简单的负载均衡逻辑,话不多说,直接开干!
1. 原理
在开干之前讲一下这个负载均衡的原理,很简单,就是拿到集群的服务列表,然后根据某种算法拿到一个服务来进行调用,算法可以是轮询,也可以是一致性哈希,也可以根据你的想法去定制,花里胡哨的都可以,Do what you think! 话不多说,来咯~
看下主角DiscoveryClient类
1.1 DiscoveryClient类
/**
* Represents read operations commonly available to discovery services such as Netflix
* Eureka or consul.io.
*
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/
public interface DiscoveryClient extends Ordered {
/**
* Default order of the discovery client.
*/
int DEFAULT_ORDER = 0;
/**
* A human-readable description of the implementation, used in HealthIndicator.
* @return The description.
*/
String description();
/**
* Gets all ServiceInstances associated with a particular serviceId.
* @param serviceId The serviceId to query.
* @return A List of ServiceInstance.
*/
List<ServiceInstance> getInstances(String serviceId);
/**
* @return All known service IDs.
*/
List<String> getServices();
/**
* Default implementation for getting order of discovery clients.
* @return order
*/
@Override
default int getOrder() {
return DEFAULT_ORDER;
}
}
这是个接口,主要方法是它的getServices方法和getInstances方法,能从注册中心获取所有的服务或服务实例,那谁去实现它呢,当然是springcloud家族的一些注册中心去实现它啦,我们在创建注册中心的时候会引入一些starter包,这些包里面就有DiscoveryClient的实现,看下图:
哇喔,看明白了不~,用的时候直接autowired就行了!
2. 准备工作
2.1 先建注册中心
注册中心用的是Eureka,很简单就不同多说啦
- EurekaServerBoot.java
@SpringBootApplication
@EnableEurekaServer //该服务为注册中心
public class EurekaServerBoot {
public static void main(String[] args) {
SpringApplication.run(EurekaServerBoot.class,args);
}
}
- application.yaml
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #这里我改了主机host,将eureka7002.com指向localhost
client:
register-with-eureka: false #是否向自己注册,作为注册中心不需要向自己注册
fetch-registry: false #表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ #向另一个节点注册
2.2 服务提供者
写两个服务提供者模拟集群服务,只是暴露的端口不同,其他都是一样的,挑一个看里面的配置,其他的就不介绍了
server:
port: 8002
spring:
application:
name: springcloud-payment-service #服务名,也就是放进注册中心的名字
http:
encoding:
force: true
charset: UTF-8
enabled: true
# 一定要有端口号和服务名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver #数据库驱动包
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
username: root
password: root
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.yolyn.springcloud.entities #所有entity别名所在包
eureka:
client:
register-with-eureka: true
fetch-registry: true #是否从EurekaServer获取已有的注册信息,默认为ture
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8002 #id别名
prefer-ip-address: true # 显示ip信息
要着重注意的是application.name,决定注册进注册中心的应用名叫什么,这个应用名可以对应多个不同的服务实例,后面的负载均衡也就是通过这个应用名获取所有的服务实例,在springcloud-payment-service里面实现了什么功能暂时不用关心,后面只是用负载均衡拿到某个服务实例信息就行了。
3. 实操
3.1 写pom
主要引入以下次几个依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
3.2 写启动类
/**
* @author Yolyn
* @version 1.0
* @date 2020/5/3 10:35
* @project springcloud-2020
*/
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class })
@EnableEurekaClient
//@RibbonClient(configuration = MyRibbonRule.class,name = "springcloud-payment-service")
//@EnableDiscoveryClient//是否注册本地服务,该注解用于向使用consul或者zk作为注册中心时注册服务
public class OrderBoot {
public static void main(String[] args) {
SpringApplication.run(OrderBoot.class,args);
}
}
启动类就不介绍了,我这里是选用Eureka作为注册中心,@EnableEurekaClient 是Netflix用于注册服务的注解,而 @EnableDiscoveryClient 是springcloud默认用于注册服务的注解,通常选zk或者consul作为注册中心时,可以选这个注解。
3.3 application.yaml
server:
port: 80
debug: true
spring:
application:
name: cloud-order-service
# zipkin:
# base-url: http://localhost:9411 # 监控链地址
# sleuth:
# sampler:
# probability: 1 #采样率值介于 0 到 1 之间,1 则表示全部采集
eureka:
client:
register-with-eureka: true
fetch-registry: true #是否从EurekaServer获取已有的注册信息,默认为ture
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
注释的不用看
3.4 定义LoadBalancer接口
在这里我们先定义一个LoadBalancer接口,在接口里面定义一个getInstance方法。
/**
* @author Yolyn
* @version 1.0
* @date 2020/5/5 14:14
* @project springcloud-2020
*/
public interface LoadBalancer {
/**
* 获取服务实例
* @param serviceInstanceList
* @return
*/
ServiceInstance getInstance(List<ServiceInstance> serviceInstanceList);
}
后面我们实现这个方法,利用某种算法从传过来的服务列表里面挑一个服务来进行调用,看下面:
3.5 实现接口
/**
* @author Yolyn
* @version 1.0
* @date 2020/5/5 14:15
* @project springcloud-2020
*/
@Component
public class MyRoundLB implements LoadBalancer {
private AtomicInteger invokeTimes = new AtomicInteger(0);
public final int getAndIncrement() {
int current, next;
do {
current = this.invokeTimes.get();
next = current >= Integer.MAX_VALUE ? 0 : current + 1;//防止调用次数过多
} while (!this.invokeTimes.compareAndSet(current, next));
return next;
}
@Override
public ServiceInstance getInstance(List<ServiceInstance> serviceInstanceList) {
int index= getAndIncrement()%serviceInstanceList.size();
return serviceInstanceList.get(index);
}
}
这里getAndIncrement算法很简单咯,调用一次对invokeTimes作+1操作,防止并发用了cas,然后在getInstance方法中用调用次数对集群服务数取余得到下标,而实现的一个轮询算法。
3.6 调用
@RestController
public class OrderController {
@Autowired
private LoadBalancer loadBalancer;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/consumer/payment/lbService")
public ResultModel getPaymentLbService() {
List<ServiceInstance> instanceList = discoveryClient.getInstances("springcloud-payment-service");
if (null == instanceList || instanceList.size() <= 0) {
return null;
}
return new ResultModel().setSuccess(loadBalancer.getInstance(instanceList).getMetadata());
}
}
先后启动注册中心,两个服务提供者,和第三节的服务消费者。
注册中心:
3. 小总结
一句话:负载均衡无非就是拿到集群的服务列表,然后根据某种算法挑选一个服务来进行调用,算法实现可以根据实际场景来选择,如加权轮询、一致性哈希和LRU等等。
转载:https://blog.csdn.net/qq_34088913/article/details/106170299