分佈式系統的負載均衡模塊的設計與實現
應用場景
某分佈式系統在其業務處理過程中需要通過網絡連接調用下游部件提供的服務,即發送請求給下游部件。下游部件是一個集羣環境(即多臺主機對外提供相同的服務)。因此,該系統調用其下游部件服務的時候需要進行負載均衡控制,即保證下游部件的各臺主機上接收到的請求數分佈均勻(統計意義上的均勻)。
場景分析
該系統在調用其下游部件時的負載均衡模塊需要在不重啓應用程序、服務器的情況下滿足以下幾點要求:
- 需要支持多種負載均衡算法,例如隨機輪詢算法和加權隨機輪詢算法等
- 需要支持在系統運行中動態調整負載均衡算法,如從使用隨機輪詢算法調整爲使用加權隨機輪詢算法。
- 在調用下游部件的過程中,下游部件中的非在線主機(如出現故障的主機)需要被排除在外,即發送給下游部件的請求不能派發給非在線主機(因爲那樣會導致請求處理失敗)
- 下游部件的節點信息可動態調整,如處於維護的需要臨時刪除一個節點過後又將其重新添加回來。
設計與實現
爲滿足要求1,使用LoadBalancer接口對負載均衡算法進行抽象,爲系統支持的每個負載均衡算法創建一個LoadBalancer實現類。
public interface LoadBalancer {
void updateCandidate(final Candidate candidate);
Endpoint nextEndpoint();
}
使用volatile變量修飾loadBalancer,滿足當系統的啓動線程或者配置管理線程更新變量loadBalancer的值之後,所有的業務線程在無須使用鎖的情況下也能夠讀取到更新後的loadBalancer變量值。即要求2
public class ServiceInvoker {
//保存當前類的唯一實例
private static final ServiceInvoker INSTANCE =new ServiceInvoker();
//負載均衡器實例,使用volatile變量保證可見性
private volatile LoadBalancer loadBlancer;
//私有構造器
private ServiceInvoker(){
//什麼也不做
}
/*
獲取當前類的唯一實例
*/
public static ServiceInvoker getInstance(){
return INSTANCE;
}
/**
* 根據指定的負載均衡器派發請求到特定的下游部件
* @param request
*/
public void dispatchRequest(Request request){
//這裏讀取volatile變量loadBlancer
Endpoint endpoint = getLoadBalancer().nextEndpoint();
if (null==endpoint){
// 省略其他代碼
return;
}
//將請求發給下游部件
dispatchToDownstream(request,endpoint);
}
//真正將指定的請求派發給下游部件
private void dispatchToDownstream(Request request, Endpoint endpoint){
Debug.info("Dispatch request to "+endpoint+":"+request);
//省略其他代碼
}
public LoadBalancer getLoadBalancer(){
//讀取負載均衡器實例
return loadBlancer;
}
public void setLoadBlancer(LoadBalancer loadBlancer){
//設置或者更新負載均衡器實例
this.loadBlancer = loadBlancer;
}
}
維護一個心跳線程來定時檢測下游部件各個節點的狀態,並根據檢測的結果來更新相應的節點的online實例變量,從而滿足要求3.
public class Endpoint {
public final String host;
public final int port;
public final int weight;
private volatile boolean online = true;
public Endpoint(String host, int port, int weight) {
this.host = host;
this.port = port;
this.weight = weight;
}
public boolean isOnline() {
return online;
}
public void setOnline(boolean online) {
this.online = online;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + port;
result = prime * result + weight;
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Endpoint other = (Endpoint) obj;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (port != other.port)
return false;
if (weight != other.weight)
return false;
return true;
}
public String toString() {
return "Endpoint [host=" + host + ", port=" + port + ", weight=" + weight + ", online=" + online + "]\n";
}
}
如果要變更下游部件的節點信息,那麼配置管理器只需要調整AbstractBalancer子類的updateCandidate方法即可。updateCandidate方法直接更新candidate變量的值,這裏volatile保證了這個操作的原子性和可見性。即滿足要求4.
public void updateCandidate(final Candidate candidate) {
if (null == candidate || 0 == candidate.getEndpointCount()) {
throw new IllegalArgumentException("Invalid candidate " + candidate);
}
//更新volatile變量candidate
this.candidate = candidate;
}
完整代碼
負載均衡接口:
public interface LoadBalancer {
void updateCandidate(final Candidate candidate);
Endpoint nextEndpoint();
}
負載均衡算法抽象類:
import java.util.Random;
import java.util.logging.Logger;
/**
* 負載均衡算法抽象實現類,所有負載均衡算法實現類的父類
*/
public abstract class AbstractLoadBalancer implements LoadBalancer {
private final static Logger LOGGER = Logger.getAnonymousLogger();
//使用volatile變量替代鎖(有條件替代)
protected volatile Candidate candidate;
protected final Random random;
//心跳線程
private Thread heartbeatThread;
public AbstractLoadBalancer(Candidate candidate) {
if (null == candidate || 0 == candidate.getEndpointCount()) {
throw new IllegalArgumentException("Invalid candidate " + candidate);
}
this.candidate = candidate;
random = new Random();
}
public synchronized void init() throws Exception {
if (null == heartbeatThread) {
heartbeatThread = new Thread(new HeartbeatTask(), "LB_Heartbeat");
heartbeatThread.start();
}
}
@Override
public void updateCandidate(final Candidate candidate) {
if (null == candidate || 0 == candidate.getEndpointCount()) {
throw new IllegalArgumentException("Invalid candidate " + candidate);
}
//更新volatile變量candidate
this.candidate = candidate;
}
/**
* 留給子類實現的抽象方法
*
* @return
*/
@Override
public abstract Endpoint nextEndpoint();
protected void monitorEndpoints() {
//讀取volatile變量
final Candidate currCandidate = candidate;
boolean isTheEndpointOnline;
//檢測下游部件狀態是否正常
for (Endpoint endpoint : currCandidate) {
isTheEndpointOnline = endpoint.isOnline();
if (doDetect(endpoint) != isTheEndpointOnline) {
endpoint.setOnline(!isTheEndpointOnline);
if (isTheEndpointOnline) {
LOGGER.log(java.util.logging.Level.SEVERE, endpoint + " offline!");
} else {
LOGGER.log(java.util.logging.Level.INFO, endpoint + " is online now!");
}
}
}
//for循環結束
}
//檢測指定的節點是否在線
private boolean doDetect(Endpoint endpoint) {
boolean online = true;
// 模擬待測服務器隨機故障
int rand = random.nextInt(1000);
if (rand <= 500) {
online = false;
}
return online;
}
private class HeartbeatTask implements Runnable {
@Override
public void run() {
try {
while (true) {
//檢測節點列表中的所有節點是否在線
monitorEndpoints();
Thread.sleep(2000);
}
} catch (InterruptedException e) {
//什麼也不做
}
}
}
//HeartbeatTask類結束
}
加權輪詢負載均衡算法:
/**
* 加權輪詢負載均衡算法實現類
*/
public class WeightedRoundRobinLoadBalancer extends AbstractLoadBalancer {
//私有構造器
private WeightedRoundRobinLoadBalancer(Candidate candidate) {
super(candidate);
}
//通過該靜態方法創建該類的實例
public static LoadBalancer newInstance(Candidate candidate) throws Exception {
WeightedRoundRobinLoadBalancer lb = new WeightedRoundRobinLoadBalancer(candidate);
lb.init();
return lb;
}
//在該方法中實現相應的負載均衡算法
@Override
public Endpoint nextEndpoint() {
Endpoint selectedEndpoint = null;
int subWeight = 0;
int dynamicTotalWeight;
final double rawRnd = super.random.nextDouble();
int rand;
//讀取volatile變量candidate
final Candidate candidate = super.candidate;
dynamicTotalWeight = candidate.totalWeight;
for (Endpoint endpoint : candidate) {
//選取節點以及計算總權重時跳過非在線節點
if (!endpoint.isOnline()) {
dynamicTotalWeight -= endpoint.weight;
continue;
}
rand = (int) (rawRnd * dynamicTotalWeight);
subWeight += endpoint.weight;
if (rand <= subWeight) {
selectedEndpoint = endpoint;
break;
}
}
return selectedEndpoint;
}
}
下游部件節點類:
/*
表示下游部件的節點
*/
public class Endpoint {
public final String host;
public final int port;
public final int weight;
private volatile boolean online = true;
public Endpoint(String host, int port, int weight) {
this.host = host;
this.port = port;
this.weight = weight;
}
public boolean isOnline() {
return online;
}
public void setOnline(boolean online) {
this.online = online;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + port;
result = prime * result + weight;
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Endpoint other = (Endpoint) obj;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (port != other.port)
return false;
if (weight != other.weight)
return false;
return true;
}
public String toString() {
return "Endpoint [host=" + host + ", port=" + port + ", weight=" + weight + ", online=" + online + "]\n";
}
}
下游部件節點列表:
import util.ReadOnlyIterator;
import java.util.Iterator;
import java.util.Set;
public final class Candidate implements Iterable<Endpoint> {
//下游部件節點列表
private final Set<Endpoint> endpoints;
//下游部件節點的總權重
public final int totalWeight;
public Candidate(Set<Endpoint> endpoints) {
int sum = 0;
for (Endpoint endpoint : endpoints) {
sum += endpoint.weight;
}
totalWeight = sum;
this.endpoints = endpoints;
}
public int getEndpointCount() {
return endpoints.size();
}
public final Iterator<Endpoint> iterator() {
return ReadOnlyIterator.with(endpoints.iterator());
}
public String toString() {
return "Candidate [endpoints=" + endpoints + ", totalWeight=" + totalWeight + "]";
}
}
調用下游部件服務類:
import util.Debug;
public class ServiceInvoker {
//保存當前類的唯一實例
private static final ServiceInvoker INSTANCE =new ServiceInvoker();
//負載均衡器實例,使用volatile變量保證可見性
private volatile LoadBalancer loadBlancer;
//私有構造器
private ServiceInvoker(){
//什麼也不做
}
/*
獲取當前類的唯一實例
*/
public static ServiceInvoker getInstance(){
return INSTANCE;
}
/**
* 根據指定的負載均衡器派發請求到特定的下游部件
* @param request
*/
public void dispatchRequest(Request request){
//這裏讀取volatile變量loadBlancer
Endpoint endpoint = getLoadBalancer().nextEndpoint();
if (null==endpoint){
// 省略其他代碼
return;
}
//將請求發給下游部件
dispatchToDownstream(request,endpoint);
}
//真正將指定的請求派發給下游部件
private void dispatchToDownstream(Request request, Endpoint endpoint){
Debug.info("Dispatch request to "+endpoint+":"+request);
//省略其他代碼
}
public LoadBalancer getLoadBalancer(){
//讀取負載均衡器實例
return loadBlancer;
}
public void setLoadBlancer(LoadBalancer loadBlancer){
//設置或者更新負載均衡器實例
this.loadBlancer = loadBlancer;
}
}
請求派發類:
import java.io.InputStream;
public class Request {
private final long transactionId;
private final int transactionType;
private InputStream in;
public Request(long transactionId, int transactionType) {
this.transactionId = transactionId;
this.transactionType = transactionType;
}
public long getTransactionId() {
return transactionId;
}
public int getTransactionType() {
return transactionType;
}
public InputStream getIn() {
return in;
}
public void setIn(InputStream in) {
this.in = in;
}
}
系統啓動類:
import java.util.HashSet;
import java.util.Set;
public class SystemBooter {
public static void main(String[] args) throws Exception {
SystemBooter systemBooter = new SystemBooter();
ServiceInvoker rd = ServiceInvoker.getInstance();
LoadBalancer lb = systemBooter.createLoadBalance();
//在main線程中設置負載均衡器實例
rd.setLoadBlancer(lb);
}
//根據系統配置創建負載均衡器實例
private LoadBalancer createLoadBalance() throws Exception {
LoadBalancer lb;
Candidate candidate = new Candidate(loadEndpoints());
lb = WeightedRoundRobinLoadBalancer.newInstance(candidate);
return lb;
}
private Set<Endpoint> loadEndpoints() {
Set<Endpoint> endpoints = new HashSet<>();
// 模擬從數據庫加載以下信息
endpoints.add(new Endpoint("192.168.101.100", 8080, 3));
endpoints.add(new Endpoint("192.168.101.101", 8080, 2));
endpoints.add(new Endpoint("192.168.101.102", 8080, 5));
endpoints.add(new Endpoint("192.168.101.103", 8080, 7));
return endpoints;
}
}
測試函數:
import util.Tools;
public class CaseRunner {
public static void main(String[] args) throws Exception {
// 初始化請求派發器RequestDispatcher
SystemBooter.main(new String[]{});
for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
new RequestSender().start();
}
}
static class RequestSender extends Thread {
private static long id = -1;
public RequestSender() {
}
static synchronized long nextId() {
return ++id;
}
@Override
public void run() {
ServiceInvoker rd = ServiceInvoker.getInstance();
for (int i = 0; i < 100; i++) {
rd.dispatchRequest(new Request(nextId(), 1));
Tools.randomPause(100);
}
}
}
}
參考資料
《Java多線程編程實戰指南》