基于SDN网络的负载均衡研究与实现

为何须要软件定义网络

1.网络缺少可扩展性,创新正在停滞不前。python

  咱们最新的研究发现,几乎每两个组织中就有一个认为须要将网络功能扩展为采用SDN的主要业务触发因素,而不是其余催化剂。这一统计数据一点都不使人惊讶,咱们的客户须要一个足够灵活的网络来支持业务,由于每一个功能都试图对不断变化的市场条件作出更快速的响应。
  这一挑战与行业无关:在几乎全部能够想象到的行业中,企业都在尝试支持愈来愈多的应用程序和设备,由于它们在其产品和服务中添加了新的性能。网络容量和复杂性每每会阻碍这种发展,至少会延迟企业的创新能力。
  SDN为此问题提供了潜在的解决方案。它提供了一种从中心管理网络功能的方法,实现从单个设备(而不是逐个设备)跨多个设备对应用程序的更改。随着组织需求的发展,这大大减小了扩展所需的时间。算法

2.因为缺少速度,市场机会正在丧失express

  任何IT战略都必须以其目标业务成果为基础,即应该支持竞争优点。在机会窗口愈来愈短暂的市场中,若是企业没法快速创新,这种优点将会丧失。
  这是采用SDN的关键驱动因素。SDN是一种集中的,基于策略的IT资产管理方式,这意味着企业能够更快地进行创新。每一个新的应用程序均可以从中心推出,设备能够经过与控制器的连接和已经设置的新策略自动配置自本身。
  在一个顾客的需求必须获得知足,但变化迅速且不可预测的世界里,SDN能够弥补咱们日益看到的“快速失败”心理差距。新产品和服务到达目标市场的速度要更快,而且能够随时更新或更换。apache

3.公司但愿快速创新安全

  公司还告诉咱们,拥有敏捷性和灵活性来改善跨业务的服务是相当重要的。所以,SDN部署的速度被视为另外一个SDN被采用的驱动力。使每一个业务部门更快地独立相当重要。在咱们合做的许多业务中,听到不一样的部门都在尝试对相互独立的创新,但却发现它们的it基础设施不容许它们以指望的速度前进,这种状况并很多见。对于须要在工做以外访问按需服务的业务用户来讲,这是使人沮丧的。
  在此背景下,SDN的出现进一步鼓励了组织的创新能力。这种创新能力体如今它可以在多大程度上试验和推出新的计划,不管是内部仍是面向客户。SDN为网络复杂性提供了实用的解决方案,不然将威胁到实验和转型。服务器

4.安全问题阻碍了创造力网络

  在一个组织从未如此意识到网络安全和威胁程度不断提升的世界中,对重大漏洞或失败的恐惧会抑制创新。企业担忧移动太快或与新合做伙伴合做会使他们面临更多漏洞。能够理解的是,他们的反应是关注弹性,但这每每会损害改善跨业务的服务敏捷性。
  SDN能够在技术和实践方面加强企业安全性。一方面,承载加密流量的全封闭网络本质上比企业的传统网络解决方案更安全。另外一方面,SDN为组织提供了在用户的虚拟环境中构建现有应用程序安全性的机会。
这意味着企业可以更好地管理其It弹性,同时知足它们对创新的迫切追求。架构

5.效率对于长期创新相当重要并发

  若是在这个转型的新世界中,快速失败是许多组织的一个重要原则,那么失败也是廉价的。当他们尝试新的应用程序和试用新产品和服务时,面对昂贵且繁琐的IT基础设施,企业将很快不堪重负。
  SDN在中心进行管理,无需为应用程序的每次新迭代从新配置单个设备,这可能具备巨大的价值。但更长远的机遇多是将采用SDN做为向网络转型迈进的一部分,由于企业级虚拟化将为将来五年及之后的挑战提供一个精益高效的组织。app

负载均衡在新兴网络环境下的改变

  在复杂多变的网络环境下保证网络服务的稳定性和效率,是负载均衡机制解决的一个重要问题,因为传统网络架构自身存在的缺点,负载均衡很难有大的突破,随着新型网络体系SDN的提出,能够从另外一种思路出发,为负载均衡机制的改进提出新的突破,本文经过在以OpenFlow为表明的SDN架构下实施负载均衡策略,以期提升网络性能。

负载均衡经常使用算法

  软件负载均衡是指使用软件的方式来分发和均衡流量。软件负载均衡,分为7层协议和4层协议。网络协议有七层,基于第四层传输层来作流量分发的方案称为4层负载均衡,例如LVS,而基于第七层应用层来作流量分发的称为7层负载均衡,例如Nginx。
  这两种在性能和灵活性上是有些区别的。基于4层的负载均衡性能要高一些,通常能达到 几十万/秒的处理量,而基于7层的负载均衡处理量通常只在几万/秒。基于软件的负载均衡的特色也很明显,便宜。在正常的服务器上部署便可,无需额外采购,就是投入一点技术去优化优化便可,所以这种方式是互联网公司中用得最多的一种方式。SDN的负载均衡天然也属于软件负载均衡的范畴。

1.随机算法

  Random随机,按权重设置随机几率。在一个截面上碰撞的几率高,但调用量越大分布越均匀,并且按几率使用权重后也比较均匀,有利于动态调整提供者权重。

2.轮询及加权轮询

  轮询(RoundRobbin)当服务器群中各服务器的处理能力相同时,且每笔业务处理量差别不大时,最适合使用这种算法。轮循,按公约后的权重设置轮循比率。存在慢的提供者累积请求问题,好比:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,长此以往,全部请求都卡在调到第二台上。加权轮询(Weighted Round Robbin)为轮询中的每台服务器附加必定权重的算法。好比服务器1权重1,服务器2权重2,服务器3权重3,则顺序为1-2-2-3-3-3-1-2-2-3-3-3- ......

3.最小链接及加权最小链接

  最少链接(LeastConnections)在多个服务器中,与处理链接数(会话数)最少的服务器进行通讯的算法。即便在每台服务器处理能力各不相同,每笔业务处理量也不相同的状况下,也可以在必定程度上下降服务器的负载。
加权最少链接(WeightedLeastConnection)为最少链接算法中的每台服务器附加权重的算法,该算法事先为每台服务器分配处理链接的数量,并将客户端请求转至链接数最少的服务器上。

4.哈希算法

  一致性Hash,相同参数的请求老是发到同一提供者。当某一台提供者挂时,本来发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引发剧烈变更。

5.IP地址散列

  经过管理发送方IP和目的地IP地址的散列,未来自同一发送方的分组(或发送至同一目的地的分组)统一转发到相同服务器的算法。当客户端有一系列业务须要处理而必须和一个服务器反复通讯时,该算法可以以流(会话)为单位,保证来自相同客户端的通讯可以一直在同一服务器中进行处理。

6.URL散列

  经过管理客户端请求URL信息的散列,将发送至相同URL的请求转发至同一服务器的算法。

解决方案

  在多个用户并发访问台服务器的时候,服务器可能会出现性能降低甚至宕机的状况。为解决此种状况,咱们组提出的方案是将用户的访问流量分担不一样的服务器上,也就是负载均衡的实现。目前传统网络的负载均衡存在硬件设备高成本和架构难的特色。所以咱们的方案是用软件定义网络(SDN)来实现网络流量的负载均衡。在独立的SDN控制器POX控制器,经过python脚本实现与部署该方案。

1、负载均衡架构

  SDN的负载均衡的实现架构部署为三层,分别为数据层、控制层、应用层。POX控制器用Restful API实现南向接口链接控制层与应用层,北向接口链接至Mininet软件的拓扑网络。主机以GET方法请求POX控制器的内容,POX控制器监测网络数据并下发流表到各个交换机,交换机按照流标进行数据传输,选择不一样的服务器,完成流量的负载均衡,增大吞吐量,减小服务端的压力。如图所示为三层架构。

2、策略算法

ip_loadbalancer.py

  官方随机算法实现的一种负载均衡代码以下

展开查看
#Copyright 2013,2014 James McCauley
#
#Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.

"""
A very sloppy IP load balancer.

Run it with --ip=
   
   
   

  
   
  --servers=IP1,IP2,... By default, it will do load balancing on the first switch that connects. If you want, you can add --dpid= 
 
   
     to specify a particular switch. Please submit improvements. :) """ from pox.core import core import pox log = core.getLogger("iplb") from pox.lib.packet.ethernet import ethernet, ETHER_BROADCAST from pox.lib.packet.ipv4 import ipv4 from pox.lib.packet.arp import arp from pox.lib.addresses import IPAddr, EthAddr from pox.lib.util import str_to_bool, dpid_to_str, str_to_dpid import pox.openflow.libopenflow_01 as of import time import random FLOW_IDLE_TIMEOUT = 10 FLOW_MEMORY_TIMEOUT = 60 * 5 class MemoryEntry (object): """ Record for flows we are balancing Table entries in the switch "remember" flows for a period of time, but rather than set their expirations to some long value (potentially leading to lots of rules for dead connections), we let them expire from the switch relatively quickly and remember them here in the controller for longer. Another tactic would be to increase the timeouts on the switch and use the Nicira extension which can match packets with FIN set to remove them when the connection closes. """ def __init__ (self, server, first_packet, client_port): self.server = server self.first_packet = first_packet self.client_port = client_port self.refresh() def refresh (self): self.timeout = time.time() + FLOW_MEMORY_TIMEOUT @property def is_expired (self): return time.time() > self.timeout @property def key1 (self): ethp = self.first_packet ipp = ethp.find('ipv4') tcpp = ethp.find('tcp') return ipp.srcip,ipp.dstip,tcpp.srcport,tcpp.dstport @property def key2 (self): ethp = self.first_packet ipp = ethp.find('ipv4') tcpp = ethp.find('tcp') return self.server,ipp.srcip,tcpp.dstport,tcpp.srcport class iplb (object): """ A simple IP load balancer Give it a service_ip and a list of server IP addresses. New TCP flows to service_ip will be randomly redirected to one of the servers. We probe the servers to see if they're alive by sending them ARPs. """ def __init__ (self, connection, service_ip, servers = []): self.service_ip = IPAddr(service_ip) self.servers = [IPAddr(a) for a in servers] self.con = connection self.mac = self.con.eth_addr self.live_servers = {} # IP -> MAC,port try: self.log = log.getChild(dpid_to_str(self.con.dpid)) except: # Be nice to Python 2.6 (ugh) self.log = log self.outstanding_probes = {} # IP -> expire_time # How quickly do we probe? self.probe_cycle_time = 5 # How long do we wait for an ARP reply before we consider a server dead? self.arp_timeout = 3 # We remember where we directed flows so that if they start up again, # we can send them to the same server if it's still up. Alternate # approach: hashing. self.memory = {} # (srcip,dstip,srcport,dstport) -> MemoryEntry self._do_probe() # Kick off the probing # As part of a gross hack, we now do this from elsewhere #self.con.addListeners(self) def _do_expire (self): """ Expire probes and "memorized" flows Each of these should only have a limited lifetime. """ t = time.time() # Expire probes for ip,expire_at in self.outstanding_probes.items(): if t > expire_at: self.outstanding_probes.pop(ip, None) if ip in self.live_servers: self.log.warn("Server %s down", ip) del self.live_servers[ip] # Expire old flows c = len(self.memory) self.memory = {k:v for k,v in self.memory.items() if not v.is_expired} if len(self.memory) != c: self.log.debug("Expired %i flows", c-len(self.memory)) def _do_probe (self): """ Send an ARP to a server to see if it's still up """ self._do_expire() server = self.servers.pop(0) self.servers.append(server) r = arp() r.hwtype = r.HW_TYPE_ETHERNET r.prototype = r.PROTO_TYPE_IP r.opcode = r.REQUEST r.hwdst = ETHER_BROADCAST r.protodst = server r.hwsrc = self.mac r.protosrc = self.service_ip e = ethernet(type=ethernet.ARP_TYPE, src=self.mac, dst=ETHER_BROADCAST) e.set_payload(r) #self.log.debug("ARPing for %s", server) msg = of.ofp_packet_out() msg.data = e.pack() msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD)) msg.in_port = of.OFPP_NONE self.con.send(msg) self.outstanding_probes[server] = time.time() + self.arp_timeout core.callDelayed(self._probe_wait_time, self._do_probe) @property def _probe_wait_time (self): """ Time to wait between probes """ r = self.probe_cycle_time / float(len(self.servers)) r = max(.25, r) # Cap it at four per second return r def _pick_server (self, key, inport): """ Pick a server for a (hopefully) new connection """ return random.choice(self.live_servers.keys()) def _handle_PacketIn (self, event): inport = event.port packet = event.parsed def drop (): if event.ofp.buffer_id is not None: # Kill the buffer msg = of.ofp_packet_out(data = event.ofp) self.con.send(msg) return None tcpp = packet.find('tcp') if not tcpp: arpp = packet.find('arp') if arpp: # Handle replies to our server-liveness probes if arpp.opcode == arpp.REPLY: if arpp.protosrc in self.outstanding_probes: # A server is (still?) up; cool. del self.outstanding_probes[arpp.protosrc] if (self.live_servers.get(arpp.protosrc, (None,None)) == (arpp.hwsrc,inport)): # Ah, nothing new here. pass else: # Ooh, new server. self.live_servers[arpp.protosrc] = arpp.hwsrc,inport self.log.info("Server %s up", arpp.protosrc) return # Not TCP and not ARP. Don't know what to do with this. Drop it. return drop() # It's TCP. ipp = packet.find('ipv4') if ipp.srcip in self.servers: # It's FROM one of our balanced servers. # Rewrite it BACK to the client key = ipp.srcip,ipp.dstip,tcpp.srcport,tcpp.dstport entry = self.memory.get(key) if entry is None: # We either didn't install it, or we forgot about it. self.log.debug("No client for %s", key) return drop() # Refresh time timeout and reinstall. entry.refresh() #self.log.debug("Install reverse flow for %s", key) # Install reverse table entry mac,port = self.live_servers[entry.server] actions = [] actions.append(of.ofp_action_dl_addr.set_src(self.mac)) actions.append(of.ofp_action_nw_addr.set_src(self.service_ip)) actions.append(of.ofp_action_output(port = entry.client_port)) match = of.ofp_match.from_packet(packet, inport) msg = of.ofp_flow_mod(command=of.OFPFC_ADD, idle_timeout=FLOW_IDLE_TIMEOUT, hard_timeout=of.OFP_FLOW_PERMANENT, data=event.ofp, actions=actions, match=match) self.con.send(msg) elif ipp.dstip == self.service_ip: # Ah, it's for our service IP and needs to be load balanced # Do we already know this flow? key = ipp.srcip,ipp.dstip,tcpp.srcport,tcpp.dstport entry = self.memory.get(key) if entry is None or entry.server not in self.live_servers: # Don't know it (hopefully it's new!) if len(self.live_servers) == 0: self.log.warn("No servers!") return drop() # Pick a server for this flow server = self._pick_server(key, inport) self.log.debug("Directing traffic to %s", server) entry = MemoryEntry(server, packet, inport) self.memory[entry.key1] = entry self.memory[entry.key2] = entry # Update timestamp entry.refresh() # Set up table entry towards selected server mac,port = self.live_servers[entry.server] actions = [] actions.append(of.ofp_action_dl_addr.set_dst(mac)) actions.append(of.ofp_action_nw_addr.set_dst(entry.server)) actions.append(of.ofp_action_output(port = port)) match = of.ofp_match.from_packet(packet, inport) msg = of.ofp_flow_mod(command=of.OFPFC_ADD, idle_timeout=FLOW_IDLE_TIMEOUT, hard_timeout=of.OFP_FLOW_PERMANENT, data=event.ofp, actions=actions, match=match) self.con.send(msg) #Remember which DPID we're operating on (first one to connect) _dpid = None def launch (ip, servers, dpid = None): global _dpid if dpid is not None: _dpid = str_to_dpid(dpid) servers = servers.replace(","," ").split() servers = [IPAddr(x) for x in servers] ip = IPAddr(ip) #We only want to enable ARP Responder *only* on the load balancer switch, #so we do some disgusting hackery and then boot it up. from proto.arp_responder import ARPResponder old_pi = ARPResponder._handle_PacketIn def new_pi (self, event): if event.dpid == _dpid: #Yes, the packet-in is on the right switch return old_pi(self, event) ARPResponder._handle_PacketIn = new_pi #Hackery done. Now start it. from proto.arp_responder import launch as arp_launch arp_launch(eat_packets=False,**{str(ip):True}) import logging logging.getLogger("proto.arp_responder").setLevel(logging.WARN) def _handle_ConnectionUp (event): global _dpid if _dpid is None: _dpid = event.dpid if _dpid != event.dpid: log.warn("Ignoring switch %s", event.connection) else: if not core.hasComponent('iplb'): # Need to initialize first... core.registerNew(iplb, event.connection, IPAddr(ip), servers) log.info("IP Load Balancer Ready.") log.info("Load Balancing on %s", event.connection) # Gross hack core.iplb.con = event.connection event.connection.addListeners(core.iplb) core.openflow.addListenerByName("ConnectionUp", _handle_ConnectionUp) 
    

  

解决方案

  本次方案是在SDNHub_tutorial_VM_64(固然你也能够在乌班图上进行)系统上实现的。控制器使用POX,虚拟拓扑的搭建使用Mininet,服务器使用python建立简易的HTTP服务器。进入系统后,经过”sudo mn –topo single,6 –controller=remote,port=6633”建立一个简单的拓扑图,其中6633指POX控制器的端口号。
step1.建立拓扑

  实验拓扑如图所示,由六台主机地址为10.0.0.x(1-6)和交换机组成,POX控制器链接交换机。

step2.打开服务器
  经过xterm[host]打开主机h1和h2,h1和h2做为服务器实验中对服务器的要求不是那么高,因此服务器选用python的server模块中的SimpleHTTPServer做为HTTP服务器来响应请求包,HTTP服务器端口设置为80。

step3.控制器与负载均衡策略
  sudo ./pox.py log.level –DEBUG misc.ip_loadbalanced行POX控制器并同时打开了ip_loadbalancer,ip_loadbalancer主要负载均衡的策略实现,POX控制器用于处理流量。当出现“IP LOAD BALANCER READY”和“Server up”表示运行成功,负载均衡和HTTP服务器已开启。

step4.发送请求
  打开其余的主机,做为发送请求的主机,经过curl指令,对服务器server1和server2发起METHOD为GET的请求,发送一个Request packet。请求成功,服务器会回送一个网页信息。

  同时使用多台主机,重复请求控制器屡次,观察POX的流量路径走向。分析流量,能够观察到多台主机请求控制器,最终请求到的服务器不同,流量的走向也不同。
在服务器端,h1和h2上能够对收到的包进行拆包处理,在GET请求的这些过程当中,h1和h2并行工做,而且每次一样的请求不会在同一台服务器模拟上进行处理。

step5.抓包测试
  为了确保实验结果的偶然性,咱们重复进行屡次测试,且使用wireshark进行抓包分析。.此次不进行大量重复发包实验,而是使用阶段性发包处理,第一次发送Request packet包后,间隔一段时间后再次发送一个包给控制器。以下为第一次和第二结果的抓包结果,能够看到第一次处理的服务器为h2,第二次处理的服务器为h1。
第一次:
第二次:

实验视频(英语好的朋友能够尝试跟着作一遍)>youtube

相关文章
相关标签/搜索