zookeeper 笔记 (四)

一.    Zookeeper典型应用场景实现

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

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


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<String> 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配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config />

    <context:component-scan base-package="com.idea.test.zk" />

    <bean id="zkCentralConfigurer" class="com.idea.test.zk.configCenter.ZookeeperCentralConfigurer">
        <constructor-arg name="zkServers" value="192.168.121.99:2181" />
        <constructor-arg name="sessionTimeout" value="1000" />
        <constructor-arg name="zkPath" value="/jdbcCfg" />
    </bean>


    <bean id="zkPlaceholderConfigurer" class="com.idea.test.zk.configCenter.ZookeeperPlaceholderConfigurer">
        <property name="zkCentralConfigurer" ref="zkCentralConfigurer" />
        <property name="ignoreUnresolvablePlaceholders" value="true" />
        <property name="order" value="1" />
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource">
            <ref bean="dataSource" />
        </property>
    </bean>


    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
          destroy-method="shutdown">
        <property name="driverClassName" value="${driver}" />
        <property name="jdbcUrl" value="${url}" />
        <property name="username" value="${uname}" />
        <property name="password" value="${passwd}" />

        <!-- 连接只读数据库时配置为true, 保证安全 -->
        <property name="readOnly" value="false" />
        <!-- 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒 -->
        <property name="connectionTimeout" value="30000" />
        <!-- 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟 -->
        <property name="idleTimeout" value="600000" />
        <!-- 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL
            wait_timeout参数(show variables like '%timeout%';) -->
        <property name="maxLifetime" value="1800000" />
        <!-- 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count) -->
        <property name="maximumPoolSize" value="15" />
    </bean>



</beans>


2. 命名服务

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


3. 集群管理

4. Master选举

5. 分布式锁

思路:


模拟代码:

利用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<String> 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



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客户端


6.Zookeeper会话



zk会话的状态:

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

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

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

ii     sid拿出来进行左移56位

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

Session分桶:

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

session激活过程:

i.     检测会话是否过期

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

iii.     定位会话的所在区块

vi.     迁移会话

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值