一、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实现选举策略:
使用临时节点,谁创建成功,谁就是主。
实现流程:
首先创建多台服务期,创建临时节点,谁能将临时节点创建成功,谁就为主。
如果主宕机了,其他服务器重新创建临时节点。以此类推。