《MongoDB实战》笔记

《MongoDB实战》的学习笔记。

第一章 为现代Web而生的数据库

特性
mongodb适合做水平扩展的数据库。
mongodb把文档组织成集合,无schema。

索引
mongodb的二级索引是B树实现。
每个集合最多可以创建64个索引,

副本集
mongodb通过副本集(replication set)的结构提供了复制功能。
副本集有一个主节点(primary node)和一个或多个从节点(secondary node)构成。主节点支持读写,从节点只读。而且副本集支持自动故障转移:如果主节点出了问题,集群会选一个从节点自动将它提升为主节点,在先前的主节点恢复之后,它就变成一个从节点。

journaling日志
mongodb中,用户可以选择写入语义,决定是否开启Journaling日志记录,控制写入速度与持久性的之间的平衡,Journaling日志是默认开启的,所有写操作都会被提交到一个只能追加的日志里。

fire-and-forget
mongodb默认是fire-and-forget,即写操作通过TCP套接字发送,不要求数据库应答。如果需要应该,需要开启特殊的安全模式。安全模式可配置,还可以用于阻塞操作,知道写操作被复制到特定数量的服务器。

自动分片
mongodb是基于范围的分片方式,自动分片(auto-sharding)。单个分片由一个副本集组成,每个副本集至少三个节点,两个携带数据的副本,就能保证自动恢复,没有单点失败。

副本集
通常副本集由两个副本组成,再加上一个部署在第三台服务器上的仲裁进程(arbiter process)。对于mongodb的自动分片架构而言,其组建包含配置为预先分片的副本集的mongod进程,以及特殊的元数据服务器,称为配置服务器(config server),另外还有单独名为mongos的路由服务器向适当的分片发送请求。

_id
所有文档都要有一个主键,存储在_id字段里,只要保证唯一性,也可以输入自定义_id.如果省略了_id,会自动插入一个mongo对象ID。

第二章 MongoDB Javascript Shell

针对性更新(targeted modification)

1
db.users.update({“favorites.movies”:”sxxx”},{$addToSet:{“favorites.movies”:”ssff”},false,true)

第四个参数为多项更新,如果为false,则更新(默认)只应用于查询选择器匹配的第一个文档。

创建索引
ensureIndex({num:1}),其中1表示升序,getIndexes()验证。
在查询语句后面紧跟.explain()可以看到查询计划。

db.stats
获取数据库与集合更底层的信息,db.stats()。
stats只是辅助方法,他封装了shell的命令调用方法,等同于
db.runCommand({dbstats:1}),往runCommand传递文档定义。
看下文档定义,执行去掉无括号的版本,db.runCommand。

第三章 使用MongoDB编程程序

mongo数据驱动
mongodb数据驱动三个功能:

  • 生成mongodb对象ID,即所有文档_id字段的默认值。
  • 将所有语言特定的文档表述和BSON(mongodb的二级制数据格式)互相转化。
  • 使用mongodb的网络协议通过TCP套接字与数据库通信。

对象ID
mongodb对象ID是全局唯一的标识符,不会重复。
它由12个字节构成,
4字节时间戳,3字节机器ID,2字节进程ID,3字节计数器
如:4c291856 238d3b 19b2 000001
可以看到对象ID包含了时间戳,从而提供对象创建时间(秒)

BSON数据类型
BSON规范包含了19种数据类型,如UTF-8字符串,32位和64位整数,双精度浮点数,布尔值,时间戳,UTC时间(datetime),针对模糊对象的大数据(opaque blob),部分语言支持的符号类型(symbol type)

BSON格式
文档转化为BSON:头部4字节表明文档的大小,接下来N个键值对,
每对都由一个表示其类型的字节开头,随后由null结尾的字符串表示键名,然后是被存储的值,最后是一个null字节表示文档结束。如图:
BSON格式

对象ID请使用BSON对象
如果要存储mongodb对象id,应该使用mongodb对象ID,
而不是字符串,除了遵循对象ID的存储惯例,BSON对象还能比字符串省一半以上的空间。

安全模式
mongodb安全模式写入时,驱动会在插入消息后追加一条getlaterror命令。它做了两件事,getlasterror命令需要与服务器做一次通信,它确保写操作已经送达服务器。第二,验证服务器在当前链接中没有抛出任何错误。

第四章 面向文档的数据

事务与原子性
mongodb不支持事务,但它支持多种原子更新操作,用于复杂文档。

数据库文件
mongodb创建数据库时候,会在磁盘上分配一组数据文件,所有集合,索引和数据库其他元数据都保存在这个文件里。数据文件都被放在启动mongod时指定的dbpath里,如未指定,则保存在/data/db里。
如图:
数据库文件
各文件说明:
mongod.lock文件,其中存储了服务器进程ID(不要删除,会影响异常恢复)。
数据库文件本身依赖所属的数据库命令,garden.ns是第一个生成的文件,ns后缀表示namespaces,数据库每个集合和索引都有自己的namespace,namespace的元数据都放在这个文件里。

默认情况下.ns文件固定在16MB,大约可以存24000个命令空间,也就是数据库索引和集合总数不能超过24000,通过—nssize配置。
看文件大小garden.0 64mb,garden.1 128mb,这些是mongoldb预先分配的数据文件,新数据文件大小是前一个的两倍,直达上限2G,通过—noprealloc和—small files配置。

空间使用
stats命令检查已使用空间和已分配空间。
如图:
空间使用
其中fileSize字段表示数据分配文件空间的总和。也就是前面garden数据库两个文件之和。
dataSize是数据库BSON对象的实际大小,storageSize是dataSize加上集合增长预留的额外空间和未分配的已删除空间。
indexSize是索引大小的总和。
另:集合的每个数据文件里按照块分配文件,这些块称为区段(extent)。storageSize就是集合区段所分配空间的总和。

固定集合capped collection
原本只针对高性能日志场景设计的。大小固定,满了以后,后续插入会覆盖集合最先插入的文档。
与标准集合的区别:

  • 固定集合默认不为_id创建索引,插入更快,也可以自己构建。(不定义索引情况下,最好把固定集合用于顺序处理的数据结构,而非用于随机查询,为此,mongodb提供了一个特殊的排序操作符$natural,按照文档的插入顺序返回文档。
1
db.user.actions.find().sort({“$natural”:-1}))
  • 固定集合不能删除文档,不能执行任何增加文档大小的更新操作。

mongodb使用固定集合来完成复制,每个副本集的成员都会把所有的写操作记录到一个特殊的oplog.rs固定集合里。从节点顺序读取这个集合内容,然后应用到自己数据库内。

BSON数字类型
BSON只定义了三种数字类型,double,int,long。而javascript只支持一种数据类型Number,等价于IEEE的双精度浮点数。如果要存储为整数,需要NumberLong()或者NumberInt

1
db.numbers.save({n:NumberLong(5)})

BSON type
每种BSON类型有一个整数标识,
db.numbers.find({n:{$type:18}});
{“_id”: ObjectId(“4c581c9bd5bbeb2365a838fa”)}

BSON缺少对小数的支持

BSON 日期与时间
javascript里月份是从零开始
new Date(2011,5,11)是2011年6月11号

文档大小限制
v2.0中BSON文档大小限制在16MB,不同版本输入db.isMaster查看maxBsonObjectSize字段。
大小限制的原因,一是防止设计深层嵌套,二是性能,在服务器端查询大文档,在讲结果发送给客户端前需要将文档复制到缓冲区里,同时驱动反序列化开销很大。

第五章 查询与聚合

find与findOne
find返回的是游标对象,findOne返回一个文档

分页
分页可以使用:skip,limit,sort,如:

1
products = db.products.find({‘category_id’:category[‘_id']}).skip((page_number-1)*12).limit(12).sort({helpful_votes:-1})

集合操作
$in,$all,$nin,接受数组参数

布尔操作符
$ne,$not,$or,$and,$exists
如:

1
db.products.find(‘details.manufacturer’:’ACME’,tags:{$ne:”gardening”})

or可以应用于条件选择器数组

1
db.products.find({$or:[{‘details.color’:’blue’},{‘details.manufacturer’:’ACME'}]})

数组下标表示
db.users.find({‘address.0.state’:’NY’})
其中0表示数组的第一个

$elemMatch
用于子文档的多个条件限制查询
db.users.find({address: {$elemMatch: {name: ‘home’,state: ’NY’}})

size
根据数组大小查询,$size,不走索引,只支持精确查找。推荐将数组大小作为一个字段放入集合中。

$where
可以手动编写javacript执行
db.reviews.find({$where: “function(){ return this.helpful_votes > 3;}”})
this指向当前文档,不走索引,性能低。
可以配合一般查询语句缩小遍历集合后使用,

正则表达式
使用了忽略大小写的选项则无法在查询中使用索引,就算在前缀匹配中也是如此。原生正则表达式例子:
db.reviews.find({user_id: ObjectId(“xxxx”), text: /best|worst/i})
不能用原生,则用$regex和$options

投影
返回结果中去掉不需要的字段,如
db.users.find({}, {address:0, payment_method:0})

$slince
返回头多少条或者尾多少条信息,也可以接受两参数的形式,一个是跳过多少,一个是返回元素个数限制。

$skip
类似于offset ,最好省略skip,添加一个范围条件,提高性能。

min与max
不提供min(),max(),自己实现,按照某字段降序排序,再limit。

distinct
默认覆盖整个集合,返回字段的不同值集合,也可传入查询器。

group
group最少需要三个参数,第一个参数定义对key怎么分组,第二个参数是聚合的js函数,叫reduce函数,第三个参数为reduce函数初始文档。

1
2
3
4
5
6
7
8
9
results = db.views.group({key:{user_id:true}, initial = {review:0, vote:0.0},
reduce = function(doc, aggregator){
aggregator.views += 1.0;
aggregator.votes += doc.votes;
},
finalize: function(doc){
doc.average_votes = doc.votes /doc.reviews;
}
});

distinct与group结果集合大小限制
distinct和group返回结果集合不能超过16MB,因为这两个命令是对特殊$cmd集合的查询。同时group不会处理多余10000个唯一键,不能满足的情况,使用map-reduce。

map-reduce
定义一个map函数,内部调用emit(),emit方法第一个参数是分组依据的key,第二个参数是包含执行reduce的文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
map = function(){
var shipping_moth = this.purchase_date.getMonth() + ‘-‘+this.purchase_data.getFullYear();
var items = 0;
this.line_items.forEach(function(item){
tmpItems += item.qunatity;
});
emit(shipping_month,{order_total:this.sub_total, item_total:0})
}
reduce = function(key,values){
var tmpTotal = 0;
var tmpItems = 0;
tmpTotal += doc.order_total;
tmpItems += doc.items_total;
return ({total: tmpTotal, items:tmpItems)});
}

第六章 更新、原子操作与删除

原子性
所有发往核心服务器的更新都是原子的,以文档为单位进行隔离。说更新操作符是原子性的是因为他们能在不先查询的前提下完成更新。

findAndModify

1
2
3
4
db.orders.findAndModify({
query:{}
update:{}
})

默认情况下findAndModify返回更新前的文档,要返回更新后的文档,要制定{new:true}

多文档更新
db.products.update({},{$addToSet:{tags:’cheap’’}}, false,true)

upsert
如果查询选择器匹配到文档则更新,无匹配则新增。一次只插入或更新一个文档。

$inc
递增,递减操作

$unset
能删除文档中特定的键,但作用在数组上,只是置为null,要删除删除,调用$pull和$pop.

$addToSet和$each
如果想在一个操作里向数组添加多个唯一的值,必须结合$each操作符来使用$addToSet

1
db.products.update({slug:’shovel’},{$addToSet: {’tags’: {$each: [’tools’,’dirts']}})

$pop
删除最后添加的内容

$pull
与$pop类似,但更高级,可以明确制定删除哪个数组元素,而不是位置。

基于位置的更新

1
2
3
4
query = {_id: ObjectId(“4c4d1476238d3b4dd5003981”),
‘line_items.sku’:”10027”}
update = {$set: {‘line_items.$.quantity’:5}}
db.orders.update(query,update)

在line_items.$quantity字符串中$表示位置操作符,如果查询选择器匹配到了这个文档,那么有10027这个sku的文档的下标就会替换位置操作符,从而正确更新文档。

mongodb锁策略
全局锁,已废弃,具体参考mongodb jira
写锁让步,可以使用$atomic参数控制隔离执行,不被暂停

更新性能
文档更新三种:

  • BSON大小不改变
  • BSON大小改变,头四个字节需要修改,或者增加字段
  • 重写文档,预分配磁盘放不下,需要移动,为降低这类开销,mongodb会根据每个集合的情况动态调整填充因子(padding factor),也就是如果一个集合会发生很多重新分配的情况,会自动增加填充因子。填充因子*插入文档的大小后,为要额外创建的空间。

第七章 索引与查询优化

$or与索引
$or查询里,每个$or查询子句,都能使用不同的索引,但每个子句本身只能使用一个索引。

tip
就算拥有正确索引,还是可能得不到快速的查询,索引和数据集无法完全加入内存,是要考虑的问题。

标准索引
索引是B树,集合存储为双向列表
mongodb的B树实现里,新节点会被分配8192字节,也就是说实际上每个节点能包含几百个键。请牢记,默认情况下,B节点内容有意维持在60%左右。

稀疏索引
当集合中大量文档都不包含被索引键。创建时指定{sparse:true}。在稀疏索引中,只会出现被索引键有值的情况。

声明索引要小心
构建会花很长时间,则无法中止。最明智的建议,将索引构建当成某类数据库迁移来看。

后台索引
不暂停数据库访问,在后台构建索引,对读操作主动让步。{background:true}

备份
如果想在备份中包含索引,需要直接备份mongodb的数据文件。

reIndex
reIndex,重建索引,占写锁,实例暂时无法使用。

慢查询
mongod服务器启动,—slowms 50会把筛选日志。

剖析器

1
2
use stocks
db.setProfilingLevel(2.50)

第一个参数,2表示读写,第二参数表示超过50ms都写日志。

查询优化器的三个规则

  • 避免scanAndOrder,使用索引排序
  • 通过有效索引约束来满足所有字段
  • 如果查询包含范围查询或者排序,对于选择的索引,其中最后用到的键需能满足该范围查询或者索引

查询计划
explain,传入true,输出查询计划

查询计划器的缓存
在发现了一个成功的计划之后,会记录下查询模式(query pattern)/nscanned的值以及索引说明,如
{pattern: {stock_symbol:’equality’, close: ‘bound’}, index:{stock_symbol:1}, scanned:894}
查询模式记录下每个键匹配的类型,你正请求对stock_symbol的精确匹配(相等),对close的范围匹配,就会使用这个索引。

查询计划期缓存过期

  • 对集合执行了100次写操作
  • 在集合上增加或删除了索引
  • 虽然使用了缓存的查询计划,但工作量大于预期,及nscanned超过缓存nsscanned的10倍

第八章 复制

两种复制风格
mongodb提供了两种复制风格:主从复制,副本集。都是主节点写,异步同步到从节点。
推荐使用副本集,因为支持自动故障转移,只有mongodb需要超过11个从节点,则需要主从复制。

虽然副本是冗余的,但副本不是备份的替代品。

副本集
副本集,至少三个节点,包括一个仲裁节点,选主。
启服务略
isMaster查看副本信息,rs.status()更详细信息,启动完成stateStr字段会从revocering到primary,secondary,arbiter。

测试选主
ctrl-c,kill -2或者连接上主节点,db.shutdownServer()

副本集的基础机制
副本集依赖于两个基础机制,oplog和心跳。
oplog是个固定集合,位于每个复制节点的local数据库里,记录所有对数据的变更。查看当前副本状态的基本信息,db.getReplicationInfo()

local库
replset.minvalid指定副本集成员的初始同步信息
system.replset保存了副本集配置文档
me和slaves实现写专注
system.indeses标准索引

副本同步
从节点从主节点赋值oplog,做三件事

  • 查看oplog最后一条的时间戳
  • 查询主节点oplog里所有大于此时间戳的记录
  • 将这些记录添加到自己库里

心跳检测
默认情况下,每个副本集成员每两秒ping一下其他所有成员。
如果没有多数节点,主节点会自动降为从节点。

回滚
当mongodb从节点升为主节点,会触发其他从节点回滚。略
在数据路径下rollback子目录保存了呗回滚的写操作,对每个回滚写操作集合,创建独立BSON文件,通过bsondump查询,mongorestore恢复。

重新配置副本集
无论何时,重新配置副本集导致重新选举新的主节点,那么所有客户端的链接都会被关闭,防止fire-and-forget风格的写操作。

配置文件

  • arbiterOnly:仲裁节点只存储配置数据。
  • priority,决定了选举的权重,设置为0,表示被动节点,永远不会被选为主节点,可以用于灾难恢复节点。
  • buildIndex:如果永远不会成为主节点,priority为0,可以设置。
  • slaveDelay:如果要设置大于0,务必保证priority为0.
    其他略

写关注
getlastError(w,timeout,j:true)
第一个参数为需要同步到服务器的数量,w可以等于”majority”。
第二参数为超时时间。
j表示强制同步到journaing日志。

读扩展
Mongo:: ReplSetConnection.new([‘arete’, 40000], [‘arete’,40001], :read => :secondary)
read的设置,读会从选择附近一个从节点读取。

副本扩展无法处理一致性读,需要将一致性读的部分抽取出来。

第九章 分片

分片集群
分片集群由分片,mongos路由器和配置服务器组成。
分片集群

mongos路由器
通常运行于应用服务器相同的机器上,提供所有读写请求的统一系统视图。mongos进程是轻量级非持久化的。

配置服务器
持久化分片集群的元数据,包括集群配置,每个数据库集合特定范围数据的位置,一份变更记录,保证数据在分片之间迁移的历史。

mongos对配置服务器写入时候,使用二阶段提交。配置服务器最好三个以上,同时存在于不同机器实现冗余。

分片与块
分片是基于范围的,分片建(shared key)。
块(chunk),位于分片种的一段连续的分片范围,如
块与分片

分片的重点是块的拆分和迁移,拆分是对元数据的逻辑操作,迁移是均衡器(balancer)处理的物理操作。

分片索引
每个分片都维护了自己的索引,分片集合上的索引声明,会对所有分片起效。
分片集合只允许在_id和分片键上添加唯一索引。

分片键无法修改。

拓扑结构
运行mongodb两分片集群,一共要启动九个进程,每个副本集三个mongod,外加三个配置服务器。其中副本集是资源密集型,需要暂用独立的机器,仲裁节点不需要,配置服务器间不共用机器。如图:
两分片集群

考虑到灾难恢复
两分片集群+容灾

监控
mongos上运行serverStatus和currentOp看到所有分片的聚合统计信息,或者查询config数据库,对于不均衡的块,进行split,movechunk。

增加分片
考虑向新分片移动的时间,预计每分钟100-200MB。在现有索引和工作集达到分片的90%就要开始计划。

移除分片
removeshard,会删除块,重新分配到其他分片上。

集合去分片
导出集合,再用不同名字将数据恢复到一个新的集合里。用mongodump连接mongos导出。

第十章 部署与管理

时钟
不同服务器见都是用NTP,网络时间,在linux上使用ntpd守护进程。

journaling日志

  • 会降低写操作性能
  • 不保证不丢失写操作,只保证恢复一致状态。每100ms将写缓冲同步到磁盘。

副本集验证
启动—keyfile指定密码文件,为至少6个base64字符集。

服务器监控

  • serverStatus
    输出页错误,B树访问率,打开连接数,总的插入,更新,查询和删除等。globalLock会显示所有锁的时间,currentQueue显示读写队列。mem部分是内存,理想状况下所有工作集都应该放到内存中。
  • top
    操作计数器,显示操作的平均耗时。
  • db.currentOp()
    返回正在运行的所有操作。要杀掉,db.killOp(opid)
  • mongostat
    显示实时活动视图,以固定时间查询服务器信息,显示统计数据的矩阵,从每秒插入数到常驻内存量,再到B树页丢失频率。
  • web控制台
    mongod的端口+1000为web控制台端口,一般为28017.

诊断工具
mongosniff,mongodump略

数据备份与恢复
两种方式

  • 一个是mongodump,mongorestore
  • 一个是基于原始数据的备份,大多数情况更快,但是要求锁定数据库,可以选择锁定从节点(可以保留全部索引)。

锁定数据库

1
2
use admin
db.rumCommand({fsync:1,lock:true})

此时数据库是写锁定的。
解锁:

1
db.$cmd.sys.unlock.findOne()

再查看db.currentOp是否解锁

压紧与修复
mongod —repair
或者单个数据库
use cloud-docs
db.runCommand({repairDatabase:1})

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)


参考:《MongoDB实战》:http://book.douban.com/subject/19977785/

《快学scala》习题解答-第一章-基础

《快学Scala》(英文版:《Scala for the Impatient》),代码已传github:

https://github.com/vernonzheng/scala-for-the-Impatient

书为第一版。scala为2.11.4,jdk1.7.45,操作系统Mac OS X Yosemite 10.10.1。

第一章 基础

1.1
在Scala REPL中键入3,然后按Tab键。有哪些方法可以被应用?

答:

1
2
3
% + > >>> isInstanceOf toDouble toLong unary_+ |
& - >= ^ toByte toFloat toShort unary_-
* / >> asInstanceOf toChar toInt toString unary_~

1.2
在Scala REPL中,计算3的平方根,然后再对该值求平方。现在,这个结果与3相差多少?(提示:res变量是你的朋友)

答:

1
2
3
4
5
6
7
8
scala> import scala.math._
import scala.math._
scala> sqrt(3)
res7: Double = 1.7320508075688772
scala> 3 - res7
res8: Double = 1.2679491924311228

1.3
res变量是val还是var?

答:
res变量是val

1.4
Scala允许你用数字去乘字符串—去REPL中试一下”crazy”*3。这个操作做什么?在Scaladoc中如何找到这个操作?

答:

1
2
scala> "crazy"*3
res9: String = crazycrazycrazy

scala docs 去查StringOps:http://www.scala-lang.org/api/current/#scala.collection.immutable.StringOps

1.5
10 max 2的含义是什么?max方法定义在哪个类中?

答:

1
2
scala> 10 max 2
res10: Int = 10

返回两者中比较大的一个
max方法定义在RichInt方法中。http://www.scala-lang.org/api/current/#scala.runtime.RichInt

1.6
用BigInt计算2的1024次方

答:

1
2
scala> BigInt(2).pow(1024)
res13: scala.math.BigInt = 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216

1.7
为了在使用probablePrime(100,Random)获取随机素数时不在probablePrime和Radom之前使用任何限定符,你需要引入什么?

答:
import需要的包。Random在scala.util中,而probablePrime是BigInt中的方法。

1
2
3
4
5
import scala.math.BigInt._
import scala.util.Random
probablePrime(100,Random)
res15: scala.math.BigInt = 1137139793510393954801305013479

1.8
创建随机文件的方式之一是生成一个随机的BigInt,然后将它转换成三十六进制,输出类似”qsnvbevtomcj38o06kul”这样的字符串。查阅Scaladoc,找到在Scala中实现该逻辑的办法。

答:

1
2
scala> scala.math.BigInt(scala.util.Random.nextInt).toString(36)
res19: String = -wda3r0

1.9
在Scala中如何获取字符串的首字符和尾字符?

答:

1
2
3
4
5
6
7
8
9
10
11
scala> "Hello"(0)
res20: Char = H
scala> "Hello".take(1)
res21: String = H
scala> "Hello".reverse(0)
res22: Char = o
scala> "Hello".takeRight(1)
res23: String = o

1.10
take,drop,takeRight和dropRight这些字符串函数是做什么用的?和substring相比,他们的优点和缺点都是哪些?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scala> "Hello world".take(3)
res26: String = Hel
scala> "Hello world".takeRight(3)
res27: String = rld
scala> "Hello world".drop(3)
res28: String = lo world
scala> "Hello world".dropRight(3)
res29: String = Hello wo
scala> "Hello world".take(3).drop(1)
res32: String = el

take,drop,takeRight,dropRight适合从两边处理字符串,很方便可以配合使用,substring适合处理中间的字符串。


参考:
《快学Scala》:http://book.douban.com/subject/19971952/

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)

2014年终小结与书单

年初觉得可能发生的事情,大多数都发生了。

一、2014小结

去了青岛,舟山,上海,路过香港,台湾12天环岛。(台湾之行,坚定了走遍世界的dream)

11月份,根据地从杭州搬到了北京。(搬家是件痛苦的事,书都得处理掉,也是件开心的事,丢完东西一身轻)

工作换了两家,经历了一些波折,11月连续两周的面试,第一次体会了面霸的感觉,这也是大北京的优势,公司多。

离年初和同学戏称的30岁salary目标,意外的跨了一大步,同时又觉得路程还很远,养成习惯和身体健康的重要性。

学习方面,英语没有任何质变。。唯一欣慰的是今年读了39本书。

附:
我的足迹:
2014_foot_print

二、2015目标

  • 英语:90min/d
  • 刷书:技术类30本,其他类20本,英语原版书3本以上。
  • 坚持锻炼:养成每天早上起床锻炼的习惯,《囚徒健身》。
  • 旅行:出国游一次。周边省市游一次,N个北京景点。
  • 职业:高级应用开发->高级类库框架开发

三、2014书单

我的2014书单,书封面就不贴了,可以参考我的豆瓣读书:http://book.douban.com/people/vernonzheng/

3.1 IT技术类

  • 《深入理解Java虚拟机:JVM高级特性与最佳实践》
    周志明
    深入理解Java虚拟机
    5星,非常推荐。

  • 《Java并发编程实践》
    Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowbeer、David Holmes、Doug Lea / 童云兰
    Java并发编程实践
    5星,非常推荐,有点理论程度的。

  • 《高性能MySQL》
    Baron Schwartz、Peter Zaitsev、Vadim Tkachenko、Jeremy D.Zawodny、Arjen Lent、Derek J.Ballin / 王小东、李军、康建勋
    高性能MySQL
    5星,非常推荐,特别是4,5,6章的索引,查询优化等等讲的很透彻。

  • 《领域特定语言》
    Martin Fowler / ThoughtWorks中国
    领域特定语言
    5星,Martin难得出那么厚的书,写的很好,只看了前几章理论部分,后面的实用性挺高。

  • 《设计模式:可复用面向对象软件的基础》
    GOF
    设计模式
    5星,不是很推荐,惜字如金,有点难度,以后还得翻。

  • 《点石成金:访客至上的网页设计秘笈》
    [美] 史蒂夫·克鲁克 / De Dream’
    点石成金
    5星,推荐,英文名是Don’t make me think,特点是很薄,147页。

  • 《大型网站系统与Java中间件开发实践》
    曾宪杰
    大型网站系统与Java中间件开发实践
    5星,没有特别高深的概念,能学会解决这类问题的思路,结合非常实际的案例在讲。

  • 《人月神话》
    弗雷德里克.布鲁克斯 / UMLChina翻译组、汪颖
    人月神话
    4星,多少年前的书,却相当经典。

  • 《MySQL技术内幕:SQL编程》
    姜承尧
    MySQL技术内幕:SQL编程
    4星,对于有一些数据库设计开发经验的人,有许多技巧性和细节性的建议和提高。

  • 《测试驱动开发:实战与模式解析》
    Kent Beck / 白云鹏
    测试驱动开发
    4星,翻译不太好,kent beck的书实在少,只能看个经典旧书了。

  • 《The Little Book on CoffeeScript》
    Alex MacCaw
    The Little Book on CoffeeScript
    4星,实用,简短,快速上手。

  • 《Node.js实战》
    [美] Mike Cantelon、[美] TJ Holowaychuk、[美] Nathan Rajlich
    Node.js实战
    4星,有些翻译问题,适合做nodejs的前两本入门书。

  • 《Spring实战》
    沃尔斯 (Craig Walls) / 耿渊、张卫滨
    Spring实战
    4星,过一下基础。

  • 《Maven实战》
    许晓斌
    Maven实战
    4星,内容比较全,稍微有点拖沓。

  • 《Robust Java中文版:Java异常处理、测试与调试》
    史德汀
    Robust Java中文版
    3星,介绍了java异常的体系结构,设计,开发,测试,架构,比较基础和全面。

  • 《Java 7 Concurrency Cookbook》
    Fernandez Javier / Packt Publishing
    Java 7 Concurrency Cookbook
    3星,就当查漏补缺了。

  • 《Netty权威指南》
    李林峰
    Netty权威指南
    3星,作者没有用心写感觉。。

3.2 IT非技术类

  • 《重来》
    [美] 贾森·弗里德、[丹] 戴维·海涅迈尔·汉森 / 李瑜偲
    重来
    5星,推荐,设计商业,创业,软件设计,需求,技术等,触发思考的内容远超一个创业书的范围。

  • 《黑客与画家》
    [美] Paul Graham / 阮一峰
    黑客与画家
    5星,硅谷创业之父Paul Graham文集,黑客精神,创业,lisp等。

  • 《暗时间》
    刘未鹏
    暗时间
    5星,知识密度相当大

  • 《MacTalk 人生元编程》
    池建强
    MacTalk 人生元编程
    3星,如果你喜欢编程的乐趣和mac相关内容,可以翻一翻。

  • 《软件架构师的12项修炼》
    Dave Hendricksen / 张菲
    软件架构师的12项修炼
    2星,讲的太泛了,没啥实用性

3.3 其他杂类

  • 《人间词话》
    王国维
    人间词话
    5星,有一半是对古人词的赏析,之外对于境界的理解部分收获很多。

  • 《正见:佛陀的证悟》
    正见
    宗萨蒋扬钦哲仁波切 / 姚仁喜
    5星,有兴趣的人可以看看,讲佛家四见地的。因为书中“佛陀看一个人,是看到他的圆熟和衰朽同时在发生。”这句话,去看了这本书。

  • 《囚徒健身》
    保罗·威德
    囚徒健身
    5星,简单,实用。

  • 《少有人走过的路:心智成熟的旅程》
    [美] M·斯科特·派克 / 于海生
    少有人走过的路:心智成熟的旅程
    5星,推荐前半部分

  • 《撒哈拉的故事》
    三毛
    撒哈拉的故事
    5星,echo作品里最喜欢的一本。她的文字和独特壮阔的生活方式,有一种特别的魅力,无论是孤独还是爱的追寻,真实&真挚的让人感动。

  • 《穷游锦囊:骑行台湾》
    穷游网
    穷游锦囊
    5星,穷游系列的书都很不错。台湾环岛攻略就靠这个系列。

  • 《如何阅读一本书》
    [美] 莫提默·J. 艾德勒、查尔斯·范多伦 / 郝明义、朱衣
    如何阅读一本书
    4星,推荐,多读书,多总结适合自己的方式。

  • 《女人的性爱动机》
    【美】辛迪•梅斯顿、【美】戴维•巴斯 / 海兰、插画师:顾爷
    女人的性爱动机
    4星,生理和心理角度看待问题,前几章不错。

  • 《我所理解的生活》
    韩寒
    我所理解的生活
    4星,多年没读韩寒,变化挺大。

  • 《天才在左,疯子在右》
    高铭
    天才在左,疯子在右
    4星,国内第一本精神病人访谈手记,你可以把它当做故事书,很有趣。

  • 《慢慢来,一切都来得及》
    meiya
    慢慢来,一切都来得及
    4星,被打动的一本书,讲述奋斗路上作者的生命体验。

  • 《魔鬼约会学》
    魔鬼咨询师
    魔鬼约会学
    4星,男女思考行为的差距,还有一些活泼的例子。

  • 《5分钟和陌生人成为朋友-101个瞬间化解尴尬的沟通技巧-II : 101个瞬间化解尴尬的沟通技巧》
    唐·加博尔 / 何云
    5分钟和陌生人成为朋友
    3星,例子比较实用,先有心,后有术。

  • 《二十五岁的世界:我在25座城市,遇见的25个人》
    (西)马克•塞雷纳 / 吴娴敏
    二十五岁的世界
    3星,印度,菲利宾,津巴布韦到日本寺庙。。有许多细节被震撼到。

  • 《我是个算命先生》
    易之
    我是个算命先生
    3星,amazon上卖的很火,但是和盗墓笔记比差太多,是纯小说。

  • 《潜伏职场心理学》
    张超
    潜伏职场心理学
    3星,茶语饭后翻一番,还是有收获的。

  • 《软件架构师的12项修炼》
    Dave Hendricksen / 张菲
    软件架构师的12项修炼
    2星,讲的太泛了,没啥实用性

完。

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)

NodeJS实战经验总结

看完《Node.js 实战》,整理总结了觉得比较有价值的内容。

1、require

require是少有的同步i/o操作,请只在模块初始化时候使用require。

2、exports与module.exports的区别

最终程序里导出的都是module.exports。
而exports只是对module.exports的一个全局引用,如exports.myFunc为module.exports.myFunc的简写。
为了不破坏exports对module.exports的引用,不能设置exports。
如果破坏了,修复方式:
module.exports = exports = Currency;

3、模块缓存与猴子补丁:

Node 能把模块作为对象缓存起来。
如果程序中的两个文件引入了相同的模块,第一个文件会把模块返回的数据存到程序的内存中,这样第二个文件就不用再去访问和计算模块的源文件了。
实际上在第二个引入是有机会修改缓存数据的。这种方式称为“猴子补丁”(monkey patching ):让一个模块可以改变另一个模块的行为,开发人员可以不用创建它的新版本。

4、Node两种常用的响应逻辑组织方式

一次性为回调函数,绑定的为事件(继承event emitter事件发射器,emit发射消息)

这里给个event emitter的例子:

扩展文件监视器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**Watcher构造器**/
function Watch(watchDir, processDir){
this.watchDir = watchDir;
this.processedDir = processDir;
}
/**继承eventEmitter行为**/
var events = require(‘events’);
util = reuqire(‘util’);
util = util.inherits(Watcher, event.EventEmitter);
/**相当于Watcher.prototype = new events.EventEmitter();**/
/**再增加两个功能**/
var fs = require(‘fs’)
, watchDir = ‘./watch’
, processedDir = ‘./done’
Watcher.prototype.watch = function(){
var watcher = this;
fs.readdir(this.watchDir, function(err, files){
if(err) throw err;
for( var index in files){
watcher.emit(‘process’, files[index]);
}
}
}
Watcher.prototype.start = function(){
var watcher = this;
fs.watchFile(watchDir, function(){
watcher.watch();
});
}

5、减少if/else引起的回调嵌套

有两种方式,可以结合到一起用:

  • (1)嵌套间引入中间函数,通过函数调用拆分嵌套
  • (2)尽早从函数中返回

6、Node的异步回调惯例

Node大多数内置模块使用回调会带两个参数,一个是err或者er,一个是存放结果。

7、进程退出会等待事件异步完成

Node的事件轮询会跟踪还没有完成的异步逻辑,只要有未完成的异步逻辑,Node进程就不会退出。事件轮询会跟踪所有数据库连接,知道它们关闭,以防止Node退出。

8、在Node中使用闭包保留全局变量示例

示例,用闭包私有化color值:
这里有一个asyncFunction函数,对它的调用被封装到一个匿名函数里,参数为color。
这样你就可以马上执行这个匿名函数,把当前的 color 的值传给它。而color 变成了匿名函数内部的本地变量,当 匿名函数外面的color 值发生变化时,本地版的color 不会受影响。

1
2
3
4
5
6
7
8
9
function asyncFunction(callback){
setTimeout(callback, 200);
}
var color = ‘blue’;
(function(color){
asyncFunction(function(){
console.log(’The color is ‘ + color);
})(color);
color = ‘green’;

9、Node的content-length与chunk

Node默认是chunk方式传输(块编码)。当设置content-length时,会隐含禁用Node的块编码。设置content-length传输,数据更少,提升性能。

注意
content-length是字节长度,不是字符长度,一般用Buffer.byteLength(body)

10、__dirname

__dirname表示文件所在目录的路径,在开发时,这个目录和当前工作目录(CWD)是同个目录,但是生产环境可能是从另外一个目录运行。

11、中间件设计惯例

中间件一般有三个参数:请求,响应,回调函数next
为了提供可配置能力,中间件遵循一个惯例:用函数返回另一个函数(闭包)

可配置中间件的基本结构

1
2
3
4
5
function setup(options){
return function(req,res,next){
//中间件逻辑
}
}

使用:

1
app.use(setup({some:’options’}))

12、错误处理中间件

错误处理中间件与普通中间三个参数不同,多了第一个参数err,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function errorHandler(){
var env = process.env.NODE_ENV || ‘development’;
return function(err, req, res, next){
res.statusCode = 500;
switch(env){
case ‘development’:
res.setHeader(‘content-type’, ‘application/json’);
res.end(JSON.stringify(err));
break;
default:
res.end(’Server error’);
}
}
}

当Connect遇到错误,它会跳过后续的中间件,只调用错误处理中间件。

在使用connect时,错误处理有三种方式:

  • (1)使用Connect 默认的错误处理器;
  • (2)自己处理程序错误;
  • (3)使用多个错误处理中间件组件。

第三种给个示例:
多个错误处理器的实现:
api挂载在/api上:

1
2
3
4
5
6
7
8
9
10
var api = connect()
.use(users)
.use(pets)
.use(errorHandler);
var app = connec()
.use(hello)
.use('/api', api)
.use(errorPage)
.listen(3000);

connect_dumplicate_error
其中errorHandler处理来自api的所有错误,error处理来自app的所有错误。

13、connect中间件

包括:

  • (1)cookieParser:为后续中间件提供req.cookies和req.signedCookies,
    设置cookie这样用:res.setHeader(’Set-Cookie’,’’)
  • (2)bodyParser:为后续中间件提供req.body和req.files
  • (3)limit:基于给定字节长度限制请求主体的大小。必须用在bodyParser中间件之前,防止攻击。
    更灵活的使用:
1
2
app.use(type(‘application/x-www-form-urlencoded’, connect.limit(‘64kb’))
app.use(type(‘appication/json’, connect.limit(‘32kb’)))
  • (4)query:为后续中间件提供req.query
  • (5)logger:将http请求的信息输出到stdout或日志文件之类的流中
1
app.user(connect.logger(‘dev’))

输出有颜色区分的日志,便于开发调试
两种输出日志频率
immediate,表示一收到请求,就写日志。
buffer,以毫秒为单位,指定缓冲区刷新的时间间隔。

  • (6)favicon:响应/favicon.ico http请求。通常放在中间件logger前面,这样它就不会出现在你的日志中了
  • (7)methodOverride:可以替不能使用正确请求方法的浏览器仿造req.method,依赖于bodyParser
  • (8)vhost:根据制定的主机名(如nodejs.org)使用给定的中间件和http服务器实例
    可以做反向代理,缺点:一个网站崩溃,他的所有网站都会崩溃,一个都在同一个进程
  • (9)session:为用户设置一个http回话,并提供一个可以跨域请求的持久化req.session对象,依赖于cookieParser
  • (10)basicAuth:为程序提供http基础认证
  • (11)csrf:防止http表单中的跨站请求仿造攻击,依赖于session
  • (12)errorHandler:当出现错误时把堆栈跟踪信息返回给客户端。在开发时使用,不要在生产环境中使用
  • (13)static:把制定目录中的文件发给http客户端,跟connect的挂在功能配合得很好
    返回./public目录下的静态资源文件:
1
app.use(connect.static(‘public’));

默认请求/js/test.js,会去.public/js/test.js去查找。
使用带挂载的static

1
app.use(‘/app/files’, connect.static(‘public’));
  • (14)compress:用gzip压缩优化http响应
  • (15)directory:为http客户端提供目录清单服务,基于客户端的accept请求(普通文件,son或html)提供经过优化的结果

14、 Express中两种渲染视图方式

  • (1)在程序中使用app.render()
  • (2)在请求或者响应层用res.render()

14.1 视图的查找设置

1
app.set(‘views’,__dirname+’/views’);

14.2 设置模板引擎

1
2
3
4
5
6
7
8
9
app.set(‘view engine’, ‘jade’);
app.get(‘/‘, function(){
/**假定为.jade**/
res.render(‘index’);
});
app.get(‘/feed’, function(){
/**因为扩展名为.ejs,所以使用EJS**/
res.render(‘rss.ejs’);
});

14.3 视图缓存

默认会开启view cache,模板修改,需要重启生效。

14.4 视图查找

如photos为复数,暗示是一个资源列表。
express_search

15、单元测试与验收测试

有两种形态:测试驱动(TDD)和行为驱动开发(BDD)

  • 单元测试有Node的assert,Mocha,node unit,Vows以及should.js框架
  • 验收测试,Tobi和Soda框架。
    test-modules-summary

15.1 nodeunit:

例子:创建一个目录,每个测试脚本都应该用测试组装exports对象,

1
2
3
4
5
exports.testPony = function(test){
var isPony = true;
test.ok(isPony, ’This is not a pony.’);
test.done();
}

nodeunit会自动给传入它的对象中引入assert模块。
nodeunit提供test.epect验证断言执行数量是否符合预期。

15.2 mocha

只支持串行测试,默认2s的timeout,并行请用vows:
BDD风格:describe,it,before,after,beforeEach,afterEach.
TDD风格:suite,test,setup,teardown替换上述

执行mocha,会执行./test目录下的javascript文件。

  • BDD风格
1
2
3
4
5
6
7
8
9
10
11
12
13
var memdb = require(‘..’);
var assert = require(‘assert’);
describe(‘memdb’, function(){
describe(‘.save(doc)’, function(){
it(’should save the documment’, function(){
var pey = { name: ’Tobi’ );
memdb.save(pet);
var ret = memdb.first({ name: ’Tobi’ });
assert(ret == pet);
}
)
)
  • TDD风格
1
2
3
4
5
6
7
8
module.exports = {
‘memdb’ : {
‘.save(doc)’ : {
’should save the document’ : function(){
}
}
}
}
  • 测试异步
    增加done()
1
2
3
4
5
6
7
8
9
10
describe(‘.save(doc)’, function(){
it(’should save the document’, function(done){
var pey = { name: ’Tobi’};
memdb.save(pet, function(){
var ret = memdb.first({ name: ’Tobi’ });
assert(ret == pet);
done();
}
}
}

15.3 vows

支持并行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var vows = require(‘vows’);
var assert = require(‘assert’);
var Todo = require(‘./todo’);
vows.describe(’Todo’).addBatch({
‘when adding an item’:{
topic: function(){
var todo = new Todo();
todo.add(‘Feed my cat’);
return todo;
}
},
‘it should exit in my todos’: function(er, todo){
assert.equal(todo.getCount(), 1);
}
}).run();

如果你想把这段代码放到测试文件夹下,放在可以由Vows测试运行期运行,
run.()改成export(module);
然后
vows test/*

15.4 should.js

断言库,它有一个Object .prototype属性:可以写表达能力很强的断言。

15.5 Tobi和Soda

Tobi模拟浏览器测试,可以结合should.js
Soda远程控制真实的浏览器:
test_tobi

16、使用EJS过滤器处理模板数据

格式

<%=:用在转义的EJS输出上的过滤器
<%-:用在非转义的EJS输出上的过滤器

例子

1
2
3
4
5
6
7
var ejs = require(‘ejs’);
var template = ‘<%=: movies | sort | first %>’;
var context = {‘movies’: [
‘Bambi’,
‘Babe: Pig in the city’,
‘Enter the Void'
]};

看来上就是linux的管道符处理,

各种常用处理

  • (1)处理选择:last,first,get:N
  • (2)处理大小写:capitalize把第一个字母变大写,还有upcase,downcase
  • (3)处理文本:把文本截成一定数量的单词truncate:20,替换replace:’A’,’B’,
    排序sort,sort_by:’name’,其中sort返回的是对象,要返回属性的话:|get:’name’
  • (4)map:不用sort_by,再get。直接用map创建一个包含对象属性的数组,
    map ’name’| sort|

其他模板引擎:
Hotgan:实现mustache语法。
Jade:特点是空格的作用,缩进表示HTML的嵌入关系。

17、fs.watchfile()与fs.watch()

fs.watchfile()与fs.watch()是 Node.js中的两个监测文件API。

  • 比较老的是fs.watchFile,使用轮询的方式检查文件,不断的stat(),比较mtime时间戳.
1
2
3
4
5
6
var fs = require(‘fs’);
fs.watchFile(‘/var/log/system.log’, function(curr, prev){
if(cur.mtime.getTime() !== prev.mtime.getTime()){
console.log(‘“System.log” has been modified’);
}
});
  • 新的api是fs.watch(),根据平台本地的文件修改通知API监测文件,性能更优,但是不如watchFile可靠。在OSX监测目录不会报告参数filename,其他见: 档http://nodejs.org/api/fs.html#fs_caveats

18、Process模块

  • process.argv 存储了Node运行当前脚本时传入的参数
  • process.env 获取或设定环境变量
  • process不是eventEmitter实例,却可以发出exit和uncaughtException事件

注意点:
其中exit是在事件循环(event loop)停止之后才激发的,所以你没有机会在exit事件启动任何异步任务。

18.1 Process的信号处理

UINX有信号的概念,是进程间通信(IPC)的基础形式,它是一组固定的名称,不能传递参数。

信号举例如下

  • SIGUSR1:Node进入它内置的调试器
  • SIGWINCH:调试终端大小,由shell发送
  • SIGINT:ctrl+c,由shell发送,Node默认行为是杀死进程。如果你希望在杀掉服务器前,完成所有连接的处理,可以
1
2
3
4
process.on(’SIGINT’, function(){
console.log(‘Got Ctrl-C!’);
server.close();
});
  • 还有SIGUSR2和SIGKILL等等

19、子进程

在NODE中创建子进程三种

  • 高层api,exec:在回调中创建命令并缓存结果的高层api。
  • 底层api,spawn:将单例命令创建Child-Process对象中的底层API。
  • 内置的特殊IPC通道fork:用内置的IPC通道创建额外Node进程的特殊方法。

三者比较

  • cp.exec():只关心结果,不用从子进程的stdio流中访问数据(IRC协议模块有很多,irc,irc-js等等),结果需要转义,可以用execFile()

  • cp.spawn():返回一个可以交互的ChildProcess对象,允许你跟每个子进程的stdio流交互。(node-cgi范例模块)

  • cp.fork():也返回一个ChildProcess对象,区别是这个API是用IPC通道添加的, 子进程现在有一个child.send(message) 函数,并且用fork() 调用的脚本能够监听process.on(‘message’) 事件。fork出来的子进程可以参与运算。

20、其他推荐的社区模块

  • 表单提交:foridable。
  • redis:hiredis,升级node时候,需要重新编译一下hiredis,npm rebuild hiredis。
  • mongodb:mongoose,使用时有个{safe:ture}选项表明你想让数据库操作在执行回调之前完成。

参考:
《Node.js 实战》:http://book.douban.com/subject/25870705/
《Node.js in action》:http://book.douban.com/subject/6805117/

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)

整合Sentry和NodeJS实现分布式日志收集

内容包括如何利用raven-node模块,完成nodejs服务与开源日志框架Sentry的对接,实现分布式日志收集,附Http接口的性能测试,不涉及Sentry的使用。

一、什么是Sentry?

一个基于Djongo的日志收集系统。具备收集日志(对于分布式环境下,日志分布在各台服务器上)、日志统计(统计次数最多的异常,往往这就是系统的隐患所在)、监控告警(出现异常或者异常积累到一定数量以短信或者邮件的形式告警)、以及以上功能的可视化界面。

目前我们部署公网sentry是6.4.4,
http://sentry.funshion.com/dev-web-ads/hermes/
支持的raven-node是0.7.2
https://github.com/getsentry/raven-node

二、NodeJS接入Sentry

在package.json中增加dependencies:”raven”: “0.7.2”

调用方式:

1
2
var raven = require(‘raven’);
var client = new raven.Client(‘http://32位:32位@sentryHost');

2.1 raven-node两种使用方式

  • 2.1.1 全局拦截与实现

调用:

1
client.patchGlobal();

源码如下,拦截所有uncaughtException。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module.exports.patchGlobal = function patchGlobal(client, cb) {
// handle when the first argument is the callback, with no client specified
if(typeof client === 'function') {
cb = client;
client = new Client();
// first argument is a string DSN
} else if(typeof client === 'string') {
client = new Client(client);
}
// at the end, if we still don't have a Client, let's make one!
!(client instanceof Client) && (client = new Client());
process.on('uncaughtException', function(err) {
if(cb) { // bind event listeners only if a callback was supplied
client.once('logged', function() {
cb(true, err);
});
client.once('error', function() {
cb(false, err);
});
}
client.captureError(err, function(result) {
node_util.log('uncaughtException: '+client.getIdent(result));
});
});
};

测试下:

1
2
3
4
5
6
7
8
9
10
11
12
var raven = require("raven")
var testRaven = function(){
var client = new raven.Client('http://a374661ff0374e488...略');
client.patchGlobal();
try {
throw new Error("test throw error");
}catch(err){
throw new Error("i'm caught error and throw myself again!")
}
//client.captureError('test captureMessage');
}

在sentry上显示如下:
会显示function名,Error message,错误出现次数等
setry-test-item
点进详情,看到程序调用栈:
sentry-aggregation

  • 2.1.2 手动调用
1
2
3
4
// record a simple message
client.captureMessage('hello world!')
// capture an exception
client.captureError(new Error('Uh oh!!'));

2.2 raven-node推荐使用方式

统一使用第二种:
(1)对于uncaughtException使用下图方式输出到sentry:

1
2
3
4
5
process.on('uncaughtException', function(err) {
ravenClient.captureError(err)
//console.log("uncaughtException:" + err.stack);
return false;
});

(2)对于catchException或者业务错误,重写log的实现,完成可配置哪个log类型输出到sentry。

2.3 raven-node容灾考虑

看完怎么导入使用后,如果把它丢到生产环境,我想到的,还需要考虑的问题有:

(1)日志发送应该是纯异步的,不影响业务。
(2)发日志是调用tcp还是udp还是http接口。
(3)超时重发机制。
(4)sentry挂了怎么处理。
(5)sentry忙不过来怎么处理。
等等。

  • 2.3.1 raven-node支持的协议

要回答这些问题,看下sentry的transport.js。

1
2
3
4
module.exports.http = new HTTPTransport();
module.exports.https = new HTTPSTransport();
module.exports.udp = new UDPTransport();
module.exports.Transport = Transport;

支持三种方式,根据SENTRY创建项目的设置来实现,体现在SENTRY_DNS的host里。
因为我们都是对内网服务器日志的监控,一般使用http。

  • 2.3.2 raven-node中http协议实现-send函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
HTTPTransport.prototype.send = function(client, message, headers, ident) {
var options = {
hostname: client.dsn.host,
path: client.dsn.path + 'api/store/',
headers: headers,
method: 'POST',
port: client.dsn.port || this.defaultPort
}, req = this.transport.request(options, function(res){
res.setEncoding('utf8');
if(res.statusCode >= 200 && res.statusCode < 300) {
client.emit('logged', ident);
} else {
var reason = res.headers['x-sentry-error'];
var e = new Error('HTTP Error (' + res.statusCode + '): ' + reason);
e.response = res;
e.statusCode = res.statusCode;
e.reason = reason;
e.sendMessage = message;
e.requestHeaders = headers;
e.ident = ident;
client.emit('error', e);
}
// force the socket to drain
var noop = function(){};
res.on('data', noop);
res.on('end', noop);
});
req.on('error', function(e){
client.emit('error', e);
});
req.end(message);
};

代码很简短,post msg到{SENTRY_DSN}.host/dsn.path/api/store/,而且
(1)没有失败重试
(2)发送失败(resp状态码不是200或者req调用的error),发送事件到client.emit(‘error’),再看下client对error事件的处理:none。

1
this.on('error', function(e) {}); // noop

三、小结与优化

3.1 疑问解答

回答下前面对sentry和raven-node的疑问:

  • (1)日志发送应该是纯异步的,不影响业务。
    raven-node send日志后是异步回调,但是调用发送日志api是同步的。
    (因为nodejs是单进程单线程,io异步基本已满足需求,如需优化,可以考虑对整个log模块独立进程,增加重试,发送流量控制等等,但是进程间内存拷贝开销会很大,nodejs的优劣还是很明显的,具体看应用场景)。
  • (2)发日志是调用tcp还是udp还是http接口。
    内网服务日志监控推荐http/udp。
  • (3)超时重发机制。
    无retry机制
  • (4)sentry挂了怎么处理。
  • (5)sentry忙不过来怎么处理。
    (sentry接受到的请求不是实时处理,接受请求通过队列实现。性能测试可参考:
    http://blog.csdn.net/largetalk/article/details/8640854)
    sentry挂了或者忙不过来,client会接收到error,但是不会输出任何异常。
  • (6)raven-node 连接是否会复用,大量日志需要输出的时候,io和句柄占用都会影响到业务处理,是否需要过载保护?
    参考3.2

3.2 优化与使用建议

针对上面的rave-node可能存在的问题,给出以下优化建议

3.2.1 规范哪些日志需要输出到sentry

  • 新增monitor logger类型,专用于输出到sentry
  • 必须error级别以上输出到sentry
  • error包括uncaughtException,业务异常,外部依赖服务异常,内部异常。(也可以增加服务正常启动的信息给sentry)

3.2.2 raven-node优化

上面提到的潜在问题总结为

  • 日志过多导致内存,句柄等资源占用过多的情况。
  • sentry异常,发送日志堆积,与日志过多情况相似。
  • 目前与sentry交互的异常日志没有输出(有优点也有缺点)。

建议

针对上面前两个问题,对raven-node封装或者扩展,支持固定大小的预发送队列。对外部依赖服务的异常进行隔离。

针对第三个问题,异常分为初始化和正常交互过程中两种情况

可以修改raven-node的client的prototype,支持异常输出日志。

或者常规解决方法

正常交互过程中的异常:可以检测预发送队列的内容进行处理(如超过一定时间/次数,队列内容没有变更视为timeout异常),输出error日志。

初始化异常:因为封装raven-node后,client是复用的,仅当第一次初始化后,进行check,发送一个message:xxx服务启动。

四、性能测试

因sentry公网只开了Http的接口,对公网测试的Http接口性能测试如下:

环境:
本机(Mac os x 10.11),双核四线程,node 0.8,raven-node 0.7.2

tps监测方式:统计raven-node client:response中end事件的输出时间

测试数据:

4个cluster
4000条300byte消息,
560ms发送完毕
4000条消息,总耗时约5.3s
tps大概765/s

1个cluster
4000条300byte消息,
900ms发送完毕
4000条消息,总耗时约11s
tps大概365/s

测试是否对项目有连接数的限制:
200个cluster
20条300byte消息
sentry的web管理界面卡顿,raven-node client返回正常

后续追加了不同cluster的性能表现:

总消息数为4000条,每条300byte
cluster个数-tps
4-765
8-1256
12-1430
16-1752
20-1690
32-1320
注:多个cluster未做类似Barrier的实现(即cluster发起请求非同一起点)会有误差。

结论:
sentry内部利用redis实现任务队列,测试机的tps在1800左右,预估还有较大提升,受限于测试机。如果使用http,极端情况下对client有压力,如果使用udp接口,问题不大,不影响client。另外,sentry连接数没有限制,连接管理表现一般。


参考:
Raven-node github:https://github.com/getsentry/raven-node
Getsentry官网:https://www.getsentry.com/docs/
使用开源软件sentry来收集日志:http://luxuryzh.iteye.com/blog/1980364
关于Sentry:http://blog.csdn.net/largetalk/article/details/8640854

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)

Coffeescript极简教程

配合没耐心的nodejs教程,写给希望快速上手的coffeescript使用者。

一、一些简写和约定:

coffeescript和python类似,空格,缩写都是有意义的。
换行替换分号,缩进替换花括号,空格替换括号,
但是数组的’[]’不能省略。

二、全局变量:

CoffeeScript剔除了全局变量,避免js中全局变量的误用。(CoffeeScript使用一个匿名函数把所有脚本都包裹起来,将其限定在局部作用域中,并且在为所有的变量赋值前自动添加var)

当然也有需要用全局变量的时候,直接给全局对象windoow赋值即可:

1
2
exports = this
exports.tempVal = “tempVal”

这里在顶级作用域中使用,this相当于全局变量

三、函数

3.1 函数的标准写法

-> 替代了function(){}

1
2
3
4
sum = (nums...) ->
result = 0
nums.forEach(n) -> result += n
result

(nums)为传入的参数槽(splats),…表示不定参数,同时自动返回result
调用输出结果 alert sum() 或者 alert sum a,b,c ,其中空格表示括号,但是易混淆或者无参调用的时候,请带上括号。

多重赋值:

btw,因为讲到…不定参数,就提下多重赋值与…的结合:
多重赋值就是将一个数组的值逐个赋给后面的一系列变量。

1
2
3
4
5
myArray = ["A", "B", "C", "D”]
[start, middle..., end] = myArray
console.log "start is #{start}”
console.log "middle is #{middle}”
console.log "end is #{end}”

输出:

1
2
3
start is A
middle is B,C
end is D

顺便提下,数组是支持range的用法的,[1…10]

3.2函数上下文:

js的上下文变化会很频繁,尤其在回调函数内,coffeescript提供=>或者self(this)的方式,以下为=>的例子:

1
2
3
this.clickHandler = -> alert “clicked”
element.addEventListener “click”, (e) =>
his.clickHandler(e)

四、集合遍历

4.1 for循环:

1
2
3
myLetters = [“a”,”b”,”c”,”d”]
for letter in myLetters
console.log letter.toUpperCase()

for中增加when条件

1
2
for num,i in a when num < 5
console.log “第#{i}个数为#{num}"

4.2 列表推导式(Comprehension)

类似ruby的前缀表达式

1
2
3
myLetters = [“a”,”b”,”c”,”d”]
console.log letter.toUpperCase() for letter in myLetters

4.3 对象枚举的方式

(这里in换成了of)

1
2
3
4
5
6
person =
firstName: “Mark”
lastName: “Bates”
for key, value of person
console.log “#{key} is #{value}”

对象属性的枚举还支持when关键字;如果需要过滤继承属性,还可以使用own关键字

1
2
3
4
5
6
7
myObject =
name: “Mark”
Object.prototype.dob = new Date(1976, 7, 24)
for own key, value of myObject
console.log “#{key}:#{value}”

4.4 while用法

与js不同,while还可以返回结果数组

1
2
3
num = 6
minstrel = while num -= 1
num + “ Brave Sir Robin ran away”

综合例子:

1
2
3
@updateAvatars = ->
name = $(‘.avatar[data-name]’),map -> $(this).data(’name’)
Utils.findAvatar(name) for name in $.unique(names)

五、别名与存在操作符

5.1 别名:

@ 表示this的别名,如@saviour = this.saviour
:: 表示prototype

5.2 存在操作符?:

coffeescript中,?只会在变量不存在或者undefined的时候返回假,

1
praise if brain?

还可以用来替换||操作符:

1
velocity = southern ? 40

或者在访问对象时进行null检查,

1
blacknight.getLegs()?.kick()

判断属性是否存在外,你还也可以把?放到kick右边,判断函数是否存在,是否可以调用等

六、类

Coffeescript中类定义是组合模式(组合使用构造模式和原型模式),继承方式则用寄生组合模式。

6.1 定义类

1
2
3
4
5
6
7
8
9
10
class Employee
constructor: (name) ->
@name = name
dob: (year = 1976, month =7, day = 24) ->
new Date(year, month, day)
emp1 = new Employee(“Mark”)
console.log emp1.name
console.og emp11.dob()

6.2 添加方法

方法是直接定义在原型上,显示为原型添加方法,很简单:

1
Array::size = -> @length

6.3 构造函数简化

如果把参数赋值给同名属性,可以用@语法糖,将构造函数简化为:

1
constructor: (@name) ->

6.4 实例变量的使用

1
2
3
4
5
6
7
8
class Animal
price: 5
sell: =>
alert “Give me #{@price} shillings!”
animal = new Animal
$(“#sell”).click(animal.sell)

其中注意到=>的使用,表示引用方法调用的上下文,输出5.

6.5 静态变量的使用

1
2
3
4
class Animal
@find: (name) ->
Animal.find(“Parrot”)

其实就是在类属性上(this)设置变量值。

6.6 继承

1
2
3
4
5
6
7
8
class Animal
constructor: (@name) ->
class Parrot extends Animal
Animal::rip = true
parrot = new Parrot(“Macaw”)
alert(“This parrot is no more”) if parrot.rip

这里要注意的是,静态变量是直接拷贝给子类的,而不是像实例属性那样使用原型来继承。

七、Mixins混入

举个例子:
module类有两个静态方法,@extend()和@include(),分别来实现对类的静态属性和实例属性的扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
moduleKeywords = [‘exteded’, ‘included’]
class Module
@extend: (obj) ->
for key, value of obj when key not in moduleKeywords
@[key] = value
obj.extended?.apply(@)
this
@include: (obj) ->
for key, value of obj when key not in moduleKeywords
# Assign properties to the prototype
@::[key] = value
obj.included?.apply(@)
this

然后是如何使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
classProperties =
find: (id) ->
create: (attrs) ->
instanceProperties =
save: ->
class User extends Module
@extends classProperties
@include instanceProperties
# Usage:
user = User.find((1)
user = new User
user.save()

以上,我们为User类添加了静态属性find()和create(),还添加了实例属性save()。

再给一个例子,在扩展类后执行回调:

1
2
3
4
5
6
7
8
9
ORM =
find: (id) ->
create: (attrs) ->
extended: ->
@include
save: ->
class User extends Module
@extends ORM

基础讲完,接下来就可以围绕网络,文件,进程,异步等进行专项练习了,好运!


参考:
《The Little Book on CoffeeScript》:http://book.douban.com/subject/10462179/
Learn X in Y minutes Where X=coffeescript:http://learnxinyminutes.com/docs/coffeescript/
CoffeeScript极简教程:http://chunfenglee.com/blog/2013/10/13/beginning-coffeescript/

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)

没耐心的NodeJS基础教程

这是一篇没耐心的nodejs教程,写给希望迅速上手的学习者。基础篇介绍权限,模块,包,工程目录结构,npm等,不涉及常用api和中间件等。

一、权限问题

linux下,nodejs需要监听80或443端口提供HTTP(S)服务时需要root权限。
有两种方式:

  • (1)(推荐)需要js提供root权限

    $ sudo node server.js

  • (2)使用chmod+s命令让nodejs总是以root权限运行,不安全。

    $ sudo chown root /usr/local/bin/node
    $ sudo chmod +s /usr/local/bin/node

二、模块

一个文件为一个模块,一个文件路径为一个模块名。
编写模块时,有require,exports,module三个变量。

  • 2.1 required
    导入某个模块

    var foo1 = required(‘./foo’);//.js后缀可以省略
    var foo2 = required(“./foo.js”);

也可以加载或者导入一个json:

var data = require(‘./data.json’);

  • 2.2 exports
    是当前模块的导出对象,用于导出模块共有方式和属性。

    exports.hello = function(){

    console.log(‘Hello World!’);
    

    };

  • 2.3 module
    可以访问当前模块的一些信息,最多的用途就是替换当前模块的到处对象。

    module.export = function(){

     console.log(‘Hello World!’);
    

    };

三、模块初始化

NodeJS使用CMD模块系统,主模块作为程序入口点,所有模块在执行过程中只初始化一次。

初始化一次

一个模块中的JS代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象。之后,缓存起来的导出对象被重复利用。

  • 3.1 主模块
    通过命令行参数传递给NodeJS以启动程序的模块被称为主模块。主模块负责调度组成整个程序的其他模块完成工作。默认是路径下的main.js。

  • 3.2 二进制模块(不推荐)
    nodejs支持使用c/c++编写二进制模块。编译好的二进制模块除了文件扩展名是.node外,和JS块的使用方式相同。

四、模块路径解析规则

除了相对路径和绝对路径外,require函数还支持第三种形式的路径。

  • 4.1 内置模块

  • 4.2 node_modules目录
    专门存放模块的路径。比如某个模块的绝对路径是/home/user/hello.js, 在该模块中使用require(‘foo/bar’)方式加载模块,则NodeJS依次尝试使用以下路径。

    /home/user/node_modules/foo/bar
    /home/node_modules/foo/bar
    /node_moduels/foo/bar

  • 4.3 NODE_PATH环境变量
    指定额外的模块搜索路径。因为是额外,所以最后搜索。
    使用:设置NODE_PATH环境变量,linux用:分隔,windows使用;分隔。

五、包

  • 5.1 入口模块
    多个js模块,组成一个包。所有模块都是同一个路径里,且需要指定一个入口模块,入口模块的导出对象作为包的导出对象。
    比如cat目录下有main.js,lib1.js,lib2.js。
    main.js作为入口模块,require lib1.js,lib2.js。

  • 5.2 index.js
    当模块文件名为index.js,加载模块时就可以使用模块所在目录的路径代替模块文件路径,如可写成
    var cat = require(‘/home/user/lib/cat’);
    var cat = require(‘/home/user/lib/cat/index’);

  • 5.3 自定义入口模块和存放位置(package.json)
    如果你想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个package.json文件,如

    {

    “name”: “cat”,
    “main”: “./lib/main.js"
    

    }
    如此一来,就可以用require(‘/home/user/lib/cat’)加载。

六、命令行程序

将固定node命令开头执行的方式,变成命令行的方式。
node xx.js [param] => xx [param],
如$node /home/user/bin/node-echo.js Hello World
变成
$node-echo Hello World
简便很多

那么如何做到,只讲在linux下怎么做:

  • (1)node-echo.js顶部增加

    #!/usr/bin/env node

  • (2)然后增加执行权限,并再/usr/local/bin下创建软链接

    $ chmod +x /home/user/bin/node-echo.js
    $ sudo ln -s /home/user/bin/node-echo.js /usr/local/bin/node-echo

七、工程目录结构

  • /home/user/workspace/node-echo/
    • bin/
      node-echo
    • doc/
    • lib/
      echo.js
    • node_modules/
      • argv/
    • tests/
      package.json
      README.md

其中部分文件:

/ bin/node-echo /
var argv = require(‘argue’),
echo = require(‘../ilb/echo’);
console.log(echo(argv.join(‘ ‘)));

/ lib/echo.js/
module.exports = function(message){
return message;
}

/ pacakge.json /
{
“name”:”node-echo”,
“main”:”./lib/echo.js”
}

八、NPM:包管理工具

(1)允许用户从NPM服务器下载别人编写的三方包到本地使用。
(2)允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
(3)允许用户将自己编写的包或者命令行程序上传到NPM服务器供别人使用。

  • 8.1 下载第三方包

一般这样使用别人的包(如argv),
在项目目录下,npm install argv@1.1.1,那么argv包就会放到node_modules目录下,直接require(‘argv’)就可以使用了。

依赖的包比较多时,通过package.json字段做了扩展。
如:
{
“name”: “node-echo”,
“main”: “./lib/echo.js”,
“denpendencies”:{
“argv”: “0.0.2”
}
}
然后在工程目录,npm install即可。btw,传递的依赖会被自动下载。

  • 8.2 安装命令行工具

如上面例子的node-echo,只要node-echo自己配置好了对应的package.json字段,对于用户而言,这样安装:

$ npm install node-echo -g
其中-g表示全局安装,node-echo会默认安装到以下位置,并且NPM会自动创建好Linux系统下需要的软链文件。

  • /usr/local/
    • lib/node_modules/
      • node-echo/
    • bin/
      node-echo
  • 8.3 发布代码

第一次发布npm,需要注册个号码。执行npm adduser,按提示操作。账号完成设置后,编辑需要发布的项目的package.json,加入npm必须字段。
如:

{
“name”: ”node-echo”,#包名,与NPM服务器保持唯一
“version”: “1.0.0”,#当前版本号
“dependencies”:{
“argv”: “0.0.2”
},
“main”: “./lib/echo.js”,
“bin”: {
“node-echo”: “./bin/node-echo” #命令行程序名和主模块位置
}
}
然后,在package.json所在目录下运行npm publish 发布代码。

  • 8.4 版本号

使用npm下载或者发布代码都要用到版本号。
语义版版本号分为X.Y.Z三位,
分别为主版本号、(大变动,向下不兼容)
次版本号(新增功能,向下兼容)、
补丁版本号(修复bug)。

九、最后npm比较有用的命令行:

  • 在package.json目录,npm install . -g 可先在本地安装当前命令行程序,用于发布前本地测试。
  • npm update 当前目录下node_modules子目录下对应模块更新到最新版本。
  • npm update -g 把全局安装的对应命令行程序更新至最新版。
  • npm cache clear 清空NPM本地缓存,用于对于使用相同版本号发布新版本的人。
  • npm unpublish @ 可以撤销自己发布过的某个版本代码。

参考:
七天学会NodeJS:http://nqdeng.github.io/7-days-nodejs/\#3.2.1

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)

《如何阅读一本书》感悟总结

阅读一本书,就是和作者交流的过程。与直接面对面交流不同,阅读这种“静默”交流方式,需要不同的经验和技巧。刚看完《如何阅读一本书》,感触比较多,总结一下。

读书太快或者太慢都会一无所获。要知道什么时候用什么速度阅读很重要。

好的阅读是

  • (1)主动,包括主动的发现问题,思考问题,做一个自我要求的读者
    (要去理解作者在说什么,为什么这么说,有道理吗,如果有道理怎么运用它等,最后善于做笔记)。
  • (2)技巧,根据不同的阅读层次和目的,技巧不同,后面讲到。

阅读的目的有两种

一种是为获得咨询而读,一种是求得理解而读。
两种的技巧不同。
(为求得理解而读的一般发生在两种状况下:
一是一开始不对等的理解程度
二是阅读的人一定要把不对等的理解力克服到一定程度内。)

阅读的层次

  • 基础阅读
  • 检视阅读:系统化略读。获得咨询或者消遣停留在这个层次。
  • 分析阅读:特别是在追求理解。
  • 主题阅读:又称比较阅读,根据主题,阅读很多本书,例举出书之间的相关之处,总结。

各层次阅读经验

  • 检视阅读:
    (1)头一次面对一本难读的时候,从头到尾先读一遍,碰到不懂的地方不要停下来查询或者思索。
    (2)避免“逗留”和“倒退”的坏习惯。

  • 分析阅读:
    (1)根据顺序和关系,列出全书的重要部分,找出作者在问的问题,或作者想要解决的问题,诠释作者使用的关键字,与作者打成共识,找到论述,然后重构前因后果,明白哪些已解决,哪些未解决。
    (2)应该为感到困扰的问题停留,而不是感兴趣的细节。

  • 主题阅读:

各类型书的阅读经验

  • 实用型书:
    (1)“任何实用型书都不能解决该书的实际问题”,原文有点绕。。我理解为提出是思维,而非答案,最后做解决方案的还是自己。
    (2)好的阅读者,读实用型书总能读出言外之意,即衍生出来的规则,他会进一步找出这些规则应该如何应用。
    (3)评断一本实用型书,所有的事都与结果及目标有关,是否与结果达成共识,而非方法。

  • 想象性文学:
    (1)论述性文学传达的是知识,而想象性文学传达的是经验。

  • 故事书:
    (1)快,全心全意的读。

  • 戏剧:
    (1)你在读剧本时,不是在读一个已经完成的作品。
    (2)悲剧的精髓在于时间,或者说缺乏时间。

  • 历史类,计算机数学类,哲学类,社科类略。

最后感慨下,多读书,多总结出更适合自己的方式。


参考:
《如何阅读一本书》:http://book.douban.com/subject/1013208/

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)

Linux常用命令小记

最近在翻《Linux命令行与shell脚本编程大全》,整理了书中提到的常用的linux命令行。

一、文件相关

  • 链接文件
    cp -l test1 test4 创建硬链接(一个独立文件,引用了源文件,索引节点号是一样的,只适用同一个挂载点下的文件)
    cp -s test1 test5 创建符号链接,软链接(一个独立文件,只存储源文件的信息,不存储源文件数据,索引节点显示为test5 -> test1)
    ln 链接文件(硬链接)
    ln -s 链接文件(软链接)

  • 文件状态
    stat test10 查看文件统计信息,比ls更全
    file test1 显示文件类型(3类,文本文件,可执行文件,数据文件)

  • 查看文件内容
    cat -n test1 查看整个文件,同时显示行号
    more test1 查看整个文件,显示一页停下来,再向下翻页
    less test1 more的升级版,不用全部加载文件
    tail -n 100 -f 显示最后的100行,-f保持活动状态,有新内容就显示
    head -n 100 -f 与tail类似,显示最开始的100行

  • 文件排序、过滤
    sort -n 排序文件数据行,-n表示把数字识别为数字,而不是字符,让它们按值排序
    sort -t ‘:’ -k 3 -n /etc/passwd 其中-t表示指定字段的分隔符,-k指定排序的字段,最终完成按照用户ID进行数字排序
    grep -e t -e f file1 过滤文件内容,筛选匹配行,其中-e表示多个匹配模式,满足其一就被筛选出来

  • 文件压缩、归档
    zip -r test.zip test 递归压缩目录
    unzip test.zip 解压
    tar -cvf test.tar test/ test2/ 创建含有test和test2目录的test.tar归档文件。
    tar -tf test.tar 列出test.tar 内容,不解压
    tar -xvf test.tar 解压
    tar -zxvf test.tgz 解压gzip压缩过的tar文件

二、线程相关

  • 监控性能
    top 实时监控进行,系统负载等

  • 线程消息
    kill -s 信号(线程传递信息,比如HUP(1)挂起,INT(2)终端,QUIT(3)结束运行,KILL(9)无条件终止)

三、账号相关

  • 组与用户
    useradd -b default_home -g group sb
    usermod 修改用户账户的字段,比如主要组以及附加组的所属关系
    passwd username 修改密码
    groupadd shared 创建shared新组(tail /etc/group查看结果)
    usermod -G shared rich 增加shared组的成员,是-G,不是-g,-g会修改账户的默认组!
    groupmod -n sharing shared 其中-n是修复已有组的组名 -g是修改GID
    umask 777 —在/etc/profile 设置umask,即touch创建文件时候,默认给的权限
    chmod o+r newfile (o表示权限设置跟其他用户一样,u表示权限设置为属主一样,g设置为属组一样,r读权限)
    chown dan.shared newfile 改变文件的属主和属组

四、磁盘设备相关

  • 设备
    mount -t vfat /dev/sdb1 media/disk 将u盘/dev/sdb1 挂载到/media/disk
    mount /homr/rich/mnt 卸载

  • 磁盘
    df -h 显示设备的磁盘空间。
    du -h 显示特定目录下磁盘使用情况。用来判断系统某个目录是否有超大文件的快速方式。


参考:
《Linux命令行与shell脚本编程大全》:http://book.douban.com/subject/11589828/

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)

Java异常-《Robust Java 中文版》 笔记总结

Robust Java 中文版---Java异常处理、测试与调试,这本书主要讲解了Java异常 的原理及处理方法,系统地阐述了体系结构、设计、开发、测试和调试等主题,并讨论了如何构建健壮的系统。

一、异常基础

java异常的产生是代码处,异常会作为消息发送至应用程序的catch部分或者抛出。

1.1 Throwable的内部关系

异常的祖先是Throwable(可抛出),底下是Exception(异常)和Error(错误)
Exception异常:为“合理程序程序需要捕获的情况”,可预测,可恢复。
Error错误:合理应用程序不应试图捕获它,反常情况。

1.2 可检测异常与非检测异常

区别:可检测异常经编译器验证,而非检测异常不需要。
主要的非检测异常:RuntimeException和Error。(对于RuntimeException,一般针对特定操作,请增加校验。)

1.3 Trowable的属性

Throwable 存储3个属性:

  • message(消息):String //描述性文本 读(只能在构造函数设置)
  • stack trace(栈跟踪):StackTraceElement[] //引发异常的所有方法调用的记录 读-写
  • cause(原因):Trowable //产生此异常的原因 读-写(只能设置一次) getCause和initCause获取和设置,或者构造函数设置

除3个属性外,Throwable还包含toString和getLocalizedMessage 方法。
toString方法:覆盖Object类,返回异常名(对于非空消息字符串)。
getLocalizedMessge方法:返回消息字符串的值。

二、异常的实践

2.1 处理还是声明异常

经验法则:尽可能去处理异常,如果没有能力处理就声明异常。
从本质上讲,仅当方法缺少自我处理异常的信息、上下文或资源时,才将异常信息传给调用者。

三、异常高级特性

3.1 链表异常

就是异常允许设置两个异常之间的关联,称为链表异常。参考1.3里的cause属性。
设置方法有两种,作为构造函数参数传入,或者调用initCause(Throwable)方法设置,只能设置一次。

一般不常用,处理链表异常的方式可以参考如下:

try{
throublesomeObject.riskyMethod();
}catch(ChainedException exc){
System.out.println(“We’re in trouble now!!!”);
Throwable currentException = exc;
do{
System.out.println(currentException.toString());
currentException = currentException.getCause();
}while(currentException != null)
}

3.2 异常的本地化和国际化

如果需要本地化异常相关文本,可在自定义异常中覆盖getLocalizedMessage(),以加载本地专用的异常信息。

java使用java.util和java.text包的类支持110n和i18n.java.util.resourceBundle和java.util.Locale是两个关键类。

实现步骤为:

  • 创建ResourceBundle子类来存储消息
  • 为不同地区继承ResourceBundle类
  • 创建覆盖getLocalizedMessage的自定义异常类并用ResourceBundle检索消息
  • 具体大家自己查下吧,就不贴代码了。

3.3 不声明异常,声明异常,try-catch处理异常的性能比较

有兴趣的可以自己写个测试类,javap -c -verbose一下。

可以看到:
声明异常和不声明的两个方法字节码相同,声明异常的抛出语句在类的方法查找表中声明。
而内部try-catch的方法,会增加一些如goto, astore等的字节码指令,以及一个与该方法相关的异常表(Exception table)。若产生异常,异常表控制代码路由。实际上行为与前两者类似。

那么这三者的性能比较会怎样?实际测试是区别不明显

总结下最佳实践
尽量避免抛出异常。
如果条件允许就处理异常。
如果条件不允许就声明异常。

四、异常与多线程

4.1 多线程与异常的关系

看run方法签名,知道线程的异常都必须内部处理。
当run方法抛出非检测异常(RuntimeException或Error)时,负责该执行路径的Thread将终止,而其他线程无感知,理想情况下,多线程不抛出受检或非受检异常。

关于dumpstack

多线程程序中,每个线程都有自己的执行路径,也有自己的栈跟踪。要分析Thread的栈跟踪,调用Thread类的dumpStack方法,将其显示在标准错误流中(System.err)。
如果正常运行时执行该操作,显示Thread在其调用栈的当前位置。
如果产生异常,则显示标准异常栈跟踪。

4.2 同步代码块中的异常

比较正确的写法:除非保证不损坏同步块,决不抛出传到同步块意外的异常:

try{
synchronized(this){
}
}catch(RuntimeException exc){

}

五、异常设计的最佳实践

  • (1)在应用程序中,可根据潜在故障集来描述各个用例。可根据在运行程序中出现的可能性及严重程度,按层次定义这些故障。
  • (2)只解决对程序有重大影响且不可避免的问题和错误。
  • (3)方法只传递程序中需要广而告之的错误,或传递因为方法缺少上下文或资源而无法解决的错误。
  • (4)方法应根据使用者的角色和职责,以使用者可轻易理解的方式传递错误。
  • (5)方法仅在以下情况下抛出多个异常:
    a.互相排斥(起因不同)
    b.对使用者有不同作用
    c.表示完全不同的故障
  • (6)在任何软件实体中(类、组件、系统、架构或API),异常都应称为实体合约(实体与外部的接口)的标准部分。

六、测试技术体系

6.1 测试角色和职责

  • 6.1.1 开发人员(TDD适合开发人员的单元测试,集成测试)
    白盒:静态和动态测试,包括单元,集成,烟雾(smoke)和回归测试
    体系结构:大多数测试形式,但有效性测试最好由用户来执行
  • 6.1.2 测试人员
    黑盒:大多数测试形式,特别是探索和基于风险的测试
    系统结构:大多数测试形式
  • 6.1.3 用户
    黑盒:alpha,beta和用户满意度测试
    体系结构:有效性测试

七、调试技术体系

低级:code-resident技术

  • 日志记录API
  • 异常和错误
  • 专用测试方法

中级:模式和架构技术

  • 收集器(被动数据收集)
  • 监控器(被动数据监控)
  • 模仿器(主动被调用行为)
  • 模拟器(主动调用行为)

高级:系统技术

  • 体系结构测试方法和架构
  • 基础结构收集方法
  • JPDA(java平台调试器体系结构)

参考:

(转载本站文章请注明作者和出处 Vernon Zheng(郑雪峰) – vernonzheng.com ,请勿用于任何商业用途)