先看看這張圖,業務服務在/web節點下注冊臨時節點,如server1服務對應/web/web1臨時節點,然後監控/web節點的孩子節點事件,使用一個集合保存這些點數據,當新增和移除時,分別在記錄這些節點數據,下邊我們實現一個加權隨機負載均衡的模擬實現
1.定義節點數據,保護節點路徑,服務ip,服務端口,權重
/**
* @ClassName NodeData
* @Author mjlft
* @Date 2020/1/21 15:11
* @Version 1.0
* @Description TODO
*/
public class NodeData implements Serializable {
private static final long serialVersionUID = 4848971284812662834L;
private String ip;
private Integer port;
private Integer weight;
private String path;
public NodeData() {
}
public NodeData(String ip, Integer port, Integer weight, String path) {
this.ip = ip;
this.port = port;
this.weight = weight;
this.path = path;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeData nodeData = (NodeData) o;
return ip.equals(nodeData.ip) &&
port.equals(nodeData.port);
}
@Override
public int hashCode() {
return Objects.hash(ip, port);
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
@Override
public String toString() {
return "NodeData{" +
"ip='" + ip + '\'' +
", port=" + port +
", weight=" + weight +
", path='" + path + '\'' +
'}';
}
}
2.使用Curator時,保存到節點的數據必須時byte數組,所以這裏我們需要定義一個工具類將節點數據轉換byte數組,
/**
* @ClassName DataUtil
* @Author mjlft
* @Date 2020/1/21 15:21
* @Version 1.0
* @Description TODO
*/
public class DataUtil {
//將object轉換爲bytes
public static byte[] getBytesFromObject(Object object) throws IOException {
ObjectOutputStream out = null;
ByteArrayOutputStream bos = null;
try {
// System.out.println(object);
bos = new ByteArrayOutputStream();
out = new ObjectOutputStream(bos);
out.writeObject(object);
out.flush();
byte[] yourBytes = bos.toByteArray();
return yourBytes;
} finally {
if (bos != null) {
bos.close();
}
if (out != null) {
out.close();
}
}
}
//
public static Object getObjectFromBytes(byte[] bytes) throws IOException, ClassNotFoundException {
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new ByteArrayInputStream(bytes));
return in.readObject();
} finally {
if (in != null) {
in.close();
}
}
}
}
3.定義服務註冊類,這裏我們把每個webService當初一臺物理服務,他們會像zookeeper註冊服務
/**
* @ClassName Service
* @Author mjlft
* @Date 2020/1/21 10:24
* @Version 1.0
* @Description TODO
*/
public class Service {
protected CuratorFramework client;
//開啓客戶端
public void start() {
ClientUtil.start(this.client);
}
//關閉客戶端
public void stop() {
System.out.println(this.configData.getNodeName() + "節點宕機");
ClientUtil.stop(this.client);
}
}
/**
* @ClassName WebService
* @Author mjlft
* @Date 2020/1/21 15:11
* @Version 1.0
* @Description TODO
*/
public class WebService extends Service {
public final static String SERVICE_PATH = "/web";
private NodeData nodeData;
public WebService(CuratorFramework client, NodeData nodeData) {
super.client = client;
this.nodeData = nodeData;
}
//節點初始化
public void init() throws Exception {
String path = SERVICE_PATH + "/" + nodeData.getPath();
//首先判斷當前節點是否已經存在
Stat stat = client.checkExists().forPath(path);
if (stat == null) {
String parentpath = ClientUtil.getparrentpath(path);
ClientUtil.createParentPath(parentpath, client);
//如果不存在就創建一個新的節點
System.out.println(nodeData);
client.create().withMode(CreateMode.EPHEMERAL).forPath(path, DataUtil.getBytesFromObject(this.nodeData));
} else {
//如果已經存在,則更新數據
client.setData().forPath(path, DataUtil.getBytesFromObject(this.nodeData));
}
}
}
4.負載均衡類,這個類中我們註冊了一個節點監聽器,監聽子節點的變化,如果由新的子節點被註冊,那麼在集合nodeDatas添加新增節點數據,如果有服務下線,那麼對應的子節點也會被刪除,應爲這裏創建的都是臨時節點,那麼對應的節點數據也會從集合中刪除,
爲什麼要使用監聽器,因爲服務上線和下線的頻率相對較低,那麼在進行負載算法的時候就不用每次到zookeeper中獲取節點數據,負載每發送一次請求都要到zookeeper中獲取,這樣又多了好幾次網絡來回。
/**
* @ClassName LoadBalanceService
* @Author mjlft
* @Date 2020/1/21 15:35
* @Version 1.0
* @Description 服務監控
*/
public class LoadBalanceService extends Service {
private List<NodeData> nodeDatas = new ArrayList<>(16);
public LoadBalanceService(CuratorFramework client) {
super.client = client;
}
//拿去已經註冊上來的所有節點
public void init() throws Exception {
List<String> children = client.getChildren().forPath(WebService.SERVICE_PATH);
for(String path : children){
path = WebService.SERVICE_PATH + "/" + path;
try {
byte[] data = client.getData().forPath(path);
NodeData nodeData = (NodeData) DataUtil.getObjectFromBytes(data);
nodeDatas.add(nodeData);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void register() throws Exception {
PathChildrenCache watcher = new PathChildrenCache(client, WebService.SERVICE_PATH, true/*,false, service*/);
watcher.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
if(pathChildrenCacheEvent.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)){
System.out.println(pathChildrenCacheEvent.getData().getPath() + "上線");
//新服務註冊
NodeData data = (NodeData)DataUtil.getObjectFromBytes(pathChildrenCacheEvent.getData().getData());
nodeDatas.add(data);
}
if(pathChildrenCacheEvent.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)){
//服務下架或宕機
System.out.println(pathChildrenCacheEvent.getData().getPath() + "下線");
NodeData data = (NodeData)DataUtil.getObjectFromBytes(pathChildrenCacheEvent.getData().getData());
nodeDatas.remove(data);
}
}
});
watcher.start(PathChildrenCache.StartMode.NORMAL);
}
//負載算法,隨機選擇當前在線的一臺服務
public NodeData loadBalance(){
ThreadLocalRandom random = ThreadLocalRandom.current();
NodeData result = null;
if(nodeDatas.isEmpty()){
return null;
}
synchronized (nodeDatas){
if(nodeDatas.isEmpty()){
return null;
}
int all = 0;
for (NodeData nodeData : nodeDatas){
all += nodeData.getWeight();
}
int index = random.nextInt(all);
for (NodeData nodeData: nodeDatas){
if(index <= nodeData.getWeight()){
result = nodeData;
break;
}
index -= nodeData.getWeight();
}
}
return result;
}
}
最後測試
/**
* @ClassName Test
* @Author mjlft
* @Date 2020/1/21 15:57
* @Version 1.0
* @Description TODO
*/
public class Test {
public static void main(String[] args) throws Exception {
RetryPolicy retryPolicy = new RetryNTimes(3, 100);
for(int i = 0; i < 10; i ++){
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.107:2181, 192.168.1.107:2182",
30*60*1000, 5*1000, retryPolicy);
NodeData nodeData = new NodeData("192.168.1."+i, 8080, i, "web"+i);
WebService webService = new WebService(client, nodeData);
webService.start();
webService.init();
}
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.107:2181, 192.168.1.107:2182",
30*60*1000, 5*1000, retryPolicy);
LoadBalanceService loadBalanceService = new LoadBalanceService(client);
loadBalanceService.start();
loadBalanceService.init();
loadBalanceService.register();
while (true){
Scanner sc = new Scanner( System.in );
String nextCommand = sc.nextLine();
NodeData nodeData = loadBalanceService.loadBalance();
System.out.println("本次請求由:" + nodeData.getIp() + ":" + nodeData.getPort() + " 執行");
}
}
}
測試結果:
/web/web9上線
/web/web8上線
/web/web7上線
/web/web6上線
/web/web5上線
/web/web4上線
/web/web3上線
/web/web2上線
/web/web1上線
/web/web0上線
1
本次請求由:192.168.1.5:8080 執行
1
本次請求由:192.168.1.5:8080 執行
1
本次請求由:192.168.1.3:8080 執行
1
本次請求由:192.168.1.8:8080 執行
1
本次請求由:192.168.1.3:8080 執行
1
本次請求由:192.168.1.2:8080 執行
1
本次請求由:192.168.1.5:8080 執行
2
本次請求由:192.168.1.1:8080 執行
1
本次請求由:192.168.1.9:8080 執行
111
本次請求由:192.168.1.7:8080 執行
1
本次請求由:192.168.1.9:8080 執行
1
本次請求由:192.168.1.2:8080 執行
1
本次請求由:192.168.1.8:8080 執行
11
本次請求由:192.168.1.6:8080 執行
本次請求由:192.168.1.9:8080 執行
1
本次請求由:192.168.1.8:8080 執行
1
本次請求由:192.168.1.4:8080 執行
1
本次請求由:192.168.1.9:8080 執行
1
本次請求由:192.168.1.8:8080 執行
1
本次請求由:192.168.1.8:8080 執行
1
本次請求由:192.168.1.8:8080 執行
/web/web3下線
/web/web8下線
/web/web3下線
/web/web8下線