开篇碎碎念:不要试图用断点,或者你断点位置要放好,不然你就会收获许多连接异常。这绝对是我目前翻译过的最流畅的。咳,不是官网流畅,是我笔记流畅,也许是我成长了。(屏障就是人齐开饭,都吃完散场。然后队列是操作系统的生产者-消费者模型

ZooKeeper的屏障(Barrier)和队列(Queue)教程

  • 引言
    • SyncPrimitive
  • 屏障(Barrier)
    • 构造函数
    • enter()
    • leave()
  • 生产者-消费者队列(Queue)
    • 构造函数
    • produce()
    • consume()
  • 测试
    • 代码
    • 调用方法
    • 调试
      • 队列调试
      • 屏障调试
  • 完整源码

引言

这个教程展示屏障和生产者-消费者队列的实现。下文主要涉及Barrier和Queue类。你需要启动至少一个zookeeper服务器。
这两个类都继承了SyncPrimitive

注释前的序号是连起来的,若前文没有,可以看后面

SyncPrimitive

package barrierexample;import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;public class SyncPrimitive implements Watcher {static ZooKeeper zk = null;static Integer mutex;String root;SyncPrimitive(String address) {//3 zookeeper实例不存在,则父类自己创造if (zk == null) {try {System.out.println("Starting ZK:");zk = new ZooKeeper(address,3000,this);mutex = new Integer(-1);System.out.println("Finished starting ZK: "+zk);} catch (IOException e) {System.out.println(e.toString());zk = null;}}}@Overridesynchronized public void process(WatchedEvent event) {synchronized (mutex) {mutex.notify();}}
}

我们在第一次实例化barrier对象或queue对象时创建了zookeeper对象。并声明一个静态变量作为该对象的引用。Barrier 和 Queue 的后续实例检查 ZooKeeper 对象是否存在。或者,我们可以让应用程序创建一个 ZooKeeper 对象,并将其传递给 Barrier 和 Queue 的构造函数。
我们使用process()方法去处理watch触发的通知。watch作为内部结构,可以让zookeeper通知客户端节点的变更。若一个客户端在等待其他客户端离开barrier,那么他可以设置一个watch监视节点修改。

屏障(Barrier)

Barrier是一个原语,他的作用是同步 计算的开始和结束。

有多个进程调用了,等他们都投进去的时候,开始。等他们都结束的时候结束,宏观上看是同步计算的?

实现方法是使用一个屏障节点作为单个流程节点的父节点“/b1”,每个进程"p"创建节点"/b1/p"。只要有足够的进程创建了相应的节点,就可以开始计算了。

想象一个二叉树,b1是父节点,p是子节点

构造函数

public class SyncPrimitive implements Watcher {....../*** Barrier*/static public class Barrier extends SyncPrimitive {int size;String name;/*** 实例化Barrier对象的进程的参数如下:* @param address zookeeper服务器地址(如"zoo1.foo.com:2181")* @param root zookeeper上屏障节点的路径(如"/b1")* @param size 一组进程的大小*/Barrier(String address, String root, int size) {//1 Barrier的构造函数将zookeeper服务器的地址传递给父类的构造函数super(address);this.root = root;this.size = size;//1.5 如果zk对象存在if (zk != null) {try {//2 验证根节点是否存在//(此root非zookeeper的root哦)Stat s = zk.exists(root, false);//2.5 不存在则,Barrier的构造函数在zookeeper上创建一个Barrier节点,作为上述进程节点的父节点,被称作rootif (s == null) {zk.create(root,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);}} catch (KeeperException e) {System.out.println("Keeper exception when instantiating queue: "+ e.toString());} catch (InterruptedException e) {System.out.println("Interrupted exception");}}//获取本地主机地址的完全限定域名try {name = new String(InetAddress.getLocalHost().getCanonicalHostName().toString());} catch (UnknownHostException e) {System.out.println(e.toString());}}......}
}

进入屏障的方法是enter()

enter()

public class SyncPrimitive implements Watcher {......static public class Barrier extends SyncPrimitive {...../*** 进入屏障* @return* @throws KeeperException* @throws InterruptedException*/boolean enter() throws KeeperException,InterruptedException {//4 进程用root下面创建的节点代表自己,用主机名来作为节点名。//这里如过写临时顺序节点(EPHEMERAL_SEQUENTIAL)的话,创建节点后就无法根据名字(名字会变为一串0的)删除它,所以,我们把这里设为临时节点即可zk.create(root + "/"+name,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);while (true) {//5 它会一直等待直到足够多的进程进入屏障synchronized (mutex) {//6进程通过getChidren()检查root子节点的数量是否满足要求,//getChildren两个参数,第一个表示要读取的节点,第二个表示是否设置watch(根节点发生变化时通知),true为设置List<String> list = zk.getChildren(root,true);if (list.size() < size) {mutex.wait();} else {//7 满足要求就结束等待return true;}}}}}
}

leave()

计算完成后,进程调用leave()离开屏障。

public class SyncPrimitive implements Watcher {......static public class Barrier extends SyncPrimitive {...../*** 在所有子节点被删除后来开屏障* @return* @throws KeeperException* @throws InterruptedException*/boolean leave() throws KeeperException,InterruptedException {zk.delete(root + "/"+name,0);while (true) {synchronized (mutex) {//8 删除对应子节点并设置watchList<String> list = zk.getChildren(root,true);if (list.size() > 0) {mutex.wait();} else {return true;}}}}}
}

生产者-消费者队列(Queue)

这是我操作系统中的那个生产者-消费者吗?往下看


生产者-消费者队列是一种分布式的数据结构。一组进程用它来生产和消费项目。
生产者进程创建新元素并将他们添加到队列,消费者进程从队列中移除元素并使用他们。在这个实现中,元素是相当于原子的存在。队列由根节点来代表。生产者进程是创建根节点的子节点

呀,和前面的enter()方法有点类似呦

构造函数

下面是Queue的构造函数

public class SyncPrimitive implements Watcher {....../*** 生产者-消费者队列*/static public class Queue extends SyncPrimitive {Queue(String address,String name) {//q1 调用父类构造函数,如果zookeeper对象不存在就自己创建一个super(address);this.root = name;//验证根节点是否存在,不存在自己创建if (zk != null) {try {Stat s = zk.exists(root,false);if (s == null) {//几个参数:节点路径、节点数据、acl权限、节点类型// OPEN_ACL_UNSAFE:完全开放、PERSISTENT:持久化节点zk.create(root,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);}} catch (KeeperException e) {System.out.println("Keeper exception when instantiating queue: "+ e.toString());} catch (InterruptedException e) {System.out.println("Interrupted exception");}}}}
}

produce()

生产者进程调用produce()向队列中添加元素。

public class SyncPrimitive implements Watcher {...../*** 向队列添加元素* @param i* @return* @throws KeeperException* @throws InterruptedException*/boolean produce(int i) throws KeeperException,InterruptedException {//分配堆字节缓冲区,容量为4ByteBuffer b = ByteBuffer.allocate(4);byte[] value;b.putInt(i);value = b.array();//PERSISTENT_SEQUENTIAL:持久化有序节点,可以让队列按照先进先出(操作系统+1,就是先排队的先吃饭啦)的方法使用元素zk.create(root + "/element",value, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);return  true;}}
}

consume()

消费者使用元素的方法:消费者进程consume()获取根节点的子节点,然后依据value去获取排列在最前面的元素。如果同时有两个进程去要获取这个元素,就两个都无法获取,并移除节点

死锁啦?

public class SyncPrimitive implements Watcher {....../*** 生产者-消费者队列*/static public class Queue extends SyncPrimitive {....../*** 移除队列的第一个元素* @return* @throws KeeperException* @throws InterruptedException*/int consume() throws KeeperException,InterruptedException {int retValue = -1;Stat stat = null;while(true) {synchronized (mutex) {List<String> list = zk.getChildren(root,true);//如果list为空,也就是没有子节点,消费者无法消费,所以进程陷入等待if (list.size() == 0) {System.out.println("Going to wait");mutex.wait();} else {//移除前缀。前缀7位:element 节点长啥样,为啥后面是int?Integer min = new Integer(list.get(0).substring(7));String minNode = list.get(0);//寻找最小值(因为children的顺序可能和队列不一致)for (String s: list) {//移除前缀。Integer tempValue = new Integer(s.substring(7));//System.out.println("Temporary value: " + tempValue);if (tempValue < min) {min = tempValue;minNode = s;}}System.out.println("Temporary value: " + root + "/" +minNode);//获取节点值byte[] b = zk.getData(root + "/"+minNode,false,stat);//移除节点zk.delete(root +"/"+minNode,0);//缓冲区的数据会存放在byte数组中ByteBuffer buffer = ByteBuffer.wrap(b);retValue = buffer.getInt();//返回值return retValue;}}}}}
}

测试

代码

public class SyncPrimitive implements Watcher {.....//args接收参数public static void main(String args[]) {//如果是qTest那么代表是队列的测试if (args[0].equals("qTest")) {queueTest(args);} else {//屏障测试barrierTest(args);}}public static void queueTest(String args[]) {//在地址为args[1]的zookeeper服务器上,建立/app1节点Queue q = new Queue(args[1],"/app1");System.out.println("Input:"+args[1]);//创建元素的数量int i;Integer max = new Integer(args[2]);//创建元素if (args[3].equals("p")) {System.out.println("Producer");for (i = 0; i < max; i++) {try {q.produce(10 + i);} catch (KeeperException e) {} catch (InterruptedException e) {}}} else {//消费元素System.out.println("Consumer");for (i = 0; i < max; i++) {try {int r = q.consume();System.out.println("Item:"+r);} catch (KeeperException e) {//剩余元素的数量i--;} catch (InterruptedException e) {}}}}public static void barrierTest(String args[]) {//创建一个屏障,可以容纳两个参与者Barrier b = new Barrier(args[1],"/b1",new Integer(args[2]));try{boolean flag = b.enter();System.out.println("Entered barrier: "+args[2]);if (!flag) {System.out.println("Error when entering the barrier");}} catch (KeeperException e) {System.out.println("barrierTest enter KeeperException");} catch (InterruptedException e) {System.out.println("barrierTest enter InterruptedException");}Random rand = new Random();//int r = rand.nextInt(100);int r = 200;for (int i = 0; i < r; i++) {try {Thread.sleep(100);}catch (InterruptedException e) {System.out.println("barrierTest sleep InterruptedException");}}try {b.leave();} catch (KeeperException e) {System.out.println("barrierTest leave KeeperException"+e.toString());} catch (InterruptedException e) {System.out.println("barrierTest leave InterruptedException");}System.out.println("Left barrier");}public static void barrierTest(String args[]) {//创建一个屏障,可以容纳两个参与者Barrier b = new Barrier(args[1],"/b1",new Integer(args[2]));try{boolean flag = b.enter();System.out.println("Entered barrier: "+args[2]);if (!flag) {System.out.println("Error when entering the barrier");}} catch (KeeperException e) {System.out.println("barrierTest enter KeeperException");} catch (InterruptedException e) {System.out.println("barrierTest enter InterruptedException");}Random rand = new Random();//int r = rand.nextInt(100);int r = 200;for (int i = 0; i < r; i++) {try {Thread.sleep(100);}catch (InterruptedException e) {System.out.println("barrierTest sleep InterruptedException");}}try {b.leave();} catch (KeeperException e) {System.out.println("barrierTest leave KeeperException"+e.toString());} catch (InterruptedException e) {System.out.println("barrierTest leave InterruptedException");}System.out.println("Left barrier");}
}

调用方法

  • 1 启动一个zookeeper的服务器
    (1)如果你是直接在记事本里写的代码
    可以点击zookeeper的zkServer.cmd
    (2)如果你和我一样用的idea
    可以这样(详细配置参考):

    然后启动ZooKeeperServerMain
  • 2 编译SyncPrimitive
    (1) 如果你是直接在记事本里写的代码
    然后javac的话,他可能会报一些换行符什么的问题,或者无法找到主类(也许和最上方有package有关),自己修正一下啦~,因为idea没有呢
    (2)如果你是用的idea
    先编译一下SyncPrimitive

    确保这个目录下有(代表编译成功)
  • 3 传参
    (1)第一种 我没有尝试,你自己试试啦~
    (2)idea
qTest 127.0.1:2181 100 p

调试

队列调试

掌握了调用方法,启动了zookeeper服务器就正式开始调试喽

  • 1 先生产100个元素
qTest 127.0.1:2181 100 p


生产成功与否,可以用zkCli.cmd看一下

  • 2 再消费100个元素
qTest localhost 100 c

127.0.1:2181和localhost都可以呀



好的,成功,真棒呐

下一个~

屏障调试

设置一个障碍与2个参与者

bTest localhost 2

启动之后~~~~
(1) 这里是idea的console

(2)这个是zServer.cmd的
因为他需要两个参与者,然后我们程序只创建了一个参与者,b.enter()生效,但是这样他只建立了一个参与者,这个时候,我们的程序就陷入了无限的等待中

(3)创建节点

create -e /b1/a 1

在右图创建节点后,作图触发watch,输出第一个Process:NodeChildrenChanged
然后输出Entered…
等进入后,调用sleep,睡眠之后。删除一个节点,也就是第二个Process。但是删除节点后,因为还有个节点在里面,所以会陷入等待

(4)然后 我们用

rmr /b1/a

删除另一个节点,这是触发下图的第二个Process
然后离开屏障

到此为止,恭喜你完成啦~

完整源码

package barrierexample;import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.KeyPair;
import java.util.List;
import java.util.Random;public class SyncPrimitive implements Watcher {static ZooKeeper zk = null;static Integer mutex;String root;SyncPrimitive(String address) {//3 zookeeper实例不存在,则父类自己创造if (zk == null) {try {System.out.println("Starting ZK:");zk = new ZooKeeper(address,3000,this);mutex = new Integer(-1);System.out.println("Finished starting ZK: "+zk);} catch (IOException e) {System.out.println(e.toString());zk = null;}}}@Overridesynchronized public void process(WatchedEvent event) {synchronized (mutex) {System.out.println("Process: " + event.getType());mutex.notify();}}/*** Barrier*/static public class Barrier extends SyncPrimitive {int size;String name;/*** 实例化Barrier对象的进程的参数如下:* @param address zookeeper服务器地址(如"zoo1.foo.com:2181")* @param root zookeeper上屏障节点的路径(如"/b1")* @param size 一组进程的大小*/Barrier(String address, String root, int size) {//1 Barrier的构造函数将zookeeper服务器的地址传递给父类的构造函数super(address);this.root = root;this.size = size;//1.5 如果zk对象存在if (zk != null) {try {//2 验证根节点是否存在//(此root非zookeeper的root哦)Stat s = zk.exists(root, false);//2.5 不存在则,Barrier的构造函数在zookeeper上创建一个Barrier节点,作为上述进程节点的父节点,被称作rootif (s == null) {zk.create(root,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);}} catch (KeeperException e) {System.out.println("Keeper exception when instantiating queue: "+ e.toString());} catch (InterruptedException e) {System.out.println("Interrupted exception");}}//获取本地主机地址的完全限定域名try {name = new String(InetAddress.getLocalHost().getCanonicalHostName().toString());} catch (UnknownHostException e) {System.out.println(e.toString());}}/*** 进入屏障* @return* @throws KeeperException* @throws InterruptedException*/boolean enter() throws KeeperException,InterruptedException {//4 进程用root下面创建的节点代表自己,用主机名来作为节点名。//这里如过写临时顺序节点(EPHEMERAL_SEQUENTIAL)的话,创建节点后就无法根据名字(名字会变为一串0的)删除它,所以,我们把这里设为临时节点即可zk.create(root + "/"+name,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);while (true) {//5 它会一直等待直到足够多的进程进入屏障synchronized (mutex) {//6进程通过getChidren()检查root子节点的数量是否满足要求,//getChildren两个参数,第一个表示要读取的节点,第二个表示是否设置watch(根节点发生变化时通知),true为设置List<String> list = zk.getChildren(root,true);if (list.size() < size) {mutex.wait();} else {//7 满足要求就结束等待return true;}}}}/*** 在所有子节点被删除后来开屏障* @return* @throws KeeperException* @throws InterruptedException*/boolean leave() throws KeeperException,InterruptedException {zk.delete(root + "/"+name,0);while (true) {synchronized (mutex) {//8 删除对应子节点并设置watchList<String> list = zk.getChildren(root,true);if (list.size() > 0) {mutex.wait();} else {return true;}}}}}/*** 生产者-消费者队列*/static public class Queue extends SyncPrimitive {Queue(String address,String name) {//q1 调用父类构造函数,如果zookeeper对象不存在就自己创建一个super(address);this.root = name;//验证根节点是否存在,不存在自己创建if (zk != null) {try {Stat s = zk.exists(root,false);if (s == null) {//几个参数:节点路径、节点数据、acl权限、节点类型// OPEN_ACL_UNSAFE:完全开放、PERSISTENT:持久化节点zk.create(root,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);}} catch (KeeperException e) {System.out.println("Keeper exception when instantiating queue: "+ e.toString());} catch (InterruptedException e) {System.out.println("Interrupted exception");}}}/*** 向队列添加元素* @param i* @return* @throws KeeperException* @throws InterruptedException*/boolean produce(int i) throws KeeperException,InterruptedException {//分配堆字节缓冲区,容量为4ByteBuffer b = ByteBuffer.allocate(4);byte[] value;b.putInt(i);value = b.array();//PERSISTENT_SEQUENTIAL:持久化有序节点,可以让队列按照先进先出(操作系统+1)的方法使用元素zk.create(root + "/element",value, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);return  true;}/*** 移除队列的第一个元素* @return* @throws KeeperException* @throws InterruptedException*/int consume() throws KeeperException,InterruptedException {int retValue = -1;Stat stat = null;while(true) {synchronized (mutex) {List<String> list = zk.getChildren(root,true);//如果list为空,也就是没有子节点,消费者无法消费,所以进程陷入等待if (list.size() == 0) {System.out.println("Going to wait");mutex.wait();} else {//移除前缀。前缀7位:element 节点长啥样,为啥后面是int?Integer min = new Integer(list.get(0).substring(7));String minNode = list.get(0);//寻找最小值(因为children的顺序可能和队列不一致)for (String s: list) {//移除前缀。Integer tempValue = new Integer(s.substring(7));//System.out.println("Temporary value: " + tempValue);if (tempValue < min) {min = tempValue;minNode = s;}}System.out.println("Temporary value: " + root + "/" +minNode);//获取节点值byte[] b = zk.getData(root + "/"+minNode,false,stat);//移除节点zk.delete(root +"/"+minNode,0);//缓冲区的数据会存放在byte数组中ByteBuffer buffer = ByteBuffer.wrap(b);retValue = buffer.getInt();//返回值return retValue;}}}}}//args接收参数public static void main(String args[]) {//如果是qTest那么代表是队列的测试if (args[0].equals("qTest")) {queueTest(args);} else {//屏障测试barrierTest(args);}}public static void queueTest(String args[]) {//在地址为args[1]的zookeeper服务器上,建立/app1节点Queue q = new Queue(args[1],"/app1");System.out.println("Input:"+args[1]);//创建元素的数量int i;Integer max = new Integer(args[2]);//创建元素if (args[3].equals("p")) {System.out.println("Producer");for (i = 0; i < max; i++) {try {q.produce(10 + i);} catch (KeeperException e) {} catch (InterruptedException e) {}}} else {//消费元素System.out.println("Consumer");for (i = 0; i < max; i++) {try {int r = q.consume();System.out.println("Item:"+r);} catch (KeeperException e) {//剩余元素的数量i--;} catch (InterruptedException e) {}}}}public static void barrierTest(String args[]) {//创建一个屏障,可以容纳两个参与者Barrier b = new Barrier(args[1],"/b1",new Integer(args[2]));try{boolean flag = b.enter();System.out.println("Entered barrier: "+args[2]);if (!flag) {System.out.println("Error when entering the barrier");}} catch (KeeperException e) {System.out.println("barrierTest enter KeeperException");} catch (InterruptedException e) {System.out.println("barrierTest enter InterruptedException");}Random rand = new Random();//int r = rand.nextInt(100);int r = 200;for (int i = 0; i < r; i++) {try {Thread.sleep(100);}catch (InterruptedException e) {System.out.println("barrierTest sleep InterruptedException");}}try {b.leave();} catch (KeeperException e) {System.out.println("barrierTest leave KeeperException"+e.toString());} catch (InterruptedException e) {System.out.println("barrierTest leave InterruptedException");}System.out.println("Left barrier");}
}

都看到这里了,点个赞再走呗

ZooKeeper官方文档学习笔记05-ZooKeeper的屏障(Barrier)和队列(Queue)教程相关推荐

  1. ZooKeeper官方文档学习笔记03-程序员指南03

    我的每一篇这种正经文章,都是我努力克制玩心的成果,我可太难了,和自己做斗争. ZooKeeper官方文档学习笔记04-程序员指南03 绑定 Java绑定 客户端配置参数 C绑定 陷阱: 常见问题及故障 ...

  2. ZooKeeper官方文档学习笔记01-zookeeper概述

    纠结了很久,我决定用官方文档学习 ZooKeeper概述 学习文档 学习计划 ZooKeeper:分布式应用程序的分布式协调服务 设计目标 数据模型和分层名称空间 节点和短命节点 有条件的更新和监视 ...

  3. ZooKeeper官方文档学习笔记02-ZooKeeper入门指南

    本来以为学一篇都会很难很难,但是好像也没有那么难.虽然有些名词不太理解,但我决定后续学习中应该会遇到吧? 入门:使用ZooKeeper协调分布式应用程序 先决条件 下载 独立运行 1 选择一个合适的目 ...

  4. ZooKeeper官方文档学习笔记04-ZooKeeper的Java实例

    碎碎念:启动成功了一半.可以启动,可以debug,但是有些方法无法访问,而且create在哪里,我还不清楚.那个DataMonitor,不能完全按照官网写,要像我一样改一下,不然会报werror,因为 ...

  5. ZooKeeper官方文档学习笔记03-程序员指南02

    这个太多了 我总是坚持不下来,还是分开写吧,这样更有成就感 程序员指南02 使用ACL的ZooKeeper访问控制 permission schema Zookeeper的C语言client API ...

  6. ZooKeeper官方文档学习笔记03-程序员指南

    害,终究是我高估了自己,这个也太多了.不过里面有些思想,让我感觉似曾相识,比如他的session就有点像HTTPS,然后session的管理有点像人的管理 使用 ZooKeeper 开发分布式应用程序 ...

  7. Open3D官方文档学习笔记

    Open3D官方文档学习笔记 第一部分--点云 1 可视化点云 2 体素降采样 3 顶点法线评估 4 访问顶点法线 补充:Numpy在Open3D中的应用 5 裁剪点云 补充1:获取点云坐标 补充2: ...

  8. kafka官方文档学习笔记2--QuickStart

    下载kafka https://www.apache.org/dyn/closer.cgi?path=/kafka/1.0.0/kafka_2.11-1.0.0.tgz 解压安装包 > tar ...

  9. vue.js 2.0 官方文档学习笔记 —— 01. vue 介绍

    这是我的vue.js 2.0的学习笔记,采取了将官方文档中的代码集中到一个文件的形式.目的是保存下来,方便自己查阅. !官方文档:https://cn.vuejs.org/v2/guide/ 01. ...

最新文章

  1. ppcelerator徐旸:新一代移动互联网技术
  2. java排序——插入排序
  3. xml c libxml类库使用
  4. 【原】iOS学习之Xcode8关于控制台不打印错误信息
  5. 【数据结构与算法】之深入解析“扁平化多级双向链表”的求解思路与算法示例
  6. 利用Azure Functions和k8s构建Serverless计算平台
  7. Hadoop的学习路线图
  8. 打开非遗文化新呈现方式 三七互娱“非遗广州红”游园会即将开幕
  9. 《我的第一本算法书》读书笔记
  10. Gini 系数与熵的关系
  11. WordPress仿站实战教程
  12. 软件项目管理1:开发计划和版本计划举例
  13. 影响中国信息化全面预算管理的十大案例
  14. 怎样用计算机绘制幂函数图像,几何画板如何画幂函数的图像 绘制方法介绍
  15. Tp-link路由器设置教程
  16. H5多点触控原理以及对多点触控的追踪
  17. Java从数据库中读取Blob对象图片并显示的方法
  18. windows底层编程基础
  19. Win8下双系统win7 教程详解
  20. CVE-2020-1472 Netlogon权限提升漏洞分析

热门文章

  1. 还可以这样玩?揭秘打通线上线下新思路
  2. 解决“/dev/mapper/centos-root 100%”满了
  3. sql server 分区_使用分区归档SQL Server数据
  4. ssas 度量值属性_Analysis Services(SSAS)表格模型–属性和度量
  5. sql join 示例_SQL CROSS JOIN与示例
  6. PHPSHE 1.7前台SQL注入漏洞分析
  7. maven课程 项目管理利器-maven 2-2第一个maven案例hellomaven
  8. 51nod1432 独木舟
  9. 目前最小的替换模板了,只有十几行代码
  10. 在Windows Server 2012 R2的Hyper-V中设置虚拟机启用增强会话模式