文章目录

  • 1. zookeeper生产环境的安装配置
    • 1.1 软件配置
    • 1.2 硬件配置
    • 1.3 日志配置文件
    • 1.4 配置三节点的zookeeper集群
  • 2. zookeeper的监控方法
    • 2.1 four letters命令
    • 2.2 JMX 监控方式
  • 3. 通过zookeeper observer实现跨地域部署
    • 3.1 什么是observer
    • 3.2 observer 提升 写性能
    • 3.3 observer 实现跨数据中心部署
  • 4. zookeeper 不中断服务的集群成员变更方法
  • 5. zookeeper数据存储文件:事务文件和快照文件
  • 6. 总结

关于分布式协调服务zookeeper,之前已经对整个框架以及基础使用场景和环境配置做个一个总体的介绍
一文入门zookeeper。

ps: 以下实验是在mac上进行的,有一些mac特有的配置会特别说明
本文中使用的zookeeper版本是3.5.8

本节将简单介绍zookeeper的基本运维操作,主要包括:

  • zookeeper 生产环境的安装配置
  • zookeeper 的监控方法
  • 通过zookeeper observer 实现跨地域部署
  • 通过动态配置实现不中断服务的集群成员变更
  • 如何查看zookeeper的数据存储文件:事务文件和快照文件

通过熟悉以上运维操作,能够更进一步的了解整个zookeeper的架构和实现方式,从而更好得使用它。

1. zookeeper生产环境的安装配置

1.1 软件配置

ZooKeeper 的配置项在 zoo.cfg 配置文件中配置, 另外有些配置项可以通过 Java 系统属性来 进行配置。

  • clientPort : ZooKeeper 和客户端通信的端口号。
  • dataDir :来保存快照文件的目录。如果没有设置 dataLogDir ,事务日志文件也会保存到这
    个目录。
  • dataLogDir :用来保存事务日志文件的目录。因为 ZooKeeper 在提交一个事务之前,需要保 证事务日志记录的落盘,所以需要为 dataLogDir 分配一个独占的存储设备。

基本配置文件内容如下,这里是在mac本地进行演示,并没有增加dataLogDir,默认会和dataDir在一个目录,如果有足够的测试服务器可以为该配置分配独立的目录:

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/tmp/zookeeper
# the port at which the clients will connect
clientPort=2181

1.2 硬件配置

建议为zookeeper分配独立的服务器,同时要给zookeeper的事务日志(dataLogDir)分配独立的存储设备或者分区

  • 内存:ZooKeeper 需要在内存中保存 data tree 。对于一般的 ZooKeeper 应用场景,8G 的内存足够了。
  • CPU:ZooKeeper 对 CPU 的消耗不高,只要保证 ZooKeeper 能够有一个独占的 CPU 核 即可
  • 存储:因为存储设备的写延迟会直接影响事务提交的效率,建议为 dataLogDir 分配一个独占的 SSD 盘

1.3 日志配置文件

日志配置文件默认在 conf/log4j
基本配置内容如下:

# 日志级别
zookeeper.root.logger=INFO, CONSOLE
# 对于appender来说,也是Info 或者比info 级别更严重的log会被打出来
zookeeper.console.threshold=INFOzookeeper.log.dir=.
zookeeper.log.file=zookeeper.log
zookeeper.log.threshold=INFO
zookeeper.log.maxfilesize=256MB
zookeeper.log.maxbackupindex=20zookeeper.tracelog.dir=${zookeeper.log.dir}
zookeeper.tracelog.file=zookeeper_trace.loglog4j.rootLogger=${zookeeper.root.logger}#
# console
# Add "console" to rootlogger above if you want to use this
#
# console appender 的实现类
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
# console 日志输出的格式
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 指定详细的日志格式
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n#
# Add ROLLINGFILE to rootLogger to get log file output
#
# 一般会使用appender.ROLLINGFILE作为日志追加方式
log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold}
log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file}
log4j.appender.ROLLINGFILE.MaxFileSize=${zookeeper.log.maxfilesize}
log4j.appender.ROLLINGFILE.MaxBackupIndex=${zookeeper.log.maxbackupindex}
log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n

1.4 配置三节点的zookeeper集群

在服务器充足的情况下,每一个服务区基本的配置步骤如下:

  1. 申请 ZooKeeper 节点服务器。每个 ZooKeeper 节点有两个挂载盘。
  2. 每个节点安装 JDK 8 。
  3. 在每个节点为 dataDir 初始化一个独立的文件系统 /data ,编辑 myid,表示server的编号(每个 server代表一个数字,可以echo 1 > /data/myid 即可) 。
  4. 各个节点之间配置基于 public key 的 SSH 登录。
  5. 在一个节点上下载解压 apache-zookeeper-3.5.5-bin.tar.gz ,配置 zoo.cfg 。使用 rsync 把解压的目录同步到其他节点。

到此即完成基本的环境配置,我们只需要使用zookeeper提供的cli命令进行运维层面的配置即可。

因为我只有一个mac机器,那只能在一个mac机器中启动三个server进程来模拟zookeeper的集群,这里不需要执行第四步了,对应的第三步通过指定不同的配置目录即可。

模拟的三个节点的配置如下:
需要注意的是其中的server.配置,127.0.0.1表示节点ip或者主机(hostname)名称,7777表示后续zookeeper 集群的qurom通信端口号,7778表示leader选举的端口号。

同时需要打开选项4lw.commands.whitelist=*表示开启zookeeper提供的四字母监控命令的白名单。

  • zoo-node1.cfg

    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/tmp/zookeeper1
    clientPort=2181
    server.1=127.0.0.1:7777:7778
    server.2=127.0.0.1:6666:6667
    server.3=127.0.0.1:5555:5556
    4lw.commands.whitelist=* #开启监控命令的白名单,否则后续的监控命令无法使用
    
  • zoo-node2.cfg
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/tmp/zookeeper2
    clientPort=2181
    server.1=127.0.0.1:7777:7778
    server.2=127.0.0.1:6666:6667
    server.3=127.0.0.1:5555:5556
    4lw.commands.whitelist=*
    
  • zoo-node3.cfg
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/tmp/zookeeper3 # 如果使用服务器可以指定相同的目录
    clientPort=2181
    server.1=127.0.0.1:7777:7778
    server.2=127.0.0.1:6666:6667
    server.3=127.0.0.1:5555:5556
    4lw.commands.whitelist=*
    

启动zookeeper集群:
zkServer.h start conf/zoo-node1.cfg
zkServer.h start conf/zoo-node2.cfg
zkServer.h start conf/zoo-node3.cfg

启动成功之后,检查集群状态是否有异常
echo srvr | nc localhost 2181, nc是ncat命令的缩写是一个网络工具,mac上可以通过brew install nmap安装,安装完成需要将/usr/local/Cellar目录导入$PATH,否则无法直接使用命令。

└> echo srvr | nc localhost 2181
Zookeeper version: 3.5.8-f439ca583e70862c3068a1f2a7d4d068eec33315, built on 11/15/2020 03:27 GMT
Latency min/avg/max: 0/0/0
Received: 7
Sent: 6
Connections: 1
Outstanding: 0
Zxid: 0x100000006
Mode: follower
Node count: 5

如果执行以上命令出现
srvr is not executed because it is not in the whitelist.问题,则需要修改cfg配置,加入4lw.commands.whitelist=* 配置,打开四个字母的白名单。记得修改完配置重启zkServer.sh ,配置才能生效。

2. zookeeper的监控方法

zookeeper 虽然仅仅提供分布式的协调服务,但是仍然有自己的监控系统(一组监控命令),来让自己的运维系统更加完善。
接下来主要描述两种zookeeper原生支持的监控方式,第一种是four letters命令;第二种是JMX方式,这种方式后面的演示在mac上会更方便一点。

2.1 four letters命令

其中four letters 命令由四个字母组成,可以通过 telnet 或 ncat 使用客户端端口向 ZooKeeper 发出命令。

  • ruok , 向zookeeper 集群的一个节点询问are you ok?
    echo ruok | nc localhost 2181 ,向指定节点的client通信端口发送信息
    如果回复 imok%,则该节点正常,否则返回失败。

    这个状态并不是代表集群状态,仅仅是集群的某一个节点状态。

  • conf , 查看节点配置项
    echo conf | nc localhost 2181 ,输出如下

    clientPort=2181
    secureClientPort=-1
    dataDir=/tmp/zookeeper1/version-2
    dataDirSize=67109438
    dataLogDir=/tmp/zookeeper1/version-2
    dataLogSize=67109438
    tickTime=2000
    maxClientCnxns=60
    minSessionTimeout=4000
    maxSessionTimeout=40000
    serverId=1
    initLimit=10
    syncLimit=5
    electionAlg=3
    electionPort=7778
    quorumPort=7777
    peerType=0
    membership:
    server.1=127.0.0.1:7777:7778:participant
    server.2=127.0.0.1:6666:6667:participant
    server.3=127.0.0.1:5555:5556:participant
    
  • stat, 返回当前server节点和客户端链接信息
    echo stat | nc localhost 2181

    Zookeeper version: 3.5.8-f439ca583e70862c3068a1f2a7d4d068eec33315, built on 11/15/2020 03:27 GMT
    Clients:/127.0.0.1:53629[0](queued=0,recved=1,sent=0)Latency min/avg/max: 0/0/0
    Received: 3
    Sent: 2
    Connections: 1
    Outstanding: 0
    Zxid: 0x100000006
    Mode: follower
    Node count: 5
    
  • dump, 查看zookeeper 中临时节点的信息
    echo dump | nc localhost 2181

    SessionTracker dump:
    Global Sessions(1):
    0x100077484570000       30000ms
    ephemeral nodes dump:
    Sessions with Ephemerals (1):
    0x100077484570000:/worker
    Connections dump:
    Connections Sets (3)/(2):
    0 expire at Sat Dec 12 12:43:33 CST 2020:
    1 expire at Sat Dec 12 12:43:43 CST 2020:ip: /127.0.0.1:54091 sessionId: 0x0
    1 expire at Sat Dec 12 12:43:53 CST 2020:ip: /127.0.0.1:54021 sessionId: 0x100077484570000
    
  • wchc, 查看 watch状态信息
    └> echo wchc | nc localhost 2181
    0x100077484570000/worker
    

更多的四字命令如下:

conf: 打印ZooKeeper的配置信息
cons: 列出所有的客户端会话链接
crst: 重置所有的客户端连接
dump: 打印集群的所有会话信息,包括ID,以及临时节点等信息。用在Leader节点上才有效果。
envi: 列出所有的环境参数
ruok: "谐音为Are you ok"。检查当前服务器是否正在运行。
stat: 获取ZooKeeper服务器运行时的状态信息,包括版本,运行时角色,集群节点个数等信息。
srst: 重置服务器统计信息
srvr: 和stat输出信息一样,只不过少了客户端连接信息。
wchs: 输出当前服务器上管理的Watcher概要信息
wchc: 输出当前服务器上管理的Watcher的详细信息,以session为单位进行归组
wchp: 和wchc非常相似,但是以节点路径进行归组
mntr: 输出比stat更为详细的服务器统计信息

2.2 JMX 监控方式

接下来说一下JMX,ZooKeeper 很好的支持了 JMX ,大量的监控和管理工作多可以通过 JMX 来做。
而且能够将zookeeper的JMX数据集成到Prometheus,利用其来进行zookeeper的监控。

安装了JDK环境之后,JMX默认已经安装,只需要在终端运行jconsole命令(mac)即可启动

我的环境中已经有三个zkServer,所以会有三个server.quorum,任选一个即可。
直接点击连接,会弹出一个不安全连接的弹窗(因为我们并没有配置集群的ssl安全连接配置),所以这里直接选择 “不安全连接“即可

能够看到当前节点的 zookeeper server的资源消耗概览:

更加详细的节点细节数据可以通过进入MBean页面查看。

比如我使用zookeeper客户端做了如下操作:

原本在jconsole看到的节点个数的信息是
刷新后可以看到有变更:

以上的监控访问都是通过本地访问,现在jmx也能够支持远程访问,在被访问机器配置环境变量:

JMXPORT=8081

重启zookeeper server以及jconsole

然后在当前节点的jconsole连接窗口输入如下内容,仍然使用不安全的 连接方式即可访问远程节点的zookeeper数据。

到此zookeeper的原生监控已经描述完毕,可以看到zookeeper的运维系统还是非常完善的,更加方便的监控就是结合jmx数据和premethus 完成更加完善全面的监控。

3. 通过zookeeper observer实现跨地域部署

3.1 什么是observer

就像是字面含义中所说的 观察者,不参与,不决策,万物皆可动,唯我心永恒。
它作为zookeeper集群中的一个角色而存在,和集群中其他节点的唯一交互就是接受来自leader的inform信息,更新到自己的本地存储,不参与集群中的事务提交,leader选举等过程。

下图为zookeeper写请求的时序图:

上图中:节点2: leader, 节点1和节点3都是follower

  • 客户端提交写请求到其所连接的节点1
  • 节点1 将请求转发给节点2(节点2是leader),只有leader可写
  • 节点2 发送提案 给集群所有节点
  • 节点2等待,只有有超过半数的节点回复,即可进行commit
  • 节点1 收到commit消息,即可向客户端返回成功。

3.2 observer 提升 写性能

将节点1 设置为observer,这样能够减少网络rpc(一次propose和accept),减少磁盘I/O(只需要等待leader的通知返回客户端即可)

3.3 observer 实现跨数据中心部署


如上场景,整个集群包括leader节点在内有5个节点,其中北京有3个节点,香港有2个节点。
假如我们这5个节点的集群只有1个leader和其他4个follower,leader在北京,那么远在香港的两个follower每一次写请求都需要参与到整个集群的propose和accept,这两个rpc对于跨地域的集群部署来说代价非常大,会严重降低系统可用性和性能。

这个时候如上图,我们将香港的两个节点设置为observer,2个rpc就可以节省下来,observer只需要forward到leader 并接受leader的inform即可。

当然这样的集群部署模式在实际的场景还不如分成两个本地集群,从而降低网络分区对系统可用性的影响。

那么如何将一个节点部署成observer节点?

实际的部署配置可以 修改observer所在节点的zoo.cfg启动配置的集群选项配置如下:

server.1=127.0.0.1:7777:7778
server.2=127.0.0.1:6666:6667
server.3=127.0.0.1:5555:5556
server.4=127.0.0.1:3333:3334:observer #增加集群的配置选项,将当前节点指定为observer
4lw.commands.whitelist=*

启动第四个节点之后,通过echo srvr | nc $ip 2181 指定第四个ip的端口即可知道我们设置的角色是否成功(observer角色)。

4. zookeeper 不中断服务的集群成员变更方法

上文中我们也有关于调整集群成员的一些操作,总体步骤如下:

  1. 停止整个集群中所有的server
  2. 更改现有集群中所有server的cfg项中的server.n项(集群成员变更可能导致某一个节点已有数据被覆盖)
  3. 重新启动集群中所有的server

如果zookeeper集群达到一定规模,这一系列操作low且耗时,尤其是中断服务,降低系统可用性。

由于这种手动方式调整代价很高,所有zookeeper在3.5.0版本合入了 dynamic reconfiguration特性。
这个特性能够支持不停止zookeeper服务的前提下调整集群成员。

  • 获取一个给super用的digest, 其中supper是zookeeper提供的认证机制。

    package org.yao;import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;/*** Generate digest for ZooKeeper super user authentication.*/
    public class DigestGenerator {public static void main(String[] args) throws Exception {System.out.println(DigestAuthenticationProvider.generateDigest("super:z_stand"));}
    }
    

    运行代码即可生成一个digest认证xxx

  • 导入该认证到环境变量中
    export SERVER_JVMFLAGS=-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xxx
    
  • zookeeper配置文件
    zoo-node2.cfg,将server.n相关配置放在了单独的配置文件中,因为后续动态修改的话是需要修改该文件的内容。

    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/tmp/zookeeper2
    clientPort=2181
    4lw.commands.whitelist=*
    dynamicConfigFile=conf/dyn.cfg
    

    dyn.cfg

    server.1=127.0.0.1:7777:7778:participant;127.0.0.1:2181
    server.2=127.0.0.1:6666:6667:participant;127.0.0.1:2181
    server.3=127.0.0.1:5555:5556:participant;127.0.0.1:2181
    

所有节点的配置文件修改好之后可以将每个节点的server都启动起来。

  • 开启客户端,这个客户端需要连接到我们第一步配置认证的那个节点上运行,当然这里是mac上模拟的,所以也就直接连接本地即可。

    zkCli.sh -server localhost:2181
    
  • 在客户端的交互命令行中 添加认证
    addauth digest super:z_stand 即可
    接下来展示客户端交互命令如何动态配置节点情况

    • config 显示集群中的server配置
    • reconfig -remove 3 移除server.3
    • config查看集群成员的变更情况
    • reconfig -add server.4=127.0.0.1:3333:3334:participant;127.0.0.1:2181向集群中增加新的成员

到此集群成员的动态变更方式基本描述清楚了,感兴趣的同学可以尝试一下,需要注意的supper认证部分,zkCli.sh连接的server是需要完成认证的server节点。

5. zookeeper数据存储文件:事务文件和快照文件

zookeeper本地存储中的数据形态如下图:

data tree 中的znode信息都是存放在内存中的
而磁盘上主要的是两种文件:事务文件和快照文件

事务文件中的entry是追加的,每一次有zookeeper集群的写入(创建znode,删除znode)都会作为事务更新到事务文件中。事务使用64位的整数zxid唯一标识,其中高四个字节是epoch,低四个字节是counter。

快照文件是当节点启动/重启时会创建一个文件

除此之外还会有两个文件:acceptedEpochcurrentEpoch分别保存当前集群的一些版本信息。

└> ls /tmp/zookeeper1/version-2
acceptedEpoch      log.100000001      log.600000001      snapshot.0
currentEpoch       log.400000001      log.a00000001      snapshot.600000001

zookeeper提供了工具能够查看其中的内容zkTxnLogToolkit.sh,可以看到如下信息

└> zkTxnLogToolkit.sh log.a00000001
/usr/bin/java
ZooKeeper Transactional Log File with dbid 0 txnlog format version 2
2020/12/12 CST 下午3:18:27 session 0x100080708e50000 cxid 0x0 zxid 0xa00000001 createSession 30000
2020/12/12 CST 下午3:37:55 session 0x100080708e50000 cxid 0x0 zxid 0xa00000002 closeSession
2020/12/12 CST 下午3:55:57 session 0x100080708e50001 cxid 0x0 zxid 0xa00000003 createSession 30000
EOF reached after 3 txns.

当对集群中的数据进行变更时这一些日志条目也会发生变更。

查看zookeeper的快照文件,这里zookeeper仅仅提供了一个类org.apache.server.SnapshotFormatter来查看,可以编写如下脚本snapshot.sh实现快照文件的查看:

#!/bin/bash
zkEnv.sh
export CLASSPATH="$CLASSPATH"
java org.apache.server.SnapshotFormatter "$@"

最后通过运行sh snapshot.sh /tmp/zookeeper3/version-2/snapshot.0来查看具体的快照文件内容。

ps: 只要zookeeper节点发生重启,或者启动 就会生成新的快照文件,记录当前状态下zookeeper的内存状态。

剩下的两个epoch文件acceptedEpochcurrentEpoch在单机模式下并不存在, 只有在集群模式下才会生成。

6. 总结

以上从zookeeper集群模式的搭建,到基本zookeeper监控,再到zookeeper实现跨地域部署的优缺点 以及 更加高级的 不中断服务的场景下实现集群成员变更。利用以上特性,我们能够很好的运维一个zookeeper集群。

对于更加底层的细节:zookeeper的读写流程 中如何保证集群写入的原子性(ZAB协议–multi paxos的一种变种),如何完成节点异常时的数据重放,这一些有趣的分布式细节会在后续讨论。

一文运维zookeeper相关推荐

  1. ZooKeeper: 简介, 配置及运维指南

    1. 概览 ZooKeeper是一个供其它分布式应用程序使用的软件, 它为其它分布式应用程序提供所谓的协调服务. 所谓的协调服务, 是指ZooKeeper的如下能力 naming 命名 configu ...

  2. 运维提升首选技能KubernetesPrometheus,你了解多少?(文末福利)

    2021年,产品云端化已成为一种趋势.在一线城市,很多公司都已经构建了自己的私有云环境,比如阿里云.网易云.华为云等. 而 Kubernetes 作为基于容器编排领域的王者,具备扩展集群.滚动升级回滚 ...

  3. 运维老鸟告诉你这个经典Zookeeper问题的根因

    来自:DBAplus社群 作者介绍 邹春华,新炬网络中间件专家.10年软件开发工作经验,9年运营商行业IT系统维护经验.精通C.C++.JAVA.PHP.SHELL等语言,有着深厚的大型IT软件系统开 ...

  4. linux awk语法格式,Awk是什么?一文带运维小白快速掌握Linux Awk用法

    原标题:Awk是什么?一文带运维小白快速掌握Linux Awk用法 作者:a8 Awk.sed与grep,俗称Linux下的三剑客,它们之间有很多相似点,但是同样也各有各的特色,相似的地方是它们都可以 ...

  5. Linux系统运维工程师PDF文档精选

    收藏先,O(∩_∩)O~ Linux系统运维工程师PDF文档精选 1.高级Bash脚本编程指南  http://www.unixhot.com/pdf/bash.pdf 2.Linux 策略路由和流量 ...

  6. zookeeper 运维管理

    2019独角兽企业重金招聘Python工程师标准>>> zookeeper 运维管理(转) link: http://blog.163.com/bdweizhong@yeah/blo ...

  7. zookeeper运维管理

    目录: 1.运维管理 2.淘宝的关于zookeeper的多篇文章: link:http://nileader.blog.51cto.com/1381108/1068033 zookeeper 运维管理 ...

  8. starops 云效运维 文档_阿里云 SAE 携手云效助力「石家庄掌讯」持续交付、降本提效...

    背景 石家庄掌讯信息技术有限公司创立于2009年,是一家提供企业信息化咨询.创新型软件产品.电商代运营服务,标准化管理.快速发展的高新技术企业.当前公司正处于企业互联网市场突破转型重要阶段,希望将更多 ...

  9. 【讲师专访】Oracle ACE 总监侯圣文:不懂开发的运维终将被淘汰

    [编者按] 云和恩墨大讲堂每周都会邀请业内外大咖进行一小时的线上主题分享,本期我们邀请到了Oracle ACE总监侯圣文老师,带来题为<自治时代DBA的技能库:SQL和PL/SQL的深度编程&g ...

最新文章

  1. 为什么栈和堆的生长方向不一样
  2. 字节跳动小程序技术摘要
  3. 转载---sharepoint相关资源
  4. python中functools_python–functools的使用 | 学步园
  5. jsp页面textarea中换行替换问题
  6. python画图小猪佩奇_吊炸天!Python 20秒画出小猪佩奇
  7. 今年面试,光靠技术肯定不行了!
  8. 【英语学习】【WOTD】emote 释义/词源/示例
  9. 循环计数_FOR 循环
  10. java最基础的小总结
  11. Python——Window启动服务
  12. 作为字节跳动的研发面试官,有些话我不得不说!
  13. matlab 拉普拉斯求解,matlab解拉普拉斯方程.ppt
  14. Nature:肠道细菌能够调节果蝇运动行为
  15. 5G工业无线RTU TG511功能配置
  16. CDR安装包下载+汉化简体中文版+安装教程
  17. Python打印简单杨辉三角形
  18. 聚合支付的清算风险有哪些?
  19. 吉林大学计算机专业研究生导师,吉林大学计算机科学与技术学院导师教师简介-张晋东...
  20. 全球程序员收入出炉!北京收入排入全球第十

热门文章

  1. ng-cordova和cordova区别
  2. Java中的文件上传2(Commons FileUpload:commons-fileupload.jar)
  3. ReportDB数据库存储选型分析
  4. linux定时器(crontab)实例
  5. hdu 1561 The more, The Better_树状dp
  6. 单调队列多重背包时间复杂度O(vn)
  7. php http请求xml数据,php获取通过http协议post提交过来xml数据及解析xml
  8. 顺丰职级分成4级_14368!4月全国程序员均薪新鲜出炉!
  9. java url路径包含中文_谈谈 Java 类加载机制
  10. mysql的判断更新_mysql判断记录是否存在,存在则更新,不存在则插入