面向服务的体系架构 SOA(二) --- 服务的路由和负载均衡
2. 服务的路由和负载均衡
1.2.1 服务化的演变
SOA设计思想:分布式应用架构体系对于业务逻辑复用的需求十分强烈,上层业务都想借用已有的底层服务来快速搭建更多、更丰富的应用,降低新业务开展的人力和时间成本,快速满足瞬息万变的市场需求。公共的业务被拆分出来行程可共用的服务,最大程度的保障了代码和逻辑的复用,避免重复建设,这种设计也称为SOA 。
服务的路由:SOA架构中,服务消费者通过服务名称,在众多的服务器中找到要调用的服务的地址列表,称为服务的路由。
负载均衡:对于负载较高的服务来说,往往对应着由多台服务器组成的集群,在请求到来时,为了将请求均衡的分配到后端服务器,负载均衡程序将从服务器对应的地址列表中,通过相应的负载均衡算法和规则,选取一台服务器进行访问,这个过程称为服务的负载均衡。
服务配置中心:当服务器数量庞大时,人工来管理和维护服务及地址的配置信息变得困难,依赖单一的硬件负载均衡设备或者使用LVS、Nginx等软件方案进行路由和负载均衡调度也容易引发单点问题,一旦服务路由或者负载均衡服务器宕机,依赖它的所有服务均将失效。此时,需要一个能够动态注册和获取服务信息的地方来统一管理服务名称和其对应的服务器列表信息,称为服务配置中心。
服务配置中心的意义:服务提供者在启动时,会将其提供的服务名称、服务器地址注册到服务配置中心,服务消费者通过服务配置中心来获得需要调用的服务的机器列表,通过相应的负载均衡算法,选取其中一台服务器进行调用。当服务器宕机或者下线时,相应的机器需要动态的从服务配置中心移除,并通知相应的服务消费者,防止其调用到已经失效的服务而发生错误。在这个过程中,服务消费者只有在第一次调用服务时需要查询服务配置中心,然后将查询到的信息缓存到本地,后面的调用直接使用缓存的服务地址列表信息即可,直到服务的地址列表有变更(机器上线或者下线)。这种无中心化的结构解决了单点故障问题,并且大大减轻了服务配置中心的压力。
1.2.2 负载均衡算法
常见的负载均衡算法包括:轮询法、随机法、源地址哈希法、加权轮询法、加权随机法、最小连接法等,根据不同场景需要选取不同的算法。
我们暂时将服务器地址及其权重置于map中:
1 serverWeightMap.put("192.168.1.10", 1); 2 serverWeightMap.put("192.168.1.11", 1); 3 serverWeightMap.put("192.168.1.12", 3); 4 serverWeightMap.put("192.168.1.13", 1); 5 serverWeightMap.put("192.168.1.14", 5); 6 serverWeightMap.put("192.168.1.15", 2); 7 serverWeightMap.put("192.168.1.16", 1);
1)轮询法:
将请求按顺序轮流的分配到后端服务器上,它均衡的对待后端每一台服务器,而不关心服务器实际的连接数和当前的系统负载。
1 public String getServerIP() { 2 //新建map,避免出现服务器上线、下线、宕机引发的并发问题、数组越界等 3 Map<String, Integer> serverMap = new HashMap<String, Integer>(); 4 serverMap.putAll(serverWeightMap); 5 //取得服务器列表list 6 List<String> serverList = new ArrayList<String>(); 7 serverList.addAll(serverMap.keySet()); 8 9 String server = null; 10 Integer pos = 0;//当前服务器位置 11 synchronized (pos) { 12 if (pos >= serverList.size()) { 13 pos = 0; 14 } 15 server = serverList.get(pos); 16 pos++; 17 } 18 return server; 19 }
将服务器地址先复制到本地,可以避免被多个线程修改,但这样也会引发新的问题,当出现新增或者下线服务器时,负载均衡算法将无法获知,因此,在服务消费者的实现端需要考虑该问题并进行相应的容错处理,比如重新发起一次调用。轮询的位置pos添加synchronized锁可防止pos变量被并发修改,导致数组越界问题,但也导致轮询代码的并发吞吐量发生明显的下降。
2)随机法:
1 public String getServerIP() { 2 //新建map,避免出现服务器上线、下线引发的并发问题 3 Map<String, Integer> serverMap = new HashMap<String, Integer>(); 4 serverMap.putAll(serverWeightMap); 5 //取得服务器列表list 6 List<String> serverList = new ArrayList<String>(); 7 serverList.addAll(serverMap.keySet()); 8 9 java.util.Random random = new java.util.Random(); 10 int pos = random.nextInt(serverList.size()); 11 return serverList.get(pos); 12 }
由概率统计理论可知,随着调用量的增大,随机法的效果会越来越接近轮询的效果。因此,你还会考虑一定要使用需要付出一定性能代价的轮询算法吗?
3)源地址哈希(Hash)法
源地址哈希的思想是获取客户端访问的IP地址值,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是要访问的服务器的需要。这样相同IP地址的客户端,当后端服务器列表不变时,它都会被映射到同一个后端服务器。
1 public String getServerIP(String targetIP) { 2 //新建map,避免出现服务器上线、下线引发的并发问题 3 Map<String, Integer> serverMap = new HashMap<String, Integer>(); 4 serverMap.putAll(serverWeightMap); 5 //取得服务器列表list 6 List<String> serverList = new ArrayList<String>(); 7 serverList.addAll(serverMap.keySet()); 8 9 int hashcode = targetIP.hashCode(); 10 int pos = hashcode % serverList.size(); 11 return serverList.get(pos); 12 }
源地址哈希法可以在服务消费者与服务提供者之间建立有状态的session会话。
4)加权轮询法:
1 public String getServerIP() { 2 //新建map,避免出现服务器上线、下线引发的并发问题 3 Map<String, Integer> serverMap = new HashMap<String, Integer>(); 4 serverMap.putAll(serverWeightMap); 5 Set<String> keySet = serverMap.keySet(); 6 Iterator<String> iterator = keySet.iterator(); 7 //新建空的服务器列表list 8 List<String> serverList = new ArrayList<String>(); 9 while(iterator.hasNext()) { 10 String server = iterator.next(); 11 Integer weight = serverMap.get(server); 12 //若该服务器的权重为4,则在服务器列表中添加四个该服务器 13 for (int i = 0; i < weight; i++) { 14 serverList.add(server); 15 } 16 } 17 Integer pos = 0; 18 String server = null; 19 synchronized (pos) { 20 if (pos >= serverList.size()) { 21 pos = 0; 22 } 23 server = serverList.get(pos); 24 pos++; 25 } 26 return server; 27 }
5)加权随机法:
1 public String getServerIP() { 2 //新建map,避免出现服务器上线、下线引发的并发问题 3 Map<String, Integer> serverMap = new HashMap<String, Integer>(); 4 serverMap.putAll(serverWeightMap); 5 Set<String> keySet = serverMap.keySet(); 6 Iterator<String> iterator = keySet.iterator(); 7 //新建空的服务器列表list 8 List<String> serverList = new ArrayList<String>(); 9 while(iterator.hasNext()) { 10 String server = iterator.next(); 11 Integer weight = serverMap.get(server); 12 //若该服务器的权重为4,则在服务器列表中添加四个该服务器 13 for (int i = 0; i < weight; i++) { 14 serverList.add(server); 15 } 16 } 17 18 Random random = new Random(); 19 int pos = random.nextInt(serverList.size()); 20 return serverList.get(pos); 21 }
6)最小连接法:
以上算法我们都是为了平均给后端服务器分配工作量,最大程度的提高服务器的利用率,但是实际情况并非一定如此,我们可以从算法实施的角度来看,以后端服务器的视角来观察系统的负载,而非请求发起方来观察。因此,我们需要有其他的算法来实现可供选择,最小连接法变属于此类算法。
最小连接法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理快慢不同,它正是根据后端服务器当前的连接情况动态选取当前积压连接数最少的一台服务器来处理当前请求,尽可能的提高后端服务器的利用效率,将负载合理的分流到每一台机器。