分布式系统的负载均衡模块的设计与实现
应用场景
某分布式系统在其业务处理过程中需要通过网络连接调用下游部件提供的服务,即发送请求给下游部件。下游部件是一个集群环境(即多台主机对外提供相同的服务)。因此,该系统调用其下游部件服务的时候需要进行负载均衡控制,即保证下游部件的各台主机上接收到的请求数分布均匀(统计意义上的均匀)。
场景分析
该系统在调用其下游部件时的负载均衡模块需要在不重启应用程序、服务器的情况下满足以下几点要求:
- 需要支持多种负载均衡算法,例如随机轮询算法和加权随机轮询算法等
- 需要支持在系统运行中动态调整负载均衡算法,如从使用随机轮询算法调整为使用加权随机轮询算法。
- 在调用下游部件的过程中,下游部件中的非在线主机(如出现故障的主机)需要被排除在外,即发送给下游部件的请求不能派发给非在线主机(因为那样会导致请求处理失败)
- 下游部件的节点信息可动态调整,如处于维护的需要临时删除一个节点过后又将其重新添加回来。
设计与实现
为满足要求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多线程编程实战指南》