敏捷开发简介

 开发  敏捷开发简介已关闭评论
7月 312021
 

重温下敏捷开发概念!

敏捷开发(agile development)是非常流行的软件开发方法。据统计,2018年90%的软件开发采用敏捷开发。

但是,到底什么是敏捷开发,能说清的人却不多。本文尝试用简洁易懂的语言,解释敏捷开发。

 

一、迭代开发

敏捷开发的核心是迭代开发(iterative development)。敏捷一定是采用迭代开发的方式。

那么什么是”迭代开发”呢?迭代的英文是 iterative,直译为”重复”,迭代开发其实就是”重复开发”。

对于大型软件项目,传统的开发方式是采用一个大周期(比如一年)进行开发,整个过程就是一次”大开发”;迭代开发的方式则不一样,它将开发过程拆分成多个小周期,即一次”大开发”变成多次”小开发”,每次小开发都是同样的流程,所以看上去就好像重复在做同样的步骤。

举例来说,SpaceX 公司想造一个大推力火箭,将人类送到火星。但是,它不是一开始就造大火箭,而是先造一个最简陋的小火箭 Falcon 1。结果,第一次发射就爆炸了,直到第四次发射,才成功进入轨道。然后,开发了中型火箭 Falcon 9,九年中发射了70次。最后,才开发 Falcon 重型火箭。如果 SpaceX 不采用迭代开发,它可能直到现在还无法上天。

迭代开发将一个大任务,分解成多次连续的开发,本质就是逐步改进。开发者先快速发布一个有效但不完美的最简版本,然后不断迭代。每一次迭代都包含规划、设计、编码、测试、评估五个步骤,不断改进产品,添加新功能。通过频繁的发布,以及跟踪对前一次迭代的反馈,最终接近较完善的产品形态。

二、增量开发

迭代开发只是要求将开发分成多个迭代,并没有回答一个重要的问题:怎么划分迭代,哪个任务在这个迭代,哪个任务在下个迭代?这时,一般采用”增量开发”(incremental development)划分迭代。

所谓”增量开发”,指的是软件的每个版本,都会新增一个用户可以感知的完整功能。也就是说,按照新增功能来划分迭代。

举例来说,房产公司开发一个10栋楼的小区。如果采用增量开发的模式,该公司第一个迭代就是交付一号楼,第二个迭代交付二号楼……每个迭代都是完成一栋完整的楼。而不是第一个迭代挖好10栋楼的地基,第二个迭代建好每栋楼的骨架,第三个迭代架设屋顶……

增量开发加上迭代开发,才算真正的敏捷开发。

三、敏捷开发的好处

3.1 早期交付

敏捷开发的第一个好处,就是早期交付,从而大大降低成本。

还是以上一节的房产公司为例,如果按照传统的”瀑布开发模式”,先挖10栋楼的地基、再盖骨架、然后架设屋顶,每个阶段都等到前一个阶段完成后开始,可能需要两年才能一次性交付10栋楼。也就是说,如果不考虑预售,该项目必须等到两年后才能回款。

敏捷开发是六个月后交付一号楼,后面每两个月交付一栋楼。因此,半年就能回款10%,后面每个月都会有现金流,资金压力就大大减轻了。

3.2 降低风险

敏捷开发的第二个好处是,及时了解市场需求,降低产品不适用的风险。

请想一想,哪一种情况损失比较小:10栋楼都造好以后,才发现卖不出去,还是造好第一栋楼,就发现卖不出去,从而改进或停建后面9栋楼?

对于软件项目来说,先有一个原型产品,了解市场的接受程度,往往是项目成功的关键。有一本书叫做《梦断代码》,副标题就是”20+个程序员,三年时间,4732个bug,100+万美元,最后失败的故事”,这就是没有采用敏捷开发的结果。相反的,Instagram 最初是一个地理位置打卡 App,后来发现用户不怎么在乎地理位置,更喜欢上传照片,就改做照片上传软件,结果成了独角兽。

由于敏捷开发可以不断试错,找出对业务最重要的功能,然后通过迭代,调整软件方向。相比传统方式,大大增加了产品成功的可能性。如果市场需求不确定,或者你对该领域不熟悉,那么敏捷开发几乎是唯一可行的应对方式。

四、如何进行每一次迭代

虽然敏捷开发将软件开发分成多个迭代,但是也要求,每次迭代都是一个完整的软件开发周期,必须按照软件工程的方法论,进行正规的流程管理。

具体来说,每次迭代都必须依次完成以下五个步骤。

  1. 需求分析(requirements analysis)
  2. 设计(design)
  3. 编码(coding)
  4. 测试(testing)
  5. 部署和评估(deployment / evaluation)

每个迭代大约持续2~6周。

五、敏捷开发的价值观

《敏捷软件开发宣言》里面提到四个价值观。

  • 程序员的主观能动性,以及程序员之间的互动,优于既定流程和工具。
  • 软件能够运行,优于详尽的文档。
  • 跟客户的密切协作,优于合同和谈判。
  • 能够响应变化,优于遵循计划。

六、十二条原则

该宣言还提出十二条敏捷开发的原则。

  1. 通过早期和持续交付有价值的软件,实现客户满意度。
  2. 欢迎不断变化的需求,即使是在项目开发的后期。要善于利用需求变更,帮助客户获得竞争优势。
  3. 不断交付可用的软件,周期通常是几周,越短越好。
  4. 项目过程中,业务人员与开发人员必须在一起工作。
  5. 项目必须围绕那些有内在动力的个人而建立,他们应该受到信任。
  6. 面对面交谈是最好的沟通方式。
  7. 可用性是衡量进度的主要指标。
  8. 提倡可持续的开发,保持稳定的进展速度。
  9. 不断关注技术是否优秀,设计是否良好。
  10. 简单性至关重要,尽最大可能减少不必要的工作。
  11. 最好的架构、要求和设计,来自团队内部自发的认识。
  12. 团队要定期反思如何更有效,并相应地进行调整。

 

转自: https://www.ruanyifeng.com/blog/2019/03/agile-development.html

Redis的三种集群方案简介(Master, Slave, Sentinel, Cluster)

 redis  Redis的三种集群方案简介(Master, Slave, Sentinel, Cluster)已关闭评论
7月 292021
 

重温下redis集群的三种集群方案,分享下网络上的一篇文章,写的很好, 原文链接见文末。

 

在开发测试环境中,我们一般搭建Redis的单实例来应对开发测试需求,但是在生产环境,如果对可用性、可靠性要求较高,则需要引入Redis的集群方案。虽然现在各大云平台有提供缓存服务可以直接使用,但了解一下其背后的实现与原理总还是有些必要(比如面试), 本文就一起来学习一下Redis的几种集群方案。

Redis支持三种集群方案

  • 主从复制模式
  • Sentinel(哨兵)模式
  • Cluster模式

主从复制模式

1. 基本原理

主从复制模式中包含一个主数据库实例(master)与一个或多个从数据库实例(slave),如下图

redis-master-slave

客户端可对主数据库进行读写操作,对从数据库进行读操作,主数据库写入的数据会实时自动同步给从数据库。

具体工作机制为:

  1. slave启动后,向master发送SYNC命令,master接收到SYNC命令后通过bgsave保存快照(即上文所介绍的RDB持久化),并使用缓冲区记录保存快照这段时间内执行的写命令
  2. master将保存的快照文件发送给slave,并继续记录执行的写命令
  3. slave接收到快照文件后,加载快照文件,载入数据
  4. master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化
  5. 此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性

2. 部署示例

本示例基于Redis 5.0.3版。

redis.conf的主要配置

###网络相关###
# bind 127.0.0.1 # 绑定监听的网卡IP,注释掉或配置成0.0.0.0可使任意IP均可访问
protected-mode no # 关闭保护模式,使用密码访问
port 6379  # 设置监听端口,建议生产环境均使用自定义端口
timeout 30 # 客户端连接空闲多久后断开连接,单位秒,0表示禁用

###通用配置###
daemonize yes # 在后台运行
pidfile /var/run/redis_6379.pid  # pid进程文件名
logfile /usr/local/redis/logs/redis.log # 日志文件的位置

###RDB持久化配置###
save 900 1 # 900s内至少一次写操作则执行bgsave进行RDB持久化
save 300 10
save 60 10000 
# 如果禁用RDB持久化,可在这里添加 save ""
rdbcompression yes #是否对RDB文件进行压缩,建议设置为no,以(磁盘)空间换(CPU)时间
dbfilename dump.rdb # RDB文件名称
dir /usr/local/redis/datas # RDB文件保存路径,AOF文件也保存在这里

###AOF配置###
appendonly yes # 默认值是no,表示不使用AOF增量持久化的方式,使用RDB全量持久化的方式
appendfsync everysec # 可选值 always, everysec,no,建议设置为everysec

###设置密码###
requirepass 123456 # 设置复杂一点的密码

部署主从复制模式只需稍微调整slave的配置,在redis.conf中添加

replicaof 127.0.0.1 6379 # master的ip,port
masterauth 123456 # master的密码
replica-serve-stale-data no # 如果slave无法与master同步,设置成slave不可读,方便监控脚本发现问题

本示例在单台服务器上配置master端口6379,两个slave端口分别为7001,7002,启动master,再启动两个slave

[[email protected] master-slave]# redis-server master.conf
[[email protected] master-slave]# redis-server slave1.conf
[[email protected] master-slave]# redis-server slave2.conf

进入master数据库,写入一个数据,再进入一个slave数据库,立即便可访问刚才写入master数据库的数据。如下所示

[[email protected] master-slave]# redis-cli 
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> set site blog.jboost.cn
OK
127.0.0.1:6379> get site
"blog.jboost.cn"
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7001,state=online,offset=13364738,lag=1
slave1:ip=127.0.0.1,port=7002,state=online,offset=13364738,lag=0
...
127.0.0.1:6379> exit

[[email protected] master-slave]# redis-cli -p 7001
127.0.0.1:7001> auth 123456
OK
127.0.0.1:7001> get site
"blog.jboost.cn"

执行info replication命令可以查看连接该数据库的其它库的信息,如上可看到有两个slave连接到master

3. 主从复制的优缺点

优点:

  1. master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
  2. master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求

缺点:

  1. 不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复
  2. master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题
  3. 难以支持在线扩容,Redis的容量受限于单机配置

Sentinel(哨兵)模式

1. 基本原理

哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。如图

redis-sentinel

哨兵顾名思义,就是来为Redis集群站哨的,一旦发现问题能做出相应的应对处理。其功能包括

  1. 监控master、slave是否正常运行
  2. 当master出现故障时,能自动将一个slave转换为master(大哥挂了,选一个小弟上位)
  3. 多个哨兵可以监控同一个Redis,哨兵之间也会自动监控

哨兵模式的具体工作机制:

在配置文件中通过 sentinel monitor <master-name> <ip> <redis-port> <quorum> 来定位master的IP、端口,一个哨兵可以监控多个master数据库,只需要提供多个该配置项即可。哨兵启动后,会与要监控的master建立两条连接:

  1. 一条连接用来订阅master的_sentinel_:hello频道与获取其他监控该master的哨兵节点信息
  2. 另一条连接定期向master发送INFO等命令获取master本身的信息

与master建立连接后,哨兵会执行三个操作:

  1. 定期(一般10s一次,当master被标记为主观下线时,改为1s一次)向master和slave发送INFO命令
  2. 定期向master和slave的_sentinel_:hello频道发送自己的信息
  3. 定期(1s一次)向master、slave和其他哨兵发送PING命令

发送INFO命令可以获取当前数据库的相关信息从而实现新节点的自动发现。所以说哨兵只需要配置master数据库信息就可以自动发现其slave信息。获取到slave信息后,哨兵也会与slave建立两条连接执行监控。通过INFO命令,哨兵可以获取主从数据库的最新信息,并进行相应的操作,比如角色变更等。

接下来哨兵向主从数据库的_sentinel_:hello频道发送信息与同样监控这些数据库的哨兵共享自己的信息,发送内容为哨兵的ip端口、运行id、配置版本、master名字、master的ip端口还有master的配置版本。这些信息有以下用处:

  1. 其他哨兵可以通过该信息判断发送者是否是新发现的哨兵,如果是的话会创建一个到该哨兵的连接用于发送PING命令。
  2. 其他哨兵通过该信息可以判断master的版本,如果该版本高于直接记录的版本,将会更新
  3. 当实现了自动发现slave和其他哨兵节点后,哨兵就可以通过定期发送PING命令定时监控这些数据库和节点有没有停止服务。

如果被PING的数据库或者节点超时(通过 sentinel down-after-milliseconds master-name milliseconds 配置)未回复,哨兵认为其主观下线(sdown,s就是Subjectively —— 主观地)。如果下线的是master,哨兵会向其它哨兵发送命令询问它们是否也认为该master主观下线,如果达到一定数目(即配置文件中的quorum)投票,哨兵会认为该master已经客观下线(odown,o就是Objectively —— 客观地),并选举领头的哨兵节点对主从系统发起故障恢复。若没有足够的sentinel进程同意master下线,master的客观下线状态会被移除,若master重新向sentinel进程发送的PING命令返回有效回复,master的主观下线状态就会被移除

哨兵认为master客观下线后,故障恢复的操作需要由选举的领头哨兵来执行,选举采用Raft算法:

  1. 发现master下线的哨兵节点(我们称他为A)向每个哨兵发送命令,要求对方选自己为领头哨兵
  2. 如果目标哨兵节点没有选过其他人,则会同意选举A为领头哨兵
  3. 如果有超过一半的哨兵同意选举A为领头,则A当选
  4. 如果有多个哨兵节点同时参选领头,此时有可能存在一轮投票无竞选者胜出,此时每个参选的节点等待一个随机时间后再次发起参选请求,进行下一轮投票竞选,直至选举出领头哨兵

选出领头哨兵后,领头者开始对系统进行故障恢复,从出现故障的master的从数据库中挑选一个来当选新的master,选择规则如下:

  1. 所有在线的slave中选择优先级最高的,优先级可以通过slave-priority配置
  2. 如果有多个最高优先级的slave,则选取复制偏移量最大(即复制越完整)的当选
  3. 如果以上条件都一样,选取id最小的slave

挑选出需要继任的slave后,领头哨兵向该数据库发送命令使其升格为master,然后再向其他slave发送命令接受新的master,最后更新数据。将已经停止的旧的master更新为新的master的从数据库,使其恢复服务后以slave的身份继续运行。

2. 部署演示

本示例基于Redis 5.0.3版。

哨兵模式基于前文的主从复制模式。哨兵的配置文件为sentinel.conf,在文件中添加

sentinel monitor mymaster 127.0.0.1 6379 1 # mymaster定义一个master数据库的名称,后面是master的ip, port,1表示至少需要一个Sentinel进程同意才能将master判断为失效,如果不满足这个条件,则自动故障转移(failover)不会执行
sentinel auth-pass mymaster 123456 # master的密码

sentinel down-after-milliseconds mymaster 5000 # 5s未回复PING,则认为master主观下线,默认为30s
sentinel parallel-syncs mymaster 2  # 指定在执行故障转移时,最多可以有多少个slave实例在同步新的master实例,在slave实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长
sentinel failover-timeout mymaster 300000 # 如果在该时间(ms)内未能完成故障转移操作,则认为故障转移失败,生产环境需要根据数据量设置该值

一个哨兵可以监控多个master数据库,只需按上述配置添加多套

分别以26379,36379,46379端口启动三个sentinel

[[email protected] sentinel]# redis-server sentinel1.conf --sentinel
[[email protected] sentinel]# redis-server sentinel2.conf --sentinel
[roo[email protected] sentinel]# redis-server sentinel3.conf --sentinel

也可以使用redis-sentinel sentinel1.conf 命令启动。此时集群包含一个master、两个slave、三个sentinel,如图,

redis-cluster-instance

我们来模拟master挂掉的场景,执行 kill -9 3017 将master进程干掉,进入slave中执行 info replication查看,

[[email protected] sentinel]# redis-cli -p 7001
127.0.0.1:7001> auth 123456
OK
127.0.0.1:7001> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7002
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
# 省略
127.0.0.1:7001> exit
[[email protected] sentinel]# redis-cli -p 7002
127.0.0.1:7002> auth 123456
OK
127.0.0.1:7002> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7001,state=online,offset=13642721,lag=1
# 省略

可以看到slave 7002已经成功上位晋升为master(role:master),接收一个slave 7001的连接。此时查看slave2.conf配置文件,发现replicaof的配置已经被移除了,slave1.conf的配置文件里replicaof 127.0.0.1 6379 被改为 replicaof 127.0.0.1 7002。重新启动master,也可以看到master.conf配置文件中添加了replicaof 127.0.0.1 7002的配置项,可见大哥(master)下位后,再出来混就只能当当小弟(slave)了,三十年河东三十年河西。

3. 哨兵模式的优缺点

优点:

  1. 哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有
  2. 哨兵模式下,master挂掉可以自动进行切换,系统可用性更高

缺点:

  1. 同样也继承了主从模式难以在线扩容的缺点,Redis的容量受限于单机配置
  2. 需要额外的资源来启动sentinel进程,实现相对复杂一点,同时slave节点作为备份节点不提供服务

Cluster模式

1. 基本原理

哨兵模式解决了主从复制不能自动故障转移,达不到高可用的问题,但还是存在难以在线扩容,Redis容量受限于单机配置的问题。Cluster模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题。如图

redis-cluster

Cluster采用无中心结构,它的特点如下:

  1. 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
  2. 节点的fail是通过集群中超过半数的节点检测失效时才生效
  3. 客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

Cluster模式的具体工作机制:

  1. 在Redis的每个节点上,都有一个插槽(slot),取值范围为0-16383
  2. 当我们存取key的时候,Redis会根据CRC16的算法得出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作
  3. 为了保证高可用,Cluster模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点
  4. 当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点都宕机了,那么该集群就无法再提供服务了

Cluster模式集群节点最小配置6个节点(3主3从,因为需要半数以上),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。

2. 部署演示

本示例基于Redis 5.0.3版。

Cluster模式的部署比较简单,首先在redis.conf中

port 7100 # 本示例6个节点端口分别为7100,7200,7300,7400,7500,7600 
daemonize yes # r后台运行 
pidfile /var/run/redis_7100.pid # pidfile文件对应7100,7200,7300,7400,7500,7600 
cluster-enabled yes # 开启集群模式 
masterauth passw0rd # 如果设置了密码,需要指定master密码
cluster-config-file nodes_7100.conf # 集群的配置文件,同样对应7100,7200等六个节点
cluster-node-timeout 15000 # 请求超时 默认15秒,可自行设置 

分别以端口7100,7200,7300,7400,7500,7600 启动六个实例(如果是每个服务器一个实例则配置可一样)

[[email protected] cluster]# redis-server redis_7100.conf
[[email protected] cluster]# redis-server redis_7200.conf
...

然后通过命令将这个6个实例组成一个3主节点3从节点的集群,

redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7100 127.0.0.1:7200 127.0.0.1:7300 127.0.0.1:7400 127.0.0.1:7500 127.0.0.1:7600 -a passw0rd

执行结果如图

redis-cluster-deploy

可以看到 7100, 7200, 7300 作为3个主节点,分配的slot分别为 0-5460, 5461-10922, 10923-16383, 7600作为7100的slave, 7500作为7300的slave,7400作为7200的slave。

我们连接7100设置一个值

[[email protected] cluster]# redis-cli -p 7100 -c -a passw0rd
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:7100> set site blog.jboost.cn
-> Redirected to slot [9421] located at 127.0.0.1:7200
OK
127.0.0.1:7200> get site
"blog.jboost.cn"
127.0.0.1:7200>

注意添加 -c 参数表示以集群模式,否则报 (error) MOVED 9421 127.0.0.1:7200 错误, 以 -a 参数指定密码,否则报(error) NOAUTH Authentication required错误。

从上面命令看到key为site算出的slot为9421,落在7200节点上,所以有Redirected to slot [9421] located at 127.0.0.1:7200,集群会自动进行跳转。因此客户端可以连接任何一个节点来进行数据的存取。

通过cluster nodes可查看集群的节点信息

127.0.0.1:7200> cluster nodes
eb28aaf090ed1b6b05033335e3d90a202b422d6c 127.0.0.1:[email protected] slave c1047de2a1b5d5fa4666d554376ca8960895a955 0 1584165266071 5 connected
4cc0463878ae00e5dcf0b36c4345182e021932bc 127.0.0.1:[email protected] slave 5544aa5ff20f14c4c3665476de6e537d76316b4a 0 1584165267074 4 connected
dbbb6420d64db22f35a9b6fa460b0878c172a2fb 127.0.0.1:[email protected] master - 0 1584165266000 1 connected 0-5460
d4b434f5829e73e7e779147e905eea6247ffa5a2 127.0.0.1:[email protected] slave dbbb6420d64db22f35a9b6fa460b0878c172a2fb 0 1584165265000 6 connected
5544aa5ff20f14c4c3665476de6e537d76316b4a 127.0.0.1:[email protected] myself,master - 0 1584165267000 2 connected 5461-10922
c1047de2a1b5d5fa4666d554376ca8960895a955 127.0.0.1:[email protected] master - 0 1584165268076 3 connected 10923-16383

我们将7200通过 kill -9 pid杀死进程来验证集群的高可用,重新进入集群执行cluster nodes可以看到7200 fail了,但是7400成了master,重新启动7200,可以看到此时7200已经变成了slave。

3. Cluster模式的优缺点

优点:

  1. 无中心架构,数据按照slot分布在多个节点。
  2. 集群中的每个节点都是平等的关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
  3. 可线性扩展到1000多个节点,节点可动态添加或删除
  4. 能够实现自动故障转移,节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色转换

缺点:

  1. 客户端实现复杂,驱动要求实现Smart Client,缓存slots mapping信息并及时更新,提高了开发难度。目前仅JedisCluster相对成熟,异常处理还不完善,比如常见的“max redirect exception”
  2. 节点会因为某些原因发生阻塞(阻塞时间大于 cluster-node-timeout)被判断下线,这种failover是没有必要的
  3. 数据通过异步复制,不保证数据的强一致性
  4. slave充当“冷备”,不能缓解读压力
  5. 批量操作限制,目前只支持具有相同slot值的key执行批量操作,对mset、mget、sunion等操作支持不友好
  6. key事务操作支持有线,只支持多key在同一节点的事务操作,多key分布不同节点时无法使用事务功能
  7. 不支持多数据库空间,单机redis可以支持16个db,集群模式下只能使用一个,即db 0

Redis Cluster模式不建议使用pipeline和multi-keys操作,减少max redirect产生的场景。

总结

本文介绍了Redis集群方案的三种模式,其中主从复制模式能实现读写分离,但是不能自动故障转移;哨兵模式基于主从复制模式,能实现自动故障转移,达到高可用,但与主从复制模式一样,不能在线扩容,容量受限于单机的配置;Cluster模式通过无中心化架构,实现分布式存储,可进行线性扩展,也能高可用,但对于像批量操作、事务操作等的支持性不够好。三种模式各有优缺点,可根据实际场景进行选择。

 

转自:https://segmentfault.com/a/1190000022028642

RelativeLayout/ 相对布局简介、使用、说明

 android  RelativeLayout/ 相对布局简介、使用、说明已关闭评论
7月 012021
 

网上找到的一篇对relativeLayout使用非常好的文章,分享下, 原文地址见文件末尾


在上一节中我们对LinearLayout进行了详细的解析,LinearLayout也是我们 用的比较多的一个布局,我们更多的时候更钟情于他的weight(权重)属性,等比例划分,对屏幕适配还是 帮助蛮大的;但是使用LinearLayout的时候也有一个问题,就是当界面比较复杂的时候,需要嵌套多层的 LinearLayout,这样就会降低UI Render的效率(渲染速度),而且如果是listview或者GridView上的 item,效率会更低,另外太多层LinearLayout嵌套会占用更多的系统资源,还有可能引发stackoverflow; 但是如果我们使用RelativeLayout的话,可能仅仅需要一层就可以完成了,以父容器或者兄弟组件参考+margin +padding就可以设置组件的显示位置,是比较方便的!当然,也不是绝对的,具体问题具体分析吧! 总结就是:尽量使用RelativeLayout + LinearLayout的weight属性搭配使用吧!


1.核心属性图


2.父容器定位属性示意图


3.根据兄弟组件定位

恩,先说下什么是兄弟组件吧,所谓的兄弟组件就是处于同一层次容器的组件,如图

图中的组件1,2就是兄弟组件了,而组件3与组件1或组件2并不是兄弟组件,所以组件3不能通过 组件1或2来进行定位,比如layout_toleftof = “组件1″这样是会报错的!切记! 关于这个兄弟组件定位的最经典例子就是”梅花布局”了,下面代码实现下:

运行效果图:

实现代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    xmlns:tools="http://schemas.android.com/tools"    
    android:id="@+id/RelativeLayout1"    
    android:layout_width="match_parent"    
    android:layout_height="match_parent" >    
    
    <!-- 这个是在容器中央的 -->    
        
    <ImageView    
        android:id="@+id/img1"     
        android:layout_width="80dp"    
        android:layout_height="80dp"    
        android:layout_centerInParent="true"    
        android:src="@drawable/pic1"/>    
        
    <!-- 在中间图片的左边 -->    
    <ImageView    
        android:id="@+id/img2"     
        android:layout_width="80dp"    
        android:layout_height="80dp"    
        android:layout_toLeftOf="@id/img1"    
        android:layout_centerVertical="true"    
        android:src="@drawable/pic2"/>    
        
    <!-- 在中间图片的右边 -->    
    <ImageView    
        android:id="@+id/img3"     
        android:layout_width="80dp"    
        android:layout_height="80dp"    
        android:layout_toRightOf="@id/img1"    
        android:layout_centerVertical="true"    
        android:src="@drawable/pic3"/>    
        
    <!-- 在中间图片的上面-->    
    <ImageView    
        android:id="@+id/img4"     
        android:layout_width="80dp"    
        android:layout_height="80dp"    
        android:layout_above="@id/img1"    
        android:layout_centerHorizontal="true"    
        android:src="@drawable/pic4"/>    
        
    <!-- 在中间图片的下面 -->    
    <ImageView    
        android:id="@+id/img5"     
        android:layout_width="80dp"    
        android:layout_height="80dp"    
        android:layout_below="@id/img1"    
        android:layout_centerHorizontal="true"    
        android:src="@drawable/pic5"/>    
    
</RelativeLayout>

4.margin与padding的区别

初学者对于这两个属性可能会有一点混淆,这里区分下: 首先margin代表的是偏移,比如marginleft = “5dp”表示组件离容器左边缘偏移5dp; 而padding代表的则是填充,而填充的对象针对的是组件中的元素,比如TextView中的文字 比如为TextView设置paddingleft = “5dp”,则是在组件里的元素的左边填充5dp的空间! margin针对的是容器中的组件,而padding针对的是组件中的元素,要区分开来! 下面通过简单的代码演示两者的区别:

比较示例代码如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    xmlns:tools="http://schemas.android.com/tools"    
    android:layout_width="match_parent"    
    android:layout_height="match_parent"    
    android:paddingBottom="@dimen/activity_vertical_margin"    
    android:paddingLeft="@dimen/activity_horizontal_margin"    
    android:paddingRight="@dimen/activity_horizontal_margin"    
    android:paddingTop="@dimen/activity_vertical_margin"    
    tools:context=".MainActivity" >    
    
    <Button    
        android:id="@+id/btn1"     
        android:layout_height="wrap_content"    
        android:layout_width="wrap_content"    
        android:text="Button"/>    
    <Button    
        android:paddingLeft="100dp"     
        android:layout_height="wrap_content"    
        android:layout_width="wrap_content"    
        android:text="Button"    
        android:layout_toRightOf="@id/btn1"/>    
        
    <Button    
        android:id="@+id/btn2"     
        android:layout_height="wrap_content"    
        android:layout_width="wrap_content"    
        android:text="Button"    
        android:layout_alignParentBottom="true"/>    
    <Button    
        android:layout_marginLeft="100dp"     
        android:layout_height="wrap_content"    
        android:layout_width="wrap_content"    
        android:text="Button"    
        android:layout_toRightOf="@id/btn2"     
        android:layout_alignParentBottom="true"/>    
        
</RelativeLayout>

运行效果图比较:


5.很常用的一点:margin可以设置为负数

相信很多朋友都不知道一点吧,平时我们设置margin的时候都习惯了是正数的, 其实是可以用负数的,下面写个简单的程序演示下吧,模拟进入软件后,弹出广告 页面的,右上角的cancle按钮的margin则是使用负数的!

贴出的广告Activity的布局代码吧,当然,如果你对这个有兴趣的话可以下下demo, 因为仅仅是实现效果,所以代码会有些粗糙!

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    tools:context="com.jay.example.relativelayoutdemo.MainActivity"   
    android:background="#00CCCCFF">  
  
    <ImageView  
        android:id="@+id/imgBack"  
        android:layout_width="200dp"  
        android:layout_height="200dp"  
        android:layout_centerInParent="true"  
        android:background="@drawable/myicon" />  
  
    <ImageView  
        android:id="@+id/imgCancle"  
        android:layout_width="28dp"  
        android:layout_height="28dp"  
        android:layout_alignRight="@id/imgBack"  
        android:layout_alignTop="@id/imgBack"  
        android:background="@drawable/cancel"  
        android:layout_marginTop="-15dp"  
        android:layout_marginRight="-10dp" />  
  
</RelativeLayout>

来自:https://www.runoob.com/w3cnote/android-tutorial-relativelayout.html

objective-c中categories和extentions简介

 objective-c  objective-c中categories和extentions简介已关闭评论
12月 132019
 

网上很多关于objective-c中categories和extentions的介绍都很含糊,但下面这篇文章自己觉得写的很好,虽然内容有些老了,但不影响你理解categories和extentions,来自:https://code.tutsplus.com/tutorials/objective-c-succinctly-categories-and-extensions–mobile-22016

Categories are an Objective-C language feature that let you add new methods to an existing class, much like C# extensions. However, do not confuse C# extensions with Objective-C extensions. Objective-C’s extensions are a special case of categories that let you define methods that must be declared in the main implementation block.

These are powerful features that have many potential uses. First, categories make it possible to split up a class’ interface and implementation into several files, which provides much-needed modularity for larger projects. Second, categories let you fix bugs in an existing class (e.g., NSString) without the need to subclass it. Third, they provide an effective alternative to the protected and private methods found in C# and other Simula-like languages.

category is a group of related methods for a class, and all of the methods defined in a category are available through the class as if they were defined in the main interface file. As an example, take the Person class that we’ve been working with throughout this book. If this were a large project, Person may have dozens of methods ranging from basic behaviors to interactions with other people to identity checking. The API might call for all of these methods to be available through a single class, but it’s much easier for developers to maintain if each group is stored in a separate file. In addition, categories eliminate the need to recompile the entire class every time you change a single method, which can be a time-saver for very large projects.

Let’s take a look at how categories can be used to achieve this. We start with a normal class interface and a corresponding implementation:

// Person.h
@interface Person : NSObject
@interface Person : NSObject
@property (readonly) NSMutableArray* friends;
@property (copy) NSString* name;
- (void)sayHello;
- (void)sayGoodbye;
@end
// Person.m
#import "Person.h"
@implementation Person
@synthesize name = _name;
@synthesize friends = _friends;
-(id)init{
    self = [super init];
    if(self){
        _friends = [[NSMutableArray alloc] init];
    }
    return self;
}
- (void)sayHello {
    NSLog(@"Hello, says %@.", _name);
}
- (void)sayGoodbye {
    NSLog(@"Goodbye, says %@.", _name);
}
@end

Nothing new here-just a Person class with two properties (the friends property will be used by our category) and two methods. Next, we’ll use a category to store some methods for interacting with other Person instances. Create a new file, but instead of a class, use the Objective-C Category template. Use Relations for the category name and Person for the Category on field:

Figure 28 Creating the PersonRelations class
Creating the Person+Relations class

As expected, this will create two files: a header to hold the interface and an implementation. However, these will both look slightly different than what we’ve been working with. First, let’s take a look at the interface:

// Person+Relations.h
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Person (Relations)
- (void)addFriend:(Person *)aFriend;
- (void)removeFriend:(Person *)aFriend;
- (void)sayHelloToFriends;
@end

Instead of the normal @interface declaration, we include the category name in parentheses after the class name we’re extending. A category name can be anything, as long as it doesn’t conflict with other categories for the same class. A category’s file name should be the class name followed by a plus sign, followed by the name of the category (e.g., Person+Relations.h ).

So, this defines our category’s interface. Any methods we add in here will be added to the original Person class at run time. It will appear as though the addFriend:removeFriend:, and sayHelloToFriends methods are all defined in Person.h, but we can keep our functionality encapsulated and maintainable. Also note that you must import the header for the original class, Person.h. The category implementation follows a similar pattern:

// Person+Relations.m
#import "Person+Relations.h"
@implementation Person (Relations)
- (void)addFriend:(Person *)aFriend {
    [[self friends] addObject:aFriend];
}
- (void)removeFriend:(Person *)aFriend {
    [[self friends] removeObject:aFriend];
}
- (void)sayHelloToFriends {
    for (Person *friend in [self friends]) {
        NSLog(@"Hello there, %@!", [friend name]);
    }
}
@end

This implements all of the methods in Person+Relations.h. Just like the category’s interface, the category name appears in parentheses after the class name. The category name in the implementation should match the one in the interface.

Also, note that there is no way to define additional properties or instance variables in a category. Categories have to refer back to data stored in the main class (friends in this instance).

It’s also possible to override the implementation contained in Person.m by simply redefining the method in Person+Relations.m. This can be used to monkey patch an existing class; however, it’s not recommended if you have an alternative solution to the problem, since there would be no way to override the implementation defined by the category. That is to say, unlike the class hierarchy, categories are a flat organizational structure-if you implement the same method in two separate categories, it’s impossible for the runtime to figure out which one to use.

The only change you have to make to use a category is to import the category’s header file. As you can see in the following example, the Person class has access to the methods defined in Person.h along with those defined in the category Person+Relations.h:

// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+Relations.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *joe = [[Person alloc] init];
        joe.name = @"Joe";
        Person *bill = [[Person alloc] init];
        bill.name = @"Bill";
        Person *mary = [[Person alloc] init];
        mary.name = @"Mary";
        [joe sayHello];
        [joe addFriend:bill];
        [joe addFriend:mary];
        [joe sayHelloToFriends];
    }
    return 0;
}

And that’s all there is to creating categories in Objective-C.

To reiterate, all Objective-C methods are public-there is no language construct to mark them as either private or protected. Instead of using “true” protected methods, Objective-C programs can combine categories with the interface/implementation paradigm to achieve the same result.

The idea is simple: declare “protected” methods as a category in a separate header file. This gives subclasses the ability to “opt-in” to the protected methods while unrelated classes use the “public” header file as usual. For example, take a standard Ship interface:

// Ship.h
#import <Foundation/Foundation.h>
@interface Ship : NSObject
- (void)shoot;
@end

As we’ve seen many times, this defines a public method called shoot. To declare a protected method, we need to create a Ship category in a dedicated header file:

// Ship_Protected.h
#import <Foundation/Foundation.h>
@interface Ship(Protected)
- (void)prepareToShoot;
@end

Any classes that need access to the protected methods (namely, the superclass and any subclasses) can simply import Ship_Protected.h. For example, the Ship implementation should define a default behavior for the protected method:

// Ship.m
#import "Ship.h"
#import "Ship_Protected.h"
@implementation Ship {
    BOOL _gunIsReady;
}
- (void)shoot {
    if (!_gunIsReady) {
        [self prepareToShoot];
        _gunIsReady = YES;
    }
    NSLog(@"Firing!");
}
- (void)prepareToShoot {
    // Execute some private functionality.
    NSLog(@"Preparing the main weapon...");
}
@end

Note that if we hadn’t imported Ship_Protected.h, this prepareToShoot implementation would be a private method, as discussed in the Methods chapter. Without a protected category, there would be no way for subclasses to access this method. Let’s subclass the Ship to see how this works. We’ll call it ResearchShip:

// ResearchShip.h
#import "Ship.h"
@interface ResearchShip : Ship
- (void)extendTelescope;
@end

This is a normal subclass interface-it should not import the protected header, as this would make the protected methods available to anyone that imports ResearchShip.h, which is precisely what we’re trying to avoid. Finally, the implementation for the subclass imports the protected methods and (optionally) overrides them:

// ResearchShip.m
#import "ResearchShip.h"
#import "Ship_Protected.h"
@implementation ResearchShip
- (void)extendTelescope {
    NSLog(@"Extending the telescope");
}
// Override protected method
- (void)prepareToShoot {
    NSLog(@"Oh shoot! We need to find some weapons!");
}
@end

To enforce the protected status of the methods in Ship_Protected.h, other classes aren’t allowed to import it. They’ll just import the normal “public” interfaces of the superclass and subclass:

// main.m
#import <Foundation/Foundation.h>
#import "Ship.h"
#import "ResearchShip.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Ship *genericShip = [[Ship alloc] init];
        [genericShip shoot];
        Ship *discoveryOne = [[ResearchShip alloc] init];
        [discoveryOne shoot];
    }
    return 0;
}

Since neither main.mShip.h, nor ResearchShip.h import the protected methods, this code won’t have access to them. Try adding a [discoveryOne prepareToShoot] method-it will throw a compiler error, since the prepareToShoot declaration is nowhere to be found.

To summarize, protected methods can be emulated by placing them in a dedicated header file and importing that header file into the implementation files that require access to the protected methods. No other files should import the protected header.

While the workflow presented here is a completely valid organizational tool, keep in mind that Objective-C was never meant to support protected methods. Think of this as an alternative way to structure an Objective-C method, rather than a direct replacement for C#/Simula-style protected methods. It’s often better to look for another way to structure your classes rather than forcing your Objective-C code to act like a C# program.

One of the biggest issues with categories is that you can’t reliably override methods defined in categories for the same class. For example, if you defined an addFriend: class in Person(Relations) and later decided to change the addFriend: implementation via a Person(Security) category, there is no way for the runtime to know which method it should use since categories are, by definition, a flat organizational structure. For these kinds of cases, you need to revert to the traditional subclassing paradigm.

Also, it’s important to note that a category can’t add instance variables. This means you can’t declare new properties in a category, as they could only be synthesized in the main implementation. Additionally, while a category technically does have access to its classes’ instance variables, it’s better practice to access them through their public interface to shield the category from potential changes in the main implementation file.

Extensions (also called class extensions) are a special type of category that requires their methods to be defined in the main implementation block for the associated class, as opposed to an implementation defined in a category. This can be used to override publicly declared property attributes. For example, it is sometimes convenient to change a read-only property to a read-write property within a class’ implementation. Consider the normal interface for a Ship class:

Included code sample: Extensions

// Ship.h
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Ship : NSObject
@property (strong, readonly) Person *captain;
- (id)initWithCaptain:(Person *)captain;
@end

It’s possible to override the @property definition inside of a class extension. This gives you the opportunity to re-declare the property as readwrite in the implementation file. Syntactically, an extension looks like an empty category declaration:

// Ship.m
#import "Ship.h"
// The class extension.
@interface Ship()
@property (strong, readwrite) Person *captain;
@end
// The standard implementation.
@implementation Ship
@synthesize captain = _captain;
- (id)initWithCaptain:(Person *)captain {
    self = [super init];
    if (self) {
        // This WILL work because of the extension.
        [self setCaptain:captain];
    }
    return self;
}
@end

Note the () appended to the class name after the @interface directive. This is what marks it as an extension rather than a normal interface or a category. Any properties or methods that appear in the extension must be declared in the main implementation block for the class. In this case, we aren’t adding any new fields-we’re overriding an existing one. But unlike categories, extensions can add extra instance variables to a class, which is why we’re able to declare properties in a class extension but not a category.

Because we re-declared the captain property with a readwrite attribute, the initWithCaptain: method can use the setCaptain: accessor on itself. If you were to delete the extension, the property would return to its read-only status and the compiler would complain. Clients using the Ship class aren’t supposed to import the implementation file, so the captain property will remain read-only.

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Ship.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *heywood = [[Person alloc] init];
        heywood.name = @"Heywood";
        Ship *discoveryOne = [[Ship alloc] initWithCaptain:heywood];
        NSLog(@"%@", [discoveryOne captain].name);
        Person *dave = [[Person alloc] init];
        dave.name = @"Dave";
        // This will NOT work because the property is still read-only.
        [discoveryOne setCaptain:dave];
    }
    return 0;
}

Another common use case for extensions is for declaring private methods. In the previous chapter, we saw how private methods can be declared by simply adding them anywhere in the implementation file. But, prior to Xcode 4.3, this was not the case. The canonical way to create a private method was to forward-declare it using a class extension. Let’s take a look at this by slightly altering the Ship header from the previous example:

// Ship.h
#import <Foundation/Foundation.h>
@interface Ship : NSObject
- (void)shoot;
@end

 

Next, we’re going to recreate the example we used when we discussed private methods in the Methods chapter. Instead of simply adding the private prepareToShoot method to the implementation, we need to forward-declare it in a class extension.

// Ship.m
#import "Ship.h"
// The class extension.
@interface Ship()
- (void)prepareToShoot;
@end
// The rest of the implementation.
@implementation Ship {
    BOOL _gunIsReady;
}
- (void)shoot {
    if (!_gunIsReady) {
        [self prepareToShoot];
        _gunIsReady = YES;
    }
    NSLog(@"Firing!");
}
- (void)prepareToShoot {
    // Execute some private functionality.
    NSLog(@"Preparing the main weapon...");
}
@end

The compiler ensures the extension methods are implemented in the main implementation block, which is why it functions as a forward-declaration. Yet because the extension is encapsulated in the implementation file, other objects shouldn’t ever know about it, giving us another way to emulate private methods. While newer compilers save you this trouble, it’s still important to understand how class extensions work, as it has been a common way to leverage private methods in Objective-C programs until very recently.

This chapter covered two of the more unique concepts in the Objective-C programming language: categories and extensions. Categories are a way to extend the API of existing classes, and extensions are a way to add required methods to the API outside of the main interface file. Both of these were initially designed to ease the burden of maintaining large code bases.

The next chapter continues our journey through Objective-C’s organizational structures. We’ll learn how to define a protocol, which is an interface that can be implemented by a variety of classes.

Swift中的!和?, 问号 和叹号 的使用和区别介绍

 swift  Swift中的!和?, 问号 和叹号 的使用和区别介绍已关闭评论
8月 132019
 

一般我们在一下两种情况下会遇到!和?的使用
1.声明变量时

var number :Int?
var str : String

2.在对变量操作时

number?.hashValue
str!.hashValue

由于这两种情况的意义不同,所以分开进行解释:

1.声明变量时
在声明一个变量时如果不手动初始化,Swift不会自动初始化该变量为一个默认值的。

var a : String
var b = a           //error:因为没有初始化a,a没有值

但是对于Optional的变量则不同,Optional的变量在声明时如果不初始化,Swift会自动将该变量初始化为nil。声明变量时在类型后添加?或者!就是告诉编译器这个一个Optional的变量,如果没有初始化,你就将其初始化为nil

var a : String?           //a 为nil
var b : String!           //b 为nil
var a_test = a            //a_test为nil
var b_test = b            //b_test为nil

但是这两者之间还是有一些区别的,介绍后面之后再讲。
Optional事实上是一个枚举类型,从下图可以看出,Optional包含None和Some两种类型,而nil就是Optional.None,非nil就是Optional.some。如果Optional变量在声明时不初始化,Swift会调用init()来初始化变量为nil,而用非nil的值初始化变量时,会通过Some(T)把该原始值包装,所以在之后使用的时候我们需要通过解包取出原始值才能使用。

2.对变量进行操作时

var arrayCount = dataList?.count

这时问号的意思类似于isResponseToSelector,即如果变量是nil,则不能响应后面的方法,所以会直接返回nil。如果变量非nil,就会拆Some(T)的包,取出原始值执行后面的操作。

var arrayCount = dataList!.count

这里的叹号和之前的问号则不同,这里表示我确定dataList一定是非nil的,所以直接拆包取出原始值进行处理。因此此处如果不小心让dataList为nil,程序就会crash掉。

回到上面声明时?和!区别的问题上去
声明变量时的?只是单纯的告诉Swift这是Optional的,如果没有初始化就默认为nil,而通过!声明,则之后对该变量操作的时候都会隐式的在操作前添加一个!。

总结

  1. 问号?
    a.声明时添加?,告诉编译器这个是Optional的,如果声明时没有手动初始化,就自动初始化为nil
    b.在对变量值操作前添加?,判断如果变量时nil,则不响应后面的方法。
  2. 叹号!
    a.声明时添加!,告诉编译器这个是Optional的,并且之后对该变量操作的时候,都隐式的在操作前添加!
    b.在对变量操作前添加!,表示默认为非nil,直接解包进行处理

 

来自:https://segmentfault.com/a/1190000000533936。

Swift闭包的用法,简介, IN用法

 swift  Swift闭包的用法,简介, IN用法已关闭评论
8月 072019
 

非常好的资料https://www.jianshu.com/p/7dcecea05dbb

 

本篇将详细总结介绍Swift闭包的用法;
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift中的闭包与C和 Objective-C中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。

主要内容:
1.闭包表达式
2.闭包的使用与优化
3.值捕获
4.逃逸闭包
5.自动闭包

一、闭包表达式

Swift闭包的三种存在形式:
1.全局函数是一个有名字但不会捕获任何值的闭包
2.嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
3.闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
闭包表达式的语法一般有如下的一般形式:

{ (parameters) -> returnType in
    statements
}

说明:
1.闭包的外层是一个大括号,先写的参数和返回值,然后操作部分之前使用in;
2.闭包就相当于OC中的block, 也可以看做是匿名函数;
3.闭包表达式参数可以是in-out参数,但不能设定默认值;
4.闭包的函数体部分由关键字in引入,该关键字表示闭包参数和返回值类型已经完成,闭包函数体开始;

二、闭包的使用与优化

下面,我们使用Swift标准库中的sorted(by:)方法来测试闭包的使用。sorted(by:)方法允许外部传入一个用于排序的闭包函数将已知类型数组中的值进行排序,完成排序之后,该方法会返回一个与原数组大小相同,包含同类型元素已正确排序的新数组:

//定义一个整型数组
var someInts: [Int] = [5,9,7,0,1,3]
//定义一个排序函数
func biggerNumFirst(num1:Int, num2:Int) -> Bool{
    return num1 > num2
}
//普通用法:将biggerNumFirst函数传入sorted函数,实现排序
var sortInts = someInts.sorted(by: biggerNumFirst)
print(sortInts)     //[9, 7, 5, 3, 1, 0]

//闭包用法:为sorted函数参数传入一个闭包,实现排序
sortInts = someInts.sorted(by:{ (a:Int, b:Int) -> Bool in
    return a > b
})
print(sortInts)     //[9, 7, 5, 3, 1, 0]

注意:因为闭包不会在其他地方调用,所以不使用外部参数名

闭包使用起来十分灵活,我们可以在某些特定情况下对齐进行优化,下面是对上述闭包的优化:

2.1.根据上下文推断类型,省略参数类型与括号

由于排序闭包函数是作为sorted(by:)方法的参数传入的,Swift可以推断其类型和返回值类型。所以sorted(by:)方法被一个Int类型的数组调用,其参数必定是(Int,Int)->Bool类型的函数。最后,根据上下文推断类型,我们可以省略参数类型和参数周围的括号。

sortInts = someInts.sorted(by: {a,b in
    return a > b
})
print(sortInts)

2.2.对于不会发生歧义的闭包,可将其写成一行

sortInts = someInts.sorted(by:{a,b in return a > b})
print(sortInts)

2.3.单行闭包表达式,省略return关键字

省略return关键字的条件:
sorted(by:)方法的参数类型明确了闭包必须返回一个Bool类型值
单行闭包表达式中,其返回值类型没有歧义

sortInts = someInts.sorted(by: {a,b in a > b})
print(sortInts)

2.4.使用参数名缩写(不推荐使用)

Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过$0,$1,$2 来顺序调用闭包的参数,以此类推。
如果我们在闭包表达式中使用参数名称缩写, 我们就可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

sortInts = someInts.sorted(by: {$0>$1})
print(sortInts)

2.5.使用运算符简化闭包(不推荐使用)

Swift的Int类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个Int类型的参数并返回Bool类型的值。而这正好与sorted(by:)方法的参数需要的函数类型相符合。可以使用大于号来代替闭包

sortInts = someInts.sorted(by: >)
print(sortInts)

2.6.尾随闭包,解决长闭包的书写问题

如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包的写法:将闭包书写在函数括号之后,函数会支持将其作为最后一个参数调用,使用尾随闭包,不需要写出它的参数标签。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    //函数体部分
    closure(); //调用闭包
}

//不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    //闭包主体部分
})

//使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    //闭包主体部分
}

//注意:如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉:
someFunctionThatTakesAClosure {
    print("Hello World!")    //打印:Hello World!
}

总结Swift闭包主要的四种优化方法:
1.利用上下文推断参数和返回值类型,省略参数类型与括号
2.隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
3.参数名称缩写
4.尾随闭包语法

三、值捕获

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。Swift会为你管理在捕获过程中涉及到的所有内存操作。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

代码分析:
1.makeIncrementer函数以amount为参数,以()->Int作为返回值类型,其函数体中还嵌套了另一个函数incrementer。
2.如果我们把incrementer单独拿出来,会发现其中runingTotal和amount变量都无法使用,因为这两个变量的引用是incrementer从外部捕获的。
3.Swift会负责被捕获变量的所有内存管理工作,包括对捕获的一份值拷贝,也包括释放不再需要的变量。

现在再来测试makeIncrementer函数的使用:

let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen();    //10
incrementByTen();    //20

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()   //7
incrementBySeven();  //14

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() //30

代码分析:
1.incrementByTen与incrementBySeven,是通过makeIncrementer函数传入不同的增量参数amount而创建的;
2.两个函数都有属于各自的引用,其中的runningTotal变量都是从makeIncrementer中捕获的,但是已经各自没有关系;
3.函数和闭包都是引用类型,将其赋值给变量或者常量,都只是操作的它们的引用,而不会改变闭包或者函数本身;

四、逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。
逃逸闭包:在定义接受闭包作为参数的函数时,我们需要在参数名之前标注@escaping,以此表明这个闭包是允许”逃逸”出这个函数的。

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    //代码1:执行闭包,不需要添加@escaping
    //completionHandler();
    //代码2:函数外部对闭包进行了操作
    completionHandlers.append(completionHandler)
 }

代码分析:
someFunctionWithEscapingClosure(_:) 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果不将这个参数标记为@escaping,就会得到一个编译错误。

4.1.逃逸闭包的使用

逃逸闭包和非逃逸闭包在使用上有所不同。将一个闭包标记为@escaping(即逃逸闭包)后,在调用这个闭包时就必须在闭包中显式地引用 self。一个示例如下:

//定义一个带有非逃逸闭包参数的函数
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

//定义一个可以使用闭包的类
class SomeClass {
    var x = 10
    func doSomething() {
        //调用逃逸闭包:必须在闭包中显式引用self
        someFunctionWithEscapingClosure { self.x = 100 }
        //调用非逃逸闭包:可以隐式引用self
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)         //打印出 "200”

五、自动闭包

自动闭包:一种自动创建的闭包,用与包装传递给函数作为参数的表达式;自动闭包的特点:
1.自动闭包不接受任何参数;
2.自动闭包被调用的时候,会返回被包装在其中的表达式的值;
3.自动闭包是用一个普通的表达式来代替显式的闭包,能够省略闭包的花括号;

其实,我们经常调用采用自动闭包的函数,但是却少去实现这样的函数,assert函数就是其中之一:

 assert(condition:, message:)

assert函数中的condition参数可以接受自动闭包作为值,condition参数仅会在debug模式下被求值,在condidtion被调用返回值为false时,message参数将被使用。

5.1.自动闭包的基本使用

自动闭包能够实现延迟求值,直到调用这个闭包时,代码才会被执行。这对于有副作用或者高计算成本的代码来说是有益处的;下面的代码展示了自动闭包实现延时求值的具体做法:

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)    //打印出 “5"

//自动闭包不接受参数,只是一个表达式
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)    //打印出 “5"

//调用自动闭包
print("Now serving \(customerProvider())!")   // Prints "Now serving Chris!"
print(customersInLine.count)               //打印出 "4”

代码分析:闭包实现了移除第一元素的功能,但是在闭包被调用之前,这个元素是不会被移除的。这就实现了延迟的作用

5.2.自动闭包在函数中的使用

现在将闭包作为参数传递给一个函数,同样可以实现延时求值行为。下面的serve函数接受了一个闭包参数(具有删除第一个元素且返回这个元素的功能)。

//customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

//以闭包的形式传入参数
serve(customer: { customersInLine.remove(at: 0) } )  //打印出"Now serving Alex!”

现在使用自动闭包来实现上述函数功能,使用@autoclosure关键字,标明参数使用的是自动闭包,具体示例如下:

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
//由于标明了自动闭包,这里直接省略了闭包的花括号
serve(customer: customersInLine.remove(at: 0))  //打印出"Now serving Ewa!\n"

注意:
过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。

5.3.可”逃逸”的自动闭包

一个自动闭包可以“逃逸”,这时候应该同时使用 @autoclosure 和 @escaping 属性,下面举例说明:

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
//调用collectCustomerProviders,向数组中追加闭包
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")   //打印 "Collected 2 closures."
//循环数组中闭包,并且执行
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// 打印 "Now serving Barry!"
// 打印 "Now serving Daniella!”

代码分析:
作为逃逸闭包:
collectCustomerProviders函数中,闭包customerProvider被追加到customerProviders中,而这个数据是定义在函数作用域范围之外的,这意味数组内的闭包能够在函数返回之后被调用,所以customerProvider必须允许
“逃逸”出函数作用域。

作为自动闭包:
调用collectCustomerProviders函数时,传入的闭包是表达式的形式,自动闭包省略了闭包花括号

 

YAML 语言简介

 yaml  YAML 语言简介已关闭评论
7月 312019
 

YAML 语言教程

编程免不了要写配置文件,怎么写配置也是一门学问。

YAML 是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。

 

YAML文件编写时一定要注意空格:每个散列项冒号和值之间至少有一个空格!

 

一、简介

YAML 语言(发音 /ˈjæməl/ )的设计目标,就是方便人类读写。它实质上是一种通用的数据串行化格式。

它的基本语法规则如下。

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时不允许使用Tab键,只允许使用空格。
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

# 表示注释,从这个字符一直到行尾,都会被解析器忽略。

YAML 支持的数据结构有三种。

  • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
  • 纯量(scalars):单个的、不可再分的值

以下分别介绍这三种数据结构。

二、对象

对象的一组键值对,使用冒号结构表示。


animal: pets

转为 JavaScript 如下。

{ animal: 'pets' }

Yaml 也允许另一种写法,将所有键值对写成一个行内对象。


hash: { name: Steve, foo: bar }

转为 JavaScript 如下。

{ hash: { name: 'Steve', foo: 'bar' } }

三、数组

一组连词线开头的行,构成一个数组。

- Cat
- Dog
- Goldfish

转为 JavaScript 如下。

[ 'Cat', 'Dog', 'Goldfish' ]

数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。

-
 - Cat
 - Dog
 - Goldfish

转为 JavaScript 如下。

[ [ 'Cat', 'Dog', 'Goldfish' ] ]

数组也可以采用行内表示法。


animal: [Cat, Dog]

转为 JavaScript 如下。

{ animal: [ 'Cat', 'Dog' ] }

四、复合结构

对象和数组可以结合使用,形成复合结构。


languages:
 - Ruby
 - Perl
 - Python 
websites:
 YAML: yaml.org 
 Ruby: ruby-lang.org 
 Python: python.org 
 Perl: use.perl.org 

转为 JavaScript 如下。

{ languages: [ 'Ruby', 'Perl', 'Python' ],
  websites: 
   { YAML: 'yaml.org',
     Ruby: 'ruby-lang.org',
     Python: 'python.org',
     Perl: 'use.perl.org' } }

五、纯量

纯量是最基本的、不可再分的值。以下数据类型都属于 JavaScript 的纯量。

  • 字符串
  • 布尔值
  • 整数
  • 浮点数
  • Null
  • 时间
  • 日期

数值直接以字面量的形式表示。


number: 12.30

转为 JavaScript 如下。

{ number: 12.30 }

布尔值用truefalse表示。


isSet: true

转为 JavaScript 如下。

{ isSet: true }

null~表示。


parent: ~

转为 JavaScript 如下。

{ parent: null }

时间采用 ISO8601 格式。


iso8601: 2001-12-14t21:59:43.10-05:00

转为 JavaScript 如下。

{ iso8601: new Date('2001-12-14t21:59:43.10-05:00') }

日期采用复合 iso8601 格式的年、月、日表示。


date: 1976-07-31

转为 JavaScript 如下。

{ date: new Date('1976-07-31') }

YAML 允许使用两个感叹号,强制转换数据类型。


e: !!str 123
f: !!str true

转为 JavaScript 如下。

{ e: '123', f: 'true' }

六、字符串

字符串是最常见,也是最复杂的一种数据类型。

字符串默认不使用引号表示。


str: 这是一行字符串

转为 JavaScript 如下。

{ str: '这是一行字符串' }

如果字符串之中包含空格或特殊字符,需要放在引号之中。


str: '内容: 字符串'

转为 JavaScript 如下。

{ str: '内容: 字符串' }

单引号和双引号都可以使用,双引号不会对特殊字符转义。


s1: '内容\n字符串'
s2: "内容\n字符串"

转为 JavaScript 如下。

{ s1: '内容\\n字符串', s2: '内容\n字符串' }

单引号之中如果还有单引号,必须连续使用两个单引号转义。


str: 'labor''s day'

转为 JavaScript 如下。

{ str: 'labor\'s day' }

字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格。


str: 这是一段
  多行
  字符串

转为 JavaScript 如下。

{ str: '这是一段 多行 字符串' }

多行字符串可以使用|保留换行符,也可以使用>折叠换行。


this: |
  Foo
  Bar
that: >
  Foo
  Bar

转为 JavaScript 代码如下。

{ this: 'Foo\nBar\n', that: 'Foo Bar\n' }

+表示保留文字块末尾的换行,-表示删除字符串末尾的换行。


s1: |
  Foo

s2: |+
  Foo


s3: |-
  Foo

转为 JavaScript 代码如下。

{ s1: 'Foo\n', s2: 'Foo\n\n\n', s3: 'Foo' }

字符串之中可以插入 HTML 标记。


message: |

  <p style="color: red">
    段落
  </p>

转为 JavaScript 如下。

{ message: '\n<p style="color: red">\n  段落\n</p>\n' }

七、引用

锚点&和别名*,可以用来引用。


defaults: &defaults
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  <<: *defaults

test:
  database: myapp_test
  <<: *defaults

等同于下面的代码。


defaults:
  adapter:  postgres
  host:     localhost

development:
  database: myapp_development
  adapter:  postgres
  host:     localhost

test:
  database: myapp_test
  adapter:  postgres
  host:     localhost

&用来建立锚点(defaults),<<表示合并到当前数据,*用来引用锚点。

下面是另一个例子。

- &showell Steve 
- Clark 
- Brian 
- Oren 
- *showell 

转为 JavaScript 代码如下。

[ 'Steve', 'Clark', 'Brian', 'Oren', 'Steve' ]

八、函数和正则表达式的转换

这是 JS-YAML 库特有的功能,可以把函数和正则表达式转为字符串。


# example.yml
fn: function () { return 1 }
reg: /test/

解析上面的 yml 文件的代码如下。

var yaml = require('js-yaml');
var fs   = require('fs');

try {
  var doc = yaml.load(
    fs.readFileSync('./example.yml', 'utf8')
  );
  console.log(doc);
} catch (e) {
  console.log(e);
}

从 JavaScript 对象还原到 yaml 文件的代码如下。

var yaml = require('js-yaml');
var fs   = require('fs');

var obj = {
  fn: function () { return 1 },
  reg: /test/
};

try {
  fs.writeFileSync(
    './example.yml',
    yaml.dump(obj),
    'utf8'
  );
} catch (e) {
  console.log(e);
}

九、参考链接

转自:http://www.ruanyifeng.com/blog/2016/07/yaml.html

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] [email protected][
@Integer[5],
@Integer[47],
@Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] [email protected][
@Integer[2],
@Integer[5],
@Integer[317],
@Integer[503],
@Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] [email protected][
@Integer[2],
@Integer[2],
@Integer[3],
@Integer[3],
@Integer[31],
@Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] [email protected][
@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] [email protected][
@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] [email protected][
@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] [email protected][
@Object[][
@Integer[1544665400],
],
@MathGame[
],
null,
]
ts=2018-12-03 19:29:54; [cost=4.277392ms] [email protected][
@Object[][
@Integer[1544665400],
],
@MathGame[
],
@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] [email protected][
@Object[][
@Integer[47816758],
],
@MathGame[
[email protected][3905348978240129619],
[email protected][3133719055989],
[email protected][25214903917],
[email protected][281474976710655],
[email protected][1.1102230246251565E-16],
[email protected][bound must be positive],
Ba[email protected][bound must be greater than origin],
[email protected][size must be non-negative],
[email protected][-3282039941672302964],
[email protected][][isEmpty=false;size=3],
],
],
]

  • -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] [email protected][
@Integer[-18178089],
@MathGame[[email protected]],
]

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

观察异常信息的例子

								
$ 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] [email protected][
@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] [email protected][
@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] [email protected][
]

然后使用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] [email protected][8]
ts=2018-12-03 20:04:35; [cost=0.961441ms] [email protected][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                          [email protected]  #联系人的邮件地址  
  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 [email protected]
 } 
定义联系人组
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就太完美了。轻巧,敏捷,高效,简便,通用。这才是理想中的数据模型。当然就现在而言,这还只是个理想。