当我们在讨论负载均衡的时候我们在讨论什么?
上文中理清了一些基本概念,本文将着重讨论"高性能""高可用"这2个目标的其中一种解决方案:负载均衡集群;那么什么是负载均衡集群?为什么需要负载均衡?什么场景下需要做负载均衡?负载均衡存问题和风险又在哪里?如何做负载均衡?负载均衡的关注点?
什么是负载均衡集群?
其实这个高大上的名词包含的意思其实非常简单:"集群"和"负载均衡"~
假设你办了一家网站,为用户提供新闻资讯服务,刚起步的时候只有几百用户,我们从成本出发准备一台虚拟机就能解决问题;但是随着网站访问量的增加,服务器资源耗尽,用户反馈访问缓慢;需要两台或者更多的机器才能提供相同质量的服务,此时节省成本的做法是再买2台机器;3台服务器分担请求,理论上速度可以比原来快2倍(是原来的三倍)。
先说说集群:单机算力不足或者无法满足当前业务的情况下,简单的堆叠机器是廉价的解决方案,很多时候只需要简单改动甚至不需要改就能对一个系统进行多机部署,实现一个集群;
+---------------+
+----------+ | |
| | | Tomcat1 |
| +----------> |
| NGINX | +---------------+
| |
| upstream | +---------------+
| | | |
| +----------> Tomcat2 |
+----------+ | |
+---------------+
上面的就是一个简单的tomcat集群~
其中tomcat1和tomcat2部署相同的应用~
为什么需要负载均衡集群?
上面说了,单机算力不足无法满足业务需求,所以需要集群;集群之间服务器各负载一部分业务请求,为了整体业务的平衡(不至于造成一部分资源占用严重,一部分资源空闲而浪费)的情况所以需要"均衡"~好吧~可能不够严谨~但我觉得我是说明白了的~
什么场景下使用负载均衡?
1,高访问量高负载业务:对于上面的情况我们遇到的问题是单机无法支撑大量互相不关联的请求的时候,我们通过扩展N个tomcat的来实现"性能"上的提升~
2,为了提升可用性,避免单点故障;
3,同城/异地冗灾~
*负载均衡的问题和风险在哪里?
1,共享数据问题:就拿上面场景来说:我们知道http是无状态的,可往往大规模的需要集群的系统99.9999%的情况都是有状态的;尤其是带有页面的web程序,往往需要记录用户当前的会话信息(session),常规的做法是将一个sessionid写入cookie或者带再url后面~然后来回的传送他们以维持用户的状态;我们知道每个tomcat自己有session manager,默认的session manager实现是将会话数据放在内存中,这就意味着tomcat的会话数据是不共享的;tomcat前面的NGX如果不知道后端具体业务的情况,就会发生问题:比如用户登陆了,刷新一下页面就退出了之类的(实际上用户确实登录了,只是第二次被传送到了另一个没有登录的tomcat上去了)~处理这种问题一般有以下三种思路:
a,session共享:实现自定义的session manager替换掉tomcat"基于内存的default session manager",将session数据存放在共享的区域内,比如后端数据库,网络文件系统,memcached(早期推荐的方案)/redis(貌似现在大家都喜欢用这个)甚至ZooKeeper.等...对于其他共享数据,比如缓存,也可以这么来实现~
b,session复制:N个tomcat之间的session相互复制,每个机器上都存在一份相同的session;这种思路相对来说成本会更高一些,尤其是tomcat非常的多的情况,性能下降会非常快~
c,在代理层离出sessionId,然后根据特定的sessionId路由到特定的tomcat;既:用户A始终会被路由到同一个tomcat上去~这里对"路由策略"存在一定的侵略性,很容易造成系统集群的"不均衡"(你无法预料用户是)
其中"session共享"是目前大家普遍采用的方案,实际上实现实现"共享"并不困难,很早以前就有基于memcached的session共享方案了~github上也能找到redis的实现~当然你如果不满意自己实现一个也不是一个很困难的事情~
那么问题在哪里的呢?
这里存在一个坑:序列化问题~
如果你的系统是面向单机开发的,刚开始的时候可能并未考虑到需要部署到多个tomcat的情况,导致你在开发的时候很多放入session的数据没有实现序列化接口~
这些数据在单机部署的时候会被存入内存,所以不会存在任何问题,可如果这些数据需要在"网络""文件"中传输存储的时候就必须能够序列化~一些老旧的系统往往因为没注意这个问题导致一些奇怪的问题~
一般而言,你都需要在系统开发前叮嘱一遍你的开发人员"存入session的数据必须可以序列化"~
2,资源隔离粒度不够问题:
怎么理解这个事情呢?上文中说到一个"收藏夹"和"订单系统"竞争资源的问题:假设你的系统把这些业务都放在一个进程内实现~收藏夹和订单业务放在一起~当你的系统负载很高,访问量很大的时候可能出现"重要性低但使用率高"的收藏夹业务抢占了"重要性高的"订单业务(如果大量的人正在使用收藏夹,可能会导致订单业务无法正常使用)~
后期说的"分布式计算(也就是服务化)"就能很好的做到资源隔离~使得某些系统支撑不住的时候核心业务依然可以正常开展~但相应的成本也会增加~
如何做实现负载均衡集群?
这里不会告诉你具体如何实现一个系统的负载均衡~这些配置性的东西你或许可以根据自己的需求自己搜索得到~这里只讨论思路,讨论在你面临一些问题的时候能快速想到应该在哪个层面解决更好~
多机场景业务或系统之间协调交互常用的方法是通过"网络";所以对于集群的实现我们往往会从"网络"层面入手~
上图中这两个箭头是负载均衡很关键的部分,对于上述场景,NGX做反向代理,根据策略将请求发送到特定的后端tomcats,这里使用相同或者不同的协议,比如http->http,https->http,早在httpd时代我们使用mod_jk来提高效率~到现在tomcat的8009端口默认还是开着的~有兴趣可以看一下AJP协议(很多关于tomcat源码解析的书籍都会提到这个东西)~
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
一,数据路由
路由或者线路切换问题是负载均衡的核心;(如何根据业务(这很重要)来实现路由策略是一个很大的难题;这需要考虑上面的"均衡问题"以及下面的"节点增加或者丢失重新均衡",总而言之理由要做的不仅仅是简单的线路上的问题。)
当然这里讲的就只是线路切换问题:注意:线路切换往往依赖于网络;
A,基于解析服务(中介)
请求往往不会直接到达前置服务器,更多的时候我们使用DNS,他是一个中间人的角色,通过解析一个域名获得前置服务器的具体IP,随后再直接基于解析出来的IP地址;再比如dubbo的注册中心我们可以认为是一个中间人;真正请求发出前我们先问问中间人应该走那条路线~
B,物理线路切换
简单粗暴的将网线拔下来,插在另一台相同服务器上是简单粗暴解决可用性的一个好方法,呵呵(手动笑而不语);
然而你不可能做到一秒钟内插拔200次甚至更多;当然你可以说我在光纤种使用不同长度的波,或者无线网络,我基于不同的频段或者频道来做(实际上这种方式并不能很好的用于此种场景);就目前而言物理层线路切换的代价太高了。
C,三/四层的转发
多数情况下基于IP层的转发是效率相对好也是普遍,基本硬件负载或者基于IPVS(LVS)的负载只需解析重新封装三/四层的协议,他不会产生产生流量,也不需要维护两份连接句柄(七层转发种会提到),如果基于LVS软负载,基于linux net filter实现的转发,只需在linux内核层工作,避免了内核层和用户层的内存拷贝所带来的开销,甚至一台廉价的戴尔PC服务器就能稳定的到达10W的connection,没有足够的资金购买硬件负载,而访问量极高的系统建议采用此种方式。
D,七层转发
所谓的七层协议,就是应用层协议(有的书上会说五层 what ever?你懂了就行?)
HTTP协议就是第七层的协议,mysql服务器或者redis自己实现的协议也是第七层协议;使用nNginx或者Httpd解析七层协议,然后rewrite/upstream是目前主流的解决办法;
这种方法设置简单,维护方便,可自定义程度高。相比于LVS我相信绝大多数系统工程师对NGX会更加熟悉一些,这就带来了设置接单维护成本低,出了问题好解决;
相比第三四层的协议,七层可以得到大灵活度,解析HTTP协议获得URL获得用户传递过来某一个参数(比如sessionid等),简单的脚本或者正则就能定义个性化的转发策略。同样MYSQL proxy,AMIBA这种代理中间件通过解析MYSQL协议获得发送的SQL,通过简单的脚本语言(比如mysql 使用lua...)可以很灵活的更具SQL的内容来确定具体的后方服务器,比如读写分离;
需要注意的是:七层转发会产生流量的,同时七层代理服务器需要维护前后两个连接句柄(收到用户请求产生并维持一个服务连接,同时作为客户端向后端服务发起请求是另外一个连接),这种转发模式一般在用户层实现,系统需要将内核层的数据拷贝到用户空间,完成解析和处理,再将用户空间的数据拷贝到内核空间,由linux的网络子系统转发,所以我们说他会产生流量,相比三四层的转发其代价就要高很多。
E,基于客户端的路由
我们在客户端实现请求具体去哪一个服务器,通过配置一个服务器集群列表或者从"名字服务器"查找和下载这个列表,然后由客户端决定具体选择哪条线路的路由。redis官方的"哨兵"方案就是这种模式。这种方式相比中间件(代理/转发)的方式的代价就是在一定程度上改动一些代码;
综述:
DNS轮询属于中间人模式,dubbo同样采用中间人方式;三四七层转发属于代理模式(Proxy);基于用户选择实际上是讲路由的功能放到客户端实现,比如淘宝的数据中间件等采用了这种模式(dubbo是向注册中心获取服务提供者列表并且存储的,策略在注册中心实现,同样服务器提供者的负载策略也不完全取决于服务的消费者);选择四层转发(交换)还是七层不仅仅取决于性能需求,更多的还是取决于功能需求和成本,比如你的系统要求通过URL的某个关键字或者参数来决定具体线路的,你可能就必须得选择"七层设备"来做代理了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
二,如何均衡?
我们有两台一毛一样的服务器,我们更多的是希望于两台服务器分担相同量的工作,而不是一台服务器很忙,而另一台服务器翘着二郎腿喝咖啡~
这往往很难,均衡问题不仅仅取决于使用的方法或者策略,更取决于你对问题的预判,还有对现实问题的妥协。
的做法根据业务是不断的调整,积累经验逐步调优。
基本也是简单的均衡策略是:随机,当然还有轮询,基于后端少使用或者人为权重;
A,随机(Random):
不考虑实际情况,将问题或者请求随机的抛给两台服务器,就像跑硬币实验一样,访问量越高,就越均衡。
B,轮询(Round Robin):
不考虑后端服务器的实际情况,ABCABCABCABCABC...的方式依次给A,B,C三台服务器;可以很简单使用"求余"来得到这样的效果;
C,少使用(LeastUse/LeastActive)
服务器选择相对空闲的机器为下一个请求的处理者。这相对要复杂一些,因为实现这个策略需要负载设备和ABC三台台服务器通信,得到AB和C的资源使用情况,当然对每个后端的服务维护一个简单的计数器,调度小活跃的机器也是一个很好的办法。
D,人为权重
本质上和随机没什么区别只是加入了人为因子(我们可以理解随机策略往往得不到理想中均衡,需要人为设置一些因子作为调整,从而得到一个相对均衡)
E,HASH
使用hash或者一致性hash是一种很好策略。
F,你可以按照你的喜好和场景实现不同的策略~