Bootstrap

04. 微服务 - 示例搭建 - LoadBalancer(一)

前言

  1. 基于Eureka示例搭建时的代码
  2. hosts增加域名dingsu-300
  3. 两种设备服务提供者(软交换-sip、300)各两个节点,用于测试负载路由情况

负载均衡概念

依据各项指标(可使用硬件资源、节点数、请求速率、业务场景等)进行权重考量,将负载(访问请求、工作任务等)分摊到多个服务节点上,从而提升系统整体的稳定性、可靠性。

LoadBalancer介绍

  1. 本次样例搭建使用的是Spring Cloud的 J 版本,H版本(某个小版本😄)之前默认引用的是Netflix Ribbon,在2020年之后不再维护,因此Spring Cloud官方推出并推荐使用Spring Cloud LoadBalancer
  2. Nacos默认引用Ribbon,后期搭建Nacos时,会替换为LoadBalancer
  3. LoadBalancer是一个客户端负载均衡器(调用方负责),而nginx则是服务端负载均衡器(服务提供方负责)
  4. 提供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相关代码

  1. 所有业务都在模块dingsu-devices(设备管理服务)中
  2. dingsu-sip实现随机访问两个节点
  3. 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,期望:依此轮询
    测试结果
    轮询
;