1 负载均衡
1.1 图解
上图所示,如果客户在不同时间段访问,将会导致不同服务器在某个时间段的压力会很大,而在某个时间段某些服务器又很空闲导致资源的分配不合理而影响体验。
因此如上图需要有个中间件来处理请求的分发。让资源得到充分利用。这就是负载均衡,即将用户请求平躺的分配到多个服务上,从何达到系统的高可用。
1.2 分类
- 集中式:即在服务的消费方和提供方之间使用独立的负载均衡设置,如 Nginx(方向代理服务器),由该设施负责把请求通过某种策略转发至服务的提供方。
- 进程式:
- 将负载均衡集成到消费方,消费方从服务注册中心获知哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
- Ribbon 就输入进程式负载均衡,它只有一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
2 搭建服务提供者集群
再搭建一个 9002 端口的的服务提供者,服务名和 9001 一样。(【之前项目】)
2.1 application.yml
server:
port: 9002
spring:
application:
name: springcloud-provider
datasource:
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/datasource?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai
# 数据库连接池
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 最大连接池数量
max-active: 20
# 初始化连接池的数量
initial-size: 5
# 最小连接池 数量
min-idle: 2
# 这里建议配置为TRUE,防止取到的连接不可用
test-on-borrow: true
test-on-return: false
# 验证连接有效与否的SQL,不同的数据配置不同
validation-query: select
#通过别名的方式配置扩展插件,常用的插件有:
#监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
filters: stat,slf4j,wall
# 配置获取连接等待超时的时间 单位毫秒
max-wait: 6000
# mybatis 配置
mybatis:
# 指定 mapper.xml 的路径
mapper-locations: classpath*:mappers/*.xml
# mapper.xml 中的 resultType 或 resultMap 返回的实体类会自动在此包下找
type-aliases-package: com.pky.springcloud.provider9002.domain
# settings 下的配置
configuration:
# 开启驼峰命名功能 其它的一些属性参考 mybatis-config.xml中的settings属性
map-underscore-to-camel-case: true
# eureka 配置
eureka:
client:
service-url:
# eureka 服务端地址(即注册地址),若 eureka 开启了权限认证,则需要携带账号密码
# defaultZone: http://pkyShare:123456@localhost:7001/eureka/
defaultZone: http://pkyShare:123456@eureka7001.com:7001/eureka/,http://pkyShare:123456@eureka7002.com:7002/eureka/,http://pkyShare:123456@eureka7003.com:7003/eureka/
2.2 Controller
修改相应信息后,在Controller 中输出对应端口号,便于确认请求分发到哪个服务端。
@RestController
@RequestMapping(value = "users")
public class UserInfoController {
@Autowired
UserInfoService userInfoService;
/**
* 通过用户名获取用户
* @param username
* @return
*/
@GetMapping(value = "/{username}")
public UserInfo getByName(@PathVariable String username) {
System.out.println("9002");
UserInfo userInfo = userInfoService.getByUsername(username);
return userInfo;
}
}
2.3 启动注册
下图表明已注册。
3 Ribbon 整合
3.1 pom.xml
在客户端(服务消费者【之前项目】)项目中添加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
2.2 @LoadBalanced
因为是用 RestTemplate 请求模板进行请求,因此需要在该模板上配置负载均衡:
@Configuration
public class RestConfig {
/**
* 配置 restTemplate 请求模板的 bean
* @return
*/
@Bean
@LoadBalanced // 开启负载均衡
public RestTemplate getTemplate() {
return new RestTemplate();
}
}
3.3 Controller
修改客户端的请求地址,即将之前固定写死的 localhost:9001 改为注册在 Eureka 中服务端的服务名。这样 TestTemplate 在 Eureka 中寻找相应的服务名,然后通过 Ribbon 的负载均衡算法将请求分发到相服务端。
@RestController
@RequestMapping(value = "users")
public class UserInfoController {
@Autowired
RestTemplate restTemplate;
// private String url = "http://localhost:9001/" // 之前的写法
private String url = "http://springcloud-provider/"; // 将地址改为注册在 Eureka 上服务端的服务名
/**
* 通过用户名获取用户
* @param username
* @return
*/
@GetMapping(value = "/{username}")
public UserInfo getByName(@PathVariable String username) {
UserInfo userInfo = restTemplate.getForObject(url + "users/" + username, UserInfo.class);
return userInfo;
}
}
3.4 启动并请求
启动后在浏览器中多次请求客户端:http://localhost:8001/users/pkyShare,可以发现在相应的控制台中轮询输出 9001、9002。这样就体现了负载均衡。
4 自定义 Ribbon 负载均衡算法
4.1 IRule
IRule 是 Ribbon 的路由组件,很多路由算法,默认是轮询算法。
- RoundRobinRule: 服务轮询,默认算法。
- RandomRule:随机服务算法。
- AvailabilityFilteringRule:过滤掉不可用的服务服务,对剩余的服务列表按照轮询策略进行访问算法。
- WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时如果统计信息不足,则使用 RoundRobinRule 策略,等统计信息足够,会切换到 WeightedResponseTimeRule。
- RetryRule:先按照 RoundRobinRule 的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
- BestAvailableRule :会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。
ZoneAvoidanceRule:复合判断 server 所在区域的性能和 server 的可用性选择服务器。
点击以上算法可以看到,都是继承了相应的父类,这些父类又最终实现了 IRule 接口。因此,只需要自定义一个类,然后继承相应父类,实现相应接口即可。以 RandomRule 为例。
4.2 自定义 RandomRule
自定义 Ribbon 算法需要自定义 Ribbon 客户端,即 @ RibbonClient(name = “服务名”, configuration = “自定义算法.class”)。但是在服务启动时,【自定义算法】类不能被扫描到,因此不能和启动类在同级包下(我这里的启动类在 com.pky.springcloud.consumer)。
在 /src/main/java 路径下新建 ribbon.router 包,创建 RouterRule 类
public class RouterRule extends AbstractLoadBalancerRule {
public RouterRule () {
}
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
/**
* 以下代码是源码策略,可以修改为自定义的策略
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
int index = this.chooseRandomInt(serverCount);
server = (Server)upList.get(index);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
**/
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
在同一包下创建 RouterBeanConfig 类
@Configuration
public class RouterBeanConfig {
@Bean
public IRule myRouter(){
return new RouterRule();// 默认是轮询,改为 RouterRule ()
}
在启动类声明
@SpringBootApplication
@EnableEurekaClient // 表明自己是个 eureka 客户端,会自动注册到 eureka 服务端中
@RibbonClient(name = "springclud-consumer",configuration = RouterBeanConfig.class)
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
这样自定义负载均衡算法就完成了。