Java架构学习(四十四)Zookeeper实现负载均衡和选举策略&ZK负载均衡原理&ZK实现分布式锁步骤&搭建zk负载均衡环境&zk代码实现负载均衡&zk实现轮询机制算法&zk选举策略

一、Zookeeper实现负载均衡原理

分布式微服务中的概念:
生产者、消费者、服务、注册中心、服务治理
dubbo是使用的临时节点。


zk使用的是临时节点来存储,服务的地址的。
负载均衡是在订单服务里面做的。

在这里插入图片描述
二、使用Zookeeper实现分布式锁

ZK实现分布式锁流程:
	使用临时节点的特征,ZK连接断开就删除,使用ZK的删除通知来判断
	是否释放锁来,加入没有运行删除通知没有监听到删除节点的操作,说明没有释放锁。
	就会一直被锁住。
zk实现分布式锁流程:
临时节点的特征:会话连接失效过后,值自动删除掉。
流程:使用ZK临时节点,首先多个服务在zk上创建同一个临时节点。
只要谁创建临时节点成功,就拿到了当前的锁,其他连接就会等待。
那什么时候唤醒呢,使用事件通知,获取节点被删除的事件,就会继续进入获取锁的流程。

三、搭建zookeeper负载均衡项目环境

开始搭建代码:

客户端
package com.leeue.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;
/**
 * 
 * @classDesc: 功能描述:(zookeeper 客户端)
 * @author:<a href="leeue@foxmail.com">李月</a>
 * @Version:v1.0
 * @createTime:2018年11月27日 上午10:21:12
 */
public class ZkServerClient {
	public static List<String> listServer = new ArrayList<String>();

	public static void main(String[] args) {
		initServer();
		ZkServerClient 	client= new ZkServerClient();
		BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
		while (true) {
			String name;
			try {
				name = console.readLine();
				if ("exit".equals(name)) {
					System.exit(0);
				}
				client.send(name);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	// 注册所有server   包括开始启动的的ZkserverScoekt
	public static void initServer() {
		listServer.clear();
		listServer.add("127.0.0.1:18080");
	}

	/**
	 * 获取当前server信息  默认获取的地址是第0个
	 * @return
	 */
	public static String getServer() {
		return listServer.get(0);
	}
	/***
	 * 发生信息到服务器端
	 * @param name
	 */
	public void send(String name) {

		String server = ZkServerClient.getServer();
		String[] cfg = server.split(":");

		Socket socket = null;
		BufferedReader in = null;
		PrintWriter out = null;
		try {
			socket = new Socket(cfg[0], Integer.parseInt(cfg[1]));
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			out = new PrintWriter(socket.getOutputStream(), true);

			out.println(name);
			//死循环  可以一直输入值 送到服务器
			while (true) {
				String resp = in.readLine();
				if (resp == null)
					break;
				else if (resp.length() > 0) {
					System.out.println("Receive : " + resp);
					break;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (out != null) {
				out.close();
			}
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (socket != null) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
服务器端:
package com.leeue.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 
 * @classDesc: 功能描述:(scoket服务端)
 * @author:<a href="leeue@foxmail.com">李月</a>
 * @Version:v1.0
 * @createTime:2018年11月27日 上午10:38:47
 */
//##ServerScoekt服务端
public class ZkServerScoekt implements Runnable {
	private int port = 18080;

	public static void main(String[] args) throws IOException {
		int port = 18080;
		ZkServerScoekt server = new ZkServerScoekt(port);
		Thread thread = new Thread(server);
		thread.start();
	}

	public ZkServerScoekt(int port) {
		this.port = port;
	}

	public void run() {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(port);
			System.out.println("Server start port:" + port);
			Socket socket = null;
			//死循环  一直在等待接受客户端发来的信息
			while (true) {
				socket = serverSocket.accept();
				new Thread(new ServerHandler(socket)).start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (serverSocket != null) {
					serverSocket.close();
				}
			} catch (Exception e2) {

			}
		}
	}

}
服务期端接受信息的线程
package com.leeue.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

//ServerHandler
/**
 * 
 * @classDesc: 功能描述:(接受信息的线程)
 * @author:<a href="leeue@foxmail.com">李月</a>
 * @Version:v1.0
 * @createTime:2018年11月27日 上午10:39:10
 */
public class ServerHandler implements Runnable {
	private Socket socket;

	public ServerHandler(Socket socket) {
		this.socket = socket;
	}

	public void run() {
		BufferedReader in = null;
		PrintWriter out = null;
		try {
			in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
			out = new PrintWriter(this.socket.getOutputStream(), true);
			String body = null;
			while (true) {
				body = in.readLine();
				if (body == null)
					break;
				System.out.println("Receive : " + body);
				out.println("Hello, " + body);
			}

		} catch (Exception e) {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			if (out != null) {
				out.close();
			}
			if (this.socket != null) {
				try {
					this.socket.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				this.socket = null;
			}
		}
	}
}

四、利用代码实现ZK的负载均衡

在这里插入图片描述

实现zk的负载均衡步骤:
1、会员服务启动之后,信息(ZK节点名称,服务名称,服务ip地址)注册到zk上。

代码实现:

服务端:
package com.leeue.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import org.I0Itec.zkclient.ZkClient;
/**
 *  
 * @classDesc: 功能描述:(scoket服务端)  启动了两个就是zk做了集群 端口号换了
 * @author:<a href="leeue@foxmail.com">李月</a>
 * @Version:v1.0
 * @createTime:2018年11月27日 上午10:38:47
 */
//##ServerScoekt服务端
public class ZkServerScoekt implements Runnable {
	private static int port = 18080;

	public static void main(String[] args) throws IOException {
		ZkServerScoekt server = new ZkServerScoekt(port);
		Thread thread = new Thread(server);
		thread.start();
	}

	public ZkServerScoekt(int port) {
		this.port = port;
	}


	public void run() {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(port);
			//客户端有信息发来 这个节点地址就注册到zk上面去 zk在这里做注册中心
			regServer();
			
			System.out.println("Server start port:" + port);
			Socket socket = null;
			//死循环  一直在等待接受客户端发来的信息
			while (true) {
				socket = serverSocket.accept();
				new Thread(new ServerHandler(socket)).start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (serverSocket != null) {
					serverSocket.close();
				}
			} catch (Exception e2) {

			}
		}
	}
	
	
	/**
	 * 将服务信息注册到注册中心上去
	 */
	public void regServer() {
		//1.创建zk连接 第一个是ip地址,第二个参数是设置session超时时间,第三个参数是连接超时时间
		ZkClient zkClient = new ZkClient("127.0.0.1:2181",6000,2000);
		//创建父节点
		String parentPath = "/member";  
		//检测父节点是否存在,不存在就创建
		if(!zkClient.exists(parentPath)) {
			//这个父节点最好设置成持久节点,以防后来可以查看有哪些服务曾经注册过
			zkClient.createPersistent(parentPath); 
		}
		//2.创建节点,每个服务根据端口号来区分
		String path = "/member/server-"+port;
		//3.检测当前节点是否存在,存在就删除当前的节点,
		if(zkClient.exists(path)) {
			zkClient.delete(path);
		}
		String value = "127.0.0.1:"+port;
		//4.创建临时节点 第一个是节点名称,第二个是真实的服务器ip地址
		zkClient.createEphemeral(path,value);
		System.out.println("####服务注册成功####"+value);
	}
}
客户端:
package com.leeue.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;
/**
 * 
 * @classDesc: 功能描述:(zookeeper 客户端)
 * @author:<a href="leeue@foxmail.com">李月</a>
 * @Version:v1.0
 * @createTime:2018年11月27日 上午10:21:12
 */
public class ZkServerClient {
	public static List<String> listServer = new ArrayList<String>();

	public static void main(String[] args) {
		initServer();
		ZkServerClient 	client= new ZkServerClient();
		BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
		while (true) {
			String name;
			try {
				name = console.readLine();
				if ("exit".equals(name)) {
					System.exit(0);
				}
				client.send(name);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 *  从zk上获取所有的注册服务连接
	 */
	public static void initServer() {
		listServer.clear();
		//1.创建zk连接
		final ZkClient zkClient = new ZkClient("127.0.0.1:2181",6000,2000);
		//2.获取父节点
		String memberServerPath="/member";
		//3.获取当前的父节点下面的所有子节点
		List<String> childrens = zkClient.getChildren(memberServerPath);
		//每次重新获取值的时候将之前的值先清除掉
		listServer.clear();
		//4.遍历子节点 把所有的地址放进去
		for(String p: childrens) {
			//获取到当前子节点的value值
			listServer.add((String)zkClient.readData(memberServerPath+"/"+p));
			System.out.println("###获取子节点值###");
		}
		//5.订阅子节点事件
		zkClient.subscribeChildChanges(memberServerPath, new IZkChildListener() {
			//如果监听 子节点发生变化了,就更新里面的值
			public void handleChildChange(String parentPath, List<String> childrens) throws Exception {
				listServer.clear();
				for(String subP: childrens) {
					listServer.add((String)zkClient.readData(parentPath+"/"+subP));
				}
				System.out.println("####"+parentPath+"节点发生变化####");
			}
		});
	}

	/**
	 * 获取当前server信息  默认获取的地址是第0个
	 * @return
	 */
	public static String getServer() {
		//这里 获取到的服务器每次只是取第一个值,也就实现了一主一备的概念
		return listServer.get(0); 
	}
	/***
	 * 发生信息到服务器端
	 * @param name
	 */
	public void send(String name) {

		String server = ZkServerClient.getServer();
		String[] cfg = server.split(":");

		Socket socket = null;
		BufferedReader in = null;
		PrintWriter out = null;
		try {
			socket = new Socket(cfg[0], Integer.parseInt(cfg[1]));
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			out = new PrintWriter(socket.getOutputStream(), true);

			out.println(name);
			//死循环  可以一直输入值 送到服务器
			while (true) {
				String resp = in.readLine();
				if (resp == null)
					break;
				else if (resp.length() > 0) {
					System.out.println("Receive : " + resp);
					break;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (out != null) {
				out.close();
			}
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (socket != null) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
接受消息的线程:
package com.leeue.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

//ServerHandler
/**
 * 
 * @classDesc: 功能描述:(接受信息的线程)
 * @author:<a href="leeue@foxmail.com">李月</a>
 * @Version:v1.0
 * @createTime:2018年11月27日 上午10:39:10
 */
public class ServerHandler implements Runnable {
	private Socket socket;

	public ServerHandler(Socket socket) {
		this.socket = socket;
	}

	public void run() {
		BufferedReader in = null;
		PrintWriter out = null;
		try {
			in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
			out = new PrintWriter(this.socket.getOutputStream(), true);
			String body = null;
			while (true) {
				body = in.readLine();
				if (body == null)
					break;
				System.out.println("Receive : " + body);
				out.println("Hello, " + body);
			}

		} catch (Exception e) {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			if (out != null) {
				out.close();
			}
			if (this.socket != null) {
				try {
					this.socket.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				this.socket = null;
			}
		}
	}
}

五、实现负载均衡轮训算法

1、负载均衡轮询机制使用的取模算法
取模算法是根据调用次数%服务期集群数量 = 服务器位置
调用次数count  

轮询算法的改造  主要方法如下:
	/**
	 * 获取当前server信息  默认获取的地址是第0个 这个函数写算法 ,ip绑定、轮询机制等
	 * @return
	 */
	//服务器调用数量
	private static int count = 0;
	//服务器集群数量  在事件监听 实时监听这个值
	public static String getServer() {
		//这里 获取到的服务器每次只是取第一个值,也就实现了一主一备的概念
		count++;
		int index =  count%listServer.size();
		return listServer.get(index); 
	}

六、思考使用Zookeeper实现选举策略

1.使用Zookeeper实现选举策略

2.redis 主节点 master  
slave 哨兵机制
所使用选举投票策略 ping的策略


使用zookeeper实现选举策略:
	使用临时节点,谁创建成功,谁就是主。

实现流程:
	首先创建多台服务期,创建临时节点,谁能将临时节点创建成功,谁就为主。
	如果主宕机了,其他服务器重新创建临时节点。以此类推。

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值