前言
- 基于Eureka示例搭建时的代码
- hosts增加域名dingsu-300
- 两种设备服务提供者(软交换-sip、300)各两个节点,用于测试负载路由情况
负载均衡概念
依据各项指标(可使用硬件资源、节点数、请求速率、业务场景等)进行权重考量,将负载(访问请求、工作任务等)分摊到多个服务节点上,从而提升系统整体的稳定性、可靠性。
LoadBalancer介绍
- 本次样例搭建使用的是Spring Cloud的 J 版本,H版本(某个小版本😄)之前默认引用的是Netflix Ribbon,在2020年之后不再维护,因此Spring Cloud官方推出并推荐使用Spring Cloud LoadBalancer
- Nacos默认引用Ribbon,后期搭建Nacos时,会替换为LoadBalancer
- LoadBalancer是一个客户端负载均衡器(调用方负责),而nginx则是服务端负载均衡器(服务提供方负责)
- 提供RoundRobin(轮询)和Random(随机)两种服务choose算法,当然也可以继承ReactorLoadBalancer< ServiceInstance>类,或者实现ReactorServiceInstanceLoadBalancer接口自定义算法
项目结构
- dingsu-300/-slave:设备300服务(2台)服务节点
- dingsu-device:设备管理服务
- dingsu-eureka:服务注册中心(-cluster是上篇文章遗老,忽略)
- dingsu-sip/-slave:软交换服务/设备(2台)服务节点
- dingsu-common: 通用数据结构
配置及代码
- 基于dingsu-sip模块示例,其他模块复制以下,修改配置即可
- hosts文件
127.0.0.1 confucius-eureka-server
127.0.0.1 dingsu-sip
127.0.0.1 dingsu-devices
127.0.0.1 dingsu-300
- application.yml
- 注意上一篇中提到的,defaultZone、application.name、hostname一致性的问题
spring:
application:
name: dingsu-sip
server:
port: 4512
desc: 软交换系统服务-4512
logging:
config: classpath:logback.xml
eureka:
instance:
hostname: dingsu-sip
client:
service-url:
defaultZone: http://confucius-eureka-server:4499/eureka
- pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.hd</groupId>
<artifactId>dingsu-common</artifactId>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
</dependency>
- controller
- 两种设备方便起见,这个接口路径是一致的
package com.hd.sip.modules.server.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.github.xiaoymin.knife4j.annotations.DynamicParameter;
import com.github.xiaoymin.knife4j.annotations.DynamicResponseParameters;
import com.hd.common.core.domain.R;
import com.hd.sip.modules.server.service.ServerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 丁宿
*/
@Api(tags = "服务器状态")
@ApiSupport(order = 10,author = "丁宿")
@RestController
@RequestMapping("/server")
public class ServerController {
private final ServerService serverService;
public ServerController(ServerService serverService) {
this.serverService = serverService;
}
@ApiOperation(value = "当前状态")
@ApiOperationSupport(order = 10,responses = @DynamicResponseParameters(properties = {
@DynamicParameter(value = "磁盘占有率",name = "runInfo.diskOccupancyRate")
,@DynamicParameter(value = "内存占有率",name = "runInfo.memoryOccupancyRate")
,@DynamicParameter(value = "CPU占有率",name = "runInfo.cpuOccupancyRate")
,@DynamicParameter(value = "服务描述",name = "serverDesc")
}))
@GetMapping("/info")
public R getCurrentInfo()
{
return serverService.getInfo ();
}
}
- service,如果没有的实体类、依赖包等,可以去前两篇文章中找一下
package com.hd.sip.modules.server.service;
import com.hd.common.core.domain.R;
import com.hd.common.utils.Ardith;
import com.hd.common.domain.RunInfoDomain;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import java.lang.management.ManagementFactory;
import java.util.List;
/**
* @author 丁宿
*/
@Slf4j
@Service("serverService")
public class ServerService {
@Value("${server.desc}")
private String serverDesc;
/**
* 获取服务运行信息、SIP服务器信息
* @return runInfo、serverInfo
*/
public R getInfo(){
return R.ok(getRunStatus());
}
/**
* 获取运行信息
* @return 运行信息(1.磁盘占用率 2.内存占用率 3.CPU占用率 4.网关)
*/
public RunInfoDomain getRunStatus(){
RunInfoDomain runInfoDomain = new RunInfoDomain();
runInfoDomain.setServerDesc(serverDesc);
/*
查询磁盘占用率
*/
SystemInfo si = new SystemInfo();
double diskInfo = getDiskInfo (si.getOperatingSystem ());
runInfoDomain.setDiskOccupancyRate (diskInfo);
/*
查询CPU占用率
*/
double cpuInfo = getCpuInfo ();
runInfoDomain.setCpuOccupancyRate (cpuInfo);
/*
查询内存占用率
*/
HardwareAbstractionLayer hal = si.getHardware();
double memInfo = getMemInfo (hal.getMemory ());
runInfoDomain.setMemoryOccupancyRate (memInfo);
return runInfoDomain;
}
/**
* 获取磁盘占用率
* @return 磁盘占用率
*/
private double getDiskInfo(OperatingSystem os)
{
long free = 0L;
long total = 0L;
FileSystem fileSystem = os.getFileSystem();
List<OSFileStore> fsArray = fileSystem.getFileStores();
for (OSFileStore fs : fsArray)
{
free += fs.getUsableSpace();
total += fs.getTotalSpace();
}
long used = total - free;
// log.info ("空闲:{},总量:{},使用:{},使用率:{}",free,total,used,usage);
return Ardith.mul (Ardith.div (used, total, 4), 100);
}
/**
* 查询CPU占用率
*/
private double getCpuInfo()
{
com.sun.management.OperatingSystemMXBean mxBean = (com.sun.management.OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean ();
double systemLoadAverage = mxBean.getSystemCpuLoad ();
return Ardith.mul (Ardith.round (systemLoadAverage,4),100);
}
/**
* 获取内存占用率
*/
private double getMemInfo(GlobalMemory memory)
{
long total = memory.getTotal ();
long available = memory.getAvailable ();
return Ardith.mul (Ardith.div (available, total, 4), 100);
}
}
- 返回的实体类RunInfoDomain
package com.hd.common.domain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* @author 丁宿
*/
@Getter
@Setter
@ApiModel(value = "运行信息")
public class RunInfoDomain {
@ApiModelProperty(value = "磁盘占有率")
private Double diskOccupancyRate;
@ApiModelProperty(value = "内存占有率")
private Double memoryOccupancyRate;
@ApiModelProperty(value = "CPU占有率")
private Double cpuOccupancyRate;
@ApiModelProperty(value = "服务描述")
private String serverDesc;
}
-
端口分配
- 设备管理服务:4511
- sip设备:4512/4612
- 设备300:4513/4613
- 注册中心:4499
-
Eureka运行状态
LoadBalancer相关代码
- 所有业务都在模块dingsu-devices(设备管理服务)中
- dingsu-sip实现随机访问两个节点
- dingsu-300实现轮询请求两个节点
- controller代码,通过传入服务实例名称(dingsu-sip、dingsu-300),来获取对应服务运行状态
package com.hd.devices.modules.controller;
import com.hd.common.core.domain.R;
import com.hd.devices.modules.service.DeviceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 丁宿
*/
@RestController
@RequestMapping("/device")
public class DevicesController {
@Autowired
DeviceService deviceService;
@GetMapping("/status")
public R getCurrentInfo(String moduleName)
{
return deviceService.getServerStatus (moduleName);
}
}
- service interface
package com.hd.devices.modules.service;
import com.hd.common.core.domain.R;
import org.springframework.stereotype.Service;
/**
* @author 丁宿
*/
@Service
public interface DeviceService {
public R getServerStatus(String moduleName);
}
- service implements
- 分别为两台设备的服务注入了RestTemplate ,以实现不同的均衡算法
- 打印各个服务节点配置中的设备描述,用于测试
- 为了演示,写的不优雅,不要见怪
package com.hd.devices.modules.service.impl;
import com.hd.common.core.domain.R;
import com.hd.devices.modules.service.DeviceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @author 丁宿 create in 2024/10/22
*/
@Slf4j
@Service
public class DeviceServiceImpl implements DeviceService {
@Autowired
RestTemplate sipRestTemplate;
@Autowired
RestTemplate cmRestTemplate;
@Autowired
private LoadBalancerClient eurekaClient;
@Override
public R getServerStatus(String moduleName) {
ServiceInstance choose = eurekaClient.choose(moduleName);
String hostname = choose.getHost();
int port = choose.getPort();
String uri = "/server/info";
String url = "http://" + hostname + ":" + port + uri;
R r;
RestTemplate restTemplate = null;
if (moduleName.equals("dingsu-sip")){
restTemplate = sipRestTemplate;
}else if(moduleName.equals("dingsu-300")){
restTemplate = sipRestTemplate;
}else{
restTemplate = new RestTemplate();
}
r = restTemplate.getForObject(url, R.class);
log.info(r.getData().toString());
return r;
}
}
- 设备300的RestTemplate
package com.hd.devices.conf;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author 丁宿
*/
@Configuration
@LoadBalancerClient(value = "dingsu-sip", configuration = SipLoadBalancerConfiguration.class)
public class SipRestTemplateConfig {
@LoadBalanced
@Bean("sipRestTemplate")
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 设备300的LoadBalancerClient.configuration
- 注入随机
package com.hd.devices.conf;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* @author 丁宿 create in 2024/10/25
*/
public class SipLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class)
, name
);
}
}
- 设备sip的RestTemplate
package com.hd.devices.conf;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author 丁宿
*/
@Configuration
@LoadBalancerClient(value = "dingsu-300", configuration = CmLoadBalancerConfiguration.class)
public class CmRestTemplateConfig {
@LoadBalanced
@Bean("cmRestTemplate")
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 设备sip的LoadBalancerClient.configuration
- 注入轮询
package com.hd.devices.conf;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* @author 丁宿 create in 2024/10/25
*/
public class CmLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class)
, name
);
}
}
测试结果
测试工具使用Apifox
- 设备sip
- 参数:dingsu-sip,请求次数:10,期望:随机
- 设备300
- 参数:dingsu-300,请求次数:10,期望:依此轮询