ip_hash方式
关于nginx的负载均衡,大家都知道有一个ip_hash的方式,就是将客户端的ip取hash值,然后根据服务器 的数量取模,得出的值就是最后被路由到的服务器(服务器从0开始数),但是这个算法有一个很严重的问题,就是服务器的扩容与缩容时,所有客户端ip的hash值都需要重新计算,重新路由,这样影响的范围回非常广,大量的请求会被路由到其他服务器,所以我们引入了一致性hash的负载均衡解决方案。
一致性hash方式
算法思路如下
⾸先有⼀条直线,直线开头和结尾分别定为为1和2的32次⽅减1,这相当于⼀个地址,对于这样⼀条线,弯过来构成⼀个圆环形成闭环,这样的⼀个圆环称为hash环。我们把服务器的ip或者主机名求hash值然后对应到hash环上,那么针对客户端⽤户,也根据它的ip进⾏hash求值,对应到环上某个位置,然后如何确定⼀个客户端路由到哪个服务器处理呢?按照顺时针⽅向找最近的服务器节点,如下图
假如将服务器3下线,服务器3下线后,原来路由到3的客户端重新路由到服务器4,对于其他客户端没有影响只是这⼀⼩部分受影响(请求的迁移达到了最⼩,这样的算法对分布式集群来说⾮常合适的,避免了⼤量请求迁移 ),如下图:
增加服务器5之后,原来路由到3的部分客户端路由到新增服务器5上,对于其他客户端没有影响只是这⼀⼩部分受影响,如下图:
存在的问题
⼀致性哈希算法在服务节点太少时,容易因为节点分部不均匀⽽造成数据倾斜问题。例如系统中只有两台服务器,其环分布如下,节点2只能负责⾮常⼩的⼀段,⼤量的客户端请求落在了节点1上,这就是数据(请求)倾斜问题。
为了解决这种数据倾斜问题,⼀致性哈希算法引⼊了虚拟节点机制,即对每⼀个服务节点计算多个哈希,每个计算结果位置都放置⼀个此服务节点,称为虚拟节点。
具体做法可以在服务器ip或主机名的后⾯增加其他字符来实现。⽐如,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “节点1的ip#1”、“节点1的ip#2”、“节点1的ip#3”、“节点2的ip#1”、“节点2的ip#2”、“节点2的ip#3”的哈希值,于是形成六个虚拟节点,当客户端被路由到虚拟节点的时候其实是被路由到该虚拟节点所对应的真实节点。
注意:即使使用了一致性hash算法实行负载均衡的设置,也没有办法使所有请求不需要重新路由的,只是将需要重新路由的数量尽量减少,针对重新路由后的请求,session信息丢失的问题,会在后边分布式session的解决方案一文中给出
自定义一致性hash算法:
import java.util.SortedMap;import java.util.TreeMap;public class Main {public static void main(String[] args) {// 定义服务器ipString[] tomcatServers = {"192.222.1.30","19.16.1.2","192.168.1.13"};SortedMap<Integer,String> serverMap = new TreeMap<>();// 定义针对每个真实服务器虚拟出来⼏个节点int virtaulCount = 3;for (String tomcatServer : tomcatServers) {int hash = Math.abs(tomcatServer.hashCode());serverMap.put(hash,tomcatServer);//创建虚拟节点for (int i = 0; i < virtaulCount; i++) {int virtaulHash = Math.abs((tomcatServer+"a").hashCode());serverMap.put(virtaulHash,"由"+tomcatServer+"的虚拟节点处理");}}//定义客户端String[] userServers = {"19.11.12.1","10.113.120.79","107.180.13.5"};for (String userServer : userServers){int userhash = Math.abs(userServer.hashCode());//获取到所有key大于用户hash值的map集合SortedMap<Integer,String> sortedMap = serverMap.tailMap(userhash);if(sortedMap.isEmpty()){//如果为空,则表示当前用户节点后没有服务器节点,则使用圆环中的第一个节点Integer firstKey = serverMap.firstKey();System.out.println("客户端"+userServer+"使用服务器"+serverMap.get(firstKey));}else{//不为空则寻找第一个大于用户hash的keyInteger firstKey = sortedMap.firstKey();System.out.println("客户端"+userServer+"使用服务器"+serverMap.get(firstKey));}}}}
配置一致性hash策略
nginx的负载均衡策略中不包含一致性hash,所以我们需要安装ngx_http_upstream_consistent_hash模块到我们的nginx中
可以到nginx的src/http/modules下查看已经安装的模块,比如ip_hash策略
ngx_http_upstream_consistent_hash 模块是⼀个负载均衡器,使⽤⼀个内部⼀致性hash算法来选择合适的后端节点。该模块可以根据配置参数采取不同的⽅式将请求均匀映射到后端机器,
consistent_hash $remote_addr:可以根据客户端ip映射
consistent_hash $request_uri:根据客户端请求的uri映射
consistent_hash $args:根据客户端携带的参数进⾏映
安装步骤:
1)github下载nginx⼀致性hash负载均衡模块 /replay/ngx_http_consistent_hash
2)将下载的压缩包上传到nginx服务器,并解压
3)我们已经编译安装过nginx,此时进⼊当时nginx的安装目录⽬录,执⾏如下命令,等号后边为下载的插件的解压目录
./configure —add-module=/root/ngx_http_consistent_hash-master
4)make
make install
5)在nginx.conf⽂件中配置