zookeeper 笔记 (四)

一.    Zookeeper典型应用场景实现

1. 数据发布/订阅(配置中心)

借助zookeeper的节点 和节点的监听机制来实现配置中心

zookeeper 笔记 (四)_第1张图片

eg: 连接数据库参数的配置

zkCli.sh:在zk端创建相关节点,并赋值

[zk: localhost:2181(CONNECTED) 1] ls /jdbcCfg
[driver, password, url, username]
[zk: localhost:2181(CONNECTED) 2] 

java :读取相关节点并监听其变化

 public  ZookeeperCentralConfigurer(String  zkServers,String zkPath ,int sessionTimeout ){
        this.zkServers = zkServers;
        this.zkPath = zkPath;
        this.sessionTimeout = sessionTimeout;
        this.properties = new Properties();

        // init zkClient;
        initZkClient();

        //getConfigData
        getConfigData();

        //addZkListener
        addZkListener();



    }

    private void getConfigData() {
        try {
            List list = zkClient.getChildren().forPath(zkPath);
            for (String key : list){
                String value = new String(zkClient.getData().forPath(zkPath + "/" + key));
                if(!StringUtils.isEmpty(value)){
                    properties.put(key,value);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initZkClient() {
        zkClient = CuratorFrameworkFactory
                .builder()
                .connectString(zkServers)
                .sessionTimeoutMs(sessionTimeout)
                .retryPolicy(new ExponentialBackoffRetry(1000,3))
                .build();
        zkClient.start();
    }

    private void addZkListener() {
        TreeCacheListener listener = new TreeCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
                if (event.getType() == TreeCacheEvent.Type.NODE_UPDATED) {
                    getConfigData();
                    WebApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext();
                    HikariDataSource dataSource = (HikariDataSource) ctx.getBean("dataSource");
                    System.out.println("================"+properties.getProperty("url"));
                    dataSource.setJdbcUrl(properties.getProperty("url"));
                }
            }
        };

        treeCache = new TreeCache(zkClient, zkPath);
        try {
            treeCache.start();
            treeCache.getListenable().addListener(listener);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void setZkClient(CuratorFramework zkClient) {
        this.zkClient = zkClient;
    }

    public CuratorFramework getZkClient() {
        return zkClient;
    }

    public TreeCache getTreeCache() {
        return treeCache;
    }

    public String getZkServers() {
        return zkServers;
    }

    public String getZkPath() {
        return zkPath;
    }

    public int getSessionTimeout() {
        return sessionTimeout;
    }

    public Properties getProperties() {
        return properties;
    }
}

/**
 * 重写配置文件
 */
public class ZookeeperPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
    private ZookeeperCentralConfigurer zkCentralConfigurer;

    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
        super.processProperties(beanFactoryToProcess, zkCentralConfigurer.getProperties());
    }

    public void setZkCentralConfigurer(ZookeeperCentralConfigurer zkCentralConfigurer) {
        this.zkCentralConfigurer = zkCentralConfigurer;
    }
}

spring配置:




    

    

    
        
        
        
    


    
        
        
        
    

    
        
            
        
    


    
        
        
        
        

        
        
        
        
        
        
        
        
        
        
    




2. 命名服务

利用zk的节点 来实现zk的命名服务,可以保证名称的唯一性

zookeeper 笔记 (四)_第2张图片

3. 集群管理

4. Master选举

zookeeper 笔记 (四)_第3张图片

5. 分布式锁

思路:

zookeeper 笔记 (四)_第4张图片

模拟代码:

利用ZK的节点和监听机制 实现分布式锁

模拟分布式锁:

public class ImproveLock implements Lock {
	private static Logger logger = LoggerFactory.getLogger(ImproveLock.class);

	private static final String ZOOKEEPER_IP_PORT = "192.168.1.129:2181";
	private static final String LOCK_PATH = "/LOCK";

	private ZkClient client = new ZkClient(ZOOKEEPER_IP_PORT, 1000, 1000, new SerializableSerializer());

	private CountDownLatch cdl;

	private String beforePath;// 当前请求的节点前一个节点
	private String currentPath;// 当前请求的节点

	// 判断有没有LOCK目录,没有则创建
	public ImproveLock() {
		if (!this.client.exists(LOCK_PATH)) {
			this.client.createPersistent(LOCK_PATH);
		}
	}

	public boolean tryLock() {
		// 如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentPath
		if (currentPath == null || currentPath.length() <= 0) {
			// 创建一个临时顺序节点
			currentPath = this.client.createEphemeralSequential(LOCK_PATH + '/', "lock");
			System.out.println("---------------------------->" + currentPath);
		}

		// 获取所有临时节点并排序,临时节点名称为自增长的字符串如:0000000400
		List childrens = this.client.getChildren(LOCK_PATH);
		Collections.sort(childrens);
		if (currentPath.equals(LOCK_PATH + '/' + childrens.get(0))) {// 如果当前节点在所有节点中排名第一则获取锁成功
			return true;
		} else {// 如果当前节点在所有节点中排名中不是排名第一,则获取前面的节点名称,并赋值给beforePath
			int wz = Collections.binarySearch(childrens, currentPath.substring(6));
			beforePath = LOCK_PATH + '/' + childrens.get(wz - 1);
		}

		return false;
	}

	public void unlock() {
		// 删除当前临时节点
		client.delete(currentPath);
	}

	public void lock() {
		if (!tryLock()) {
			waitForLock();
			lock();
		} else {
			logger.info(Thread.currentThread().getName() + " 获得分布式锁!");
		}
	}

	private void waitForLock() {
		IZkDataListener listener = new IZkDataListener() {
			public void handleDataDeleted(String dataPath) throws Exception {
				logger.info(Thread.currentThread().getName() + ":捕获到DataDelete事件!---------------------------");
				if (cdl != null) {
					cdl.countDown();
				}
			}

			public void handleDataChange(String dataPath, Object data) throws Exception {

			}
		};

		// 给排在前面的的节点增加数据删除的watcher
		this.client.subscribeDataChanges(beforePath, listener);
		if (this.client.exists(beforePath)) {
			cdl = new CountDownLatch(1);
			try {
				cdl.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.client.unsubscribeDataChanges(beforePath, listener);
	}

	// ==========================================
	public void lockInterruptibly() throws InterruptedException {

	}

	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return false;
	}

	public Condition newCondition() {
		return null;
	}
}

应用场景:

class OrderCodeGenerator {
	// 自增长序列
	private static int i = 0;

	// 按照“年-月-日-小时-分钟-秒-自增长序列”的规则生成订单编号
	public String getOrderCode() {
		Date now = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
		return sdf.format(now) + ++i;
	}

	public static void main(String[] args) {
		OrderCodeGenerator ong = new OrderCodeGenerator();
		for (int i = 0; i < 10; i++) {
			System.out.println(ong.getOrderCode());
		}
	}
}



public class OrderServiceImpl implements Runnable {
	private static OrderCodeGenerator ong = new OrderCodeGenerator();

	private Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
	// 同时并发的线程数
	private static final int NUM = 100;
	// 按照线程数初始化倒计数器,倒计数器
	private static CountDownLatch cdl = new CountDownLatch(NUM);

	// private static Lock lock = new ReentrantLock();

	private Lock lock = new ImproveLock();

	// 创建订单接口
	public void createOrder() {
		String orderCode = null;

		lock.lock();
		try {
			// 获取订单编号
			orderCode = ong.getOrderCode();
			System.out.println("insert into DB使用id:=======================>" + orderCode);
		} catch (Exception e) {
			// TODO: handle exception
		} finally {
			lock.unlock();
		}

		// ……业务代码,此处省略100行代码

		logger.info("insert into DB使用id:=======================>" + orderCode);
	}

	@Override
	public void run() {
		try {
			// 等待其他线程初始化
			cdl.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 创建订单
		createOrder();
	}

	public static void main(String[] args) {
		for (int i = 1; i <= NUM; i++) {
			// 按照线程数迭代实例化线程
			new Thread(new OrderServiceImpl()).start();
			// 创建一个线程,倒计数器减1
			cdl.countDown();
		}
	}
}



二.    Zookeeper特性

1.Zookeeper基本模型

a. 数据模型

节点:树模型(采用文件系统的形式,只不过去掉文件和目录),叫数据节点。

b. ACL

权限控制:权限模式(Schema)、授权对象(ID)、权限(Permission)

权限模式:

world 开放模式。意思所有人都可以访问。
IP 针对某个开放权限    
digest 用户/密码模式
Super 超级用户模式

授权对象:username

权限:

READ 只读
WRITE 只写
CREATE 创建
DELETE 删除
ADMIN 节点管理权限

eg:

使用明文

addauth digest username:password
setAcl /path auth:username:password:cdrwa

使用密文加密规则是SHA1加密,然后base64编码。

setAcl path digest|ip:username:password:c|d|r|w|a 

使用zk提供的加密小工具(执行目录在zk根目录):

java -cp ./zookeeper-3.4.6.jar:./lib/log4j-1.2.16.jar:./lib/slf4j-api-1.6.1.jar:./lib/slf4j-log4j12-1.6.1.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider user1:12345

查看权限命令:

getAcl /path


c. 版本

zookeeper版本的含义:版本指的是变更的次数。

cversion 

当前节点的权限

dataversion  当前节点数据内容的版本号
aclVersion    就是ACL版本号
CAS (compare and swap)比较然后交换。

d. watcher

zookeeper 笔记 (四)_第5张图片


2.Zookeeper服务器角色

a. Leader

Leader的概念:事务请求的唯一调度者和处理者。保证事务处理的顺序性。集群内部个服务器之间的调度者。
事务请求:导致数据一致性的请求


b. Follower

Follower的概念:处理客户端的非事务请求。事务请求必须转发给Leader服务器。 参与事务请求Proposal的投票。参与Leader选举

c. Observer

Observer的概念: 在实际运行中,它只是负责读,Leader不会将事务的投票发送给Obsserver。

3.Zookeeper序列化与通信协议

a. 序列化

Jute是zk序列化、反序列化协议。

b.通信协议

基于TCP/IP协议,所以是一个长连接。zookeeper在这个基础上完成客户端和服务器,服务器和服务器之间的通信。

Zk请求包:请求头+请求体

0-3

4-11

12-n

Len

4-7

8-11

12-15

16-(n-1)

n

xid

type

len

path

watch

Zk响应包:响应头+响应体

0-3

4-19

20 - n

Len

4-7

8-15

16-19

20-23

len位

48位

8位

xid

zxid

err

len

data

......

pzxid


4.Zookeeper数据存储

a. 内存数据

Zk的内存数据库:ZkDataBase、DataTree、DataNode

b. 日志数据 (FileTxnLog)

运行时,不停地有数据写入。

当日志的剩余空间不足4K(4096),日志就做扩充,扩充64M,后面以“0”填充。

log都是使用zxid作为文件名的后缀。

查看日志方式:

(日志使用了SHA1加密,然后base64编码,需要使用zk提供的解码小工具查看)

$ java -cp ./zookeeper-3.4.9.jar::./lib/log4j-1.2.16.jar:./lib/slf4j-api-1.6.1.jar:./lib/slf4j-log4j12-1.6.1.jar org.apache.zookeeper.server.LogFormatter ~/tmp/zookeeper/zk1/version-2/log.f00000001

c. 快照数据 FileSnapTxnLog

快照数据:在某一时刻内存所有全量数据的一个磁盘文件。举例:快照阈值100000,触发快照数据。

快照数据都是使用zxid作为文件名的后缀。

查看快照命令:

$ java -cp ./zookeeper-3.4.9.jar::./lib/log4j-1.2.16.jar:./lib/slf4j-api-1.6.1.jar:./lib/slf4j-log4j12-1.6.1.jar org.apache.zookeeper.server.SnapshotFormatter ~/tmp/zookeeper/zk1/version-2/snapshot.f00000697

快照触发机制:非“半数机制”,过半随机策略。

logcount > (snapcount/2 + randroll)

logcount: 代表当前记录日志数量

snapcount: 多少次事务日志记录后触发一次数据快照

randroll: 1~snapcount/2 之间的一个随机数


5.Zookeeper客户端

zookeeper 笔记 (四)_第6张图片


6.Zookeeper会话

zookeeper 笔记 (四)_第7张图片


zk会话的状态:

 CONNECTING  正在连接
CONNECTED   已经连接
RECONNECTING  重新连接
RECONNECTED   重新连接上
CLOSE        会话关闭

SessionID的分配(初始化)策略:

i     取时间,并且左移24位得到的结果再右移8位(高8位,低16位都是0)

ii     sid拿出来进行左移56位

iii     和第一步的结果做或运算

Session分桶:

按照Session会话过期时间进行分区块保存。

session激活过程:

i.     检测会话是否过期

ii.     计算会话下一次超时时间

iii.     定位会话的所在区块

vi.     迁移会话

 

你可能感兴趣的:(note)