Nginx 配置负载均衡

NginxLoadBalance

整理了 Nginx 负载均衡的相关配置。

起因

之前研究 MRCP 时,当 MRCP 下挂引擎到达性能瓶颈时,很容易就想到用负载均衡来实现,Nginx 配置也相对较简单。但遇到问题是 MRCP 一个 channel 内的请求可能是分批的,这就要这些分批的请求需要落在同一台 ASR 服务器上才能获得正确的结果。那需要怎么配置呢?

官方文档

负载均衡配置

Nginx 支持以下负载均衡机制:

  • round-robin/轮询:到应用服务器的请求以round-robin/轮询的方式被分发
  • least-connected/最少连接:下一个请求将被分派到活动连接数量最少的服务器
  • hash:使用 hash 算法来决定下一个请求要选择哪个服务器,hash 的对象可以是请求的连接、客户端 IP、或者请求的某个参数。

第三种方法正是我们所需要的。

默认负载均衡配置(轮询)

Nginx 中最简单的负载均衡配置看上去大体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http {
upstream myapp1 {
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}

server {
listen 80;

location / {
proxy_pass http://myapp1;
}
}
}

所有请求被代理到服务器集群 myapp1, 然后nginx实现HTTP负载均衡来分发请求。

最少连接负载均衡

使用最少连接负载均衡时,nginx试图尽量不给负荷较高的应用服务器增加过度的请求, 而是分配新请求到相对空闲的服务器实例。

nginx中通过在服务器集群配置中使用 least_conn 指令来激活最少连接负载均衡方法:

1
2
3
4
5
6
upstream myapp1 {
least_conn;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}

ip_hash

客户端 IP 地址作为 hash key 使用,用来决策选择服务器集群中的哪个服务器来处理这个客户端的请求。这个方法保证从同一个客户端发起的请求总是定向到同一台服务器,除非服务器不可用。

要配置使用ip-hash负载均衡,只要在服务器集群配置中使用ip_hash指令:

1
2
3
4
5
6
upstream myapp1 {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}

hash

我们最终想要的是能够自己选择 hash key,指令如下:

1
2
3
Syntax:    hash key [consistent];
Default: —
Context: upstream

加上 consistent 则为一致性 hash,有兴趣可以看文末的说明。

最终我们服务器配置类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upstream myapp1 {
hash $sid consistent;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
server {
if ( $request_uri ~* "[?&]sid=([^&#]+)" ) {
set $sid $1;
}

location / {
proxy_pass http://myapp1;
}
}

客户端请求,同一个语音流识别 sid 相同,都是http://xxxx?sid=1,则都分配到同一台服务器上。

权重

可以通过使用服务器权重来影响nginx的负载均衡算法。

特别是轮询,分派给服务器的请求被认为是大体相当的——假设有足够的请求,并且这些请求被以同样的方式处理而且完成的足够快。

当服务器被指定weight/权重参数时,负载均衡决策会考虑权重。

upstream myapp1 {
server srv1.example.com weight=3;
server srv2.example.com;
server srv3.example.com;
}

一致性哈希(Consistent Hashing)原理

一般 Hash

假设有 4 个 cache 服务器(后简称cache)组成的集群,当一个对象 object 传入集群时,这个对象应该存储在哪一个 cache 里呢?一种简单的方法是使用映射公式:

1
Hash(object) % 4

但若出现如下情况:

  • 由于流量增大,需要增加一台 cache,共5个 cache。这时,映射公式就变成 Hash(object) % 5。
  • 有一个cache服务器 down 掉,变成3个 cache。这时,映射公式就变成 Hash(object) % 3。

可见,无论新增还是减少节点,都会改变映射公式,而由于映射公式改变,几乎所有的 object 都会被映射到新的 cache 中,这意味着一时间所有的缓存全部失效。

一致性 Hash

一致性 Hash 的出现就是为了解决这个问题:当节点数量改变时,能够使失效的缓存数量尽可能少。

一致性 Hash 的基本思想就是分两步走:

把 object 求 hash(这一步和之前相同),把 cache 也求 hash,然后把 object 和 cache 的 hash 值放入一个 hash 空间,通过一定的规则决定每个 object 落在哪一个 cache 中。

object 求 hash:

consistent_hash_1

cache 求 hash:

consistent_hash_2

让 object 在环上顺时针转动,遇到的第一个 cache 即为对应的 cache 服务器。这样,新增或删除节点时,只需要改动少部分的节点。

为了解决数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。这里就不再具体描述了,类似下图,cache C1、cache C2 为 cache C 的虚拟节点:

consistent_hash_3

Cotin Yang wechat
欢迎订阅我的微信公众号 CotinDev
小小地鼓励一下吧~😘