Arthas简介

 Arthas  Arthas简介已关闭评论
2月 112019
 

Arthas真是好用,项目地址:https://github.com/alibaba/arthas , 回想btrace时代真是辛苦。以下文字来自官方文档摘录。

Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?

Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

快速开始

使用arthas-boot(推荐)

下载arthas-boot.jar,然后用java -jar的方式启动:

wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

打印帮助信息:

java -jar arthas-boot.jar -h
  • 如果下载速度比较慢,可以使用aliyun的镜像:java -jar arthas-boot.jar –repo-mirror aliyun –use-http

使用as.sh

Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 回车 执行即可:

curl -L https://alibaba.github.io/arthas/install.sh | sh

上述命令会下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。

直接在shell下面执行./as.sh,就会进入交互界面。

也可以执行./as.sh -h来获取更多参数信息。

快速入门

1. 启动Demo

				
wget https://alibaba.github.io/arthas/arthas-demo.jar
java -jar arthas-demo.jar

arthas-demo是一个简单的程序,每隔一秒生成一个随机数,再执行质因式分解,并打印出分解结果。

arthas-demo源代码:查看

2. 启动arthas

在命令行下面执行(使用和目标进程一致的用户启动,否则可能attach失败):

				
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

  • 执行该程序的用户需要和目标进程具有相同的权限。比如以admin用户来执行:sudo su admin && java -jar arthas-boot.jar 或 sudo -u admin -EH java -jar arthas-boot.jar。
  • 如果attach不上目标进程,可以查看~/logs/arthas/ 目录下的日志。
  • 如果下载速度比较慢,可以使用aliyun的镜像:java -jar arthas-boot.jar –repo-mirror aliyun –use-http
  • java -jar arthas-boot.jar -h 打印更多参数信息。

选择应用java进程:

				
$ $ java -jar arthas-boot.jar
* [1]: 35542
[2]: 71560 arthas-demo.jar

Demo进程是第2个,则输入2,再输入回车/enter。Arthas会attach到目标进程上,并输出日志:

				
[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki: https://alibaba.github.io/arthas
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
$

3. 查看dashboard

输入dashboard,按enter/回车,会展示当前进程的信息,按ctrl+c可以中断执行。

				
$ dashboard
ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON
17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false
27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true
11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true
9 Attach Listener system 9 RUNNAB 0 0:0 false true
3 Finalizer system 8 WAITIN 0 0:0 false true
2 Reference Handler system 10 WAITIN 0 0:0 false true
4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true
26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true
13 job-timeout system 9 TIMED_ 0 0:0 false true
1 main main 5 TIMED_ 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false
18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false
23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166
ps_survivor_space 4M 5M 5M s)
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time( 0
code_cache 3M 5M 240M 1.32% ms)
Runtime
os.name Mac OS X
os.version 10.13.4
java.version 1.8.0_162
java.home /Library/Java/JavaVir
tualMachines/jdk1.8.0
_162.jdk/Contents/Hom
e/jre

4. 通过thread命令来获取到arthas-demo进程的Main Class

thread 1会打印线程ID 1的栈,通常是main函数的线程。

				
$ thread 1 | grep 'main('
at demo.MathGame.main(MathGame.java:17)

5. 通过jad来反编绎Main Class

				
$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/arthas-demo.jar
/*
* Decompiled with CFR 0_132.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class MathGame {
private static Random random = new Random();
private int illegalArgumentCount = 0;
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
do {
game.run();
TimeUnit.SECONDS.sleep(1L);
} while (true);
}
public void run() throws InterruptedException {
try {
int number = random.nextInt();
List<Integer> primeFactors = this.primeFactors(number);
MathGame.print(number, primeFactors);
}
catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
}
}
public static void print(int number, List<Integer> primeFactors) {
StringBuffer sb = new StringBuffer("" + number + "=");
Iterator<Integer> iterator = primeFactors.iterator();
while (iterator.hasNext()) {
int factor = iterator.next();
sb.append(factor).append('*');
}
if (sb.charAt(sb.length() - 1) == '*') {
sb.deleteCharAt(sb.length() - 1);
}
System.out.println(sb);
}
public List<Integer> primeFactors(int number) {
if (number < 2) {
++this.illegalArgumentCount;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");
}
ArrayList<Integer> result = new ArrayList<Integer>();
int i = 2;
while (i <= number) {
if (number % i == 0) {
result.add(i);
number /= i;
i = 2;
continue;
}
++i;
}
return result;
}
}
Affect(row-cnt:1) cost in 970 ms.


常用命令举例:

watch

通过watch命令来查看demo.MathGame#primeFactors函数的返回值:

				
$ watch demo.MathGame primeFactors returnObj
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
@Integer[5],
@Integer[47],
@Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
@Integer[2],
@Integer[5],
@Integer[317],
@Integer[503],
@Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
@Integer[2],
@Integer[2],
@Integer[3],
@Integer[3],
@Integer[31],
@Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
@Integer[5],
@Integer[29],
@Integer[7651739],
]

更多的功能可以查看进阶使用

5. 退出arthas

如果只是退出当前的连接,可以用quit或者exit命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时可以直接连接上。

如果想完全退出arthas,可以执行shutdown命令。

watch

方法执行数据观测

让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。

参数说明

watch 的参数比较多,主要是因为它能在 4 个不同的场景观察对象

参数名称 参数说明
class-pattern 类名表达式匹配
method-pattern 方法名表达式匹配
express 观察表达式
condition-express 条件表达式
[b] 方法调用之前观察
[e] 方法异常之后观察
[s] 方法返回之后观察
[f] 方法结束之后(正常返回和异常返回)观察
[E] 开启正则表达式匹配,默认为通配符匹配
[x:] 指定输出结果的属性遍历深度,默认为 1

这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写”{params,returnObj}”,只要是一个合法的 ognl 表达式,都能被正常支持。

观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。请参考表达式核心变量中关于该节点的描述。

特别说明

  • watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后
  • 4个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
  • 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参
  • 当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在

使用参考

启动 Demo

启动快速入门里的arthas-demo。

观察方法出参和返回值

								
$ watch demo.MathGame primeFactors "{params,returnObj}" -x 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 44 ms.
ts=2018-12-03 19:16:51; [cost=1.280502ms] result=@ArrayList[
@Object[][
@Integer[535629513],
],
@ArrayList[
@Integer[3],
@Integer[19],
@Integer[191],
@Integer[49199],
],
]

观察方法入参

								
$ watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 50 ms.
ts=2018-12-03 19:23:23; [cost=0.0353ms] result=@ArrayList[
@Object[][
@Integer[-1077465243],
],
null,
]

  • 对比前一个例子,返回值为空(事件点为方法执行前,因此获取不到返回值)

同时观察方法调用前和方法返回后

								
$ watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 46 ms.
ts=2018-12-03 19:29:54; [cost=0.01696ms] result=@ArrayList[
@Object[][
@Integer[1544665400],
],
@MathGame[
random=@Random[java.util.Random@522b408a],
illegalArgumentCount=@Integer[13038],
],
null,
]
ts=2018-12-03 19:29:54; [cost=4.277392ms] result=@ArrayList[
@Object[][
@Integer[1544665400],
],
@MathGame[
random=@Random[java.util.Random@522b408a],
illegalArgumentCount=@Integer[13038],
],
@ArrayList[
@Integer[2],
@Integer[2],
@Integer[2],
@Integer[5],
@Integer[5],
@Integer[73],
@Integer[241],
@Integer[439],
],
]

  • 参数里-n 2,表示只执行两次
  • 这里输出结果中,第一次输出的是方法调用前的观察表达式的结果,第二次输出的是方法返回后的表达式的结果
  • 结果的输出顺序和事件发生的先后顺序一致,和命令中 -s -b 的顺序无关

调整-x的值,观察具体的方法参数值

								
$ watch demo.MathGame primeFactors "{params,target}" -x 3
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 58 ms.
ts=2018-12-03 19:34:19; [cost=0.587833ms] result=@ArrayList[
@Object[][
@Integer[47816758],
],
@MathGame[
random=@Random[
serialVersionUID=@Long[3905348978240129619],
seed=@AtomicLong[3133719055989],
multiplier=@Long[25214903917],
addend=@Long[11],
mask=@Long[281474976710655],
DOUBLE_UNIT=@Double[1.1102230246251565E-16],
BadBound=@String[bound must be positive],
BadRange=@String[bound must be greater than origin],
BadSize=@String[size must be non-negative],
seedUniquifier=@AtomicLong[-3282039941672302964],
nextNextGaussian=@Double[0.0],
haveNextNextGaussian=@Boolean[false],
serialPersistentFields=@ObjectStreamField[][isEmpty=false;size=3],
unsafe=@Unsafe[sun.misc.Unsafe@2eaa1027],
seedOffset=@Long[24],
],
illegalArgumentCount=@Integer[13159],
],
]

  • -x表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1。

条件表达式的例子

								
$ watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 68 ms.
ts=2018-12-03 19:36:04; [cost=0.530255ms] result=@ArrayList[
@Integer[-18178089],
@MathGame[demo.MathGame@41cf53f9],
]

  • 只有满足条件的调用,才会有响应。

观察异常信息的例子

								
$ watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 62 ms.
ts=2018-12-03 19:38:00; [cost=1.414993ms] result=@ArrayList[
@Integer[-1120397038],
java.lang.IllegalArgumentException: number is: -1120397038, need >= 2
at demo.MathGame.primeFactors(MathGame.java:46)
at demo.MathGame.run(MathGame.java:24)
at demo.MathGame.main(MathGame.java:16)
,
]

  • -e表示抛出异常时才触发
  • express中,表示异常信息的变量是throwExp

按照耗时进行过滤

								
$ watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 66 ms.
ts=2018-12-03 19:40:28; [cost=2112.168897ms] result=@ArrayList[
@Object[][
@Integer[2141897465],
],
@ArrayList[
@Integer[5],
@Integer[428379493],
],
]

  • #cost>200(单位是ms)表示只有当耗时大于200ms时才会输出,过滤掉执行时间小于200ms的调用

观察当前对象中的属性

如果想查看方法运行前后,当前对象中的属性,可以使用target关键字,代表当前对象

								
$ watch demo.MathGame primeFactors 'target'
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 52 ms.
ts=2018-12-03 19:41:52; [cost=0.477882ms] result=@MathGame[
random=@Random[java.util.Random@522b408a],
illegalArgumentCount=@Integer[13355],
]

然后使用target.field_name访问当前对象的某个属性

								
$ watch demo.MathGame primeFactors 'target.illegalArgumentCount'
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 67 ms.
ts=2018-12-03 20:04:34; [cost=131.303498ms] result=@Integer[8]
ts=2018-12-03 20:04:35; [cost=0.961441ms] result=@Integer[8]

Tornado异步笔记(三)— 持久连接 KeepAlive 简介

 tornado  Tornado异步笔记(三)— 持久连接 KeepAlive 简介已关闭评论
12月 062018
 

网上看到的3篇连载关于tornado异步的文章,写的很好,虽然有些内容有些老了,但不影响文章的借鉴意义,强烈推荐:

转自:https://www.jianshu.com/p/3cc234198567

HTTP 持久连接

HTTP通信中,client和server一问一答的方式。HTTP是基于TCP的应用层协议,通常在发送请求之前需要创建TCP连接,然后在收到响应之后会断开这个TCP连接。这就是常见的http短连接。既然有短连接,那么也有长连接。

HTTP协议最初的设计是无连接无状态的方式。为了维护状态,引入了cookie和session方式认证识别用户。早期的web开发中,为了给用户推送数据,通常使用所谓的长连接。那时的长连接还是基于短连接的方式实现,即通过client的轮询查询,在用户层面看起来连接并没有断开。随着技术的发展,又出现了Websockt和MQTT等通信协议。Websockt和MQTT则是全双工的通信协议。

相比全双工实现的长连接,我们还会在web开发中遇到伪长连接。即HTTP协议中的keepalive模式。因为HTTP设计是无连接设计,请求应答结束之后就关闭了TCP连接。在http通信中,就会有大量的新建和销毁tcp连接的过程,那怕是同一个用户同一个客户端。为了优化这种方式,HTTP提出了KeepAlive模式,即创建的tcp连接后,传输数据,server返回响应之后并不会关掉tcp连接,下一次http请求就能复用这个tcp连接。

这是一种协商式的连接,毕竟每次的http发送数据的时候,还是要单独为每个请求发送header之类的信息。相比全双工的websocket,一旦创建了连接,下一次就不需要再发送header,直接发送数据即可。因此描述http的keepalive应该是持久连接(HTTP persistent connection )更准确。

keepalive 简介

HTTP的keepalive模式提供了HTTP通信的时候复用TCP连接的协商功能。http1.0默认是关闭的,只有在http的header加入Connection: Keep-Alive才能开启。而http1.1则正相反,默认就打开了,只有显示的在header里加入Connection: close才能关闭。现在的浏览器基本都是http1.1的协议,能否使用长连接,权看服务器的支持状况了。下图说明了开启keepalive模式的持久连接与短连接的通信示意图

短连接与持久连接,图片来源网络

当开启了持久连接,就不能使用返回EOF的方式来判断数据结尾了。对于静态和动态的数据,可以使用Conent-Lenght和Transfer-Encoding`来做应用层的区分。

requests与持久连接

了解了keeplive模式,接下来我们就来使用keepalive方式。服务器使用Tornado,tornado实现了keepalive的处理,客户端我们可以分别使用同步的requests和异步的AsyncHTTPClient。

先写一个简单的服务器:

micro-server.py

import tornado.httpserver 
import tornado.ioloop 
import tornado.web 
class IndexHandler(tornado.web.RequestHandler): 
def get(self, *args, **kwargs): 
        self.finish('It works')

app = tornado.web.Application(
handlers=[
('/', IndexHandler),
],
debug=True ) if __name__ == '__main__':
server = tornado.httpserver.HTTPServer(app)
server.listen(8000)
tornado.ioloop.IOLoop().instance().start()

requests 短连接

requests不愧是一个”for human” 的软件,实现一个http客户端非常简单。

import argparse 
import requests

url = 'http://127.0.0.1:8000' 
def short_connection(): 
    resp = requests.get(url)
    print(resp.text)

    resp = requests.get(url)
    print(resp.text) 
def long_connection(): 
    pass 
if __name__ == '__main__':
    ap = argparse.ArgumentParser()
    ap.add_argument("-t", "--type", default="short")
    args = ap.parse_args()
    type_ = args.type if type_ == 'short':
        short_connection() elif type_ == 'long':
        long_connection()

运行keepalive python requests-cli.py –type=short,可以看见返回了数据,同时通过另外一个神器wireshark抓包如下:

requests 短连接

从抓包的情况来看,两次http请求,一共创建了两次tcp的握手连接和挥手断开。每次发送http数据都需要先创建tcp连接,然后就断开了连接。通常是客户端发起的断开连接。

requests 持久连接

requests的官网也说明了,基于urllib3的方式,requests百分比实现了keepalive方式,只需要创建一个客户端session即可,代码如下:

def long_connection(): s = requests.Session()

    resp = s.get(url)
    print(resp.text)

    resp = s.get(url)
    print(resp.text)

    s.close()

再次通过抓包如下图:

requests 持久连接模式

可以看到,同样也是两次http请求,只创建了一次tcp的握手和挥手。两次http请求都基于一个tcp连接。再次查看包43,可以看到下图中的报文header指定了keepalive。

http请求的数据包

AsyncHTTPClient与持久连接

tornado是一个优秀高性能异步非阻塞(non-block)web框架。如果torando的handler中也需要请求别的三方资源,使用requests的同步网络IO,将会block住整个tornado的进程。因此tornado也实现了异步的http客户端AsyncHTTPClient。

短连接

使用AsyncHTTPClient也不难,但是想要使用其异步效果,就必须把其加入事件循环中,否则只有连接的创立,而没有数据的传输就退出了。

import tornado.httpclient
import tornado.ioloop 
import time

url = 'http://127.0.0.1:8000' 
def handle_response(response):
 if response.error:
        print("Error: %s" % response.error) else:
        print(response.body)

http_client = tornado.httpclient.AsyncHTTPClient()
http_client.fetch(url, handle_response)
http_client.fetch(url, handle_response)

运行上述代码,将会看到wirshark中,创建了两次TCP连接和断开了连接,并没有发送http数据。为了发送http数据,还需要加入tornado的事件循环。即在最后一行加入tornado.ioloop.IOLoop.instance().start()

再次运行,客户端正常收到了数据,抓包如下:

async http client 短连接

抓包的结果咋一看像是持久连接,仔细一看却有两次握手和挥手的操作。的确,客户端发送异步http请求的时候,创建了两个端口49989和49990两个tcp连接。因为是异步的请求,因此先创建了两个连接,然后才发送数据,发送数据的时候都是基于所创建的端口进行的。也就是没有使用持久连接。

持久连接

AsyncHTTPClient使用持久连接也很简单。现在流行微服务架构。通常提供给客户端的服务称之为网关,网关从各种微服务中调用获取数据,通信的方式中,同步的有http和rpc,异步的有mq之类的。而http通常都是使用持久连接的方式。

下面我们介绍一下在tornado server的handler中使用async client请求微服务的资源。

再写一个简单server

#!/usr/bin/env python 
# -*- coding:utf-8 -*- 
import tornado.gen 
import tornado.httpclient 
import tornado.httpserver 
import tornado.ioloop 
import tornado.web 
class AsyncKeepAliveHandler(tornado.web.RequestHandler):
  @tornado.web.asynchronous
  @tornado.gen.coroutine
    def get(self, *args, **kwargs):
        url = 'http://127.0.0.1:8000/'
        http_client = tornado.httpclient.AsyncHTTPClient()
    response = yield tornado.gen.Task(http_client.fetch, url) 
    print response.code print response.body
        self.finish("It works")

app = tornado.web.Application(
        handlers=[
            ('/async/keepalive', AsyncKeepAliveHandler)
        ],
        debug=True ) 
if __name__ == '__main__':
    server = tornado.httpserver.HTTPServer(app)
    server.listen(5050)
    tornado.httpclient.AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
    tornado.ioloop.IOLoop().instance().start()

然后我们请求5050端口的服务,也连接发送两次http请求:

(venv)☁  keepalive  curl http://127.0.0.1:5050/async/keepalive It works%                                                                                                                                                     (venv)☁  keepalive  curl http://127.0.0.1:5050/async/keepalive It works%

再看我们的抓包情况:

tornado handler使用持久连接

从图中可以看到,即使是两个请求,最终都是复用了断开为50784的tcp连接。

因为asynchttpclient默认使用的是SimpleAsyncHTTPClient,实现持久连接只需要配置一下tornado.httpclient.AsyncHTTPClient.configure(“tornado.curl_httpclient.CurlAsyncHTTPClient”)即可。当然,这个需要tornado的版本4.2以上,当前的版本是4.5。

CurlAsyncHTTPClient依赖于pycurl。pycurl又依赖libcurl。在安装pycurl的时候,可能会出现link的问题。例如ImportError: pycurl: libcurl link-time version (7.37.1) is older than compile-time version (7.43.0) 。 解决了link问题,如果是mac系统,安装的时候可能出现error: Setup script exited with error: command ‘cc’ failed,多半是由于xcode做鬼,这里有一个解决说明

AsyncHTTPClient设置成为keepalive模式是全局性的,比较tornado是单进程单线程的,访问三方或者微服务,都是一个客户端,所有的模式都是持久连接。

短连接与持久连接的应用场景

持久连接可以减少tcp连接的创建和销毁,提升服务器的处理性能。但是并不是所有连接都得使用持久连接。长短连接都有其使用场景。

既然持久连接在于连接的持久,因此对于频繁通信,点对点的就可以使用。例如网关和微服务之间。如果创建了持久连接,就必须在意连接的存活状态。客户端一般不会主动关闭,因此服务端需要维护这个连接状态,对于一些长时间没有读写事件发生的连接,可以主动断开,节省资源。

对于一些用完就走的场景,也不需要使用持久连接。而另外一些需要全双工通信,例如推送和实时应用,则需要真正的长连接,比如MQTT实现推送和websocket实现实时应用等。

总结

微服务大行其道,从微观来看,增加了更多的网络IO。而IO又是最耗时的操作。相比之下,程式的计算速度就显得没那么紧要了。优化网络IO才是提升性能的关键。一些频繁通信的场景,使用持久连接或长连接更能优化大量TCP连接的创建和销毁。

就Python的而言,Tornado的诞生就是为了解决网络IO的瓶颈。并且很多tornado及其三方库的问题,都能在github和stackoverflow找到作者的参与和回答。可见作者对项目的负责。由于tornado单线程的特性,因此做任何IO操作,都需要考虑是否block。幸好有AsyncHTTPClinet,既可以提供异步IO,也可以实现持久连接,当然,tornado也支持websocket。

关于nagios配置简介

 nagios  关于nagios配置简介已关闭评论
4月 242018
 

一、nagios配置过程详解


1、nagios默认配置文件介绍
 nagios安装完毕后,默认的配置文件在/usr/local/nagios/etc目录下,每个文件或目录含义如下表所示:

 

2、配置文件之间的关系
 在nagios的配置过程中涉及到的几个定义有:主机、主机组,服务、服务组,联系人、联系人组,监控时间,监控命令等,从这些定义可以看出,nagios各个配置文件之间是互为关联,彼此引用的。
 成功配置出一台nagios监控系统,必须要弄清楚每个配置文件之间依赖与被依赖的关系,最重要的有四点:
 第一:定义监控哪些主机、主机组、服务和服务组
 第二:定义这个监控要用什么命令实现,
 第三:定义监控的时间段,
 第四:定义主机或服务出现问题时要通知的联系人和联系人组。

 

3、开始配置nagios
 为了能更清楚的说明问题,同时也为了维护方便,建议将nagios各个定义对象创建独立的配置文件:
 即为:
 创建hosts.cfg文件来定义主机和主机组
 创建services.cfg文件来定义服务
 用默认的contacts.cfg文件来定义联系人和联系人组
 用默认的commands.cfg文件来定义命令
 用默认的timeperiods.cfg来定义监控时间段
 用默认的templates.cfg文件作为资源引用文件

  (1)templates.cfg文件
 
 nagios主要用于监控主机资源以及服务,在nagios配置中称为对象,为了不必重复定义一些监控对象,Nagios引入了一个模板配置文件,将一些共性的属性定义成模板,以便于多次引用。这就是templates.cfg的作用。
 下面详细介绍下templates.cfg文件中每个参数的含义:

  1.  define contact{  
  2.         name                            generic-contact        #联系人名称,  
  3.         service_notification_period     24×7                   #当服务出现异常时,发送通知的时间段,这个时间段“7×24″在timeperiods.cfg文件中定义  
  4.         host_notification_period        24×7                   #当主机出现异常时,发送通知的时间段,这个时间段“7×24″在timeperiods.cfg文件中定义  
  5.         service_notification_options    w,u,c,r         #这个定义的是“通知可以被发出的情况”。w即warn,表示警告状态,u即unknown,表示不明状态,c即criticle,表示紧急状态,r即recover,表示恢复状态。也就是在服务出现警告状态、未知状态、紧急状态和重新恢复状态时都发送通知给使用者。  
  6.         host_notification_options       d,u,r         #定义主机在什么状态下需要发送通知给使用者,d即down,表示宕机状态,u即unreachable,表示不可到达状态,r即recovery,表示重新恢复状态。  
  7.         service_notification_commands   notify-service-by-email  #服务故障时,发送通知的方式,可以是邮件和短信,这里发送的方式是邮件,其中“notify-service-by-email”在commands.cfg文件中定义。  
  8.         host_notification_commands      notify-host-by-email     #主机故障时,发送通知的方式,可以是邮件和短信,这里发送的方式是邮件,其中“notify-host-by-email”在commands.cfg文件中定义。  
  9.         register                        0  
  10.         }  
  11.  define host{   
  12.         name                            generic-host    #主机名称,这里的主机名,并不是直接对应到真正机器的主机名,乃是对应到在主机配置文件里所设定的主机名。  
  13.         notifications_enabled           1              
  14.         event_handler_enabled           1               
  15.         flap_detection_enabled          1               
  16.         failure_prediction_enabled      1             
  17.         process_perf_data               1              
  18.         retain_status_information       1             
  19.         retain_nonstatus_information    1                
  20.         notification_period             24×7           #指定“发送通知”的时间段,也就是可以在什么时候发送通知给使用者。  
  21.         register                        0                
  22.         }  
  23.  
  24. define host{  
  25.         name                            linux-server    #主机名称  
  26.         use                             generic-host    #use表示引用,也就是将主机generic-host的所有属性引用到linux-server中来,在nagios配置中,很多情况下会用到引用。  
  27.         check_period                    24×7            #这里的check_period告诉nagios检查主机的时间段  
  28.         check_interval                  5                #nagios对主机的检查时间间隔,这里是5分钟。  
  29.         retry_interval                  1               #重试检查时间间隔,单位是分钟。  
  30.         max_check_attempts              10               #nagios对主机的最大检查次数,也就是nagios在检查发现某主机异常时,并不马上判断为异常状况,而是多试几次,因为有可能只是一时网络太拥挤,或是一些其他原因,让主机受到了一点影响,这里的10就是最多试10次的意思。  
  31.         check_command                   check-host-alive  #指定检查主机状态的命令,其中“check-host-alive”在commands.cfg文件中定义。  
  32.         notification_period             workhours      #主机故障时,发送通知的时间范围,其中“workhours”在timeperiods.cfg中进行了定义,下面会陆续讲到。  
  33.                                                          
  34.         notification_interval           120            #在主机出现异常后,故障一直没有解决,nagios再次对使用者发出通知的时间。单位是分钟。如果你觉得,所有的事件只需要一次通知就够了,可以把这里的选项设为0  
  35.         notification_options            d,u,r          #定义主机在什么状态下可以发送通知给使用者,d即down,表示宕机状态,u即unreachable,表示不可到达状态,r即recovery,表示重新恢复状态。  
  36.         contact_groups                  admins         #指定联系人组,这个“admins”在contacts.cfg文件中定义。  
  37.         register                        0  
  38.         }  
  39.  
  40. define service{  
  41.         name                            generic-service   #定义一个服务名称  
  42.         active_checks_enabled           1     
  43.         passive_checks_enabled          1      
  44.         parallelize_check                1      
  45.         obsess_over_service             1      
  46.         check_freshness                0  
  47.         notifications_enabled            1                 
  48.         event_handler_enabled           1                   
  49.         flap_detection_enabled           1                    
  50.         failure_prediction_enabled        1                   
  51.         process_perf_data               1                   
  52.         retain_status_information       1                     
  53.         retain_nonstatus_information    1                        
  54.         is_volatile                     0   
  55.         check_period                    24×7      #这里的check_period告诉nagios检查服务的时间段。              
  56.         max_check_attempts              3         #nagios对服务的最大检查次数。           
  57.         normal_check_interval           10       #此选项是用来设置服务检查时间间隔,也就是说,nagios这一次检查和下一次检查之间所隔的时间,这里是10分钟。   
  58.         retry_check_interval            2        #重试检查时间间隔,单位是分钟。            
  59.         contact_groups                  admins   #指定联系人组,同上。              
  60.         notification_options            w,u,c,r  #这个定义的是“通知可以被发出的情况”。w即warn,表示警告状态,u即unknown,表示不明状态,c即criticle,表示紧急状态,r即recover,表示恢复状态。也就是在服务出现警告状态、未知状态、紧急状态和重新恢复后都发送通知给使用者。  
  61.         notification_interval           60       #在服务出现异常后,故障一直没有解决,nagios再次对使用者发出通知的时间。单位是分钟。如果你认为,所有的事件只需要一次通知就够了,可以把这里的选项设为0。  
  62.         notification_period             24×7     #指定“发送通知”的时间段,也就是可以在什么时候发送通知给使用者。               
  63.          register                        0                       
  64.         }  
  65.  


(2)resource.cfg文件

 resource.cfg是nagios的变量定义文件,文件内容只有一行:
 

  1. $USER1$=/usr/local/nagios/libexec 
 其中,变量$USER1$指定了安装nagios插件的路径,如果把插件安装在了其它路径,只需在这里进行修改即可。需要注意的是,变量必须先定义,然后才能在其它配置文件中进行引用。

(3)理解Nagios宏及其工作机制

Nagios配置非常灵活,继承和引用是一大特征,另一个重要特征就是可以在命令行的定义里使用宏,通过定义宏,nagios可以灵活的获取主机、服务和其它对象的信息。
宏的工作机制
在执行命令之前,nagios将对命令里的每个宏替换成它们应当取得的值。这种宏替换发生在Nagios执行各种类型的宏时候。例如主机和服务的检测、通知、事件处理等。
 

宏的分类:
默认宏、按需而成的宏、用户自定制宏等。
 

默认宏
主机IP地址宏
当在命令定义中使用主机或服务宏时,宏将要执行所用的值指向主机或服务所带有值。看下面这个例子,假定在check_ping命令定义里使用了一个主机对象,例如这样:

  1. define host{  
  2.   host_name               ixdba  
  3.   address   192.168.12.246  
  4.   check_command               check_ping  
  5.   …  
  6.   }  
  7. define command{  
  8.   command_name    check_ping  
  9.   command_line    /usr/local/nagios/libexec/check_ping -H $HOSTADDRESS$ -w 100.0,90% -c 200.0,60%  
  10.   } 


那么执行这个主机检测命令时展开并最终执行的将是这样的:

  1. /usr/local/nagios/libexec/check_ping -H 192.168.12.246 -w 100.0,90% -c 200.0,60% 


命令参数宏
同样你可以向命令传递参数,这样可以保证命令定义更具通用性。参数指定在对象(象主机或服务)中定义,用一个“!”来分隔,例如这样:

  1. define service{  
  2.   host_name  linuxbox  
  3.   service_description PING  
  4.   check_command              check_ping!200.0,80%!400.0,40%  
  5.   …  
  6.   } 


在上例中,服务的检测命令中含有两个参数(请参考$ARGn$宏),而$ARG1$宏将是”200.0,80%”,同时$ARG2$将是”400.0,40%”(都不带引号)。假定使用之前的主机定义并这样来定义你的check_ping命令:

  1. define command{  
  2.   command_name    check_ping  
  3.   command_line    /usr/local/nagios/libexec/check_ping -H $HOSTADDRESS$ -w $ARG1$ -c $ARG2$  
  4.   } 


那么对于服务的检测命令最终将是这样子的:

  1. /usr/local/nagios/libexec/check_ping -H 192.168.12.246 -w 200.0,80% -c 400.0,40% 

 

Nagios可用的全部的宏

主机宏
$HOSTNAME$  主机简称(如”web”),取自于主机定义里的host_name域
$HOSTADDRESS$  主机地址。取自于主机定义里的address域
服务宏
$SERVICESTATE$ 服务状态描述,有w,u,c
$SERVICEDESC$   对当前服务的描述  
联系人宏
$CONTACTNAME$   表示联系人,在联系人文件中定义
通知宏
$NOTIFICATIONTYPE$    返回下面信息:(“PROBLEM”, “RECOVERY”, “ACKNOWLEDGEMENT”, “FLAPPINGSTART”, “FLAPPINGSTOP”, “FLAPPINGDISABLED”, “DOWNTIMESTART”, “DOWNTIMEEND”, or “DOWNTIMECANCELLED”).
日期/时间宏
$LONGDATETIME$  当前的日期/时间戳
文件宏
$LOGFILE$  日志文件的保存位置。
$MAINCONFIGFILE$  主配置文件的保存位置。
其他宏
$ADMINEMAIL$  全局的管理员EMail地址
$ARGn$  指向第n个命令传递参数(通知、事件处理、服务检测等)。Nagios支持最多32个参数宏

 

(4)commands.cfg文件
 此文件默认是存在的,无需修改即可使用,当然如果有新的命令需要加入时,在此文件进行添加即可。这里并未列出文件的所有内容,仅仅介绍了配置中用到的一些命令。 

  1. #下面是notify-host-by-email命令的定义  
  2. define command{  
  3.         command_name    notify-host-by-email    #命令名称,即定义了一个主机异常时发送邮件的命令。  
  4.         command_line    /usr/bin/printf “%b” “***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n” | /bin/mail -s “** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **” $CONTACTEMAIL$    #命令具体的执行方式,“-H $HOSTADDRESS$” 是定义目标主机的地址,这个地址在hosts.cfg文件中定义了。  
  5.         }  
  6. #下面是notify-host-by-email命令的定义  
  7. define command{  
  8.         command_name    notify-service-by-email   #命令名称,即定义了一个服务异常时发送邮件的命令  
  9.         command_line    /usr/bin/printf “%b” “***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\n\nService: $SERVICEDESC$\nHost: $HOSTALIAS$\nAddress: $HOSTADDRESS$\nState: $SERVICESTATE$\n\nDate/Time: $LONGDATETIME$\n\nAdditional Info:\n\n$SERVICEOUTPUT$” | /bin/mail -s “** $NOTIFICATIONTYPE$ Service Alert: $HOSTALIAS$/$SERVICEDESC$ is $SERVICESTATE$ **” $CONTACTEMAIL$  
  10.         }  
  11.  
  12. #下面是notify-host-by-email命令的定义  
  13.  define command{  
  14.         command_name    check-host-alive   #命令名称,用来检测主机状态。  
  15.        command_line    $USER1$/check_ping -H $HOSTADDRESS$ -w 3000.0,80% -c 5000.0,100% -p 5     #这里的变量$USER1$在resource.cfg文件中进行定义,即$USER1$=/usr/local/nagios/libexec  
  16. ,那么check_ping的完整路径为/usr/local/nagios/libexec/check_ping。 “-w 3000.0,80%”中“-w”说明后面的一对值对应的是“WARNING”状态,“80%”是其临界值。“-c 5000.0,100%”中“-c”说明后面的一对值对应的是“CRITICAL”,“100%”是其临界值。“-p 1”说明每次探测发送一个包。  
  17.         }  
  18. #下面是notify-host-by-email命令的定义  
  19.  define command{  
  20.         command_name    check_ftp  
  21.         command_line    $USER1$/check_ftp -H $HOSTADDRESS$ $ARG1$    #$ARG1$是指在调用这个命令的时候,命令后面的第一个参数。  
  22.         }  
  23. #下面是check_http命令的定义  
  24.  define command{  
  25.         command_name    check_http  
  26.         command_line    $USER1$/check_http -I $HOSTADDRESS$ $ARG1$  
  27.         }  
  28. #下面是check_ssh命令的定义  
  29.  define command{  
  30.         command_name    check_ssh  
  31.         command_line    $USER1$/check_ssh $ARG1$ $HOSTADDRESS$  
  32.         }  
  33.  


(5) hosts.cfg文件
 此文件默认不存在,需要手动创建,hosts.cfg主要用来指定被监控的主机地址以及相关属性信息,一个配置好的实例如下:

  1. define host{  
  2.         use                     linux-server  #引用主机linux-server的属性信息,linux-server主机在templates.cfg文件中进行了定义。  
  3.         host_name               web    #主机名  
  4.         alias                    ixdba-web  #主机别名  
  5.         address                 192.168.12.251  #被监控的主机地址,这个地址可以是ip,也可以是域名。  
  6.         }  
  7. ?  
  8.  define host{  
  9.         use                     linux-server  
  10.         host_name              mysql  
  11.         alias                    ixdba-mysql  
  12.         address                 192.168.12.237  
  13.         }  
  14. ?  
  15.  define hostgroup{      #定义一个主机组  
  16.  hostgroup_name  sa-servers        #主机组名称,可以随意指定。  
  17.  alias           sa servers    #主机组别名  
  18.  members         web,mysql   #主机组成员,其中“web”、“mysql”就是上面定义的两个主机。  
  19.  }  
  20.  


(6) services.cfg文件
 此文件默认也不存在,需要手动创建,services.cfg文件主要用于定义监控的服务和主机资源,例如监控http服务、ftp服务、主机磁盘空间、主机系统负载等等。

  1. #################  ixdba   web  #####################  
  2.  define service{  
  3.         use                             local-service  #引用local-service服务的属性值,local-service在templates.cfg文件中进行了定义。  
  4.         host_name                       web  #指定要监控哪个主机上的服务,“web”在hosts.cfg文件中进行了定义。  
  5.         service_description             PING  #对监控服务内容的描述,以供维护人员参考。  
  6.         check_command                   check_ping!100.0,20%!500.0,60% #指定检查的命令,check_ping命令在commands.cfg中定义,后跟两个参数,命令与参数间用!分割。         
  7.         }  
  8.  
  9.  define service{  
  10.         use                             local-service  
  11.         host_name                       web  
  12.         service_description              SSH  
  13.         check_command                  check_ssh    # check_ssh命令也在commands.cfg中定义。  
  14.         }  
  15.  
  16.  define service{  
  17.         use                             local-service  
  18.         host_name                       web  
  19.         service_description              SSHD  
  20.         check_command                  check_tcp!22  
  21.         }  
  22.  


(7) contacts.cfg文件
 contacts.cfg是一个定义联系人和联系人组的配置文件,当监控的主机或者服务出现故障,nagios会通过指定的通知方式(邮件或者短信)将信息发给这里指定的联系人或者使用者。

  1. define contact{  
  2.         contact_name                    sasystem  #联系人名称  
  3.         use                             generic-contact #引用generic-contact的属性信息,其中“generic-contact”在templates.cfg文件中进行定义  
  4.         alias                           sa-system  #联系人别名  
  5.         email                          ixdba@126.com  #联系人的邮件地址  
  6.         }  
  7. define contactgroup {  
  8.       contactgroup_name    admins #联系人组名称  
  9.       alias                system administrator group #联系人组描述  
  10.       members              sasystem  #联系人组成员,其中“sasystem”就是上面定义的联系人  
  11. }  
  12.  


(8) timeperiods.cfg文件
 此文件只要用于定义监控的时间段,下面是一个配置好的实例:

  1. #下面是定义一个名为24×7的时间段,即监控所有时间段  
  2. define timeperiod{  
  3.         timeperiod_name 24×7     
  4.         alias           24 Hours A Day, 7 Days A Week  
  5.         sunday          00:00-24:00  
  6.         monday          00:00-24:00  
  7.         tuesday         00:00-24:00  
  8.         wednesday       00:00-24:00  
  9.         thursday        00:00-24:00  
  10.         friday          00:00-24:00  
  11.         saturday        00:00-24:00  
  12.         }  
  13. #下面是定义一个名为workhours的时间段,即工作时间段。  
  14. define timeperiod{  
  15.         timeperiod_name workhours   
  16.         alias           Normal Work Hours  
  17.         monday          09:00-17:00  
  18.         tuesday         09:00-17:00  
  19.         wednesday       09:00-17:00  
  20.         thursday        09:00-17:00  
  21.         friday          09:00-17:00  
  22.         }  
  23.  


(9) cgi.cfg文件
 此文件用来控制相关cgi脚本,如果想在nagios的web监控界面执行cgi脚本,例如重启nagios进程、关闭nagios通知、停止nagios主机检测等,这时就需要配置cgi.cfg文件了。
 由于nagios的web监控界面验证用户为ixdba,所以只需在cgi.cfg文件中添加此用户的执行权限就可以了,需要修改的配置信息如下:

  1. default_user_name=ixdba 
  2. authorized_for_system_information=nagiosadmin,ixdba  
  3. authorized_for_configuration_information=nagiosadmin,ixdba  
  4. authorized_for_system_commands=ixdba 
  5. authorized_for_all_services=nagiosadmin,ixdba  
  6. authorized_for_all_hosts=nagiosadmin,ixdba  
  7. authorized_for_all_service_commands=nagiosadmin,ixdba  
  8. authorized_for_all_host_commands=nagiosadmin,ixdba  
  9.  

(10) nagios.cfg文件
  Nagios.cfg默认的路径为/usr/local/nagios/etc/nagios.cfg,是nagios的核心配置文件,所有的对象配置文件都必须在这个文件中进行定义才能发挥其作用,这里只需将对象配置文件在Nagios.cfg文件中进行引用即可。

  1. log_file=/usr/local/nagios/var/nagios.log  
  2. “log_file”变量用来定义nagios日志文件的路径。  
  3. cfg_file=/usr/local/nagios/etc/hosts.cfg  
  4. cfg_file=/usr/local/nagios/etc/services.cfg  
  5. cfg_file=/usr/local/nagios/etc/commands.cfg  
  6. cfg_file=/usr/local/nagios/etc/contacts.cfg  
  7. cfg_file=/usr/local/nagios/etc/timeperiods.cfg  
  8. cfg_file=/usr/local/nagios/etc/templates.cfg  
  9.  “cfg_file”变量用来引用对象配置文件,如果有更多的对象配置文件,在这里依次添加即可。  
  10. object_cache_file=/usr/local/nagios/var/objects.cache  
  11. 该变量用于指定一个“所有对象配置文件”的副本文件,或者叫对象缓冲文件  
  12.  
  13. resource_file=/usr/local/nagios/etc/resource.cfg  
  14. 该变量用于指定nagios资源文件的路径,可以在Nagios.cfg中定义多个资源文件。  
  15. status_file=/usr/local/nagios/var/status.dat  
  16. 该变量用于定义一个状态文件,此文件用于保存nagios的当前状态、注释和宕机信息等。  
  17. status_update_interval=10 
  18.  该变量用于定义状态文件(即status.dat)的更新时间间隔,单位是秒,最小更新间隔是1秒。  
  19. nagios_user=nagios  
  20. 该变量指定了Nagios进程使用哪个用户运行。  
  21. nagios_group=nagios  
  22.  该变量用于指定Nagios使用哪个用户组运行。  
  23. check_external_commands=1 
  24.  该变量用于设置是否允许nagios在web监控界面运行cgi命令,也就是是否允许nagios在web界面下执行重启nagios、停止主机/服务检查等操作。“1”为运行,“0”为不允许。  
  25. command_check_interval=2 
  26.  该变量用于设置nagios对外部命令检测的时间间隔,如果指定了一个数字加一个”s”(如10s),那么外部检测命令的间隔是这个数值以秒为单位的时间间隔。如果没有用”s”,那么外部检测命令的间隔是以这个数值的“时间单位”的时间间隔。  
  27. interval_length=60 
  28.  该变量指定了nagios的时间单位,默认值是60秒,也就是1分钟,即在nagios配置中所有的时间单位都是分钟。  

==================================================================================================================================================================================

主配置文件 nagios.cfg 需要更改的地方:
 #cfg_file=/usr/local/nagios/etc/objects/localhost.cfg
 interval_length=1 ; 间隔时间基准由 60s 改为 1s
 command_check_interval=10s ; 命令检查时间间隔,-1 表示尽可能频繁的进行检查
 date_format=iso8601 ; 日期格式
 objects/contacts.cfg 用来定义联系人:

define contact {
 contact_name sa
 alias System Administrator
 service_notification_period 24×7
 host_notification_period 24×7
 service_notification_options w,u,c,r
 host_notification_options d,u,r
 service_notification_commands notify-service-by-email
 host_notification_commands notify-host-by-email
 email admin@test.com
 } 
定义联系人组
define contactgroup {
 contactgroup_name admins
 alias Administrator Group
 members sa    ; 添加其它联系人用 “,” 分隔
 } 
主机监控的配置
define host {
 host_name host_name    ; 简短的主机名称。
 alias alias    ; 别名,可以更详细的说明主机。
 address address    ; IP 地址,也可以写主机名。如果不定义这个值, nagio 将会用 host_name 去寻找主机。
 parents host_names    ; 上一节点的名称,也就是指从 nagios 服务器到被监控主机之间经过的节点,可以是路由器、交换机、主机等等。
 hostgroups hostgroup_names    ; 简短的主机组名称。
 check_command command_name    ; 检查命令的简短名称,如果此项留空, nagios 将不会去判断主机是否 alive 。
 max_check_attempts 整数    ; 当检查命令的返回值不是 “OK” 时,重试的次数。
 check_interval 数字    ; 循环检查的间隔时间。
 active_checks_enabled [0/1]    ; 是否启用 “active_checks”
 passive_checks_enabled [0/1]    ; 是否启用 “passive_checks” ,及“被动检查”
 check_period timeperiod_name    ; 检测时间段简短名称,这只是个名称,具体的时间段要写在其他的配置文件中。
 obsess_over_host [0/1]    ; 是否启用主机操作系统探测。
 check_freshness [0/1]    ; 是否启用 freshness 检查。freshness 检查是对于启用被动检查模式的主机而言的,其作用是定期检查主机报告的状态信息,如果该状态信息已经过期,freshness 将会强制做主机检查。
 freshness_threshold 数字     ; fressness 的临界值,单位为秒。 如果定义为 “0″ ,则为自动定义。
 event_handler command_name    ; 当主机发生状态改变时,采用的处理命令的简短的名字(可以在 commands.cfg 中对其定义)
 event_handler_enabled [0/1]    ; 是否启用 event_handler
 low_flap_threshold 数字    ; 抖动的下限值。抖动,即在一段时间内,主机(或服务)的状态值频繁的发生变化。
 high_flap_threshold 数字   ; 抖动的上限值。
 flap_detection_enabled [0/1]    ; 是否启用抖动检查。
 process_perf_data [0/1]    ; 是否启用 processing of performance data
 retain_status_information [0/1]    ; 程序重启时,是否保持主机状态相关的信息。
 retain_nonstatus_information [0/1]    ; 程序重启时,是否保持主机状态无关的信息。
 contact_groups contact_groups    ; 联系人组,在此组中的联系人都会收到主机的提醒信息。
 notification_interval 整数    ; 重复发送提醒信息的最短间隔时间。默认间隔时间是 “60″ 分钟。如果这个值设置为 “0″ ,将不会发送重复提醒。
 notification_period timeperiod_name   ; 发送提醒的时间段。非常重要的主机(服务)定义为 24×7 ,一般的主机(服务)就定义为上班时间。如果不在定义的时间段内,无论发生什么问题,都不会发送提醒。
 notification_options [d,u,r,f]    ; 发送提醒包括的情况: d = 状态为 DOWN , u = 状态为 UNREACHABLE , r = 状态恢复为 OK , f = flapping
 notifications_enabled [0/1]    ; 是否开启提醒功能。”1″ 为开启,”0″ 为禁用。一般,这个选项会在主配置文件 (nagios.cfg) 中定义,效果相同。
 stalking_options [o,d,u]    ; 持续状态检测参数,o = 持续的 UP 状态 , d = 持续的 DOWN 状态 , u = 持续的 UNREACHABLE 状态
 } 
服务监控的配置

define service {
 host_name host_name
 service_description service_description
 servicegroups servicegroup_names
 is_volatile [0/1]
 check_command command_name
 max_check_attempts
 normal_check_interval
 retry_check_interval
 active_checks_enabled [0/1]
 passive_checks_enabled [0/1]
 check_period timeperiod_name
 parallelize_check [0/1]
 obsess_over_service [0/1]
 check_freshness [0/1]
 freshness_threshold
 event_handler command_name
 event_handler_enabled [0/1]
 low_flap_threshold
 high_flap_threshold
 flap_detection_enabled [0/1]
 process_perf_data [0/1]
 retain_status_information [0/1]
 retain_nonstatus_information [0/1]
 notification_interval
 notification_period timeperiod_name n
 otification_options [w,u,c,r,f]
 notifications_enabled [0/1]
 contact_groups contact_groups
 stalking_options [o,w,u,c]
 } 
服务监控的配置和主机监控的配置较为相似,就不一一说明了。 
间隔时间的计算方法为:
 normal_check_interval x interval_length 秒
 retry_check_interval x interval_length 秒
 notification_interval x interval_length 秒
 
主机监控配置的例子
define host {
 host_name web1
 alias web1
 address 192.168.0.101
 contact_groups admins
 check_command check-host-alive
 max_check_attempts 5
 notification_interval 0
 notification_period 24×7
 notification_options d,u,r
 } 
对主机 web1 进行 24×7 的监控,默认会每 10 秒检查一次状态,累计五次失败就发送提醒,并且不再重复发送提醒。
 服务监控配置的例子 
define service {
 host_name web1
 service_description check_http
 check_period 24×7
 max_check_attempts 3
 normal_check_interval 30
 contact_groups admins
 retry_check_interval 15
 notification_interval 3600
 notification_period 24×7
 notification_options w,u,c,r
 check_command check_http
 }
 
配置解释: 24×7 监控 web1 主机上的 HTTP 服务,检查间隔为 30 秒, 检查失败后每 15 秒再进行一次检查,累计三次失败就认定是故障并发送提醒。
 联系人组是 admins 。提醒后恢复到 30 秒一次的 normal_check_interval 检查。如果服务仍然没有被恢复,每个小时发送一次提醒。
 如果要检测其他服务,例如,要检查 ssh 服务是否开启,更改如下两行:
 service_description check_ssh
 check_command check_ssh 
为方便管理,对配置文件的分布做了如下修改:
 nagios.cfg 中增加了:
 cfg_dir=/usr/local/nagios/etc/hosts
 cfg_dir=/usr/local/nagios/etc/services 
在 hosts 目录中,为不同类型的主机创建了配置文件,如: app.cfg cache.cfg mysql.cfg web.cfg
 并创建了 hostgroup.cfg 文件对主机进行分组,如:

define hostgroup {
 hostgroup_name app-hosts
 alias APP Hosts
 members app1,app2
 } 
在 services 目录中创建了各种服务的配置文件,如: disk.cfg http.cfg load.cfg mysql.cfg
 并创建了 servicegroup.cfg 文件对服务进行分组,如:

define servicegroup {
 servicegroup_name disk
 alias DISK
 members cache1,check_disk,cache2,check_disk

 } 

转自: https://blog.csdn.net/lin_fs/article/details/8555960

Python模块搜索路径简介

 python, 开发  Python模块搜索路径简介已关闭评论
2月 142018
 

由于某些原因,在使用 import 时,Python 找不到相应的模块。这时,解释器就会发牢骚 – ImportError。
那么,Python 如何知道在哪里搜索模块的路径呢?
模块搜索路径
当导入名为 hello 的模块时,解释器首先搜索具有该名称的内置模块。如果没有找到,将在变量 sys.path 给出的目录列表中搜索名为 hello.py 的文件。
sys.path 从这些位置初始化:
包含输入脚本的目录(或当前目录,当没有指定文件时)
PYTHONPATH(目录名列表,与 shell 变量 PATH 语法相同)
与安装相关的默认值
>>> import sys
>>> sys.path
[”, ‘/usr/local/lib/python35.zip’, ‘/usr/local/lib/python3.5’, ‘/usr/local/lib/python3.5/plat-linux’,
 ‘/usr/local/lib/python3.5/lib-dynload’, ‘/usr/local/lib/python3.5/site-packages’]
注意: ” 表示当前目录(当前脚本所在的路径)
例如,在 /home/wang/workspace 中创建一个名为 hello.py 的模块,内容如下:
print(‘Hello’)
然后,试图加载该模块:
>>> import os
>>> 
>>> os.getcwd() # 获取当前目录
‘/home/wang’
>>> 
>>> import hello.py

ImportError: No module named ‘hello’
很遗憾,由于 sys.path 中没有包含 /home/wang/workspace 这个路径,所以找不到 hello 模块,从而引发 ImportError。
添加模块搜索路径
为了解决上述问题,需要添加模块搜索路径,可以使用以下几种方式:
1.动态增加路径 
临时生效,对于不经常使用的模块,这通常是最好的方式,因为不必用所有次要模块的路径来污染 PYTHONPATH。
2.修改 PYTHONPATH 变量 
永久生效,对于在许多程序中都使用的模块,可以采用这种方式。这将改变所有 Python 应用的搜索路径,因为启动 Python 时,它会读取这个变量,甚至不同版本的 Python 都会受影响。
3.增加 .pth 文件 
永久生效,这是最简单的、也是推荐的方式。Python 在遍历已知的库文件目录过程中,如果遇到 .pth 文件,便会将其中的路径加入到 sys.path 中,于是 .pth 中所指定的路径就可以被 Python 运行环境找到了。

方法一:动态增加路径
通过 sys 模块的 append() 方法在 Python 环境中增加搜索路径:
>>> import sys
>>> sys.path.append(‘/home/wang/workspace’)
现在,查看搜索路径:
>>> sys.path
[”, ‘/usr/local/lib/python35.zip’, ‘/usr/local/lib/python3.5’, ‘/usr/local/lib/python3.5/plat-linux’,
 ‘/usr/local/lib/python3.5/lib-dynload’, ‘/usr/local/lib/python3.5/site-packages’, ‘/home/wang/workspace’]
>>> 
>>> import hello
Hello
可以看到,路径被成功添加进去了,再次执行导入可以正常使用。

方法二:修改 PYTHONPATH 变量
打开并编辑 bashrc:
$ vim ~/.bashrc
将以下内容附加到文件末尾:
export PYTHONPATH=$PYTHONPATH:/home/wang/workspace
不要忘记重新加载 shell,方法是退出并重新启动,或者在命令行重新加载配置文件:
$ source ~/.bashrc # 或者 . ~/.bashrc

方法三:增加 .pth 文件
查看sys.path中列出的site-packages和dist-packages路径, 比如:在上例中 /usr/local/lib/python3.5/site-packages 下添加一个扩展名为 .pth 的配置文件(例如:extras.pth),内容为要添加的路径:
/home/wang/workspace

zookeeper(单机+伪集群+集群)配置简介

 zookeeper  zookeeper(单机+伪集群+集群)配置简介已关闭评论
10月 312017
 


来自:http://www.cnblogs.com/sprinng/p/5976553.html

 #下载zookeeper:

#单机模式

解压到合适目录. 进入zookeeper目录下的conf子目录, 复制zoo_sample.cfg–>zoo.cfg(如果没有data和logs就新建):
tickTime=2000 
dataDir=/Users/apple/zookeeper/data 
dataLogDir=/Users/apple/zookeeper/logs 
clientPort=2180 
参数说明:
tickTime: zookeeper中使用的基本时间单位, 毫秒值.
dataDir: 数据目录. 可以是任意目录.
dataLogDir: log目录, 同样可以是任意目录. 如果没有设置该参数, 将使用和dataDir相同的设置.
clientPort: 监听client连接的端口号

#伪集群模式
解压到合适目录(zookeeper0).进入zookeeper0目录下的conf子目录, 复制zoo_sample.cfg–>zoo.cfg(如果没有data和logs就新建):
tickTime=2000 
initLimit=5 
syncLimit=2 
dataDir=/Users/apple/zookeeper0/data 
dataLogDir=/Users/apple/zookeeper0/logs 
clientPort=4180 
server.0=127.0.0.1:8880:7770 
server.1=127.0.0.1:8881:7771 
server.2=127.0.0.1:8882:7772 
#新增了几个参数, 其含义如下:
1 initLimit: zookeeper集群中的包含多台server, 其中一台为leader, 集群中其余的server为follower. initLimit参数配置初始化连接时, follower和leader之间的最长心跳时间. 此时该参数设置为5, 说明时间限制为5倍tickTime, 即5*2000=10000ms=10s.
2 syncLimit: 该参数配置leader和follower之间发送消息, 请求和应答的最大时间长度. 此时该参数设置为2, 说明时间限制为2倍tickTime, 即4000ms.
3 server.X=A:B:C 
其中X是一个数字, 表示这是第几号server. 
A是该server所在的IP地址. 
B配置该server和集群中的leader交换消息所使用的端口. 
C配置选举leader时所使用的端口. 
由于配置的是伪集群模式, 所以各个server的B, C参数必须不同.

a、将zookeeper0的目录拷贝2份:
参照zookeeper0/conf/zoo.cfg, 配置zookeeper1/conf/zoo.cfg, 和zookeeper2/conf/zoo.cfg文件. 只需更改dataDir, dataLogDir, clientPort参数即可.
b、在之前设置的dataDir中新建myid文件, 写入一个数字, 该数字表示这是第几号server. 该数字必须和zoo.cfg文件中的server.X中的X一一对应.
/Users/apple/zookeeper0/data/myid文件中写入0, /Users/apple/zookeeper1/data/myid文件中写入1, /Users/apple/zookeeper2/data/myid文件中写入2.
c、分别进入/Users/apple/zookeeper0/bin, /Users/apple/zookeeper1/bin, /Users/apple/zookeeper2/bin三个目录, 启动server.

#集群模式

解压到合适目录(zookeeper0).进入zookeeper0目录下的conf子目录, 复制zoo_sample.cfg–>zoo.cfg(如果没有data和logs就新建):
集群模式的配置和伪集群基本一致.
由于集群模式下, 各server部署在不同的机器上, 因此各server的conf/zoo.cfg文件可以完全一样.
下面是一个示例:
tickTime=2000 
initLimit=5 
syncLimit=2 
dataDir=/home/zookeeper/data 
dataLogDir=/home/zookeeper/logs 
clientPort=4180 
server.43=10.1.39.43:2888:3888 
server.47=10.1.39.47:2888:3888 
server.48=10.1.39.48:2888:3888 
示 例中部署了3台zookeeper server, 分别部署在10.1.39.43, 10.1.39.47, 10.1.39.48上. 需要注意的是, 各server的dataDir目录下的myid文件中的数字必须不同,10.1.39.43 server的myid为43, 10.1.39.47 server的myid为47, 10.1.39.48 server的myid为48.

#zookeeper常用命令
ZooKeeper服务命令:
1. 启动ZK服务: ./zkServer.sh start
2. 查看ZK服务状态: ./zkServer.sh status
3. 停止ZK服务: ./zkServer.sh stop
4. 重启ZK服务: ./zkServer.sh restart
zk客户端命令:
ZooKeeper 命令行工具类似于Linux的shell环境,使用它可以对ZooKeeper进行访问,数据创建,数据修改等操作. 使用 zkCli.sh -server 127.0.0.1:2181 连接到 ZooKeeper 服务,连接成功后,系统会输出 ZooKeeper 的相关环境以及配置信息。
命令行工具的一些简单操作如下:
1. 显示根目录下、文件: ls / 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容
2. 显示根目录下、文件: ls2 / 查看当前节点数据并能看到更新次数等数据
3. 创建文件,并设置初始内容: create /zk “test” 创建一个新的 znode节点“ zk ”以及与它关联的字符串
4. 获取文件内容: get /zk 确认 znode 是否包含我们所创建的字符串
5. 修改文件内容: set /zk “zkbak” 对 zk 所关联的字符串进行设置
6. 删除文件: delete /zk 将刚才创建的 znode 删除
7. 退出客户端: quit
8. 帮助命令: help
ZooKeeper 常用四字命令:
ZooKeeper 支持某些特定的四字命令字母与其的交互。它们大多是查询命令,用来获取 ZooKeeper 服务的当前状态及相关信息。用户在客户端可以通过 telnet 或 nc 向 ZooKeeper 提交相应的命令
1. 可以通过命令:echo stat|nc 127.0.0.1 2181 来查看哪个节点被选择作为follower或者leader
2. 使用echo ruok|nc 127.0.0.1 2181 测试是否启动了该Server,若回复imok表示已经启动。
3. echo dump| nc 127.0.0.1 2181 ,列出未经处理的会话和临时节点。
4. echo kill | nc 127.0.0.1 2181 ,关掉server
5. echo conf | nc 127.0.0.1 2181 ,输出相关服务配置的详细信息。
6. echo cons | nc 127.0.0.1 2181 ,列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。
7. echo envi |nc 127.0.0.1 2181 ,输出关于服务环境的详细信息(区别于 conf 命令)。
8. echo reqs | nc 127.0.0.1 2181 ,列出未经处理的请求。
9. echo wchs | nc 127.0.0.1 2181 ,列出服务器 watch 的详细信息。
10. echo wchc | nc 127.0.0.1 2181 ,通过 session 列出服务器 watch 的详细信息,它的输出是一个与 watch 相关的会话的列表。
11. echo wchp | nc 127.0.0.1 2181 ,通过路径列出服务器 watch 的详细信息。它输出一个与 session 相关的路径。

Javascript的shift()和push(),unshift()和pop()方法简介

 javascript  Javascript的shift()和push(),unshift()和pop()方法简介已关闭评论
6月 192017
 

Javascript的shift()和push(),unshift()和pop()方法简介,记录下:http://www.cnblogs.com/itdream/archive/2012/06/04/2534265.html

javascript为数组专门提供了push和pop()方法,以便实现类似栈的行为。来看下面的例子:

var colors=new Array();       //创建一个数组

var count=colors.push(“red”,”green”);   //  推入两项,返回修改后数组的长度

alert(count);   // 2   返回修改后数组的长度

var item=colors.pop();   //取得最后一项

alert(item);           // “green”

alert(colors.length);   //  1

队列方法:

结合使用shift()和push()方法,可以像使用队列一样使用数组:

var colors=new Array();

var count=colors.push(“red”,”green”);  //推入两项

alert(count);   //2

count=  colors.push(“black”);  // 从数组末端添加项,此时数组的顺序是: “red”, “green” ,”black”

alert(count);  //3

var item=colors.shift();   // 取得第一项

alert(item);   // “red”

alert(colors.length);  //2

从例子中可以看出:shift()和push()方法可以从数组末端添加项,能够移除数组中的第一项并返回该项。

若是想实现相反的操作的话,可以使用unshift()和pop()方法,即在数组的前端添加项,从数组末端移除项。

var colors=new Array();

var count=colors.unshift(“red”,”green”);// 推入两项

alert(count);  // 2

count=colors.unshift(“black”);  // 从数组的前端添加项,此时数组的顺序是: “black”, “red”, “green”

alert(count);  //3

var item=colors.pop();

alert(item);    // 移除并返回的是最后一项   “green”

由以上的两组例子,大家可以清晰的看到这两组方法的用法了。

just think and do it

python中subprocess模块使用简介

 python  python中subprocess模块使用简介已关闭评论
8月 052016
 

subprocess.Popen用来创建子进程。

1)Popen启动新的进程与父进程并行执行,默认父进程不等待新进程结束。

复制代码代码如下:

def TestPopen():
  import subprocess
  p=subprocess.Popen(“dir”,shell=True)
  for i in range(250) :
    print (“other things”)

2)p.wait函数使得父进程等待新创建的进程运行结束,然后再继续父进程的其他任务。且此时可以在p.returncode中得到新进程的返回值。

复制代码代码如下:

def TestWait():
  import subprocess
  import datetime
  print (datetime.datetime.now())
  p=subprocess.Popen(“sleep 10”,shell=True)
  p.wait()
  print (p.returncode)
  print (datetime.datetime.now())

3) p.poll函数可以用来检测新创建的进程是否结束。

复制代码代码如下:

def TestPoll():
  import subprocess
  import datetime
  import time
  print (datetime.datetime.now())
  p=subprocess.Popen(“sleep 10”,shell=True)
  t = 1
  while(t <= 5):
    time.sleep(1)
    p.poll()
    print (p.returncode)
    t+=1
  print (datetime.datetime.now())

4) p.kill或p.terminate用来结束创建的新进程,在windows系统上相当于调用TerminateProcess(),在posix系统上相当于发送信号SIGTERM和SIGKILL。

复制代码代码如下:

def TestKillAndTerminate():
    p=subprocess.Popen(“notepad.exe”)
    t = 1
    while(t <= 5):
      time.sleep(1)
      t +=1
    p.kill()
    #p.terminate()
    print (“new process was killed”)

5) p.communicate可以与新进程交互,但是必须要在popen构造时候将管道重定向。

复制代码代码如下:

def TestCommunicate():
  import subprocess
  cmd = “dir”
  p=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  (stdoutdata, stderrdata) = p.communicate()
  
  if p.returncode != 0:
        print (cmd + “error !”)
  #defaultly the return stdoutdata is bytes, need convert to str and utf8
  for r in str(stdoutdata,encoding=’utf8′ ).split(“\n”):
    print (r)
  print (p.returncode)


def TestCommunicate2():
  import subprocess
  cmd = “dir”
  #universal_newlines=True, it means by text way to open stdout and stderr
  p = subprocess.Popen(cmd, shell=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  curline = p.stdout.readline()

  while(curline != “”):
        print (curline)
        curline = p.stdout.readline()
  p.wait()
  print (p.returncode)

6) call函数可以认为是对popen和wait的分装,直接对call函数传入要执行的命令行,将命令行的退出code返回。

复制代码代码如下:

def TestCall():
  retcode = subprocess.call(“c:\\test.bat”)
  print (retcode)

7)subprocess.getoutput 和 subprocess.getstatusoutput ,基本上等价于subprocess.call函数,但是可以返回output,或者同时返回退出code和output。

但是可惜的是好像不能在windows平台使用,在windows上有如下错误:'{‘ is not recognized as an internal or external command, operable program or batch file. 

复制代码代码如下:

def TestGetOutput():
  outp = subprocess.getoutput(“ls -la”)
  print (outp)

def TestGetStatusOutput():
  (status, outp) = subprocess.getstatusoutput(‘ls -la’)
  print (status)
  print (outp)

8)总结

popen的参数,第一个为字符串(或者也可以为多个非命名的参数),表示你要执行的命令和命令的参数;后面的均为命名参数;shell=True,表示你前面的传入的命令将在shell下执行,如果你的命令是个可执行文件或bat,不需要指定此参数;stdout=subprocess.PIPE用来将新进程的输出重定向,stderr=subprocess.STDOUT将新进程的错误输出重定向到stdout,stdin=subprocess.PIPE用来将新进程的输入重定向;universal_newlines=True表示以text的方式打开stdout和stderr。

 其他的不推荐使用的模块:

os.system
os.spawn*
os.popen*
popen2.*
commands.*

转自:http://www.jb51.net/article/57725.htm

YAML 简介

 java  YAML 简介已关闭评论
1月 212016
 

YAML (读音”yamel”,或”雅梅尔”)。这种格式大约是2001年出现的,写YAML要比XML快得多(无需关闭标签或引号),并且比’.ini’文档功能更强(ini文档不支持层次)为什么不是XML呢?因为:YAML的可读性好。

YAML 试图用一种比 XML 更敏捷的方式,来完成 XML 所完成的任务。



YAML Ain’t Markup Language

和GNU一样,YAML是一个递归着说“不”的名字。不同的是,GNU对UNIX说不,YAML说不的对象是XML。

YAML不是XML。

YAML LOGO

为什么不是XML呢?因为:

  • YAML的可读性好。
  • YAML和脚本语言的交互性好。
  • YAML使用实现语言的数据类型。
  • YAML有一个一致的信息模型。
  • YAML易于实现。

上面5条也就是XML不足的地方。同时,YAML也有XML的下列优点:

  • YAML可以基于流来处理;
  • YAML表达能力强,扩展性好。

总之,YAML试图用一种比XML更敏捷的方式,来完成XML所完成的任务。

更多的内容及规范参见http://www.yaml.org

语法

Structure通过空格来展示。Sequence里的项用”-“来代表,Map里的键值对用”:”分隔.

这几乎就是所有的语法了.

比如……

一般YAML文件扩展名为.yaml。比如:John.yaml

name: John Smith
age: 37
spouse:
    name: Jane Smith
    age: 25
children:
    -   name: Jimmy Smith
        age: 15
    -   name: Jenny Smith
        age 12

John今年37岁,有一个幸福的四口之家。两个孩子Jimmy 和Jenny活泼可爱。妻子Jane年轻美貌。

注意:
  1. 字串不一定要用双引号标识;
  2. 在缩排中空白字符的数目并不是非常重要,只要相同阶层的元素左侧对齐就可以了(不过不能使用TAB字符);
  3. 允许在文件中加入选择性的空行,以增加可读性;
  4. 在一个档案中,可同时包含多个文件,并用“——”分隔;
  5. 选择性的符号“…”可以用来表示档案结尾(在利用串流的通讯中,这非常有用,可以在不关闭串流的情况下,发送结束讯号)。
YAML也有用来描述好几行相同结构的数据的缩写语法,数组用'[]’包括起来,hash用'{}’来包括。

如果深入研究的话还可能发现一些社会问题。

可见YAML的可读性是不错。

JYaml——YAML的Java实现

YAML已经有了不少实现,详细的实现列表参见http://www.yaml.org/download.html

其中JYaml(http://jyaml.sourceforge.net)是YAML的Java实现。

JYaml简介

YAML使用实现语言的数据类型。我们看一下一些JYaml支持的Java数据类型:

  • 原始数据和封装类(比如int,java.lang.Integer)
  • JavaBean兼容对象(Structure支持)
  • Collection (sequence支持)

    • List
    • Set
  • Map (map支持)
  • Arrays (sequence支持)
  • BigInteger 和BigDecimal
  • Date

我们给出John.yaml的java描述:

public class Person {
    private String name;
    private int age;
    private Person sponse;
    private Person[] children;
    // setXXX, getXXX方法略.
}

现在让我们装配一个Jone:

    Person john = new Person();
    john.setAge(37);
    john.setName("John Smith");
    Person sponse = new Person();
    sponse.setName("Jane Smith");
    sponse.setAge(25);
    john.setSponse(sponse);
    Person[] children = {new Person(), new Person()};
    children[0].setName("Jimmy Smith");
    children[0].setAge(15);
    children[1].setName("Jenny Smith");
    children[1].setAge(12);
    john.setChildren(children);

使用JYaml把Jone“Dump”出来:

    File dumpfile = new File("John_dump.yaml");
    Yaml.dump(john, dumpfile);

下面我们看看John_dump.yaml是什么样子:

--- !yaml.test.internal.Person
age: 37
children: !yaml.test.internal.Person[]
  - !yaml.test.internal.Person
    age: 15
    name: Jimmy Smith
  - !yaml.test.internal.Person
    age: 12
    name: Jenny Smith
name: John Smith
sponse: !yaml.test.internal.Person
  age: 25
  name: Jane Smith

其中!yaml.test.internal.Person是一些类型的信息。load的时候需要用。

现在用JYaml把Jone_dump.yaml load进来:

    Person john2 = (Person) Yaml.loadType(dumpfile, Person.class);

还可以用下面的代码dump出没有类型信息的John.yaml:

Yaml.dump(john,dumpfile, true);

我们再来看看JYaml对流处理的支持。

为简便起见,我们只是把同一个john写10次:

    YamlEncoder enc = new YamlEncoder(new FileOutputStream(dumpfile));
    for(int i=0; i<10; i++){
        john.setAge(37+i);
        enc.writeObject(john);
        enc.flush();
    }
   enc.close();

下面再把这十个对象一个一个读出来(注意while循环退出的方式):

      YamlDecoder dec = new YamlDecoder(new FileInputStream(dumpfile));
      int age = 37;
      while(true){
          try{
              john = (Person) dec.readObject();
              assertEquals(age, john.getAge());
              age++;
          }catch(EOFException eofe){
              break;
          }
      }

JYaml对JDK14的支持

JYaml的源码是基于JVM5的。也提供对JVM5功能的一些支持,比如对枚举类型的支持。

JYaml的JVM14的支持是通过retrotranslator(http://retrotranslator.sourceforge.net). retrotranslator使用ASM(http://asm.objectweb.org )动态修改JVM5字节码成JVM14,使JYaml在JVM14下可用。会牺牲一些效率。Debug时也会失真。

我修改了JYaml作者Toby Ho的一些源码(基于Beta2.2),使之与JVM14兼容。当然,为此也去掉了对JVM5才有的类型的支持(比如枚举类型). 这个牺牲应该是值得的。需要的人可以来信索取。

YAML和SDO(只讨论双方的java实现)

如果读者想了解更多SDO的概念, 请参见http://www.ibm.com/developerworks/library/specification/j-commonj-sdowmt/index.html 。这里我们只讨论SDO的java实现。

SDO里有一个很重要的概念,DataObject。从DataObject的接口里,可以看出有两类的数据类型,一类是JYaml支持的,诸如String, List之类,一类是DataObject。因此我们只需要增加一种支持DataObject的类型,就可以完成对SDO DataObject的支持。

不妨叫YamlDataObject。下面是它的数据结构。

public class YamlDataObject {
    private String uRI;
    private String name;
    private Map attributes;
}

YamlDataObject里用到的类型,都是JYaml能够支持的。其中,uRI和name用来描述DataObject自身。attributes来描述DataObject里的值。这些值可能为DataObject,也可能是JYaml所支持的其他类型。我们希望用YamlDataObject来担当DataObject的角色,因此YamlDataObject和DataObject要能互相转化。增加两个API:

private static YamlDataObject newInstance(DataObject dataobject){...
private DataObject toDataObject() {...

上面两个之所以是private的,是因为我们打算遵照JYaml的习惯,增加两个接口,dump和load,这样newInstance和toDataObject只在内部使用了。下面是dump和load的定义:

public static void dump(DataObject dataobject, File dumpfile) 
                  throws FileNotFoundException{...
public static Object load(File file) 
                  throws FileNotFoundException{...

下面是一小段测试代码:

// ... create dataobject ...
File bodump=new File("bodump.yaml");
YamlDataObject.dump(dataobject,bodump);
assertTrue(bodump.exists());
DataObject dataobject2 = (DataObject) YamlDataObject.load(bodump);
 // check result
assertNotNull(dataobject2);
for(int i=0; i<5; i++){
   assertEquals(dataobject.get(i),dataobject2.get(i));
}

具体的实现细节就不谈了。读者可以作为练习题,熟悉一下这两个数据整合方案。

YAML和Ruby

Ruby和YAML的联系,甚至比Java与XML的联系还要紧密。Ruby把YAML用到了和数据相关的方方面面。配置文件的约定格式是YAML。同时YAML还是Ruby的文本序列化格式,就像XML是SDO的文本序列化格式一样。

不夸张的说,YAML是Ruby中流动的血液。

那,Ruby为什么不选XML呢?

其一,XML对Ruby这样的脚本语言而言,解析起来比较困难。效率肯定是会有问题。Ruby的XML解析没有完全实现,走的是实用主义的路线,可能也缘于此。而YAML要轻快很多。

另外,XML使用自定义类型。就算解析出来,也不能直接用,还要再转一次。而YAML不定义自己的类型,直接使用宿主语言本身类型,直截了当。

下面给一个Ruby YAML的简介。

我们使用Ruby写一个Person类:

class Person
  attr_accessor :name, :age, :sponse, :children
  def initialize(name, age, sponse=nil, children=nil)
    @name = name
    @age  = age
    @sponse = sponse
    @children = children
  end
end

把John装配起来:

jane = Person.new("Jane Smith", 25)
children = [Person.new("Jimmy Smith", 15), Person.new("Jenny Smith", 12)]
john = Person.new("John Smith", 37, jane, children)

Dump出John到John.yaml:

File.open('John_ruby.yaml', 'w') do |os|
  YAML::dump(john, os)
end

我们看看Dump的结果:

--- !ruby/object:Person
age: 37
children:
- !ruby/object:Person
  age: 15
  children:
  name: Jimmy Smith
  sponse:
- !ruby/object:Person
  age: 12
  children:
  name: Jenny Smith
  sponse:
name: John Smith
sponse: !ruby/object:Person
  age: 25
  children:
  name: Jane Smith
  sponse:

仔细观察会发现和JYaml Dump出来的还是有些区别的。

首先类型信息不一样。这还好说,虽然Ruby load Java的不太可能,但Java加载Ruby的应该没问题。

还有就是一些实现上细微的区别。比如Ruby::Yaml的”spone:”,JYaml认为是不合法的。还有children的不缩行。尽管Ruby::Yaml很流行,但JYaml认为这是Ruby的Bug。网上有相应的争论。

不过在一个语言里使用还是没问题的。下面是load John的代码:

john2 = File.open('John.yaml') { |is|  YAML::load(is) }

YAML的Ruby实现出现的很早。但由于某些原因,YAML的Ruby实现是不严格的。随着其他语言的YAML实现慢慢出现,这个问题日益明显了。

毕竟每个实现语言的类型不一样的,不同语言文化也不同。因此不同实现的YAML的交互是个问题。兼容性需要YAML规范来保证。而具体的实施还有很长的道路要走。

但YAML在单一语言中,YAML的应用是没有问题的。

YAML在Ruby On Rails中经常用作配置文件。比如数据库信息的配置:

test:
  adapter: mysql
  database: weblog_test
  username: root
  password:
  host: localhost

production:
  adapter: mysql
  database: weblog_production
  username: root
  password: 
  host: localhost

熟悉ROR的读者应该能看出这是`config/database.yml’文件。

YAML的适用范围

由于实现简单,解析成本很低,YAML特别适合在脚本语言中使用。列一下现有的语言实现:Ruby,Java,Perl,Python,PHP,OCaml,JavaScript。除了Java,其他都是脚本语言.

YAML比较适合做序列化。因为它是宿主语言数据类型直转的。

YAML做配置文件也不错。比如Ruby on Rails的配置就选用的YAML。对ROR而言,这很自然,也很省事.

由于兼容性问题,不同语言间的数据流转建议现在不要用YAML.

YAML存在的意义

无论多么完美的事物,都需要有对立面,有说“NO”的声音。XML也不例外。当然,站在主流的对立面,需要勇气和智慧。

YAML和XML不同,没有自己的数据类型的定义,而是使用实现语言的数据类型。这一点,有可能是出奇制胜的地方,也可能是一个败笔。如果兼容性保证的不好的话,YAML数据在不同语言间流转会有问题。如果兼容性好的话,YAML就会成为不同语言间数据流通的桥梁。建议yaml.org设立兼容认证机制,每个语言的实现必须通过认证。

假如兼容性没问题的话,YAML就太完美了。轻巧,敏捷,高效,简便,通用。这才是理想中的数据模型。当然就现在而言,这还只是个理想。