먼저 로드 밸런싱이 무엇인지 소개하겠습니다(백과사전에서 발췌)
로드 밸런싱은 기존 네트워크 구조를 활용하여 네트워크 장치와 서버의 대역폭을 확장하고, 처리량을 늘리며, 네트워크 데이터 처리 기능을 강화하고, 네트워크 유연성과 가용성을 향상시키는 저렴하고 효과적이며 투명한 방법을 제공합니다.
로드 밸런싱, 영어 이름은 Load Balance입니다. 이는 웹 서버, FTP 서버, 기업 키 애플리케이션 서버 및 기타 미션 크리티컬 서버 등과 같은 여러 운영 단위에 실행을 할당한다는 의미입니다. 함께 작업을 완료합니다.
이번 글에서는 "외부에서 특정 서버로 보내는 요청을 대칭 구조로 균등하게 분배"하기 위한 다양한 알고리즘에 대해 이야기하고, 각 알고리즘의 구체적인 구현을 Java 코드로 보여드리겠습니다. 본 주제에 들어가기 전에 먼저 IP 목록을 시뮬레이션하는 클래스를 작성하세요.
import java.util.HashMap; /** * @author [email protected] * @date 二月 07, 2017 */ public class IpMap { // 待路由的Ip列表,Key代表Ip,Value代表该Ip的权重 public static HashMap<String, Integer> serverWeightMap = new HashMap<String, Integer>(); static { serverWeightMap.put("192.168.1.100", 1); serverWeightMap.put("192.168.1.101", 1); // 权重为4 serverWeightMap.put("192.168.1.102", 4); serverWeightMap.put("192.168.1.103", 1); serverWeightMap.put("192.168.1.104", 1); // 权重为3 serverWeightMap.put("192.168.1.105", 3); serverWeightMap.put("192.168.1.106", 1); // 权重为2 serverWeightMap.put("192.168.1.107", 2); serverWeightMap.put("192.168.1.108", 1); serverWeightMap.put("192.168.1.109", 1); serverWeightMap.put("192.168.1.110", 1); } }
라운드 로빈 스케줄링 알고리즘의 원리는 IP 목록에서 IP 주소를 보내는 것입니다. 매번 사용자 요청은 1부터 시작하여 N(내부 서버 수)까지 차례로 내부 서버에 할당되고 주기 가 다시 시작됩니다. 알고리즘의 장점은 단순성입니다. 모든 현재 연결 상태를 기록할 필요가 없으므로 상태 비저장 스케줄링입니다.
코드 구현은 대략 다음과 같습니다.
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author [email protected] * @date 二月 07, 2017 */ class RoundRobin { private static Integer pos = 0; public static String getServer() { // 重建一个Map,避免服务器的上下线导致的并发问题 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); keyList.addAll(keySet); String server = null; synchronized (pos) { if (pos > keySet.size()) pos = 0; server = keyList.get(pos); pos ++; } return server; } }
serverWeightMap의 주소 목록은 동적이므로 머신은 언제든지 온라인, 오프라인 또는 다운될 수 있습니다. 가능한 동시성 문제를 피하려면 메서드 내에서 새 로컬 변수 serverMap을 생성해야 합니다. 이제 여러 스레드에 의해 수정되는 것을 방지하기 위해 serverMap의 내용을 스레드 로컬에 복사합니다. 이로 인해 새로운 문제가 발생할 수 있습니다. 복제 후 serverWeightMap에 대한 수정 사항은 serverMap에 반영되지 않습니다. 즉, 이 서버 선택 중에 로드 밸런싱 알고리즘은 새 서버가 추가되었는지 오프라인 서버가 추가되었는지 알 수 없습니다. 서버를 새로 추가해도 상관없습니다. 서버가 오프라인 상태가 되거나 충돌이 발생하면 존재하지 않는 주소에 액세스할 수도 있습니다. 따라서 서비스 호출자는 서버 선택 및 호출을 다시 시작하는 등 해당 내결함성 처리를 수행해야 합니다.
현재 폴링된 위치 변수 pos의 경우 서버 선택 순서를 보장하기 위해 작업 중에 하나의 스레드만 동시에 pos 값을 수정할 수 있도록 잠가야 합니다. pos 변수를 동시에 수정하는 경우 서버 선택 순서를 보장할 수 없으며, keyList 배열이 범위를 벗어날 수도 있습니다.
폴링 방식의 장점은 요청 전송에서 절대적인 균형을 이루려고 한다는 것입니다.
폴링 방식의 단점은 요청 전송의 절대적인 균형을 이루기 위해서는 상당한 비용을 지불해야 한다는 것입니다. 왜냐하면 pos 변수 수정의 상호 배제를 보장하기 위해 무거운 비관적 잠금 동기화 이는 이 폴링 코드의 동시 처리량을 크게 떨어뜨립니다.
시스템의 랜덤 알고리즘을 통해 백엔드 서버의 리스트 사이즈 값을 기준으로 서버 중 하나를 랜덤하게 선택하여 접속하게 됩니다. 클라이언트가 서버를 호출하는 횟수가 증가할수록
실제 효과는 백엔드에서 각 서버에 대한 호출을 균등하게 분배하는 것에 점점 가까워진다는 것을 확률과 통계 이론으로 알 수 있으며, 이는 폴링의 결과입니다.
Random 메서드의 코드 구현은 대략 다음과 같습니다.
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author [email protected] * @date 二月 07, 2017 */ class Random { public static String getServer() { // 重建一个Map,避免服务器的上下线导致的并发问题 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); keyList.addAll(keySet); java.util.Random random = new java.util.Random(); int randomPos = random.nextInt(keyList.size()); return keyList.get(randomPos); } }
전체적인 코드 아이디어는 폴링 메서드와 일치합니다. 먼저 serverMap을 다시 빌드한 다음 서버 목록을 가져옵니다. 서버를 선택할 때 Random의 nextInt 메소드를 사용하여 0~keyList.size() 범위의 임의의 값을 가져와서 서버 목록에서 서버 주소를 무작위로 얻어서 반환합니다. 확률 통계 이론에 따르면 처리량이 많을수록 무작위 알고리즘의 효과는 폴링 알고리즘의 효과에 더 가깝습니다.
소스 주소 해시의 개념은 클라이언트의 IP 주소를 기반으로 해시 함수를 통해 계산된 값을 구하고, 이 값을 사용하는 것입니다. 서버 목록을 비교하려면 크기에 대해 모듈로 연산을 수행하고 그 결과는 클라이언트가 서버에 액세스하려는 일련 번호입니다. 소스 주소 해시 방법은 로드 밸런싱에 사용됩니다. 백엔드 서버 목록이 변경되지 않은 경우 동일한 IP 주소를 가진 클라이언트는 매번 동일한 백엔드 서버에 매핑됩니다.
소스 주소 해시 알고리즘의 코드 구현은 대략 다음과 같습니다.
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author [email protected] * @date 二月 07, 2017 */ class Hash { public static String getServer() { // 重建一个Map,避免服务器的上下线导致的并发问题 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); keyList.addAll(keySet); // 在Web应用中可通过HttpServlet的getRemoteIp方法获取 String remoteIp = "127.0.0.1"; int hashCode = remoteIp.hashCode(); int serverListSize = keyList.size(); int serverPos = hashCode % serverListSize; return keyList.get(serverPos); } }
처음 두 부분은 폴링 방식과 동일하며, 차이점은 라우팅 부분에 있습니다. . 클라이언트의 IP(remoteIp)를 통해 해당 해시 값을 얻고 서버 목록의 크기를 모듈로로 구합니다. 결과는 서버 목록에서 선택한 서버의 index 값입니다.
소스 주소 해싱의 장점은 백엔드 서버 목록이 변경될 때까지 동일한 클라이언트 IP 주소가 동일한 백엔드 서버로 해싱된다는 것입니다. 이 기능에 따르면 서비스 소비자와 서비스 제공자 간에 상태 저장 세션이 설정될 수 있습니다.
源地址哈希算法的缺点在于:除非集群中服务器的非常稳定,基本不会上下线,否则一旦有服务器上线、下线,那么通过源地址哈希算法路由到的服务器是服务器上线、下线前路由到的服务器的概率非常低,如果是session则取不到session,如果是缓存则可能引发"雪崩"。如果这么解释不适合明白,可以看我之前的一篇文章MemCache超详细解读,一致性Hash算法部分。
不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。加权轮询法的代码实现大致如下:
import java.util.*; /** * @author [email protected] * @date 二月 07, 2017 */ class WeightRoundRobin { private static Integer pos; public static String getServer() { // 重建一个Map,避免服务器的上下线导致的并发问题 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); Iterator iterator = keySet.iterator(); List serverList = new ArrayList(); while (iterator.hasNext()) { String server = iterator.next(); int weight = serverMap.get(server); for (int i = 0; i < weight; i++) serverList.add(server); } String server = null; synchronized (pos) { if (pos > keySet.size()) pos = 0; server = serverList.get(pos); pos ++; } return server; } }
与轮询法类似,只是在获取服务器地址之前增加了一段权重计算的代码,根据权重的大小,将地址重复地增加到服务器地址列表中,权重越大,该服务器每轮所获得的请求数量越多。
与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。
import java.util.*; /** * @author [email protected] * @date 二月 07, 2017 */ class WeightRandom { public static String getServer() { // 重建一个Map,避免服务器的上下线导致的并发问题 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); Iterator iterator = keySet.iterator(); List serverList = new ArrayList(); while (iterator.hasNext()) { String server = iterator.next(); int weight = serverMap.get(server); for (int i = 0; i < weight; i++) serverList.add(server); } java.util.Random random = new java.util.Random(); int randomPos = random.nextInt(serverList.size()); return serverList.get(randomPos); } }
这段代码相当于是随机法和加权轮询法的结合,比较好理解,就不解释了。
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前
积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。
前面几种方法费尽心思来实现服务消费者请求次数分配的均衡,当然这么做是没错的,可以为后端的多台服务器平均分配工作量,最大程度地提高服务器的利用率,但是实际情况是否真的如此?实际情况中,请求次数的均衡真的能代表负载的均衡吗?这是一个值得思考的问题。
上面的问题,再换一个角度来说就是:以后端服务器的视角来观察系统的负载,而非请求发起方来观察。最小连接数法便属于此类。
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能地提高后端服务器的利用效率,将负载合理地分流到每一台机器。由于最小连接数设计服务器连接数的汇总和感知,设计与实现较为繁琐,此处就不说它的实现了。
위 내용은 Java는 여러 로드 밸런싱 알고리즘을 구현합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!