从入门到精通web服务

文中先详细介绍web服务的功效及其技术性归类,下面详解web服务的普遍优化算法特点及实际完成。根据这种內容,可以协助阅读者对web服务的特点及基本原理有一个比较全方位的认知能力。

一、web服务介绍

1.1. 商业网站遭遇的挑戰

商业网站都需要应对巨大的用户数量,分布式系统,海量信息等挑戰。为了更好地提高系统软件总体的特性,能够选用竖直拓展和水准拓展二种方法。

竖直拓展:在网址发展趋势初期,能够从单机版的视角根据提升硬件配置解决工作能力,例如 CPU 解决工作能力,内存空间,硬盘等层面,完成网络服务器解决工作能力的提高。可是,单机版是有特性短板的,一旦碰触短板,再想提高,投入的成本费和成本会极高。这显而易见不可以达到大中型分布式架构(网址)全部解决的大流量,分布式系统,海量信息等挑戰。

水准拓展:根据群集来分摊商业网站的总流量。群集中的网站服务器(连接点)一般被设计方案成无状态,客户能够要求一切一个连接点,这种连接点一同分摊浏览工作压力。水准拓展有两个关键点:

  • 运用群集:将同一运用布署到几台设备上,构成解决群集,接受web服务机器设备派发的要求,开展解决,并回到相对应数据信息。

  • web服务:将客户浏览要求,根据某类优化算法,派发到群集中的连接点。

1.2. 什么叫web服务

web服务(Load Balance,通称 LB)是分布式系统、高可用性系统软件不可或缺的重要部件,总体目标是 竭尽全力将数据流量均值派发到好几个网络服务器上,以提升系统软件总体的响应时间和易用性。

web服务的关键功效以下:

分布式系统:web服务根据优化算法调节负荷,竭尽全力匀称的分派运用群集中各连接点的劳动量,为此提升运用群集的高并发解决工作能力(货运量)。

弹性:加上或降低网络服务器总数,随后由web服务开展派发操纵。这促使运用群集具有弹性。

高可用性:负载均衡设备能够监管备选网络服务器,当网络服务器不能用时,自动跳过,将要求分发送给可以用的网络服务器。这促使运用群集具有高可用性的特点。

安全防范:有一些web服务手机软件或硬件给予了安全系数作用,如:黑与白名册解决、服务器防火墙,防 DDos 进攻等。

二、web服务的归类

适用web服务的技术性许多,我们可以根据不一样层面去开展归类。

2.1 媒介层面归类

从适用web服务的媒介看来,能够将web服务分成两大类:硬件配置web服务、手机软件web服务

2.1.1硬件配置web服务

硬件配置web服务,一般是在订制CPU上运作的单独web服务网络服务器,价格比较贵,富豪专享。硬件配置web服务的主要产品有:F5 和 A10。

硬件配置web服务的 优势:

  • 功能齐全:适用全局性web服务并给予较全方位的、繁杂的web服务优化算法。

  • 特性强大:硬件配置web服务因为是在专用型CPU上运作,因而货运量大,可适用单机版上百万之上的高并发。

  • 安全系数高:通常具有服务器防火墙,防 DDos 进攻等安全性作用。

硬件配置web服务的 缺陷:

  • 成本费价格昂贵:选购和维护保养硬件配置web服务的成本费都很高。

  • 扩展性差:当浏览量猛增时,超出程度不可以动态性扩充。

2.1.2 手机软件web服务

手机软件web服务,运用最普遍,不管大企业或是小公司都是会应用。

手机软件web服务从手机软件方面完成web服务,一般能够在一切规范物理学机器设备上运作。

手机软件web服务的 主要产品 有:Nginx、HAProxy、LVS

  • LVS 能够做为四层负载均衡设备。其web服务的特性要好于 Nginx。

  • HAProxy 能够做为 HTTP 和 TCP 负载均衡设备。

  • Nginx、HAProxy 能够做为四层或七层负载均衡设备。

手机软件web服务的 优势:

  • 扩展性好:融入变化规律,能够根据加上手机软件web服务案例,动态性拓展到超过原始容积的工作能力。

  • 成本费便宜:手机软件web服务能够在一切规范物理学机器设备上运作,减少了选购和运维管理的成本费。

手机软件web服务的 缺陷:

  • 特性略差:对比于硬件配置web服务,手机软件web服务的特性要稍低一些。

2.2 通信网络归类

手机软件web服务从通讯方面看来,又可以分成四层和七层web服务。

1) 七层web服务:便是能够依据浏览客户的 HTTP 请求头、URL 信息内容将要求分享到特殊的服务器。

  • DNS 跳转

  • HTTP 跳转

  • 反向代理

2) 四层web服务:根据 IP 详细地址和端口号开展要求的分享。

  • 改动 IP 详细地址

  • 改动 MAC 详细地址

2.2.1 DNS web服务

DNS web服务一般用以互联网公司,繁杂的业务管理系统不宜应用。商业网站一般应用 DNS web服务做为 第一级web服务方式,随后在內部应用其他方法做第二级web服务。DNS web服务归属于七层web服务。

DNS 即 解析域名服务项目,是 OSI 第七层网络层协议。DNS 被设计方案为一个树结构的分布式架构,由上而下先后为:根服务器域名,一级服务器域名,二级域名网络服务器,... ,当地服务器域名。显而易见,假如全部数据信息都储存在根服务器域名,那麼 DNS 查看的负荷和花销会十分巨大。

因而,DNS 查看相对性于 DNS 等级构造,是一个反向的递归算法步骤,DNS 手机客户端先后要求当地 DNS 网络服务器,上一级 DNS 网络服务器,上一级 DNS 网络服务器,... ,根 DNS 网络服务器(又叫权威性 DNS 网络服务器),一旦击中,马上回到。为了更好地降低查看频次,每一级 DNS 网络服务器都是会设定 DNS 查看缓存文件。

DNS web服务的原理便是:根据 DNS 查看缓存文件,依照负荷状况回到不一样网络服务器的 IP 详细地址。

DNS 跳转的 优势:

应用简易:web服务工作中,交到 DNS 网络服务器解决,省去了web服务服务器管理的不便

提升特性:能够适用根据详细地址的解析域名,分析成间距客户近期的服务器ip(相近 CDN 的基本原理),能够加速网站打开速度,改进特性;

DNS 跳转的 缺陷:

易用性差:DNS 分析是多级别分析,增加/改动 DNS 后,分析時间较长;分析全过程中,客户浏览网址将不成功;

扩展性低:DNS web服务的决策权在域名空间那边,没法对其做大量的改进和拓展;

可维护性差:也不可以体现网络服务器的当今运作情况;适用的优化算法少;不可以区别网络服务器的差别(不可以依据系统软件与服务项目的情况来分辨负荷)。

2.2.2 HTTP web服务

HTTP web服务是根据 HTTP 跳转完成的。HTTP web服务归属于七层web服务。

HTTP 跳转基本原理是:依据客户的 HTTP 要求测算出一个真正的服务器ip,将该服务器ip载入 HTTP 跳转回应中,回到给电脑浏览器,由电脑浏览器再次开展浏览。

HTTP 跳转的优势:计划方案简易。

HTTP 跳转的 缺陷:

特性较弱:每一次浏览必须2次要求网络服务器,提升了浏览的延迟时间。

减少自然排名:应用跳转后,百度搜索引擎会视作 SEO 舞弊。

假如负载均衡设备服务器宕机,就无法打开该网站。

因为其缺陷较为显著,因此这类web服务对策具体运用较少。

2.2.3 反向代理web服务

反向代理(Reverse Proxy)方法就是指以 服务器代理 来接纳互联网要求,随后 将要求发送给内部网中的网络服务器,并将从内部网中的网络服务器上获得的結果回到给互联网要求的手机客户端。反向代理web服务归属于七层web服务。

反向代理服务项目的主要产品:Nginx、Apache

正向代理与反向代理有什么不同?

正向代理:产生在 手机客户端,是由客户积极进行的。FQ手机软件便是典型性的正向代理,手机客户端根据积极浏览服务器代理,让服务器代理得到必须的外网地址数据信息,随后分享回手机客户端。

反向代理:产生在 服务器端,客户不清楚代理商的存有。

反向代理是怎样完成web服务的呢?以 Nginx 为例子,以下所显示:

最先,在服务器代理上设置好web服务标准。随后,当接到手机客户端要求,反向代理网络服务器阻拦特定的网站域名或 IP 要求,依据web服务优化算法,将要求派发到备选网络服务器上。次之,假如某台备选宕机,反向代理网络服务器会出现容错机制解决,例如派发要求不成功 3 次之上,将要求派发到别的备选网络服务器上。

反向代理的 优势:

  1. 多种多样web服务优化算法:适用多种多样web服务优化算法,以解决不一样的情景要求。

  2. 能够监控服务器:根据 HTTP 协议书,能够监管分享网络服务器的情况,如:系统软件负荷、响应速度、是不是可以用、线程数、总流量等,进而依据这种数据信息调节web服务的对策。

反向代理的 缺陷:

  1. 附加的分享花销:反向代理的分享实际操作自身是有特性花销的,很有可能会包含建立联接,等候联接回应,剖析回应結果等实际操作。
  1. 提升系统软件复杂性:反向代理常见于做分布式架构的水准拓展,但反向代理服务项目存有下列难题,为了更好地处理下列难题会给系统软件总体提升附加的复杂性和运维管理成本费:
  • 反向代理服务项目假如本身服务器宕机,就无法打开网站,因此必须有 高可用性 计划方案,普遍的计划方案有:主备方式(一主一备)、双主模式(互为主导备)。

  • 反向代理服务项目本身也存有特性短板,伴随着必须分享的要求量持续飙升,必须有 可拓展 计划方案。

2.2.4 IPweb服务

IP web服务是在传输层根据改动要求目地详细地址开展web服务。

如上图所述所显示,IP 平衡解决步骤大概为:

手机客户端要求 192.168.137.10,由web服务网络服务器接受到报文格式。

web服务网络服务器依据优化算法挑选出一个服务项目连接点 192.168.0.1,随后将报文格式要求详细地址改成该连接点的 IP。

真正服务项目连接点接到要求报文格式,解决后,回到回应数据信息到web服务网络服务器。

web服务网络服务器将回应数据信息的服务器ip改web服务服务器ip,回到给手机客户端。

IP web服务在核心过程进行数据信息派发,较反向代理web服务有更强的从解决特性。可是,因为全部要求回应都需要历经web服务网络服务器,群集的货运量受限于web服务网络服务器的网络带宽。

2.2.5 数据链路层web服务

数据链路层web服务就是指在通讯协议的数据链路层改动 mac 详细地址开展web服务。

在 Linux 服务平台上最好是的链路层web服务开源系统商品是 LVS (Linux Virtual Server)。LVS 是根据 Linux 核心中 netfilter 架构完成的web服务系统软件。netfilter 是核心态的 Linux 服务器防火墙体制,能够在数据文件流过全过程中,依据标准设定数个副本(hook 涵数)来实行有关的实际操作。

LVS 的工作内容大概以下:

当客户浏览 www.sina.com.cn 时,客户数据信息根据逐层互联网,最终根据网络交换机进到 LVS 网络服务器网口,并进到核心传输层。

进到 PREROUTING 后历经路由器搜索,明确浏览的目地 VIP 是该设备 IP 详细地址,因此数据文件进到到 INPUT 链上

IPVS 是工作中在 INPUT 链上,会依据浏览的 vip port 分辨要求是不是 IPVS 服务项目,如果是则启用申请注册的 IPVS HOOK 涵数,开展 IPVS 有关主流程,强制改动数据文件的有关数据信息,并将数据文件发往 POSTROUTING 链上。

POSTROUTING 上接到数据文件后,依据总体目标 IP 详细地址(后端开发网络服务器),根据路由器选路,将数据文件最后发往后面端网络服务器上。

开源系统 LVS 版本号有 3 种工作模式,每一种方式原理迥然不同,说各种各样方式都是有自身的优点和缺点,各自合适不一样的应用领域,但是最后实质的作用全是能完成平衡的总流量生产调度和优良的扩展性。关键包含三种方式:DR 方式、NAT 方式、Tunnel 方式。

三、web服务优化算法

负载均衡设备的完成能够分成2个一部分:

依据web服务优化算法在备选服务器列表挑选出一个网络服务器;

将要求数据信息发送至该网络服务器上。

web服务优化算法是web服务服务项目关键中的关键。web服务商品各种各样,可是各种各样web服务优化算法基本原理是关联性的。web服务优化算法有很多种多样,各自适用不一样的应用领域,文中仅详细介绍更为普遍的web服务优化算法的特点及基本原理:轮循、任意、最少活跃性数、服务器iphach、一致性哈希

:web服务优化算法的完成,阅读推荐 Dubbo 官方网web服务优化算法表明 ,源代码解读十分详尽,十分非常值得参考。

3.1 任意

3.1.1 随机算法

任意(Random) 优化算法将要求任意派发到备选网络服务器。

随机算法 合适服务器的配置同样的情景。学习培训过摡率论的都了解,启用量较小的时候,很有可能负荷并不匀称,启用量越大,负荷越平衡。

【实例】随机算法完成实例

web服务插口

public interface LoadBalance<N extends Node> {

    N select(List<N> nodes, String ip);

}

web服务内部类

public abstract class BaseLoadBalance<N extends Node> implements LoadBalance<N> {
​
    @Override
    public N select(List<N> nodes, String ip) {
        if (CollectionUtil.isEmpty(nodes)) {
            return null;
        }
​
        // 假如 nodes 目录中仅有一个 node,立即回到就可以,不用开展web服务
        if (nodes.size() == 1) {
            return nodes.get(0);
        }
​
        return doSelect(nodes, ip);
    }
​
    protected abstract N doSelect(List<N> nodes, String ip);
​
}


服务器节点类

public class Node implements Comparable<Node> {
​
    protected String url;
​
    protected Integer weight;
​
    protected Integer active;
​
    // ...
}

随机算法完成

public class RandomLoadBalance<N extends Node> extends BaseLoadBalance<N> implements LoadBalance<N> {
​
    private final Random random = new Random();
​
    @Override
    protected N doSelect(List<N> nodes, String ip) {
        // 在目录中任意选择一个连接点
        int index = random.nextInt(nodes.size());
        return nodes.get(index);
    }
​
}

3.1.2 权重计算随机算法

权重计算任意(Weighted Rando****m) 优化算法在随机算法的基本上,依照几率调节权重值,开展负荷分派。

【实例】权重计算随机算法完成实例

public class WeightRandomLoadBalance<N extends Node> extends BaseLoadBalance<N> implements LoadBalance<N> {
​
    private final Random random = ThreadLocalRandom.current();
​
    @Override
    protected N doSelect(List<N> nodes, String ip) {
​
        int length = nodes.size();
        AtomicInteger totalWeight = new AtomicInteger(0);
        for (N node : nodes) {
            Integer weight = node.getWeight();
            totalWeight.getAndAdd(weight);
        }
​
        if (totalWeight.get() > 0) {
            int offset = random.nextInt(totalWeight.get());
            for (N node : nodes) {
                // 让任意值 offset 减掉权重
                offset -= node.getWeight();
                if (offset < 0) {
                    // 回到相对应的 Node
                    return node;
                }
            }
        }
​
        // 立即任意回到一个
        return nodes.get(random.nextInt(length));
    }
​
}

3.2 轮循

3.2.1 轮循优化算法

轮循(Round Robin)优化算法的对策是:将要求先后派发到备选网络服务器。

如下图所显示,负载均衡设备接到来源于手机客户端的 6 个要求,(1, 3, 5) 的要求会被发送至网络服务器 1,(2, 4, 6) 的要求会被发送至网络服务器 2。

该优化算法合适情景:各网络服务器解决工作能力相仿,且每一个事务管理劳动量差别并不大。假如存有很大差别,那麼解决比较慢的网络服务器就很有可能会库存积压要求,最后没法担负过大的负荷。

【实例】轮循优化算法实例

轮循web服务优化算法完成

public class RoundRobinLoadBalance<N extends Node> extends BaseLoadBalance<N> implements LoadBalance<N> {
​
    private final AtomicInteger position = new AtomicInteger(0);
​
    @Override
    protected N doSelect(List<N> nodes, String ip) {
        int length = nodes.size();
        // 假如部位值早已相当于连接点数,重设为 0
        position.compareAndSet(length, 0);
        N node = nodes.get(position.get());
        position.getAndIncrement();
        return node;
    }
​
}

3.2.2 权重计算轮循优化算法

权重计算轮循(Weighted Round Robbin)优化算法在轮循优化算法的基本上,提升了权重值特性来调整分享网络服务器的要求数量。特性高、响应速度快的连接点应当设定高些的权重值,促使派发时优先选择将要求派发到权重值较高的连接点上。

如下图所显示,网络服务器 A 设定权重值为 5,网络服务器 B 设定权重值为 1,负载均衡设备接到来源于手机客户端的 6 个要求,那麼 (1, 2, 3, 4, 5) 要求会被发送至网络服务器 A,(6) 要求会被发送至网络服务器 B。

【实例】权重计算轮循优化算法完成实例

下列完成根据 Dubbo 权重计算轮循优化算法干了一些简单化。

public class WeightRoundRobinLoadBalance<N extends Node> extends BaseLoadBalance<N> implements LoadBalance<N> {
​
    /**
     * 60秒
     */
    private static final int RECYCLE_PERIOD = 60000;
​
    /**
     * Node hashcode 到 WeightedRoundRobin 的投射关联
     */
    private ConcurrentMap<Integer, WeightedRoundRobin> weightMap = new ConcurrentHashMap<>();
​
    /**
     * 分子升级锁
     */
    private AtomicBoolean updateLock = new AtomicBoolean();
​
    @Override
    protected N doSelect(List<N> nodes, String ip) {
​
        int totalWeight = 0;
        long maxCurrent = Long.MIN_VALUE;
​
        // 获得获取当前时间
        long now = System.currentTimeMillis();
        N selectedNode = null;
        WeightedRoundRobin selectedWRR = null;
​
        // 下边这一循环系统关键干了那样几个事儿:
        //   1. 解析xml Node 目录,检验当今 Node 是不是有相对应的 WeightedRoundRobin,沒有则建立
        //   2. 检验 Node 权重值是不是发生了转变,若转变了,则升级 WeightedRoundRobin 的 weight 字段名
        //   3. 让 current 字段名再加上本身权重值,等额的于 current  = weight
        //   4. 设定 lastUpdate 字段名,即 lastUpdate = now
        //   5. 找寻具备较大  current 的 Node,及其 Node 相匹配的 WeightedRoundRobin,
        //      储存起來,留着后用
        //   6. 测算权重值总数
        for (N node : nodes) {
            int hashCode = node.hashCode();
            WeightedRoundRobin weightedRoundRobin = weightMap.get(hashCode);
            int weight = node.getWeight();
            if (weight < 0) {
                weight = 0;
            }
​
            // 检验当今 Node 是不是有相匹配的 WeightedRoundRobin,沒有则建立
            if (weightedRoundRobin == null) {
                weightedRoundRobin = new WeightedRoundRobin();
                // 设定 Node 权重值
                weightedRoundRobin.setWeight(weight);
                // 储存 url 唯一标志 identifyString 到 weightedRoundRobin 的投射关联
                weightMap.putIfAbsent(hashCode, weightedRoundRobin);
                weightedRoundRobin = weightMap.get(hashCode);
            }
            // Node 权重值并不等于 WeightedRoundRobin 中储存的权重值,表明权重值转变了,这时开展升级
            if (weight != weightedRoundRobin.getWeight()) {
                weightedRoundRobin.setWeight(weight);
            }
​
            // 让 current 再加上本身权重值,等额的于 current  = weight
            long current = weightedRoundRobin.increaseCurrent();
            // 设定 lastUpdate,表明最近升级过
            weightedRoundRobin.setLastUpdate(now);
            // 找到较大 的 current
            if (current > maxCurrent) {
                maxCurrent = current;
                // 将具备较大  current 权重值的 Node 取值给 selectedNode
                selectedNode = node;
                // 将 Node 相匹配的 weightedRoundRobin 取值给 selectedWRR,留着后用
                selectedWRR = weightedRoundRobin;
            }
​
            // 测算权重值总数
            totalWeight  = weight;
        }
​
        // 对 weightMap 开展查验,过虑掉长期未被升级的连接点。
        // 该连接点很有可能挂掉,nodes 中不包含该连接点,因此该连接点的 lastUpdate 长期没法被升级。
        // 若未升级时间超出阀值后,便会被移祛除,默认设置阀值为60秒。
        if (!updateLock.get() && nodes.size() != weightMap.size()) {
            if (updateLock.compareAndSet(false, true)) {
                try {
                    // 解析xml改动,即清除到期纪录
                    weightMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
                } finally {
                    updateLock.set(false);
                }
            }
        }
​
        if (selectedNode != null) {
            // 让 current 减掉权重值总数,等额的于 current -= totalWeight
            selectedWRR.decreaseCurrent(totalWeight);
            // 回到具备较大  current 的 Node
            return selectedNode;
        }
​
        // should not happen here
        return nodes.get(0);
    }
​
    protected static class WeightedRoundRobin {
​
        // 服务供应商权重值
        private int weight;
        // 当今权重值
        private AtomicLong current = new AtomicLong(0);
        // 最后一次更新
        private long lastUpdate;
​
        public long increaseCurrent() {
            // current = current   weight;
            return current.addAndGet(weight);
        }
​
        public long decreaseCurrent(int total) {
            // current = current - total;
            return current.addAndGet(-1 * total);
        }
​
        public int getWeight() {
            return weight;
        }
​
        public void setWeight(int weight) {
            this.weight = weight;
            // 原始状况下,current = 0
            current.set(0);
        }
​
        public AtomicLong getCurrent() {
            return current;
        }
​
        public void setCurrent(AtomicLong current) {
            this.current = current;
        }
​
        public long getLastUpdate() {
            return lastUpdate;
        }
​
        public void setLastUpdate(long lastUpdate) {
            this.lastUpdate = lastUpdate;
        }
​
    }
​
}

3.3 最少活跃性数

最少活跃性数(Least Active)优化算法 将要求派发到线程数/要求数至少的备选网络服务器(现阶段解决要求至少的网络服务器)。

  • 特性:依据备选网络服务器当今的要求线程数,动态分配。

  • 情景:适用系统对负荷比较比较敏感或要求联接时间相距很大的情景。

因为每一个要求的联接时间不一样,假如选用简易的轮循或随机算法,都很有可能发生一些网络服务器当今线程数过大,而另一些网络服务器的联接过小的状况,这就导致了负荷并不是真真正正平衡。尽管,轮循或优化算法都能够根据加权重值特性的方法开展负荷调节,但权重计算方法无法解决变化规律。

比如下面的图中,(1, 3, 5) 要求会被发送至网络服务器 1,可是 (1, 3) 迅速就断开,这时仅有 (5) 要求连接网络 1;(2, 4, 6) 要求被发送至网络服务器 2,仅有 (2) 的联接断掉。该系统软件再次运作时,网络服务器 2 会担负过大的负荷。

最少活跃性数优化算法会纪录当今時刻,每一个备选连接点已经解决的线程数,随后挑选线程数最少的连接点。该对策可以动态性、即时地反映网络服务器的现实状况,比较有效地将承担分派匀称,适用对当今系统软件负荷比较比较敏感的情景。

比如下面的图中,网络服务器 1 当今线程数最少,那麼新来临的要求 6 便会被发送至网络服务器 1 上。

权重计算最少活跃性数(Weighted Least Connection)在最少活跃性数的基本上,依据网络服务器的特性为每台网络服务器分派权重值,再依据权重系数出每台网络服务器能解决的线程数。

最少活跃性数优化算法完成关键点:活跃性启用数越小,说明该服务项目连接点解决工作能力越高,单位时间内可解决大量的要求,应优先选择将要求分发送给该服务项目。在实际完成中,每一个服务项目连接点相匹配一个活跃性数 active。原始状况下,全部服务供应商活跃性数均为 0。每接到一个要求,活跃性数加 1,进行要求后则将活跃性数减 1。在服务项目运作一段时间后,特性好的服务供应商解决要求的速率更快,因而活跃性数降低的也越来越快,这时那样的服务供应商可以优先选择获得到新的服务项目要求、这就是最少活跃性数web服务优化算法的基本上观念。

【实例】最少活跃性数优化算法完成

下列完成根据 Dubbo 最少活跃性数web服务优化算法干了一丝修改。

public class LeastActiveLoadBalance<N extends Node> extends BaseLoadBalance<N> implements LoadBalance<N> {
​
    private final Random random = new Random();
​
    @Override
    protected N doSelect(List<N> nodes, String ip) {
        int length = nodes.size();
        // 最少的活跃性数
        int leastActive = -1;
        // 具备同样“最少活跃性数”的工作者服务提供者(下列用 Node 别称)总数
        int leastCount = 0;
        // leastIndexs 用以纪录具备同样“最少活跃性数”的 Node 在 nodes 目录中的字符信息内容
        int[] leastIndexs = new int[length];
        int totalWeight = 0;
        // 第一个最少活跃性数的 Node 权重,用以与别的具备同样最少活跃性数的 Node 的权重值开展比照,
        // 以检验是不是“全部具备同样最少活跃性数的 Node 的权重值”均相同
        int firstWeight = 0;
        boolean sameWeight = true;
​
        // 解析xml nodes 目录
        for (int i = 0; i < length; i  ) {
            N node = nodes.get(i);
            // 发觉更小的活跃性数,从头开始
            if (leastActive == -1 || node.getActive() < leastActive) {
                // 应用当今活跃性数升级最少活跃性数 leastActive
                leastActive = node.getActive();
                // 升级 leastCount 为 1
                leastCount = 1;
                // 纪录当今下数值到 leastIndexs 中
                leastIndexs[0] = i;
                totalWeight = node.getWeight();
                firstWeight = node.getWeight();
                sameWeight = true;
​
                // 当今 Node 的活跃性数 node.getActive() 与最少活跃性数 leastActive 同样
            } else if (node.getActive() == leastActive) {
                // 在 leastIndexs 中纪录下当今 Node 在 nodes 结合中的字符
                leastIndexs[leastCount  ] = i;
                // 累积权重值
                totalWeight  = node.getWeight();
                // 检验当今 Node 的权重值与 firstWeight 是不是相同,
                // 不相同则将 sameWeight 置为 false       if (sameWeight && i > 0
                    && node.getWeight() != firstWeight) {
                    sameWeight = false;
                }
            }
        }
​
        // 当只有一个 Node 具备最少活跃性数,这时立即回到该 Node 就可以
        if (leastCount == 1) {
            return nodes.get(leastIndexs[0]);
        }
​
        // 有好几个 Node 具备同样的最少活跃性数,但他们中间的权重值不一样
        if (!sameWeight && totalWeight > 0) {
            // 随机生成一个 [0, totalWeight) 中间的数据
            int offsetWeight = random.nextInt(totalWeight);
            // 循环系统让随机数字减掉具备最少活跃性数的 Node 的权重,
            // 当 offset 不大于0时,回到相对应的 Node
            for (int i = 0; i < leastCount; i  ) {
                int leastIndex = leastIndexs[i];
                // 获得权重,并让随机数字减掉权重
                offsetWeight -= nodes.get(leastIndex).getWeight();
                if (offsetWeight <= 0) {
                    return nodes.get(leastIndex);
                }
            }
        }
        // 假如权重值同样或权重值为0时,任意回到一个 Node
        return nodes.get(leastIndexs[random.nextInt(leastCount)]);
    }
​
}

3.4 服务器iphach

服务器iphach(IP Hash)优化算法 依据要求源 IP,根据hach测算获得一个标值,用该标值在备选服务器列表的开展取模运算,获得的結果就是选定的网络服务器。

能够确保同一 IP 的手机客户端的要求会分享到同一台网络服务器上,用于完成对话黏滞(Sticky Session)。

特性:确保特殊客户一直要求到同样的网络服务器,若宕机,对话会遗失。

【实例】服务器iphash算法完成实例

public class IpHashLoadBalance<N extends Node> extends BaseLoadBalance<N> implements LoadBalance<N> {
​
    @Override
    protected N doSelect(List<N> nodes, String ip) {
        if (StrUtil.isBlank(ip)) {
            ip = "127.0.0.1";
        }
​
        int length = nodes.size();
        int index = hash(ip) % length;
        return nodes.get(index);
    }
​
    public int hash(String text) {
        return HashUtil.fnvHash(text);
    }
​
}

3.5 一致性哈希

一致性哈希(Consistent Hash)优化算法的总体目标是:同样的要求尽量落入同一个网络服务器上。

一致性哈希 能够非常好的处理 可靠性难题,能够将全部的 储存连接点 排序在 首尾相连 的 Hash 环上,每一个 key 在预估 Hash 之后 顺时针方向 寻找 临接 的 储存连接点 储放。而当有连接点 添加 或 撤出 时,仅危害该连接点在 Hash环上顺时针方向邻近的事后连接点。

1)同样的要求就是指:一般在应用一致性哈希时,必须特定一个 key 用以 hash 测算,可能是:

客户 ID

要求方 IP

要求服务项目名字,主要参数目录组成的串

2)尽量就是指:网络服务器很有可能产生左右线,极少数网络服务器的转变不应该危害大部分的要求。

当某台备选宕机时,本来寄往该网络服务器的要求,会根据虚似连接点,分摊到其他备选网络服务器,不容易造成强烈变化。

优势:添加 和 删掉 连接点只危害 hach环 中 顺时针 的 邻近的连接点,对别的连接点无危害。

缺陷:加减法连接点 会导致 hach环 中一部分数据信息 没法击中。当应用 小量连接点 时,连接点转变 将大范畴危害 hach环 中 数据信息投射,不宜 小量数据信息连接点 的分布式系统计划方案。一般 的 一致性哈希系统分区 在调整连接点时必须 增加一倍 或 减掉一半 连接点才可以确保 数据信息 和 负荷的平衡。

留意:由于 一致性哈希系统分区 的这种缺陷,一些分布式架构选用 虚似槽 对 一致性哈希 开展改善,例如 Dynamo 系统软件。

【实例】一致性哈希优化算法实例

public class ConsistentHashLoadBalance<N extends Node> extends BaseLoadBalance<N> implements LoadBalance<N> {
​
    private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<>();
​
    @SuppressWarnings("unchecked")
    @Override
    protected N doSelect(List<N> nodes, String ip) {
        // 分块数,这儿设为连接点数的 4 倍
        Integer replicaNum = nodes.size() * 4;
        // 获得 nodes 初始的 hashcode
        int identityHashCode = System.identityHashCode(nodes);
​
        // 假如 nodes 是一个新的 List 目标,代表着连接点总数发生了转变
        // 这时 selector.identityHashCode != identityHashCode 标准创立
        ConsistentHashSelector<N> selector = (ConsistentHashSelector<N>) selectors.get(ip);
        if (selector == null || selector.identityHashCode != identityHashCode) {
            // 建立新的 ConsistentHashSelector
            selectors.put(ip, new ConsistentHashSelector<>(nodes, identityHashCode, replicaNum));
            selector = (ConsistentHashSelector<N>) selectors.get(ip);
        }
        // 启用 ConsistentHashSelector 的 select 方式挑选 Node
        return selector.select(ip);
    }
​
    /**
     * 一致性哈希选择符
     */
    private static final class ConsistentHashSelector<N extends Node> {
​
        /**
         * 储存虚似连接点
         */
        private final TreeMap<Long, N> virtualNodes;
​
        private final int identityHashCode;
​
        /**
         * 构造器
         *
         * @param nodes            连接点目录
         * @param identityHashCode hashcode
         * @param replicaNum       分块数
         */
        ConsistentHashSelector(List<N> nodes, int identityHashCode, Integer replicaNum) {
            this.virtualNodes = new TreeMap<>();
            this.identityHashCode = identityHashCode;
            // 获得虚似连接点数,默认设置为 100
            if (replicaNum == null) {
                replicaNum = 100;
            }
            for (N node : nodes) {
                for (int i = 0; i < replicaNum / 4; i  ) {
                    // 对 url 开展 md5 计算,获得一个长短为16的字节数二维数组
                    byte[] digest = md5(node.getUrl());
                    // 对 digest 一部分字节数开展 4 次 hash 计算,获得四个不一样的 long 型整数
                    for (int j = 0; j < 4; j  ) {
                        // h = 0 时,取 digest 中字符为 0 ~ 3 的4个字节数开展位运算
                        // h = 1 时,取 digest 中字符为 4 ~ 7 的4个字节数开展位运算
                        // h = 2, h = 3 时全过程跟上面一样
                        long m = hash(digest, j);
                        // 将 hash 到 node 的投射关联储存到 virtualNodes 中,
                        // virtualNodes 必须给予高效率的查看实际操作,因而采用 TreeMap 做为存储结构
                        virtualNodes.put(m, node);
                    }
                }
            }
        }
​
        public N select(String key) {
            // 对主要参数 key 开展 md5 计算
            byte[] digest = md5(key);
            // 取 digest 二维数组的前四个字节数开展 hash 计算,再将 hash 值发送给 selectForKey 方式,
            // 找寻适合的 Node
            return selectForKey(hash(digest, 0));
        }
​
        private N selectForKey(long hash) {
            // 搜索第一个大于或等于当今 hash 的连接点
            Map.Entry<Long, N> entry = virtualNodes.ceilingEntry(hash);
            // 假如 hash 超过 Node 在hach环上较大 的部位,这时 entry = null,
            // 必须将 TreeMap 的头连接点取值给 entry
            if (entry == null) {
                entry = virtualNodes.firstEntry();
            }
            // 回到 Node
            return entry.getValue();
        }
​
    }
​
    /**
     * 测算 hash 值
     */
    public static long hash(byte[] digest, int number) {
        return (((long) (digest[3   number * 4] & c0FF) << 24)
            | ((long) (digest[2   number * 4] & c0FF) << 16)
            | ((long) (digest[1   number * 4] & c0FF) << 8)
            | (digest[number * 4] & c0FF))
            & c0FFFFFFFFL;
    }
​
    /**
     * 测算 MD5 值
     */
    public static byte[] md5(String value) {
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        md5.reset();
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        md5.update(bytes);
        return md5.digest();
    }
​
}

之上实例根据 Dubbo 的一致性哈希web服务优化算法干了一些简单化。

四、参考文献

1. Comparing Load Balancing Algorithms

2. 《大型网站技术架构:核心原理与案例分析》

3. 大中型网站结构系列产品:web服务详细说明(1)

4. 什么叫web服务

5. What Is Load Balancing

6. Dubbo 官方网web服务优化算法表明

7. web服务优化算法及方式

8. 运用 dns 分析来完成网址的web服务

创作者:vivo互联网技术精英团队-Zhang Peng

评论(0条)

刀客源码 游客评论