算法-负载均衡策略及代码示例

负载均衡策略

一、负载均衡的场景

负载均衡简单的讲就是讲多个请求(通过中间节点)合理的分配给多个响应节点 ,依据不同场景下节点的处理能力和请求及响应特性等产生了不同的分配请求方式。

其中对后侧的响应节点包含了一个假设 ,就是对于一个请求任意一个节点理论上都是能够处理的(可能受限于节点当前已经在处理请求的压力并不能够及时处理或响应 ),对于有请求偏好的也就是有些节点能处理有些节点不能处理,这种涉及路由选择的方式不在此文讨论。

常见的负载均衡方式有随机方式、加权随机方式、轮询方式、加权轮询方式、最小连接数方式、一致性哈希方式

二、 不同方式的特点

随机方式: 按概率等可能性的讲请求分配给后侧的节点,算法只考虑节点的总数量,实现非常简单

加权随机方式: 从后侧的服务节点考量处理能力,给不同的节点进行多分或少分,在随机方式上做进一步加强,可以更好的利用不同节点的处理能力 。

轮询方式: 维护一个环形服务节点队列,将请求依次分给节点处理,和随机方式的分配从概率上具有一样的等可能性

加权轮询方式: 考虑了服务节点不同的处理能力,是对轮询方式的进一步加强

最小连接数方式: 对于一些长连接的请求,往往以连接数量的多寡来相应的评估服务器的处理压力,此时最小连接数对应的服务器可能是压力最小的,将新请求分配给这个节点,通常较为合适。

一致性哈希方式: 对于一些有状态的请求,往往将同一请求来源的分配到同一服务节点,能够减少服务器的处理时间,或者是对于一些请求端和服务节点存在地域远近的情况,分配近的服务节点能够减少网络延时,所以从依据请求中的特定来源信息做分配是有很多应用的

三、 代码的简略实现

代码示例只展示部分表示核心功能的代码,非必要或者很简单的逻辑不做展示,以便更好的理解要点。

1、模拟

用几个Java类来模拟负载均衡场景涉及的关键角色:请求信息、负载均衡器、选择器(算法策略实现)、服务节点 。

请求信息:
// 代表请求
public class Request implements Cloneable {

    // 模拟地域 或  userType 类型
    private String area ;

    // 地址  以便后续做一致性哈希映射
    private int ip ;
}
基础的负载均衡器

/**
 * @author : wangchaodee
 * @Description: 基础的负载均衡器
 * 1、初始化时就注册选择器(设定负载均衡的算法方式)
 * 2、然后注册服务器列表
 * 3、执行请求
 */
public class LoadBalance {

    /**
     * 先简化逻辑 就用List 表示服务器列表 ,
     * 如需扩展服务器的动态维护功能 需要用Map结构更为合适
     */
    private List<Server> serverList;

    private Selector selector ;

    public LoadBalance(Selector selector){
        this.selector = selector ;
    }

    /**
     *  将服务器集群 注册到负载均衡器中
     * @param serverList  不为空
     */
    public void registerServerList(List<Server> serverList){
        this.serverList = serverList;
        count = serverList.size() ;
        selector.registerServerList(getServerList());
    }

    /**
     *  给一个请求分配一个处理节点 // 让具体子类实现
     * @param request
     * @return
     */
     Server handleRequest(Request request){
         Server server = serverList.get(selector.generateIdx());
         server.handle(request);
         return server;
     }
}
选择器

/**
 * @author : wangchaodee
 * @Description: 选择器
 */
public interface Selector {

    /**
     *  返回按特定策略计算出的服务器ID
     * @return
     */
    int generateIdx() ;

    void registerServerList(List<Server> serverList);

}
服务节点

/**
 * @author : wangchaodee
 * @Description: 服务节点
 */
public class Server {

    // 代表server 的唯一标识, 模拟 , 要求不重复, 相当于ip 或 uri,
      // 简化逻辑下直接用位序 ,不用id查找
    private int id ;
    //已处理的连接数量
    private int processed;

    // 假定可设置 100 ~ 1000 范围   服务器的最大处理链接  用于加权
    private int maxConnect ;

    private int currentConnect ;

    public void handle(Request request){
        processed++;
    }

    /**
     * 模拟返回 当前连接的数量
     * @return
     */
    public int getCurrentConnect(Random random){
        currentConnect = random.nextInt(maxConnect);
        return currentConnect;
    }
}

2、随机方式

/**
 * @author : wangchaodee
 * @Description: 随机方式的负载均衡
 */
public class RandomSelector implements Selector {

    private Random random;

    private int count ;

    public RandomSelector() {
        this.random = new Random();
        this.count =1 ;
    }

    @Override
    public int generateIdx() {
        return  random.nextInt(this.count);
    }

    @Override
    public void registerServerList(List<Server> serverList) {
        count= serverList.size() ;
    }

}

3、加权随机方式

先写个通用的加权处理方式


/**
 * @author : wangchaodee
 * @Description: 增加加权过滤
 */
public abstract class WeightSelector implements Selector {

    // 前缀和 数组
    private int[] pre;
    // 总数
    private int total;

    protected int cur;

    /**
     *   选择器如果直接选择个Server 返回也是可以的
     *
     *   如果后续不用到server  , 那么直接给个array 是否更合适,
     *
     * @param serverList
     */
    public void registerServerList(List<Server> serverList) {
        int N = serverList.size();
        int[] array = new int[N];
        for (int i = 1; i < N; i++) {
            array[i] = serverList.get(i).getMaxConnect();
        }
        int gcd = getGcd(array);
        array = processing(array, gcd);
        pre = new int[N];
        pre[0] = array[0];
        for (int i = 1; i < N; i++) {
            pre[i] = array[i] + pre[i - 1];
        }
        total = pre[N - 1] +1 ;
        cur = 0 ;
    }

    private int[] processing(int[] array, int gcd) {
        if (gcd == 1) return array;
        for (int i = 0; i < array.length; i++) {
            array[i] /= gcd;
        }
        return array;
    }

    private int getGcd(int[] nums) {
        // 找公约数
    }

    private int binarySearch(int x) {
       // 二分查找
    }


    public int generateIdx() {
        int idx = binarySearch(generateCur());
        return idx;
    }

    /**
     * 子类复写这个方法就可以
     * @return
     */
    public abstract int generateCur();


}

再实现随机的加权处理


/**
 * @author : wangchaodee
 * @Description: 加权随机方式的负载均衡   力扣 528 加权随机
 */
public class WeightRandomSelector extends WeightSelector {

    private Random random;

    public WeightRandomSelector() {
        this.random = new Random();
    }

    /**
     * 用随机方式生成cur 的值
     * @return
     */
    public int generateCur() {
        cur = random.nextInt(getTotal());
        return cur;
    }

}

4、轮询方式


/**
 * @author : wangchaodee
 * @Description: 轮询方式的负载均衡
 */
public class RoundRobinSelector implements Selector {

    private int idx ;
    private int count ;

    @Override
    public int generateIdx() {
        idx =( idx +1 ) % this.count ;
        return idx;
    }

    @Override
    public void registerServerList(List<Server> serverList) {
        count= serverList.size() ;
        idx =0 ;
    }
}

5、加权轮询方式

/**
 * @author : wangchaodee
 * @Description: 加权轮询方式的负载均衡
 */
public class WeightRoundRobinSelector extends WeightSelector {
    /**
     * 用轮询方式生成 当前cur 即可
     * @return
     */
    public int generateCur(){
        cur =( cur +1 ) % this.getTotal() ;
        return cur;
    }
}

6、最小连接数方式


/**
 * @author : wangchaodee
 * @Description: 最小连接方式的负载均衡
 */
public class MiniConnectSelector implements Selector {

    private Random random;

    // 用优先队列维护服务器链接数量的排序
    private PriorityQueue<Server> queue ;
    public MiniConnectSelector() {
        this.random = new Random();
        this.queue = new PriorityQueue<>((a,b)-> a.getCurrentConnect()- b.getCurrentConnect());
    }

    public Random getRandom() {
        return random;
    }

    @Override
    public int generateIdx() {
        Server server = queue.peek();
        // 模拟间隔的刷新 服务器的当前连接数量
        if(server.getCurrentConnect()==server.getMaxConnect()){
            mockRefreshConnect();
        }
        // 取得最少连接 ,加1后 放回排序
        server = queue.poll();
        server.increaseCurrentConnect();
        queue.offer(server);
        // 这里用的是id ,  lb执行用的是list中的位置序号, 按理应该用map 映射 
        // 为了简化 在实际生成测试时 将位序直接赋值给了id 
        return server.getId();
    }

    @Override
    public void registerServerList(List<Server> serverList) {
        refreshQueue(serverList);
    }
    // 模拟
    private void refreshQueue(List<Server> serverList){
        queue.clear();
        for (Server server : serverList){
            server.refreshCurrentConnect(random);
            queue.offer(server);
        }
    }
    // 模拟
    private void mockRefreshConnect(){
        List<Server> serverList = new ArrayList<>();
        while (!queue.isEmpty()){
            serverList.add(queue.poll());
        }
        refreshQueue(serverList);
    }

7、一致性哈希方式

一致性哈希需要取得Request 中信息进行哈希映射到服务器


/**
 * @author : wangchaodee
 * @Description: 一致性哈希方式的负载均衡
 */
public class ConsistentHashSelector implements HashSelector {

    private static final int DEFAULT_VSERVER_NUM =5 ; //150

    private int vServerNum ;
    private int count ;

    private SortedMap<Integer ,Server> vServers = new TreeMap<>();


    @Override
    public Server getByRequest(Request request) {
        int hash = hash(request.getArea());
        SortedMap<Integer,Server> subMap = vServers.tailMap(hash);
        if(!subMap.isEmpty()){
            return subMap.get(subMap.firstKey());
        }
        return vServers.get(vServers.firstKey());
    }

    @Override
    public void registerServerList(List<Server> serverList) {
        count= serverList.size() ;
        for (Server server : serverList) {
            for (int j = 0; j < vServerNum; j++) {
                String vNodeName =server.getId() +"_vnode_" + j;
                vServers.put(hash(vNodeName) , server);
            }
        }
    }

    private int hash(String name){
        return Math.abs(name.hashCode()) % 1023;
    }
}

8、测试对比


/**
 * @author : wangchaodee
 * @Description: 负载均衡的模拟测试
 */
public class LoadBalanceTest {

    int serverNum = 5 ;
    int requestNum = 15000 ;


    /**
     *  服务器处理能力一样
     * @return
     */
    private List<Server> generateServerList(){
        List<Server> serverList = new ArrayList<>();
        for (int i = 0; i < serverNum; i++) {
            Server server = new Server(i);
            // 服务器处理能力不一样
            //  Server server = new Server(i,100 * (i+1) );
            serverList.add(server);
        }
        return serverList ;
    }

   
    /**
     *  产生通用的空白请求 ,负载分发时不依赖请求中的信息
      */
    private List<Request> generateBlankRequestList(){
        // 执行请求
        List<Request> requestList = new ArrayList<>();
        Request base = new Request();
        for (int i = 0; i < requestNum; i++) {
            Request request = base.clone();
            
            requestList.add(request);
        }
        return requestList ;
    }

    private void execute(LoadBalance loadBalance , List<Request> requestList){
        for (int i = 0; i < requestList.size(); i++) {
            loadBalance.handleRequest(requestList.get(i));
        }
    }

    @Test
    public void testRandomLb(){
        
        LoadBalance loadBalance = new LoadBalance(new RandomSelector());
        System.out.println("SelectorName :" + loadBalance.getSelector().getClass().getSimpleName());
        loadBalance.registerServerList(generateServerList());
        // 执行请求
        execute(loadBalance,generateBlankRequestList());

        for(Server server : loadBalance.getServerList()){
            System.out.printf(" Server :%s , processed Request : %d \n " , server.hashCode() , server.getProcessed());
        }
    }

 @Test
    public void testConsistentHashLb(){

        LoadBalance loadBalance = new LoadBalance(new ConsistentHashSelector());
        System.out.println("SelectorName :" + loadBalance.getSelector().getClass().getSimpleName());
        loadBalance.registerServerList(generateServerList());
        // 执行请求
        executeHash(loadBalance,generateRequestList());

        for(Server server : loadBalance.getServerList()){
            System.out.printf(" Server :%s , processed Request : %d \n " , server.hashCode() , server.getProcessed());
        }
    }

    private void executeHash(LoadBalance loadBalance , List<Request> requestList){
        for (int i = 0; i < requestList.size(); i++) {
            loadBalance.handleRequestByHash(requestList.get(i));
        }
    }
    // 其他测试用例 自由的组合替换 即可  代码略 

模拟的测试结果仅供参考

SelectorName :RoundRobinSelector
 Server :1330106945 , processed Request : 3000 
  Server :859417998 , processed Request : 3000 
  Server :5592464 , processed Request : 3000 
  Server :1830712962 , processed Request : 3000 
  Server :1112280004 , processed Request : 3000 
 SelectorName :WeightRoundRobinSelector
 Server :104739310  maxConnect : 100, processed Request : 1000 
  Server :1761291320  maxConnect : 200, processed Request : 2000 
  Server :1451043227  maxConnect : 300, processed Request : 3000 
  Server :783286238  maxConnect : 400, processed Request : 4000 
  Server :1500056228  maxConnect : 500, processed Request : 5000 
 SelectorName :RandomSelector
 Server :105704967 , processed Request : 2903 
  Server :392292416 , processed Request : 3013 
  Server :1818402158 , processed Request : 3127 
  Server :1590550415 , processed Request : 3000 
  Server :1058025095 , processed Request : 2957 
 SelectorName :WeightRandomSelector
 Server :1876631416  maxConnect : 100, processed Request : 1021 
  Server :1359044626  maxConnect : 200, processed Request : 1941 
  Server :692342133  maxConnect : 300, processed Request : 2952 
  Server :578866604  maxConnect : 400, processed Request : 3972 
  Server :353842779  maxConnect : 500, processed Request : 5114 
SelectorName :MiniConnectSelector
 Server :1830712962 , processed Request : 2845 
  Server :104739310 , processed Request : 3204 
  Server :1761291320 , processed Request : 2820 
  Server :1451043227 , processed Request : 3003 
  Server :783286238 , processed Request : 3128 
SelectorName :ConsistentHashSelector
 Server :1595212853 , processed Request : 2130 
  Server :1911728085 , processed Request : 4004 
  Server :754666084 , processed Request : 6643 
  Server :88558700 , processed Request : 464 
  Server :1265210847 , processed Request : 1758 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值