精华内容
下载资源
问答
  • 主要介绍了Java加权负载均衡策略实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了Java使用Gateway自定义负载均衡过滤器,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • Java代码实现负载均衡五种算法

    万次阅读 2018-10-29 09:20:19
    版权声明:本文为博主原创文章,未经博主允许不得转载。... 前言:    负载均衡是为了解决并发情况下,多个请求访问,把请求通过提前约定好...在用java代码编写这几种算法之前,先来了解一下负载均衡这个概念。 1...

    版权声明:本文为博主原创文章,未经博主允许不得转载。    https://blog.csdn.net/u012904383/article/details/78358354


    前言:

      

         负载均衡是为了解决并发情况下,多个请求访问,把请求通过提前约定好的规则转发给各个server。其中有好几个种经典的算法。在用java代码编写这几种算法之前,先来了解一下负载均衡这个概念。

    1.概念

        负载,从字面意思可以分析,是指后端server可以承受的压力。这个一方面是服务器的性能,另一方面就是代码的质量了。

        均衡,就是说把服务部署在多态server,如何调度这些资源。根据服务器性能不同,进行一个权衡。

        当web访问量增加,服务器性能不同,更好的去利用服务器,我们需要负载均衡算法。

    2.几种负载均衡算法简介

    主要的负载均衡算法是图中这些,在代码实现之前,我们先简单回顾一下他们的概念。

    轮询法:

       轮询算法按顺序把每个新的连接请求分配给下一个服务器,最终把所有请求平分给所有的服务器。

       优点:绝对公平

       缺点:无法根据服务器性能去分配,无法合理利用服务器资源。

    加权轮询法:

        该算法中,每个机器接受的连接数量是按权重比例分配的。这是对普通轮询算法的改进,比如你可以设定:第三台机器的处理能力是第一台机器的两倍,那么负载均衡器会把两倍的连接数量分配给第3台机器。加权轮询分为:简单的轮询、平滑的轮询。

         什么是平滑的轮询,就是把每个不同的服务,平均分布。在Nginx源码中,实现了一种叫做平滑的加权轮询(smooth weighted round-robin balancing)的算法,它生成的序列更加均匀。5个请求现在分散开来,不再是连续的。

    随机法:

        负载均衡方法随机的把负载分配到各个可用的服务器上,通过随机数生成算法选取一个服务器。毕竟随机,,有效性受到了质疑。

    加权随机法:

         获取带有权重的随机数字,随机这种东西,不能看绝对,只能看相对。

    IP_Hash算法:

     

        hash(object)%N算法,通过一种散列算法把请求分配到不同的服务器上。

    3.Java代码实现负载均衡五种算法

        1.轮询法:


    /**
     * Title:轮询
     * Description:
     *
     * @author Created by Julie
     * @version 1.0
     * @date on 15:49 2017/10/26
     */
    package com.test.loadbalance;
     
     
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
     
     
    public class TestRoundRobin {
     
     
     
        //    1.定义map, key-ip,value-weight
        static Map<String,Integer> ipMap=new HashMap<>();
        static {
            ipMap.put("192.168.13.1",1);
            ipMap.put("192.168.13.2",1);
            ipMap.put("192.168.13.3",1);
     
        }
     
    //    Integer sum=0;
        Integer  pos = 0;
     
        public String RoundRobin(){
            Map<String,Integer> ipServerMap=new ConcurrentHashMap<>();
            ipServerMap.putAll(ipMap);
     
            //    2.取出来key,放到set中
            Set<String> ipset=ipServerMap.keySet();
     
            //    3.set放到list,要循环list取出
            ArrayList<String> iplist=new ArrayList<String>();
            iplist.addAll(ipset);
     
            String serverName=null;
     
            //    4.定义一个循环的值,如果大于set就从0开始
            synchronized(pos){
                if (pos>=ipset.size()){
                    pos=0;
                }
                serverName=iplist.get(pos);
                //轮询+1
                pos ++;
            }
            return serverName;
     
        }
     
        public static void main(String[] args) {
            TestRoundRobin testRoundRobin=new TestRoundRobin();
            for (int i=0;i<10;i++){
                String serverIp=testRoundRobin.RoundRobin();
                System.out.println(serverIp);
            }
        }
     
    }

        2.加权轮询法

    package com.test.loadbalance;
     
     
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
     
    /**
     * Title:
     * Description:加权轮询
     *
     * @author Created by Julie
     * @version 1.0
     * @date on 18:05 2017/10/26
     */
    public class TestWeightRobin {
        //    1.map, key-ip,value-weight
        static Map<String,Integer> ipMap=new HashMap<>();
        static {
            ipMap.put("192.168.13.1",1);
            ipMap.put("192.168.13.2",2);
            ipMap.put("192.168.13.3",4);
     
     
        }
        Integer pos=0;
        public String WeightRobin(){
            Map<String,Integer> ipServerMap=new ConcurrentHashMap<>();
            ipServerMap.putAll(ipMap);
     
            Set<String> ipSet=ipServerMap.keySet();
            Iterator<String> ipIterator=ipSet.iterator();
     
            //定义一个list放所有server
            ArrayList<String> ipArrayList=new ArrayList<String>();
     
            //循环set,根据set中的可以去得知map中的value,给list中添加对应数字的server数量
            while (ipIterator.hasNext()){
                String serverName=ipIterator.next();
                Integer weight=ipServerMap.get(serverName);
                for (int i = 0;i < weight ;i++){
                    ipArrayList.add(serverName);
                }
            }
            String serverName=null;
            if (pos>=ipArrayList.size()){
                pos=0;
            }
            serverName=ipArrayList.get(pos);
            //轮询+1
            pos ++;
     
     
            return  serverName;
        }
     
        public static void main(String[] args) {
            TestWeightRobin testWeightRobin=new TestWeightRobin();
            for (int i =0;i<10;i++){
                String server=testWeightRobin.WeightRobin();
                System.out.println(server);
            }
     
     
        }
    }

         3.随机法:

    package com.test.loadbalance;
     
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
     
    /**
     * Title:
     * Description:随机
     *
     * @author Created by Julie
     * @version 1.0
     * @date on 18:25 2017/10/26
     */
    public class TestRandom {
        //    1.定义map, key-ip,value-weight
        static Map<String,Integer> ipMap=new HashMap<>();
        static {
            ipMap.put("192.168.13.1",1);
            ipMap.put("192.168.13.2",2);
            ipMap.put("192.168.13.3",4);
     
     
        }
        public String Random() {
            Map<String,Integer> ipServerMap=new ConcurrentHashMap<>();
            ipServerMap.putAll(ipMap);
     
            Set<String> ipSet=ipServerMap.keySet();
     
            //定义一个list放所有server
            ArrayList<String> ipArrayList=new ArrayList<String>();
            ipArrayList.addAll(ipSet);
     
            //循环随机数
            Random random=new Random();
            //随机数在list数量中取(1-list.size)
            int pos=random.nextInt(ipArrayList.size());
            String serverNameReturn= ipArrayList.get(pos);
            return  serverNameReturn;
        }
     
        public static void main(String[] args) {
            TestRandom testRandom=new TestRandom();
            for (int i =0;i<10;i++){
                String server=testRandom.Random();
                System.out.println(server);
            }
     
     
        }
    }

        4.加权随机:

    package com.test.loadbalance;
     
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
     
    /**
     * Title:
     * Description:加权随机
     *
     * @author Created by Julie
     * @version 1.0
     * @date on 18:42 2017/10/26
     */
    public class TestRobinRandom {
     
        //    1.定义map, key-ip,value-weight
        static Map<String,Integer> ipMap=new HashMap<>();
        static {
            ipMap.put("192.168.13.1",1);
            ipMap.put("192.168.13.2",2);
            ipMap.put("192.168.13.3",4);
     
     
        }
        public String RobinRandom(){
            Map<String,Integer> ipServerMap=new ConcurrentHashMap<>();
            ipServerMap.putAll(ipMap);
     
            Set<String> ipSet=ipServerMap.keySet();
            Iterator<String> ipIterator=ipSet.iterator();
     
            //定义一个list放所有server
            ArrayList<String> ipArrayList=new ArrayList<String>();
     
            //循环set,根据set中的可以去得知map中的value,给list中添加对应数字的server数量
            while (ipIterator.hasNext()){
                String serverName=ipIterator.next();
                Integer weight=ipServerMap.get(serverName);
                for (int i=0;i<weight;i++){
                    ipArrayList.add(serverName);
                }
            }
     
            //循环随机数
            Random random=new Random();
            //随机数在list数量中取(1-list.size)
            int pos=random.nextInt(ipArrayList.size());
            String serverNameReturn= ipArrayList.get(pos);
            return  serverNameReturn;
        }
     
        public static void main(String[] args) {
            TestRobinRandom testRobinRandom=new TestRobinRandom();
            for (int i =0;i<10;i++){
                String server=testRobinRandom.RobinRandom();
                System.out.println(server);
            }
     
     
        }
    }

        5.IP_Hash算法:

    package com.test.loadbalance;
     
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
     
    /**
     * Title:
     * Description:
     *
     * @author Created by Julie
     * @version 1.0
     * @date on 18:35 2017/10/26
     */
    public class ipHash {
        //    1.定义map, key-ip,value-weight
        static Map<String,Integer> ipMap=new HashMap<>();
        static {
            ipMap.put("192.168.13.1",1);
            ipMap.put("192.168.13.2",2);
            ipMap.put("192.168.13.3",4);
        }
        public String ipHash(String clientIP){
            Map<String,Integer> ipServerMap=new ConcurrentHashMap<>();
            ipServerMap.putAll(ipMap);
     
            //    2.取出来key,放到set中
            Set<String> ipset=ipServerMap.keySet();
     
            //    3.set放到list,要循环list取出
            ArrayList<String> iplist=new ArrayList<String>();
            iplist.addAll(ipset);
     
            //对ip的hashcode值取余数,每次都一样的
            int hashCode=clientIP.hashCode();
            int serverListsize=iplist.size();
            int pos=hashCode%serverListsize;
            return iplist.get(pos);
     
        }
     
        public static void main(String[] args) {
            ipHash iphash=new ipHash();
            String servername= iphash.ipHash("192.168.21.2");
            System.out.println(servername);
        }
     
    }

    --------------------- 
    作者:我是周洲 
    来源:CSDN 
    原文:https://blog.csdn.net/zhou2s_101216/article/details/78358354 
    版权声明:本文为博主原创文章,转载请附上博文链接!

    展开全文
  • 在几年前,负载均衡还是“高端玩家”的游戏,我这种小白还难以触及,现在“负载均衡”已经有点看似烂大街的趋势了。 提起负载均衡,首先要理解负载均衡到底是想解决什么样的问题,维基百科有这么一段描述: 主要...

    在几年前,负载均衡还是“高端玩家”的游戏,我这种小白还难以触及,现在“负载均衡”已经有点看似烂大街的趋势了。

    提起负载均衡,首先要理解负载均衡到底是想解决什么样的问题,维基百科有这么一段描述:

    主要作用是将大量作业合理地分摊到多个操作单元上进行执行,用于解决互联网架构中的高并发高可用的问题。

    在现在的互联网系统中,为了避免出现单点问题,最常见的做法就是将系统部署到多台机器上,也就是集群。但是这样会出现两个问题:

    • 如何让机器均衡或者相对均衡的接收到到流量;
    • 当集群的某个节点宕机,让流量不会打到这个节点上;

    负载均衡技术就是来解决这两个问题。但这时候还要考虑两个问题:

    • 避免负载均衡系统成为单点;
    • 负载均衡系统是否需要关注服务器自身的状态差异;

    第一点比如可以基于 standby 模式搭建负载均衡系统集群,现在也有基于 Gossip 实现去中心化的软件负载解决方案。

    关于第二点,首先当服务器宕机或者与负载均衡系统连接中断的时候,负载均衡系统是必须要能够感知到的。但是在负载均衡算法中是否需要加入服务器自身状态的指标也是值得考虑的,如根据负载均衡系统和服务器之间的连接数、服务器的 CPU 负载、I/O 负载、服务器的响应时间等,但是这样负载均衡系统需要接收服务器的指标状态,而指标收集可能还会涉及到收集的时间节点,这会大大增加系统的复杂度。

    真正的大型高并发系统负载均衡是非常复杂的,比如还需要考虑系统预热的情况,要想深入了解还是需要多看分布式相关的书籍和进行实践。本文主要是通过代码实现几种简单的算法,借此体会负载均衡的思想。

    先定义一个 Invoker,存储一些基本的信息,一般来说这个类会抽象处理:

    package dongguabai.demo;
    
    import lombok.EqualsAndHashCode;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    import lombok.ToString;
    
    /**
     * @author Dongguabai
     * @Description
     * @Date 创建于 2020-06-24 09:16
     */
    @NoArgsConstructor
    @Getter
    @Setter
    @ToString
    @EqualsAndHashCode(of = "address")
    public class Invoker {
    
        private String address;
    
        /**
         * 权重
         */
        private int weight = 1;
        
        private int currentWeight = 0;
    
        public Invoker(String address) {
            this.address = address;
        }
    
        public Invoker(String address, int weight) {
            this.address = address;
            this.weight = weight;
        }
    }
    

    定义一个抽象类:

    package dongguabai.demo;
    
            import com.google.common.collect.HashMultiset;
            import com.google.common.collect.Multiset;
            import org.springframework.util.CollectionUtils;
    
            import java.util.Comparator;
            import java.util.List;
            import java.util.Set;
            import java.util.stream.IntStream;
    
    /**
     * @author Dongguabai
     * @Description 抽象类
     * @Date 创建于 2020-06-23 13:41
     */
    public abstract class AbstractLoadBalance {
    
        protected List<Invoker> invokers;
    
        public AbstractLoadBalance(List<Invoker> invokers) {
            this.invokers = invokers;
        }
    
        protected final Multiset<Invoker> results = HashMultiset.create();
    
        public Invoker selectHost(){
            List<Invoker> list = getInvokers();
            if (CollectionUtils.isEmpty(list)) {
                return null;
            }
            if (list.size() == 1) {
                return list.get(0);
            }
            return doSelect();
        }
    
        public Invoker selectHost(Object client){
            List<Invoker> list = getInvokers();
            if (CollectionUtils.isEmpty(list)) {
                return null;
            }
            if (list.size() == 1) {
                return list.get(0);
            }
            return doSelect(client);
        }
    
        protected Invoker doSelect(){
            return null;
        }
    
        protected Invoker doSelect(Object client){
            return null;
        }
    
        //todo 优化refesh
        protected boolean addInvoker(Invoker invoker){
            if (getInvokers() != null){
                return invokers.add(invoker);
            }
            return false;
        }
    
        //todo 优化refesh
        protected boolean removeInvoker(Invoker invoker){
            if (getInvokers() != null){
                return invokers.remove(invoker);
            }
            return false;
        }
    
        //todo 不要返回集合
        protected List<Invoker> getInvokers(){
            return invokers;
        }
    
        public void result(int loop) {
            results.clear();
            if (loop < 1) {
                throw new IllegalArgumentException();
            }
            IntStream.range(0, loop).forEach(i -> results.add(selectHost()));
            Set<Multiset.Entry<Invoker>> entrySet = results.entrySet();
            entrySet.stream().sorted(Comparator.comparingInt(Multiset.Entry::getCount)).forEach(System.out::println);
        }
    
    }
    

    完全随机

    随机算法是用得比较多,也是比较简单的一种算法,属于任务数量的绝对平分。在调用次数比较多的时候会相对平均分布到每台机器上。

    package dongguabai.demo.random;
    
    import dongguabai.demo.AbstractLoadBalance;
    import dongguabai.demo.Invoker;
    
    import java.util.List;
    import java.util.Random;
    
    /**
     * @author Dongguabai
     * @Description 随机
     * @Date 创建于 2020-06-23 18:57
     */
    public class SimpleRandomLoadBalance extends AbstractLoadBalance {
    
        public SimpleRandomLoadBalance(List<Invoker> invokers) {
            super(invokers);
        }
    
        @Override
        protected Invoker doSelect() {
            return getInvokers().get(new Random().nextInt(getInvokers().size()));
        }
    
        @Override
        public List<Invoker> getInvokers() {
            return invokers;
        }
    
    }
    

    测试:

        @Test
        public void testSimpleRandomLoadBalance(){
            List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("176.170.209.1"),
                    new Invoker("176.170.209.2"),
                    new Invoker("176.170.209.3"),
                    new Invoker("176.170.209.4"),
                    new Invoker("176.170.209.5"),
                    new Invoker("176.170.209.6"),
                    new Invoker("176.170.209.7"),
                    new Invoker("176.170.209.8"),
                    new Invoker("176.170.209.9"),
                    new Invoker("176.170.209.10"));
            AbstractLoadBalance randomLoadBalance = new SimpleRandomLoadBalance(invokers);
            randomLoadBalance.result(20000);
        }
    

    运行结果:

    Invoker(address=176.170.209.8, weight=1) x 1933
    Invoker(address=176.170.209.9, weight=1) x 1953
    Invoker(address=176.170.209.10, weight=1) x 1953
    Invoker(address=176.170.209.7, weight=1) x 1983
    Invoker(address=176.170.209.3, weight=1) x 2001
    Invoker(address=176.170.209.6, weight=1) x 2003
    Invoker(address=176.170.209.2, weight=1) x 2017
    Invoker(address=176.170.209.5, weight=1) x 2027
    Invoker(address=176.170.209.1, weight=1) x 2058
    Invoker(address=176.170.209.4, weight=1) x 2072
    

    样本数量越大,越平均。

    加权随机

    实际线上可能机器的性能各不相同,为了让性能更好的机器分配更多的流量,可以为每台机器设置一个权重,按照一定的比例进行任务的均分。

    权重一般是一个整型,最简单的思路就是机器的权重是多少,那么在机器的地址集合中就复制多少。

    package dongguabai.demo.random;
    
    import dongguabai.demo.Invoker;
    import dongguabai.demo.random.SimpleRandomLoadBalance;
    import org.springframework.util.CollectionUtils;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.IntStream;
    
    /**
     * @author Dongguabai
     * @Description 加权随机(复制)
     * @Date 创建于 2020-06-24 10:30
     */
    public class CopyWeightRandomLoadBalance extends SimpleRandomLoadBalance {
    
        private List<Invoker> weightInvokers = new ArrayList<>();
    
        public CopyWeightRandomLoadBalance(List<Invoker> invokers) {
            super(invokers);
            if (!CollectionUtils.isEmpty(invokers)){
                invokers.forEach(invoker -> {
                    if (invoker.getWeight() < 2) {
                        weightInvokers.add(invoker);
                        return;
                    }
                    IntStream.range(0,invoker.getWeight()).forEach(i->weightInvokers.add(invoker));
                });
            }
        }
    
        @Override
        public final List<Invoker> getInvokers() {
            return weightInvokers;
        }
    }
    

    测试:

        @Test
        public void testCopyWeightRandomLoadBalance(){
            List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("176.170.209.1",1),
                    new Invoker("176.170.209.2",9),
                    new Invoker("176.170.209.3",1),
                    new Invoker("176.170.209.4",9),
                    new Invoker("176.170.209.5",4),
                    new Invoker("176.170.209.6",6),
                    new Invoker("176.170.209.7",1),
                    new Invoker("176.170.209.8",9),
                    new Invoker("176.170.209.9",1),
                    new Invoker("176.170.209.10",9));
            AbstractLoadBalance simpleWeightRandomLoadBalance1 = new CopyWeightRandomLoadBalance(invokers);
            simpleWeightRandomLoadBalance1.result(20000);
        }
    

    运行结果:

    Invoker(address=176.170.209.1, weight=1) x 376
    Invoker(address=176.170.209.3, weight=1) x 386
    Invoker(address=176.170.209.7, weight=1) x 397
    Invoker(address=176.170.209.9, weight=1) x 398
    Invoker(address=176.170.209.5, weight=4) x 1559
    Invoker(address=176.170.209.6, weight=6) x 2380
    Invoker(address=176.170.209.8, weight=9) x 3567
    Invoker(address=176.170.209.10, weight=9) x 3622
    Invoker(address=176.170.209.4, weight=9) x 3632
    Invoker(address=176.170.209.2, weight=9) x 3683
    

    可以发现是按照权重比例进行分配。

    但是这种算法需要对调用信息进行复制,当数量较大时对内存的消耗相对是比较大的,所以一般会使用另外一种方式实现。

    还是以这个权重为例:

     List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("176.170.209.1",1),
                    new Invoker("176.170.209.2",9),
                    new Invoker("176.170.209.3",1),
                    new Invoker("176.170.209.4",9),
                    new Invoker("176.170.209.5",4),
                    new Invoker("176.170.209.6",6),
                    new Invoker("176.170.209.7",1),
                    new Invoker("176.170.209.8",9),
                    new Invoker("176.170.209.9",1),
                    new Invoker("176.170.209.10",9));
    

    首先计算出总的权重,我这里是 50,生成 [0,50) 的随机数 n;根据这个权重为每一个 Invoker 分配一个区间:

    01101120243031404150
    

    这个区间包左不包右,比如生成的随机数 0<= n <1,那么就是“176.170.209.1”这个机器,以此类推。

    具体实现如下:

    package dongguabai.demo.random;
    
    import dongguabai.demo.Invoker;
    
    import java.util.List;
    import java.util.Random;
    
    /**
     * @author Dongguabai
     * @Description
     * @Date 创建于 2020-06-24 17:10
     */
    public class SectionWeightRandomLoadBalance extends SimpleRandomLoadBalance {
    
        private boolean averageWeight = true;
    
        private int totalWeight;
    
        public SectionWeightRandomLoadBalance(List<Invoker> invokers) {
            super(invokers);
            for (int i = 0; i < invokers.size(); i++) {
                Invoker invoker = invokers.get(i);
                if (averageWeight && i > 0 && invoker.getWeight() != invokers.get(i - 1).getWeight()) {
                    averageWeight = false;
                }
                totalWeight += invoker.getWeight();
            
        }
    
        @Override
        protected Invoker doSelect() {
            if (averageWeight || totalWeight < 1) {
                return super.doSelect();
            }
            int index = new Random().nextInt(totalWeight);
            for (Invoker invoker : invokers) {
                if (index < invoker.getWeight()) {
                    return invoker;
                }
                index -= invoker.getWeight();
            }
            return super.doSelect();
        }
    }
    

    测试:

        @Test
        public void testWeightRandomLoadBalance(){
            List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("176.170.209.1",1),
                    new Invoker("176.170.209.2",9),
                    new Invoker("176.170.209.3",1),
                    new Invoker("176.170.209.4",9),
                    new Invoker("176.170.209.5",4),
                    new Invoker("176.170.209.6",6),
                    new Invoker("176.170.209.7",1),
                    new Invoker("176.170.209.8",9),
                    new Invoker("176.170.209.9",1),
                    new Invoker("176.170.209.10",9));
            AbstractLoadBalance simpleWeightRandomLoadBalance1 = new SectionWeightRandomLoadBalance(invokers);
            simpleWeightRandomLoadBalance1.result(20000);
        }
    

    运行结果:

    Invoker(address=176.170.209.3, weight=1) x 386
    Invoker(address=176.170.209.9, weight=1) x 405
    Invoker(address=176.170.209.7, weight=1) x 411
    Invoker(address=176.170.209.1, weight=1) x 430
    Invoker(address=176.170.209.5, weight=4) x 1598
    Invoker(address=176.170.209.6, weight=6) x 2450
    Invoker(address=176.170.209.4, weight=9) x 3543
    Invoker(address=176.170.209.10, weight=9) x 3557
    Invoker(address=176.170.209.2, weight=9) x 3573
    Invoker(address=176.170.209.8, weight=9) x 3647
    

    轮询

    轮询算法也很好理解,就是轮流执行每台服务器,所以有一个请求序号的概念。

    package dongguabai.demo.roundrobin;
    
    import dongguabai.demo.AbstractLoadBalance;
    import dongguabai.demo.Invoker;
    
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author Dongguabai
     * @Description
     * @Date 创建于 2020-06-25 13:48
     */
    public class SimpleRoundRobinLoadBalance extends AbstractLoadBalance {
    
        protected AtomicInteger offset = new AtomicInteger(-1);
    
        public SimpleRoundRobinLoadBalance(List<Invoker> invokers) {
            super(invokers);
        }
    
        @Override
        protected Invoker doSelect() {
            return getInvokers().get(offset.addAndGet(1) % getInvokers().size());
        }
    
    }
    

    测试:

        @Test
        public void testSimpleRoundRobinLoadBalance(){
            List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("176.170.209.1"),
                    new Invoker("176.170.209.2"),
                    new Invoker("176.170.209.3"),
                    new Invoker("176.170.209.4"),
                    new Invoker("176.170.209.5"),
                    new Invoker("176.170.209.6"),
                    new Invoker("176.170.209.7"),
                    new Invoker("176.170.209.8"),
                    new Invoker("176.170.209.9"),
                    new Invoker("176.170.209.10"));
            AbstractLoadBalance roundRobinLoadBalance = new SimpleRoundRobinLoadBalance(invokers);
            IntStream.range(0,22).parallel().forEach(i-> System.out.println(roundRobinLoadBalance.selectHost()));
        }
    

    执行结果:

    Invoker(address=176.170.209.7, weight=1)
    Invoker(address=176.170.209.5, weight=1)
    Invoker(address=176.170.209.9, weight=1)
    Invoker(address=176.170.209.2, weight=1)
    Invoker(address=176.170.209.6, weight=1)
    Invoker(address=176.170.209.8, weight=1)
    Invoker(address=176.170.209.3, weight=1)
    Invoker(address=176.170.209.4, weight=1)
    Invoker(address=176.170.209.1, weight=1)
    Invoker(address=176.170.209.4, weight=1)
    Invoker(address=176.170.209.8, weight=1)
    Invoker(address=176.170.209.9, weight=1)
    Invoker(address=176.170.209.10, weight=1)
    Invoker(address=176.170.209.7, weight=1)
    Invoker(address=176.170.209.2, weight=1)
    Invoker(address=176.170.209.6, weight=1)
    Invoker(address=176.170.209.5, weight=1)
    Invoker(address=176.170.209.3, weight=1)
    Invoker(address=176.170.209.2, weight=1)
    Invoker(address=176.170.209.1, weight=1)
    Invoker(address=176.170.209.10, weight=1)
    Invoker(address=176.170.209.1, weight=1)
    

    加权轮询

    有一个方式是根据权重复制,同上文介绍的加权随机的第一种方式。

    package dongguabai.demo.roundrobin;
    
    import dongguabai.demo.Invoker;
    import org.springframework.util.CollectionUtils;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.IntStream;
    
    /**
     * @author Dongguabai
     * @Description
     * @Date 创建于 2020-06-25 17:33
     */
    public class CopyWeightRoundRobinLoadBalance extends SimpleRoundRobinLoadBalance{
    
        private List<Invoker> weightInvokers = new ArrayList<>();
    
        public CopyWeightRoundRobinLoadBalance(List<Invoker> invokers) {
            super(invokers);
            if (!CollectionUtils.isEmpty(invokers)){
                invokers.forEach(invoker -> {
                    if (invoker.getWeight() < 2) {
                        weightInvokers.add(invoker);
                        return;
                    }
                    IntStream.range(0,invoker.getWeight()).forEach(i->weightInvokers.add(invoker));
                });
            }
        }
    
        @Override
        public final List<Invoker> getInvokers() {
            return weightInvokers;
        }
    }
    

    测试:

        @Test
        public void testCopyWeightRoundRobinLoadBalance(){
            List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("176.170.209.1",1),
                    new Invoker("176.170.209.2",9),
                    new Invoker("176.170.209.3",1),
                    new Invoker("176.170.209.4",9),
                    new Invoker("176.170.209.5",4),
                    new Invoker("176.170.209.6",6),
                    new Invoker("176.170.209.7",1),
                    new Invoker("176.170.209.8",9),
                    new Invoker("176.170.209.9",1),
                    new Invoker("176.170.209.10",9));
            AbstractLoadBalance copyWeightRoundRobinLoadBalance = new CopyWeightRoundRobinLoadBalance(invokers);
            IntStream.range(0,50).forEach(i-> System.out.println(copyWeightRoundRobinLoadBalance.selectHost()));
           // copyWeightRoundRobinLoadBalance.result(20000);
        }
    

    输出结果:

    Invoker(address=176.170.209.1, weight=1)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.3, weight=1)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.5, weight=4)
    Invoker(address=176.170.209.5, weight=4)
    Invoker(address=176.170.209.5, weight=4)
    Invoker(address=176.170.209.5, weight=4)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.7, weight=1)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.9, weight=1)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    

    上面是通过复制进行加权,同随机加权一样,还有一种通过权重区间进行轮询。

    package dongguabai.demo.roundrobin;
    
    import dongguabai.demo.Invoker;
    
    import java.util.List;
    
    /**
     * @author Dongguabai
     * @Description
     * @Date 创建于 2020-06-27 14:22
     */
    public class SectionWeightRoundRobinLoadBalance extends SimpleRoundRobinLoadBalance {
    
        private boolean averageWeight = true;
    
        private int totalWeight;
    
        public SectionWeightRoundRobinLoadBalance(List<Invoker> invokers) {
            super(invokers);
            for (int i = 0; i < invokers.size(); i++) {
                Invoker invoker = invokers.get(i);
                if (averageWeight && i > 0 && invoker.getWeight() != invokers.get(i - 1).getWeight()) {
                    averageWeight = false;
                }
                totalWeight += invoker.getWeight();
            }
        }
    
        @Override
        protected Invoker doSelect() {
            if (averageWeight || totalWeight < 1) {
                return super.doSelect();
            }
            int index = offset.addAndGet(1) % totalWeight;
            for (Invoker invoker : invokers) {
                if (index < invoker.getWeight()) {
                    return invoker;
                }
                index -= invoker.getWeight();
            }
            return super.doSelect();
        }
    }
    

    测试:

        @Test
        public void testSectionWeightRoundRobinLoadBalance(){
            List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("176.170.209.1",1),
                    new Invoker("176.170.209.2",9),
                    new Invoker("176.170.209.3",1),
                    new Invoker("176.170.209.4",9),
                    new Invoker("176.170.209.5",4),
                    new Invoker("176.170.209.6",6),
                    new Invoker("176.170.209.7",1),
                    new Invoker("176.170.209.8",9),
                    new Invoker("176.170.209.9",1),
                    new Invoker("176.170.209.10",9));
            AbstractLoadBalance sectionWeightRoundRobinLoadBalance = new SectionWeightRoundRobinLoadBalance(invokers);
            IntStream.range(0,50).forEach(i-> System.out.println(sectionWeightRoundRobinLoadBalance.selectHost()));
        }
    

    运行结果:

    Invoker(address=176.170.209.1, weight=1)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.2, weight=9)
    Invoker(address=176.170.209.3, weight=1)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.4, weight=9)
    Invoker(address=176.170.209.5, weight=4)
    Invoker(address=176.170.209.5, weight=4)
    Invoker(address=176.170.209.5, weight=4)
    Invoker(address=176.170.209.5, weight=4)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.6, weight=6)
    Invoker(address=176.170.209.7, weight=1)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.8, weight=9)
    Invoker(address=176.170.209.9, weight=1)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    Invoker(address=176.170.209.10, weight=9)
    

    平滑加权轮询

    上面两种轮询的实现有一个很大的问题就是会造成某台权重较高的机器在短时间内负载较高,其实轮询我们希望的是,比如 A(1),B(2),C(3) 三台机器我们希望的是每 6 次调用,有 1 次是 A,2 次是 B,3 次是 C 即可。当然也可以进行改动,比如复制法可以将复制后的机器列表打乱。

    在 Nginx 中有一种平滑加权的轮询算法。在上面的其他权重算法中,每台机器会有一个权重 weight 属性,这个属性是配置好之后就不变的,在平滑加权轮询中每台机器还有一个当前权重的属性 currentWeight,这个属性是会变化的。比如 A(1),B(2),C(3) 三台机器,它们的初始化 currentWeight 都是 0,发生调用的时候,就会每台机器的 currentWeight += weight,然后选择所有机器中最大的 currentWeight 所在的机器(如果出现相同,则按照顺序选择第一个),随后这台机器的 currentWeight 减去总 weight 权重之和 totalWeight。

    调用序号选择前当前权重Max(currentWeight)选择机器选择后当前权重
    11,2,33C1,2,-3
    22,4,04B2,-2,0
    33,0,33A-3,0,3
    4-2,2,66C-2,2,0
    5-1,4,34B-1,-2,3
    60,0,66C0,0,0

    可以看到经过 6 次调用,每台机器被调用的次数与权重的占比是一致的。

    这个算法中 currentWeight 总和是不变的,因为被选择的机器的 currentWeight 减去 totalWeight 后每台机器的 currentWeight 又会分别加上自身的 weight,即 weight 越大的机器被选择的次数就会越多。这个算法的数学证明可以参见:https://www.fanhaobai.com/2018/12/load-balance-smooth-weighted-round-robin.html

    代码实现:

    package dongguabai.demo.roundrobin;
    
    import dongguabai.demo.Invoker;
    
    import java.util.List;
    
    /**
     * @author Dongguabai
     * @Description 平滑加权轮询
     * @Date 创建于 2020-06-27 16:35
     */
    public class SmoothWeightRoundRobinLoadBalance extends SimpleRoundRobinLoadBalance {
    
        private boolean averageWeight = true;
    
        private int totalWeight;
    
        private final Object lock = new Object();
    
        public SmoothWeightRoundRobinLoadBalance(List<Invoker> invokers) {
            super(invokers);
            for (int i = 0; i < invokers.size(); i++) {
                Invoker invoker = invokers.get(i);
                if (averageWeight && i > 0 && invoker.getWeight() != invokers.get(i - 1).getWeight()) {
                    averageWeight = false;
                }
                totalWeight += invoker.getWeight();
            }
        }
    
        @Override
        protected Invoker doSelect() {
            if (averageWeight || totalWeight < 1) {
                return super.doSelect();
            }
            synchronized (lock) {
                Invoker doInvoker = selectInvoker(invokers);
                before(doInvoker);
                return doInvoker;
            }
        }
    
        private Invoker selectInvoker(List<Invoker> invokers) {
            Invoker maxCurrentInvoker = invokers.get(0);
            for (Invoker invoker : invokers) {
                invoker.setCurrentWeight(invoker.getWeight() + invoker.getCurrentWeight());
                if (maxCurrentInvoker.getCurrentWeight() < invoker.getCurrentWeight()) {
                    maxCurrentInvoker = invoker;
                }
            }
            return maxCurrentInvoker;
        }
    
        private void before(Invoker doInvoker) {
            doInvoker.setCurrentWeight(doInvoker.getCurrentWeight() - totalWeight);
        }
    }
    

    测试:

        @Test
        public void testSmoothWeightRoundRobinLoadBalance(){
            List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("A",1),
                    new Invoker("B",2),
                    new Invoker("C",3));
            AbstractLoadBalance smoothWeightRoundRobinLoadBalance = new SmoothWeightRoundRobinLoadBalance(invokers);
            IntStream.range(0,6).forEach(i-> System.out.println(smoothWeightRoundRobinLoadBalance.selectHost()));
        }
    

    运行结果:

    Invoker(address=C, weight=3, currentWeight=-3)
    Invoker(address=B, weight=2, currentWeight=-2)
    Invoker(address=A, weight=1, currentWeight=-3)
    Invoker(address=C, weight=3, currentWeight=0)
    Invoker(address=B, weight=2, currentWeight=-2)
    Invoker(address=C, weight=3, currentWeight=0)
    

    哈希

    哈希算法在分布式中的应用场景是很多的,如分布式的数据存储、负载均衡等,如 Redis Cluster 中的 Hash Slot 算法。在负载均衡中就是根据一些请求信息进行哈希运算,将相同的哈希值分配到同一台机器上。如源地址 Hash、SessionId Hash,用户信息 Hash 等,也能顺带解决了 分布式 Session 的问题。

    一个最简单的想法是直接用哈希来计算, 对机器数取模。在请求信息足够分散的情况下,是可以满足均衡性的,但一旦有机器加入或者宕机时,所有的原有机器都会受到影响。无法保证稳定性。

    这时候就有了一致性哈希算法。一致性哈希算法会将哈希值空间组织成一个虚拟的圆环,即哈希环,普通哈希算法仅仅只针对请求信息进行哈希运算,但是一致性哈希会对服务器也进行哈希运算,从而将两者的哈希值映射在一个圆环上,即哈希环:

    在这里插入图片描述

    当请求哈希后落在 Node1 和 Node2 之间,就会根据顺时针选择到 Node2 机器上。这样保证了稳定性,机器的增加或者退出不会影响所有的机器,但是还是会存在一个问题,假如 Node3 机器宕机,那么 Node1 到 Node4 的请求都会落在 Node1 机器上,这样 Node1 机器的压力就会变大。

    于是又有了虚拟节点,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,通过这种方式将机器节点分散开来,通过虚拟节点均衡各个机器的请求量。

    在这里插入图片描述

    在网上找到了一个哈希的算法实现:

    package dongguabai.demo.hash;
    
    import lombok.AccessLevel;
    import lombok.NoArgsConstructor;
    
    /**
     * @author Dongguabai
     * @Description
     * @Date 创建于 2020-06-27 21:45
     */
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public class HashUtils {
    
        /**
         * 引用:https://www.jianshu.com/p/4660a8a1f132
         * 计算Hash值, 使用FNV1_32_HASH算法
         *
         * @param str
         * @return
         */
        public static int getHash(String str) {
            final int p = 16777619;
            int hash = (int) 2166136261L;
            for (int i = 0; i < str.length(); i++) {
                hash = (hash ^ str.charAt(i)) * p;
            }
            hash += hash << 13;
            hash ^= hash >> 7;
            hash += hash << 3;
            hash ^= hash >> 17;
            hash += hash << 5;
    
            if (hash < 0) {
                hash = Math.abs(hash);
            }
            return hash;
        }
    }
    

    一致性哈希算法实现,这里是不关心权重的:

    package dongguabai.demo.hash;
    
    import com.google.common.collect.Multiset;
    import dongguabai.demo.AbstractLoadBalance;
    import dongguabai.demo.Invoker;
    
    import java.util.Comparator;
    import java.util.List;
    import java.util.Set;
    import java.util.SortedMap;
    import java.util.TreeMap;
    import java.util.stream.IntStream;
    
    /**
     * @author Dongguabai
     * @Description
     * @Date 创建于 2020-06-27 21:38
     */
    public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    
        /**
         * 虚拟节点数量
         */
        private static final int VIRTUAL_NODE_NUM = 1000;
    
        /**
         * hash 和 虚拟节点映射关系
         */
        private SortedMap<Integer, Invoker> virtualNodesMap = new TreeMap<>();
    
        public ConsistentHashLoadBalance(List<Invoker> invokers) {
            super(invokers);
            putVirtualNodes(invokers);
        }
    
    
        /**
         * 添加虚拟节点映射(环)
         * @param invokers
         */
        private void putVirtualNodes(List<Invoker> invokers) {
            for (Invoker invoker : invokers) {
                for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                    int hash = HashUtils.getHash(invoker.getAddress() + "-" + i);
                    virtualNodesMap.put(hash,invoker);
                }
            }
        }
    
        /**
         * 根据客户端的信息获取 Invoker
         * @param client
         * @return
         */
        public Invoker getInvoker(Object client){
            if (client == null){
                return null;
            }
            //获取大于等于 hash 的第一个 Node
            SortedMap<Integer, Invoker> subMap = virtualNodesMap.tailMap(HashUtils.getHash(client.toString()));
            if (subMap.isEmpty()) {
                // hash值在最尾部,映射到第一个Node上
                return virtualNodesMap.get(virtualNodesMap.firstKey());
            }
            return subMap.get(subMap.firstKey());
        }
    
    
        @Override
        public void result(int loop) {
            results.clear();
            if (loop < 1) {
                throw new IllegalArgumentException();
            }
            IntStream.range(0, loop).forEach(i -> results.add(selectHost("test-client-"+i)));
            Set<Multiset.Entry<Invoker>> entrySet = results.entrySet();
            entrySet.stream().sorted(Comparator.comparingInt(Multiset.Entry::getCount)).forEach(System.out::println);
        }
    
        @Override
        protected boolean addInvoker(Invoker invoker) {
            if (super.addInvoker(invoker)){
                List<Invoker> invokers = getInvokers();
                virtualNodesMap.clear();
                putVirtualNodes(invokers);
                return true;
            }
            return false;
        }
    
        @Override
        protected boolean removeInvoker(Invoker invoker) {
            if (super.removeInvoker(invoker)){
                List<Invoker> invokers = getInvokers();
                virtualNodesMap.clear();
                putVirtualNodes(invokers);
                return true;
            }
            return false;
        }
    
        @Override
        protected Invoker doSelect(Object client) {
            return getInvoker(client);
        }
    }
    

    测试:

        @Test
        public void testConsistentHashLoadBalance(){
            List<Invoker> invokers = Lists.newArrayList(
                    new Invoker("176.170.209.1",1),
                    new Invoker("176.170.209.2",9),
                    new Invoker("176.170.209.3",1),
                    new Invoker("176.170.209.4",9),
                    new Invoker("176.170.209.5",4),
                    new Invoker("176.170.209.6",6),
                    new Invoker("176.170.209.7",1),
                    new Invoker("176.170.209.8",9),
                    new Invoker("176.170.209.9",1),
                    new Invoker("176.1709.10",9));
            AbstractLoadBalance consistentHashLoadBalance = new ConsistentHashLoadBalance(invokers);
            consistentHashLoadBalance.result(1000000);
            Invoker changeInvoker = new Invoker("176.1709.11", 1);
            System.out.println("add......");
            consistentHashLoadBalance.addInvoker(changeInvoker);
            consistentHashLoadBalance.result(1000000);
            System.out.println("remove.....");
            consistentHashLoadBalance.removeInvoker(changeInvoker);
            consistentHashLoadBalance.result(1000000);
        }
    

    运行结果:

    Invoker(address=176.170.209.8, weight=9, currentWeight=0) x 93728
    Invoker(address=176.1709.10, weight=9, currentWeight=0) x 95566
    Invoker(address=176.170.209.1, weight=1, currentWeight=0) x 98669
    Invoker(address=176.170.209.5, weight=4, currentWeight=0) x 99376
    Invoker(address=176.170.209.4, weight=9, currentWeight=0) x 99596
    Invoker(address=176.170.209.6, weight=6, currentWeight=0) x 101373
    Invoker(address=176.170.209.7, weight=1, currentWeight=0) x 101706
    Invoker(address=176.170.209.3, weight=1, currentWeight=0) x 102884
    Invoker(address=176.170.209.9, weight=1, currentWeight=0) x 103137
    Invoker(address=176.170.209.2, weight=9, currentWeight=0) x 103965
    add......
    Invoker(address=176.1709.10, weight=9, currentWeight=0) x 85667
    Invoker(address=176.170.209.8, weight=9, currentWeight=0) x 86560
    Invoker(address=176.170.209.1, weight=1, currentWeight=0) x 90289
    Invoker(address=176.170.209.7, weight=1, currentWeight=0) x 90905
    Invoker(address=176.170.209.4, weight=9, currentWeight=0) x 90932
    Invoker(address=176.1709.11, weight=1, currentWeight=0) x 91236
    Invoker(address=176.170.209.5, weight=4, currentWeight=0) x 92081
    Invoker(address=176.170.209.9, weight=1, currentWeight=0) x 92808
    Invoker(address=176.170.209.2, weight=9, currentWeight=0) x 92847
    Invoker(address=176.170.209.6, weight=6, currentWeight=0) x 93085
    Invoker(address=176.170.209.3, weight=1, currentWeight=0) x 93590
    remove.....
    Invoker(address=176.170.209.8, weight=9, currentWeight=0) x 93728
    Invoker(address=176.1709.10, weight=9, currentWeight=0) x 95566
    Invoker(address=176.170.209.1, weight=1, currentWeight=0) x 98669
    Invoker(address=176.170.209.5, weight=4, currentWeight=0) x 99376
    Invoker(address=176.170.209.4, weight=9, currentWeight=0) x 99596
    Invoker(address=176.170.209.6, weight=6, currentWeight=0) x 101373
    Invoker(address=176.170.209.7, weight=1, currentWeight=0) x 101706
    Invoker(address=176.170.209.3, weight=1, currentWeight=0) x 102884
    Invoker(address=176.170.209.9, weight=1, currentWeight=0) x 103137
    Invoker(address=176.170.209.2, weight=9, currentWeight=0) x 103965
    

    可以看到增加或者减少节点也是可以保证负载均衡的。

    负载最低优先

    前面几种算法主要是站在负载均衡服务的角度,保证每个机器节点获得的调用次数均衡或者相对均衡,但是实际生产环境调用次数相同并不一定真的能够让每台机器真的实现均衡,于是就有了负载最低优先的策略,负载最低优先可以使用如最小连接数、CPU/IO 负载,这里引入《从零开始学架构》中的介绍:

    负载最低优先算法基本上能够比较完美地解决轮询算法的缺点,因为采用这种算法后,负载均衡系统需要感知服务器当前的运行状态。当然,其代价是复杂度大幅上升。通俗来讲,轮询可能是 5 行代码就能实现的算法,而负载最低优先算法可能要 1000 行才能实现,甚至需要负载均衡系统和服务器都要开发代码。负载最低优先算法如果本身没有设计好,或者不适合业务的运行特点,算法本身就可能成为性能的瓶颈,或者引发很多莫名其妙的问题。所以负载最低优先算法虽然效果看起来很美好,但实际上真正应用的场景反而没有轮询(包括加权轮询)那么多。

    总结

    本文主要简单的用 Java 代码实现了几种常见的负载均衡策略及其代码实现,但是有一个值得思考的问题是,要尽可能的将系统关键的节点进行简化,如机器权重,在容器化技术流行的今天,是否可以保证每台机器的性能一致,从而避免因为权重问题增加负载均衡的复杂度;同时负载均衡策略的选择可以具体根据业务场景进行不同的选择,在一些复杂的业务处理场景,如订单业务中根据订单号进行哈希从而将相同的订单交由相同的机器进行处理,避免出现多台机器间数据的不同步,也能够保证业务的处理顺序。

    References

    欢迎关注公众号
    ​​​​​​在这里插入图片描述

    展开全文
  • 定时器实现负载均衡

    2018-06-29 16:30:56
    关于定时器在2台服务器中实现负载均衡我问题,已执行情况状态和服务器名作为判
  • 2. 负载均衡的几种简单实现:  (1) 轮询法(Round Robin)  (2)随机法(Random)  (3)源地址Hash法(Hash)  (4)加权轮询法(Weight Round Robin)  (5)加权随机法(Weight Random)  (6)最小连接数法...

    读完本文你将知道: 
    1. 什么是负载均衡? 
    2. 负载均衡的几种简单实现: 
    (1) 轮询法(Round Robin) 
    (2)随机法(Random) 
    (3)源地址Hash法(Hash) 
    (4)加权轮询法(Weight Round Robin) 
    (5)加权随机法(Weight Random) 
    (6)最小连接数法(Least Connections)

    1. 什么是负载均衡?

    负载均衡(Load Balance),指由多台服务器以对称的形式组成的一个服务器集合,每台服务器都有等价的地位,都可以单独对外提供服务而无需其他服务器的辅助。通过某种负载分担技术,将外部发送来的请求均匀分配到对称结构中的一台服务器上,而接收到请求的服务器独立地回应客户的请求。负载均衡能够平均分配客户请求到服务器陈列,借此提供快速获取重要数据,解决大量并发访问服务问题,这种集群技术可以用最少的投资获得接近于大型主机的性能。

    本文主要讲解将“外部发送来的请求均匀分配到对称结构中的某一台服务器上”的各种算法, 并以Java代码来模拟实现。

    下面首先来以一个IP列表来模拟集群中的机器的IP集合。

    package distributed;
    
    import java.util.HashMap;
    
    /**
     * Created by louyuting on 17/2/7.
     */
    public class IPMap {
    
        public static HashMap<String, Integer> serverWeightMap = new HashMap<String, Integer>();
    
        static {
            //第一个参数是IP地址,第二个是权重.
            serverWeightMap.put("192.168.1.100", 1);
            serverWeightMap.put("192.168.1.101", 2);
            serverWeightMap.put("192.168.1.102", 3);
            serverWeightMap.put("192.168.1.103", 4);
            serverWeightMap.put("192.168.1.104", 3);
            serverWeightMap.put("192.168.1.105", 2);
            serverWeightMap.put("192.168.1.106", 1);
            serverWeightMap.put("192.168.1.107", 2);
            serverWeightMap.put("192.168.1.108", 1);
            serverWeightMap.put("192.168.1.109", 4);
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    1.轮训法

    轮训法其实就是按照所有地址的序列,从前往后一次访问,实现代码如下:

    package distributed;
    
    import java.util.*;
    
    /**
     * 轮训法
     */
    public class RoundRobin {
    
        private static Integer pos=0;
    
        public static String getServerIP(){
    
            //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
            Map<String, Integer>  serverMap = new HashMap<String, Integer>();
            serverMap.putAll(IPMap.serverWeightMap);
    
            //取的IP地址的Set
            Set<String> ips = serverMap.keySet();
    
            List<String> iplist = new ArrayList<String>();
            iplist.addAll(ips);
    
            String server = null;
    
            synchronized (pos){
                if(pos > iplist.size())
                    pos=0;
    
                server = iplist.get(pos);
    
                pos++;
            }
            return server;
        }
    
        /**
         *
         * @param args
         */
        public static void main(String[] args) {
            String result = null;
    
            for(int i=0; i<10; i++){
                result = getServerIP();
                System.out.println("load balance 的地址是:" + result);
            }
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    由于serverWeightMap中的地址列表是动态的,随时可能有机器上线、下线或者宕机,因此为了避免可能出现的并发问题,方法内部要新建局部变量serverMap,现将serverMap中的内容复制到线程本地,以避免被多个线程修改。这样可能会引入新的问题,复制以后serverWeightMap的修改无法反映给serverMap,也就是说这一轮选择服务器的过程中,新增服务器或者下线服务器,负载均衡算法将无法获知。新增无所谓,如果有服务器下线或者宕机,那么可能会访问到不存在的地址。因此,服务调用端需要有相应的容错处理,比如重新发起一次server选择并调用。

    对于当前轮询的位置变量pos,为了保证服务器选择的顺序性,需要在操作时对其加锁,使得同一时刻只能有一个线程可以修改pos的值,否则当pos变量被并发修改,则无法保证服务器选择的顺序性,甚至有可能导致iplist数组越界。

    轮询法的优点在于:试图做到请求转移的绝对均衡。

    轮询法的缺点在于:为了做到请求转移的绝对均衡,必须付出相当大的代价,因为为了保证pos变量修改的互斥性,需要引入重量级的悲观锁synchronized,这将会导致该段轮询代码的并发吞吐量发生明显的下降。

    2.随机法

    通过系统随机函数,根据后端服务器列表的大小值来随机选择其中一台进行访问。由概率统计理论可以得知,随着调用量的增大,其实际效果越来越接近于平均分配流量到每一台后端服务器,也就是轮询的效果。

    实现代码如下:

    package distributed;
    
    import java.util.*;
    
    /**
     * Created by louyuting on 17/2/7.
     * 随机法
     */
    public class RandomIP {
    
        public static String getServerIP() {
    
            //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
            Map<String, Integer> serverMap = new HashMap<String, Integer>();
            serverMap.putAll(IPMap.serverWeightMap);
    
            //取的IP地址的Set
            Set<String> ips = serverMap.keySet();
    
            List<String> iplist = new ArrayList<String>();
            iplist.addAll(ips);
    
            String server = null;
    
            //获取IP的策略
            Random random = new Random();
            int pos = random.nextInt(iplist.size());
            return iplist.get(pos);
        }
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            String result = null;
            for(int i=0; i<10; i++){
                result = getServerIP();
                System.out.println("load balance 的地址是:" + result);
            }
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    整体代码思路和轮询法一致,先重建serverMap,再获取到server列表。在选取server的时候,通过Random的nextInt方法取0~iplist.size()区间的一个随机值,从而从服务器列表中随机获取到一台服务器地址进行返回。基于概率统计的理论,吞吐量越大,随机算法的效果越接近于轮询算法的效果。

    3.源地址哈希法

    源地址哈希的思想是获取客户端访问的IP地址值,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是要访问的服务器的序号。源地址哈希算法的代码实现大致如下:

    package distributed;
    
    import java.util.*;
    
    /**
     * Created by louyuting on 17/2/7.
     * 源地址 hash法
     */
    public class HashIP {
    
        public static String getServerIP() {
    
            //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
            Map<String, Integer> serverMap = new HashMap<String, Integer>();
            serverMap.putAll(IPMap.serverWeightMap);
    
            //取的IP地址的Set
            Set<String> ips = serverMap.keySet();
    
            List<String> iplist = new ArrayList<String>();
            iplist.addAll(ips);
    
            //获取IP的策略
            //获取远端请求的IP地址
            String remoteIP = "127.0.0.11";
            int hashCode = remoteIP.hashCode();
            hashCode = Math.abs(hashCode);//确保hash值是正数. 如果hash值是负数
            int ipSize = iplist.size();
            int pos = hashCode % ipSize;
    
            return iplist.get(pos);
        }
    
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            String result = null;
            for(int i=0; i<10; i++){
                result = getServerIP();
                System.out.println("load balance 的地址是:" + result);
            }
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    这个方法需要注意的一点是,当获取到远端ip的hash值之后,一定要取绝对值,因为如果是负数最后定位的索引就会是负数,导致数组越界。其实不同负载均衡算法的重点就在于路由算法,通过客户端的ip也就是remoteIp,取得它的Hash值,对服务器列表的大小取模,结果便是选用的服务器在服务器列表中的索引值。

    源地址哈希法的优点在于:保证了相同客户端IP地址将会被哈希到同一台后端服务器,直到后端服务器列表变更。根据此特性可以在服务消费者与服务提供者之间建立有状态的session会话。

    源地址哈希算法的缺点在于:除非集群中服务器的非常稳定,基本不会上下线,否则一旦有服务器上线、下线,那么通过源地址哈希算法路由到的服务器是服务器上线、下线前路由到的服务器的概率非常低,如果是session则取不到session,如果是缓存则可能引发”雪崩”。(缓存雪崩指:某些原因导致缓存全部失效,意味着所有的数据请求都会到达数据库,导致数据库请求暴增,导致数据库挂掉。)

    4. 加权轮询法

    不同的服务器可能机器配置和当前系统的负载并不相同,因此它们的抗压能力也不尽相同,给配置高、负载低的机器配置更高的权重,让其处理更多的请求,而低配置、高负载的机器,则给其分配较低的权重,降低其系统负载。加权轮询法可以很好地处理这一问题,并将请求顺序按照权重分配到后端。加权轮询法的代码实现大致如下:

    package distributed;
    
    import java.util.*;
    
    /**
     * 加权轮训法
     */
    public class WeightRoundRobin {
    
        private static Integer pos=0;
    
        public static String getServerIP(){
    
            //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
            Map<String, Integer>  serverMap = new HashMap<String, Integer>();
            serverMap.putAll(IPMap.serverWeightMap);
    
            //取的IP地址的Set
            Set<String> ips = serverMap.keySet();
            Iterator<String> iterator = ips.iterator();
    
            List<String> iplist = new ArrayList<String>();
            while (iterator.hasNext()){
                String server = iterator.next();
                int weight = serverMap.get(server);
                //按照权重来添加比例.
                for(int i=0; i<weight; i++){
                    iplist.add(server);
                }
            }
    
            String server=null;
            synchronized (pos){
                if(pos > iplist.size())
                    pos=0;
    
                server = iplist.get(pos);
    
                pos++;
            }
            return server;
        }
    
        /**
         *
         * @param args
         */
        public static void main(String[] args) {
            String result = null;
    
            for(int i=0; i<10; i++){
                result = getServerIP();
                System.out.println("load balance 的地址是:" + result);
    
            }
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    与轮询法类似,只是在获取服务器地址之前增加了一段权重计算的代码,根据权重的大小,将地址重复地增加到服务器地址列表中,权重越大,该服务器每轮所获得的请求数量越多。

    5. 加权随机

    与加权轮询法类似,加权随机法也是根据后端服务器不同的配置和负载情况来配置不同的权重。不同的是,它是按照权重来随机选择服务器的,而不是顺序。加权随机法的代码实现如下:

    package distributed;
    
    import java.util.*;
    
    /**
     * Created by louyuting on 17/2/7.
     * 随机法
     */
    public class WeightRandom {
    
        public static String getServerIP() {
    
            //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
            Map<String, Integer>  serverMap = new HashMap<String, Integer>();
            serverMap.putAll(IPMap.serverWeightMap);
    
            //取的IP地址的Set
            Set<String> ips = serverMap.keySet();
            Iterator<String> iterator = ips.iterator();
    
            List<String> iplist = new ArrayList<String>();
            while (iterator.hasNext()){
                String server = iterator.next();
                int weight = serverMap.get(server);
                //按照权重来添加比例.
                for(int i=0; i<weight; i++){
                    iplist.add(server);
                }
            }
    
            //获取IP的策略
            Random random = new Random();
            int pos = random.nextInt(iplist.size());
            return iplist.get(pos);
        }
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            String result = null;
            for(int i=0; i<10; i++){
                result = getServerIP();
                System.out.println("load balance 的地址是:" + result);
    
            }
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    这段代码相当于是随机法和加权轮询法的结合,比较好理解,就不解释了。

    6. 最小连接数法

    前面几种方法费尽心思来实现服务消费者请求次数分配的均衡,当然这么做是没错的,可以为后端的多台服务器平均分配工作量,最大程度地提高服务器的利用率,但是实际情况是否真的如此?实际情况中,请求次数的均衡真的能代表负载的均衡吗?这是一个值得思考的问题。

    上面的问题,再换一个角度来说就是:以后端服务器的视角来观察系统的负载,而非请求发起方来观察。最小连接数法便属于此类。

    最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能地提高后端服务器的利用效率,将负载合理地分流到每一台机器。由于最小连接数设计服务器连接数的汇总和感知,设计与实现较为繁琐,此处就不说它的实现了。

    展开全文
  • zookeeper实现负载均衡与选举策略

    千次阅读 2018-10-03 10:26:45
    zookeeper实现负载均衡 解析:首先会员服务在注册中心上注册,这个时候就在zookeeper上生成几个临时节点,节点名是/member/8080和/member/8081,值是127.0.0.1:8080,127.0.0.1:8081(zookeeeper每个节点都有节点名和...

    zookeeper实现负载均衡

    解析:首先会员服务在注册中心上注册,这个时候就在zookeeper上生成几个临时节点,节点名是/member/8080和/member/8081,值是127.0.0.1:8080,127.0.0.1:8081(zookeeeper每个节点都有节点名和节点值),/member是会员服务的入口,这个时候订单服务调用会员服务,使用本地负载均衡策略,如下所示:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    使用Zookeeper实现负载均衡原理

    思路
    使用Zookeeper实现负载均衡原理,服务器端将启动的服务注册到,zk注册中心上,采用临时节点。客户端从zk节点上获取最新服务节点信息,本地使用负载均衡算法,随机分配服务器。
    Maven依赖

        <dependencies>
    		<dependency>
    			<groupId>com.101tec</groupId>
    			<artifactId>zkclient</artifactId>
    			<version>0.8</version>
    		</dependency>
    	</dependencies>
    

    创建Server服务端

    ZkServerScoekt服务

    package com.itmayiedu;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import org.I0Itec.zkclient.ZkClient;
    //##ServerScoekt服务端
    public class ZkServerScoekt implements Runnable {
    	private int port = 18080;
    	public static void main(String[] args) throws IOException {
    		int port = 18080;
    		ZkServerScoekt server = new ZkServerScoekt(port);
    		Thread thread = new Thread(server);
    		thread.start();
    	}
    	public ZkServerScoekt(int port) {
    		this.port = port;
    	}
    	// 将服务信息注册到注册中心上去
    	public void regServer() {
    		ZkClient zkClient = new ZkClient("127.0.0.1:2181", 6000, 1000);
    		String path = "/member/server-" + port;
    		if (zkClient.exists(path)) {
    			zkClient.delete(path);
    		}
    		String value="127.0.0.1:" + port;
    		zkClient.createEphemeral(path, "127.0.0.1:" + port);
    		System.out.println("##服务注册成功###"+value);
    	}
    	public void run() {
    		ServerSocket serverSocket = null;
    		try {
    			serverSocket = new ServerSocket(port);
    			regServer();
    			System.out.println("Server start port:" + port);
    			Socket socket = null;
    			while (true) {
    				socket = serverSocket.accept();
    				new Thread(new ServerHandler(socket)).start();
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				if (serverSocket != null) {
    					serverSocket.close();
    				}
    			} catch (Exception e2) {
    
    			}
    		}
    	}
    }
    

    ServerHandler.java

    package com.itmayiedu;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.Socket;
    
    //ServerHandler
    public class ServerHandler implements Runnable {
    	private Socket socket;
    
    	public ServerHandler(Socket socket) {
    		this.socket = socket;
    	}
    
    	public void run() {
    		BufferedReader in = null;
    		PrintWriter out = null;
    		try {
    			in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
    			out = new PrintWriter(this.socket.getOutputStream(), true);
    			String body = null;
    			while (true) {
    				body = in.readLine();
    				if (body == null)
    					break;
    				System.out.println("Receive : " + body);
    				out.println("Hello, " + body);
    			}
    
    		} catch (Exception e) {
    			if (in != null) {
    				try {
    					in.close();
    				} catch (IOException e1) {
    					e1.printStackTrace();
    				}
    			}
    			if (out != null) {
    				out.close();
    			}
    			if (this.socket != null) {
    				try {
    					this.socket.close();
    				} catch (IOException e1) {
    					e1.printStackTrace();
    				}
    				this.socket = null;
    			}
    		}
    	}
    }
    
    

    ZkServerClient

    package com.itmayiedu;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    import org.I0Itec.zkclient.IZkChildListener;
    import org.I0Itec.zkclient.ZkClient;
    public class ZkServerClient {
    	// 获取所有的服务地址
    	public static List<String> listServer = new ArrayList<String>();
    	public static void main(String[] args) {
    		initServer();
    		ZkServerClient client = new ZkServerClient();
    		BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
    		while (true) {
    			String name;
    			try {
    				name = console.readLine();
    				if ("exit".equals(name)) {
    					System.exit(0);
    				}
    				client.send(name);
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	// 注册所有server
    	public static void initServer() {
    		listServer.clear();
    		// 从zk获取最新获取的注册服务连接
    		final String memberServerPath = "/member";
    		final ZkClient zkClient = new ZkClient("127.0.0.1:2181", 6000, 1000);
    		// 获当前下子节点
    		List<String> children = zkClient.getChildren(memberServerPath);
    		listServer.clear();
    		for (String p : children) {
    			// 读取子节点value值
    			listServer.add((String) zkClient.readData(memberServerPath + "/" + p));
    		}
    		System.out.println("最新服务信息listServer:" + listServer.toString());
    		// 订阅子节点事件
    		zkClient.subscribeChildChanges(memberServerPath, new IZkChildListener() {
    			// 子节点发生变化
    			public void handleChildChange(String parentPath, List<String> childrens) throws Exception {
    				listServer.clear();
    				for (String subP : childrens) {
    					// 读取子节点value值
    					listServer.add((String) zkClient.readData(memberServerPath + "/" + subP));
    				}
    			}
    		});
    	}
    	// 服务调用次数
    	private static int count = 1;
    	// 会员服务集群数量,实际开发中不要写死,
    	private static int memberServerCount = 2;
    	// 获取当前server信息
    	public static String getServer() {
    		String serverName = listServer.get(count % memberServerCount);
    		++count;
    		return serverName;
    	}
    	public void send(String name) {
    		String server = ZkServerClient.getServer();
    		String[] cfg = server.split(":");
    		Socket socket = null;
    		BufferedReader in = null;
    		PrintWriter out = null;
    		try {
    			socket = new Socket(cfg[0], Integer.parseInt(cfg[1]));
    			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    			out = new PrintWriter(socket.getOutputStream(), true);
    			out.println(name);
    			while (true) {
    				String resp = in.readLine();
    				if (resp == null)
    					break;
    				else if (resp.length() > 0) {
    					System.out.println("Receive : " + resp);
    					break;
    				}
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			if (out != null) {
    				out.close();
    			}
    			if (in != null) {
    				try {
    					in.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    			if (socket != null) {
    				try {
    					socket.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }
    
    

    解析:当我们运行ZkServerScoekt.java后,再运行ZkServerClient.java,在ZkServerClient.java里面有一个main方法,接下来说执行流程:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这是client.send方法的定义
    在这里插入图片描述
    接下来我们来看服务器端的代码分析:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    运行效果截图:
    这是zkServerClient控制台:
    在这里插入图片描述

    这是18081服务器控制台:
    在这里插入图片描述
    这是18080服务器控制台:
    在这里插入图片描述
    从以上结果可以看出,实现了负载均衡的轮询机制

    使用Zookeeper实现选举策略

    解析:谁先在zookeeper上创建临时节点,谁就为主节点(太上皇),如果主节点挂了,那么就从剩下的从节点中(事件监听到主挂了)开始比赛,谁先创建临时节点,谁就为主(皇上),(就算太上皇活了,也只能为从,服从新皇的指挥)
    在这里插入图片描述

    展开全文
  • Java小毛驴 2017-10-22 12:52 概述 负载均衡(Load Balance,简称LB)是一种服务器或网络设备的集群技术。负载均衡将特定的业务(网络服务、网络流量等)分担给多个服务器或网络设备,从而提高了业务处理能力,保证了...
  • 文章目录如何实现负载均衡java远程调用实现负载均衡测试负载均衡 如何实现负载均衡 轮询调度算法的原理是每一次把来自用户的请求轮流分配给内部中的服务器,从1开始,直到N(内部服务器个数),然后重新开始循环。 ...
  • 【夯实Spring Cloud】Spring Cloud中使用Ribbon实现负载均衡详解(上) 【夯实Spring Cloud】Spring Cloud中使用Ribbon实现负载均衡详解(下) 【夯实Spring Cloud】Spring Cloud中自定义Ribbon负载均衡策略 ...
  • 对于多实例的服务的调用,提供对多个服务实例的负载均衡调度,实现负载按照预定的调度算法进行调度执行。 服务流量控制。通过设置请求数或连接数上限,动态实现对各服务接口的流控管理。 服务访问控制与黑白名单机制...
  • 两种方法实现轮询负载均衡算法

    千次阅读 2018-01-22 10:19:32
    package com.yanek.soa.loadbalance; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class MyLoadBalance { /** * @param args */ publi
  • springcloud-gateway-(九)ribbon的7种负载均衡策略 ribbon有7种负载均衡策略可供选择: // 其中Rule是所有负载均衡算法的父接口 public interface IRule { ...自定义负载策略:自己实现Rule这个接口或者继承Ab
  • 【夯实Spring Cloud】Spring Cloud中使用Ribbon实现负载均衡详解(上) 【夯实Spring Cloud】Spring Cloud中使用Ribbon实现负载均衡详解(下) 【夯实Spring Cloud】Spring Cloud中自定义Ribbon负载均衡策略 ...
  • 项目中如何修改负载策略 一. 基础概述 自己做的记录,推荐看大神的Ribbon的负载均衡策略及原理 什么是负载均衡: 简单来说就是根据算法指定将用户的请求平摊分片到多个服务上,或打到指定的服务上,从而达到服务的高...
  • Kafka负载均衡策略 6.1 分区器 分区器是生产者层面的负载均衡。Kafka 生产者生产消息时,根据分区器将消息投递到指定的分区中,所以 Kafka 的负载均衡很大程度上依赖于分区器。 Kafka 默认的分区器是 Kafka 提供的 ...
  • java高并发和负载均衡

    万次阅读 2018-01-02 11:07:10
    一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构、性能的要求都很简单,随着互联网业务的不断丰富,网站相关...
  • 它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有...
  • GRPC 负载均衡实现

    千次阅读 2019-05-12 01:11:33
    导读 gRPC 是google开源的非常优秀的RPC框架,支持PYTHON/...在负载均衡方面不如dubbo的组件那么丰富,但是其提供了服务发现的接口, 可以通过实现其接口,灵活实现负载均衡功能。 下面通过本地配置文件,启动时...
  • Java线程池饱和策略

    千次阅读 2017-03-01 16:14:24
    最近在看 Java并发编程的艺术一书,第9章提到了线程池的实现原理,其中有这么一句话: 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来...
  • 一、前言 ...因此配置负载均衡策略即为对ribbon进行配置。 目前公司的生产系统负载均衡、高可用等均使用集群管理平台Kubernetes(K8S)实现。本部门人员此部分了解即可,不指导生产使用 Spring Cl...
  • 基于springBoot2.0的springCloud路由网管负载均衡及拦截过滤的简单实现.
  • 自定义Eureka集群负载均衡策略

    千次阅读 2019-09-11 15:42:19
    学习更多Java干货,戳上面的蓝色字题关注我!相信看了 前两篇的同学都了解到了我的套路,没错,本篇博客同样是为了解决上篇的问题的。上篇我们使用Eureka默认的负载均衡解...
  • Ribbon+Retry服务实现负载均衡和服务请求重试
  • Ribbon负载均衡策略详解

    千次阅读 2018-11-07 11:35:54
    目前主流的负载方案分为两种,一种是集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的,比如F5,也有软件的,比如Nginx。 另一种则是客户端自己做负载均衡,根据自己的请求情况做...
  • 关于服务器百万并发的一些整理1 初始架构图 这篇文章是我在学习完服务器百万并发后的一些知识总结 ...5.当消费者执行业务时,如果有多个服务的生产者时,采用负载均衡的思想挑选其中的一个服务进行访问(RPC)
  • Java EE 目录:https://blog.csdn.net/dkbnull/article/details/87932809 ...在上一篇文章 Spring Cloud服务注册与发现Eureka 中,我们使用Eureka的负载均衡策略解决了服务消费者在调用服务提供者接...
  • DOCKER实现服务负载均衡集群

    千次阅读 2019-03-01 17:43:54
    docker实现项目的负载均衡集群 通过配置文件修改docker容器端口映射 Docker安装/docker实现Mysql主从复制参考地址 通过配置文件修改docker容器端口映射参考地址 (精)Docker容器内部端口映射到外部宿主机...
  • Dubbo 实现了常见的集群策略,并提供扩展点予以自行实现。 Random LoadBalance:随机选取提供者策略,随机转发请求,可以加权 RoundRobin LoadBalance:轮循选取提供者策略,请求平均分布 LeastActive Load...
  • 在集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 random 随机调用。 二. 负载均衡策略 1. Random LoadBalance 随机,按权重设置随机概率。 在一个截面上碰撞的概率高,但调用量越大分布越均...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 102,468
精华内容 40,987
关键字:

java实现负载策略

java 订阅