庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

2012年3月19日


很久没有更新博客,没想到更新是搬迁公告。这个博客累计的访问量突破百万,是我建立的时候完全没有想过的事情。博客对我来说更多是记录、记忆的地方,我时常因为想不起某个东西,来翻自己的博客,查找旧知,发现新知。阅读很多人的博客,也是我跟踪、学习新知的主要方式。虽然微博兴起,不过博客作为更系统性的记录的地方,不会过时。

非常感谢blogjava提供这么优秀的平台。只是我今年给自己的一个目标是建立自己的博客,因此现在要搬迁,加上其实现在也写的少,其实搬迁不搬迁,意义也不大了。算是一个通告,有兴趣的可以订阅我的新博客,没兴趣的请自行略过,谢谢大家。

新博客地址:http://blog.fnil.net/
RSS地址:http://blog.fnil.net/index.php/feed

新博客的第一篇记忆是《Leiningen教程中文版》,从现在开始,这个博客将不再发布任何新的文章,已有的也不会删除,部分可能会导到我的知识库上去。

最后,祝福blogjava越办越好。

posted @ 2012-12-10 01:24 dennis 阅读(11995) | 评论 (6)编辑 收藏

It's my weekend project——node-shorten: URL Shortener just like t.cn,goo.gl etc.

Is is written in NodeJS,using express.js for MVC framework,and using MySQL for storage and Redis for caching.

A demo online: http://fnil.me/

The project is at https://github.com/killme2008/node-shorten

Feel free to modify and use it.Have fun.

posted @ 2012-11-25 20:31 dennis| 编辑 收藏

很久没写博客,一是工作忙,二是没有太多的事情可说。

最近在公司大佬的支持下,建立了一个Clojure语言中文方面的博客和问答网站,欢迎任何对Clojure这门基于JVM之上的函数式语言感兴趣的童鞋贡献原创文章或者资料,申请帐号请看这里

博客地址:  http://blog.clojure.cn/
问答网站:  http://ask.clojure.cn/

欢迎转发和注册使用,谢谢。

邮件列表仍然使用google group:https://groups.google.com/group/cn-clojure/

posted @ 2012-09-25 12:51 dennis 阅读(12221) | 评论 (4)编辑 收藏

Home: https://github.com/killme2008/ring.velocity

A Clojure library designed to render velocity template for ring in clojure.

Usage

Adds dependency in leiningen project.clj:

  [ring.velocity "0.1.0-SNAPSHOT"] 

Create a directory named templates in your project directory to keep all velocity templates.

Create a template templates/test.vm:

  hello,$name,your age is $age. 

Use ring.velocity in your namespace:

  (use '[ring.velocity.core :only [render]]) 

Use render function to render template with vars:

  (render "test.vm" :name "dennis" :age 29) 

The test.vm will be interpreted equals to:

  hello,dennis,your age is 29. 

Use ring.velocity in compojure:

  (defroutes app-routes      
(GET "/" [] (render "test.vm" :name "dennis" :age 29))
(route/not-found "Not Found"))

Use ring.velocity in ring:

  (use '[ring.util.response])   
(response (render "test.vm" :name "dennis" :age 29))

Custom velocity properties,just put a file named ring-velocity.properties to your classpath or resource paths.The default velocity properties is in src/default/velocity.properties.

License

Copyright © 2012 dennis zhuang[killme2008@gmail.com]

Distributed under the Eclipse Public License, the same as Clojure.

Home: https://github.com/killme2008/ring.velocity

posted @ 2012-07-18 00:07 dennis 阅读(9566) | 评论 (0)编辑 收藏


    Clojure的一大优点就是跟Java语言的完美配合,Clojure和Java之间可以相互调用,Clojure可以天然地使用Java平台上的丰富资源。在Clojure里调用一个类的方法很简单,利用dot操作符:

user=> (.substring "hello" 3)
"lo"
user=> (.substring "hello" 0 3)
"hel"

    上面的例子是在clojure里调用String的substring方法做字符串截取。Clojure虽然是一门弱类型的语言,但是它的Lisp Reader还是能识别大多数常见的类型,比如这里hello是一个字符串就可以识别出来,3是一个整数也可以,通过这些类型信息可以找到最匹配的substring方法,在生成字节码的时候避免使用反射,而是直接调用substring方法(INVOKEVIRTUAL指令)。

    但是当你在函数里调用类方法的时候,情况就变了,例如,定义substr函数:
(defn substr [s begin end] (.substring s begin end))

    我们打开*warn-on-reflection*选项,当有反射的时候告警:

user=> (set! *warn-on-reflection* true)
true
user=> (defn substr [s begin end] (.substring s begin end))
Reflection warning, NO_SOURCE_PATH:22 - call to substring can't be resolved.
#'user/substr
   
    问题出现了,由于函数substr里没有任何关于参数s的类型信息,为了调用s的substring方法,必须使用反射来调用,clojure编译器也警告我们调用substring没办法解析,只能通过反射调用。众所周知,反射调用是个相对昂贵的操作(对比于普通的方法调用有)。这一切都是因为clojure本身是弱类型的语言,对参数或者返回值你不需要声明类型而直接使用,Clojure会自动处理类型的转换和调用。ps.在leiningen里启用反射警告很简单,在project.clj里设置:

;; Emit warnings on all reflection calls.
  :warn-on-reflection true
   
过多的反射调用会影响效率,有没有办法避免这种情况呢?有的,Clojure提供了type hint机制,允许我们帮助编译器来生成更高效的字节码。所谓type hint就是给参数或者返回值添加一个提示:hi,clojure编译器,这是xxx类型,我想调用它的yyy方法,请生成最高效的调用代码,谢谢合作:
user=> (defn substr [^String s begin end] (.substring s begin end))
#'user/substr
     
    这次没有警告,^String就是参数s的type hint,提示clojure编译器说s的类型是字符串,那么clojure编译器会从java.lang.String类里查找名称为substring并且接收两个参数的方法,并利用invokevirtual指令直接调用此方法,避免了反射调用。除了target对象(这里的s)可以添加type hint,方法参数和返回值也可以添加type hint:
user=> (defn ^{:tag String} substr [^String s ^Integer begin ^Integer end] (.substring s begin end))
#'user/substr
    
    返回值添加type hint是利用tag元数据,提示substr的返回类型是String,其他函数在使用substr的时候可以利用这个类型信息来避免反射;而参数的type hint跟target object的type hint一样以^开头加上类型,例如这里begin和end都提示说是Integer类型。

    问题1,什么时候应该为参数添加type hint呢?我的观点是,在任何为target object添加type hint的地方,都应该相应地为参数添加type hint,除非你事先不知道参数的类型。为什么呢?因为clojure查找类方法的顺序是这样:

1.从String类里查找出所有参数个数为2并且名称为substring方法
2.遍历第一步里查找出来的Method,如果你有设置参数的type hint,则
查找最匹配参数类型的Method;否则,如果第一步查找出来的Method就一个,直接使用这个Method,相反就认为没有找到对应的Method。
3.如果第二步没有找到Method,使用反射调用;否则根据该Method元信息生成调用字节码。

   因此,如果substring方法的两个参数版本刚好就一个,方法参数有没有type hint都没有关系(有了错误的type hint反而促使反射的发生),我们都会找到这个唯一的方法;但是如果目标方法的有多个重载方法并且参数相同,而只是参数类型不同(Java里是允许方法的参数类型重载的,Clojure只允许函数的参数个数重载),那么如果没有方法参数的type hint,Clojure编译器仍然无法找到合适的调用方法,而只能通过反射。
   
   看一个例子,定义get-bytes方法调用String.getBytes:

user=> (defn get-bytes [s charset] (.getBytes s charset))
Reflection warning, NO_SOURCE_PATH:26 - call to getBytes can't be resolved.
#'user/get-bytes
user=> (defn get-bytes [^String s charset] (.getBytes s charset))
Reflection warning, NO_SOURCE_PATH:27 - call to getBytes can't be resolved.
#'user/get-bytes

    第一次定义,s和charset都没有设置type hint,有反射警告;第二次,s设置了type hint,但是还是有反射警告。原因就在于String.getBytes有两个重载方法,参数个数都是一个,但是接收不同的参数类型,一个是String的charset名称,一个Charset对象。如果我们明确地知道这里charset是字符串,那么还可以为charset添加type hint:
user=> (defn get-bytes [^String s ^String charset] (.getBytes s charset))
#'user/get-bytes
   
    这次才真正的没有警告了。总结:在设置type hint的时候,不要只考虑被调用的target object,也要考虑调用的方法参数。

    问题2:什么时候应该添加tag元数据呢?理论上,在任何你明确知道返回类型的地方都应该添加tag,但是这不是教条,如果一个偶尔被调用的方法是无需这样做的。这一点只对写库的童鞋要特别注意。

    Type hint的原理在上文已经大概描述了下,具体到clojure源码级别,请参考clojure.lang.Compiler.InstanceMethodExpr类的构造函数和emit方法。最后,附送是否使用type hint生成substr函数的字节码之间的差异对比:
未使用type hint 使用type hint

  // access flags 1

  public invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

   L0

    LINENUMBER 14 L0

   L1

    LINENUMBER 14 L1

    ALOAD 1

    ACONST_NULL

    ASTORE 1

    LDC "substring"

    ICONST_2

    ANEWARRAY java/lang/Object

    DUP

    ICONST_0

    ALOAD 2

    ACONST_NULL

    ASTORE 2

    AASTORE

    DUP

    ICONST_1

    ALOAD 3

    ACONST_NULL

    ASTORE 3

    AASTORE

    INVOKESTATIC clojure/lang/Reflector.invokeInstanceMethod (Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;

   L2

    LOCALVARIABLE this Ljava/lang/Object; L0 L2 0

    LOCALVARIABLE s Ljava/lang/Object; L0 L2 1

    LOCALVARIABLE begin Ljava/lang/Object; L0 L2 2

    LOCALVARIABLE end Ljava/lang/Object; L0 L2 3

    ARETURN

    MAXSTACK = 0

    MAXLOCALS = 0

public invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

   L0

    LINENUMBER 15 L0

   L1

    LINENUMBER 15 L1

    ALOAD 1

    ACONST_NULL

    ASTORE 1

    CHECKCAST java/lang/String

    ALOAD 2

    ACONST_NULL

    ASTORE 2

    CHECKCAST java/lang/Number

    INVOKESTATIC clojure/lang/RT.intCast (Ljava/lang/Object;)I

    ALOAD 3

    ACONST_NULL

    ASTORE 3

    CHECKCAST java/lang/Number

    INVOKESTATIC clojure/lang/RT.intCast (Ljava/lang/Object;)I

    INVOKEVIRTUAL java/lang/String.substring (II)Ljava/lang/String;

   L2

    LOCALVARIABLE this Ljava/lang/Object; L0 L2 0

    LOCALVARIABLE s Ljava/lang/Object; L0 L2 1

    LOCALVARIABLE begin Ljava/lang/Object; L0 L2 2

    LOCALVARIABLE end Ljava/lang/Object; L0 L2 3

    ARETURN

    MAXSTACK = 0

    MAXLOCALS = 0


    
    对比很明显,没有使用type hint,调用clojure.lang.Reflector的invokeInstanceMethod方法,使用反射调用(具体见clojure.lang.Reflector.java),而使用了type hint之后,则直接使用invokevirtual指令(其他方法可能是invokestatic或者invokeinterface等指令)调用该方法,避免了反射。
      

    参考:

posted @ 2012-07-10 20:37 dennis 阅读(12021) | 评论 (1)编辑 收藏


    HouseMD是淘宝的聚石写的一个非常优秀的Java进程运行时诊断和调试工具,如果你接触过btrace,那么HouseMD也许你应该尝试下,它比btrace更易用,不需要写脚本,类似strace的方式attach到jvm进程做跟踪调试。

    基本的安装和使用请看这篇文档《UserGuide》,恕不重复。以下内容都假设你正确安装了housemd。

    本文主要介绍下怎么用housemd诊断跟踪clojure进程。Clojure的java实现也是跑在JVM里,当然也可以用housemd。

    我们以一个简单的例子开始,假设我们有如下clojure代码:
(loop [x 1]
  (Thread/sleep 1000)
  (prn x)
  (recur (inc x)))

    这段很简单,只是间隔一秒不断地打印递增的数字x。我们准备用housemd跟踪这个程序的运行,首先运行这个程序,你可以用lein,也可以直接java命令运行:
java -cp clojure.jar clojure.main test.clj

    运行时不断地在控制台打印数字,通过jps或者ps查询到该进程的id,假设为pid,使用housemd连接到该进程:
housemd <pid>
    顺利进入housemd的交互控制台,通过help命令可以查询支持的命令:

housemd> help

quit      terminate the process.
help      display this infomation.
trace     display or output infomation of method invocaton.
loaded    display loaded classes information.

    要用housemd调试clojure,你需要对clojure的实现有一点点了解,有兴趣可以看过去的一篇blog《clojure hacking guide》,简单来说,clojure的编译器会将clojure代码编译成java类并运行。对于JVM来说,clojure生成的类,跟java编译器生成类没有什么不同。
    具体到上面的clojure代码,会生成一个名为user$eval1的类,user是默认的namespace,而eval1是clojure编译器自动生成的一个标示类名,通过loaded命令查询类的加载情况:
housemd> loaded user$eval1 -h
user$eval1 -> null
    - clojure.lang.DynamicClassLoader@1d25d06e
        - clojure.lang.DynamicClassLoader@1d96f4b5
            - sun.misc.Launcher$AppClassLoader@a6eb38a
                - sun.misc.Launcher$ExtClassLoader@69cd2e5f

    通过-h选项打印了加载user$eval1的类加载器的层次关系,因为user$eval1是动态生成的(clojure启动过程中),因此它不在任何一个class或者jar文件中。除了查询user namespace的类之外,你还可以查询clojure.core,clojure.lang,clojure.java等任何被加载进来的类,例如查询clojure.core.prn的类,在clojure里这是一个函数,在jvm看来这只是一个类:
housemd> loaded -h core$prn
clojure.core$prn -> /Volumes/HDD/Users/apple/clojure/clojure.jar
    - sun.misc.Launcher$AppClassLoader@a6eb38a
        - sun.misc.Launcher$ExtClassLoader@69cd2e5f
   注意,不需要完整的namespace——clojure.core,直接core$prn即可。其他也是类似。小技巧:如果你实在不知道clojure编译器生成的类名,你可以利用jvm自带的jmap命令来查询。

   接下来,我们尝试用trace命令跟踪方法的运行,例如例子中的clojure代码用到了loop和recur两个sepcial form,我们跟踪下loop:
housemd> trace -t 5 core$loop
INFO : probe class clojure.core$loop
core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null
core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null

core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null
core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null

core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null
core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null

core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null
core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null

core$loop.doInvoke(Object, Object, Object, Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null
core$loop.getRequiredArity()                          sun.misc.Launcher$AppClassLoader@a6eb38a            0            -ms    null

INFO : Ended by timeout
INFO : reset class clojure.core$loop

    在5秒内,clojure.core$loop类有两个方法各被调用了5次,doInvoke是实际的调用,而getRequiredArity用来查询loop所需要的参数个数。trace还可以跟踪到具体的方法,例如我们跟踪prn函数的调用情况:
housemd> trace -t 5 core$prn.doInvoke
INFO : probe class clojure.core$prn
core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            1            1ms    clojure.core$prn@3e4ac866

core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            2           <1ms    clojure.core$prn@3e4ac866

core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            3           <1ms    clojure.core$prn@3e4ac866

core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            4           <1ms    clojure.core$prn@3e4ac866

core$prn.doInvoke(Object)    sun.misc.Launcher$AppClassLoader@a6eb38a            5           <1ms    clojure.core$prn@3e4ac866

INFO : Ended by timeout
INFO : reset class clojure.core$prn
  
   trace打印了方法的调用次数(5秒内)和每次调用的时间(毫秒级别),以及调用的target object。小技巧:没有可变参数的函数生成类最终调用的是invoke方法(参数个数可能重载),有可变参数的函数调用的是doInvoke方法。

   trace命令还支持打印调用堆栈到文件,例如:
trace -t 5 -d -s  core$prn.doInvoke

   利用-s和-d命令会将详细的调用信息输出到临时目录,临时目录的路径可以通过trace help命令查询到,在我的机器上是/tmp/trace/<pid>@host目录下。调用堆栈的输出类似:
example$square.invoke(Long) call by thread [main]
    example$eval9.invoke(test.clj:11)
    clojure.lang.Compiler.eval(Compiler.java:6465)
    clojure.lang.Compiler.load(Compiler.java:6902)
    clojure.lang.Compiler.loadFile(Compiler.java:6863)
    clojure.main$load_script.invoke(main.clj:282)
    clojure.main$script_opt.invoke(main.clj:342)
    clojure.main$main.doInvoke(main.clj:426)
    clojure.lang.RestFn.invoke(RestFn.java:421)
    clojure.lang.Var.invoke(Var.java:405)
    clojure.lang.AFn.applyToHelper(AFn.java:163)
    clojure.lang.Var.applyTo(Var.java:518)
    clojure.main.main(main.java:37)

   上面这个简单的例子展示了使用housemd跟踪诊断clojure进程的方法。

   自定义ns和函数的调试与此类似,假设我们有下面的clojure代码:
(ns example)
(defn square [x]
  (* x x))

(loop [x 1]
  (Thread/sleep 1000)
  (square x)
  (recur (inc x)))
 
   ns为example,自定义函数square并定期循环调用。使用housemd诊断这段代码:
loaded -h example$square     #查询square的加载情况
trace -t 10 -d -s example$square.invoke  #跟踪10秒内square的调用情况

posted @ 2012-06-15 02:52 dennis 阅读(12237) | 评论 (2)编辑 收藏

我们在维护的淘宝开源消息中间件的metaqgithub分支,今天发布了1.4.2版本,主要做了如下改进:

1.支持发送和订阅分离,可以细粒度地控制Broker或者某个Topic是否接收消息和接受订阅。服务端添加新选项acceptPublish和acceptSubscribe。

2.更友好地关闭Broker,梳理关闭流程并通过JMX调用方法关闭替代原来简单的kill。

3.更新python客户端到0.2版本,可以通过pip安装:  pip install metaq

4.发布ruby语言客户端meta-ruby 0.1版本。

5.其他小改进:升级gecko到1.1.1版本,升级quartz到2.1.4版本,添加集成测试工程和内部重构等。

6.新文档《使用log4j扩展发送消息》

简介:https://github.com/killme2008/Metamorphosis/wiki/介绍
下载:https://github.com/killme2008/Metamorphosis/downloads

文档:https://github.com/killme2008/Metamorphosis/wiki

posted @ 2012-06-04 10:03 dennis 阅读(11328) | 评论 (1)编辑 收藏


    你有个任务,需要用到某个开源项目;或者老大交代你一个事情,让你去了解某个东西。怎么下手呢?如何开始呢?我的习惯是这样:

1.首先,查找和阅读该项目的博客和资料,通过google你能找到某个项目大体介绍的博客,快速阅读一下就能对项目的目的、功能、基本使用有个大概的了解。

2.阅读项目的文档,重点关注类似Getting started、Example之类的文档,从中学习如何下载、安装、甚至基本使用该项目所需要的知识。

3.如果该项目有提供现成的example工程,首先尝试按照开始文档的介绍运行example,如果运行顺利,那么恭喜你顺利开了个好头;如果遇到问题,首先尝试在项目的FAQ等文档里查找答案,再次,可以将问题(例如异常信息)当成关键词去搜索,查找相关的解决办法,你遇到了,别人一般也会遇到,热心的朋友会记录下解决的过程;最后,可以将问题提交到项目的邮件列表,请大家帮你看看。在没有成功运行example之前,不要尝试修改example。

4.运行了第一个example之后,尝试根据你的理解和需要修改example,测试高级功能等。

5.在了解基本使用后,需要开始深入的了解该项目。例如项目的配置管理、高级功能以及最佳实践。通常一个运作良好的项目会提供一份从浅到深的用户指南,你并不需要从头到尾阅读这份指南,根据时间和兴趣,特别是你自己任务的需要,重点阅读部分章节并做笔记(推荐evernote)。

6.如果时间允许,尝试从源码构建该项目。通常开源项目都会提供一份构建指南,指导你如何搭建一个用于开发、调试和构建的环境。尝试构建一个版本。

7.如果时间允许并且有兴趣,可以尝试阅读源码:
(1)阅读源码之前,查看该项目是否提供架构和设计文档,阅读这些文档可以了解该项目的大体设计和结构,读源码的时候不会无从下手。
(2)阅读源码之前,一定要能构建并运行该项目,有个直观感受。
(3)阅读源码的第一步是抓主干,尝试理清一次正常运行的代码调用路径,这可以通过debug来观察运行时的变量和行为。修改源码加入日志和打印可以帮助你更好的理解源码。
(4)适当画图来帮助你理解源码,在理清主干后,可以将整个流程画成一张流程图或者标准的UML图,帮助记忆和下一步的阅读。
(5)挑选感兴趣的“枝干”代码来阅读,比如你对网络通讯感兴趣,就阅读网络层的代码,深入到实现细节,如它用了什么库,采用了什么设计模式,为什么这样做等。如果可以,debug细节代码。
(6)阅读源码的时候,重视单元测试,尝试去运行单元测试,基本上一个好的单元测试会将该代码的功能和边界描述清楚。
(7)在熟悉源码后,发现有可以改进的地方,有精力、有意愿可以向该项目的开发者提出改进的意见或者issue,甚至帮他修复和实现,参与该项目的发展。

8.通常在阅读文档和源码之后,你能对该项目有比较深入的了解了,但是该项目所在领域,你可能还想搜索相关的项目和资料,看看有没有其他的更好的项目或者解决方案。在广度和深度之间权衡。

    以上是我个人的一些习惯,我自己也并没有完全按照这个来,但是按照这个顺序,基本上能让你比较高效地学习和使用某个开源项目。

posted @ 2012-05-22 23:12 dennis 阅读(25757) | 评论 (9)编辑 收藏


    很久没更新博客了,在北京工作,忙碌并且充实。目前来说,Clojure最好的开发编辑器应该是Emacs + Slime的组合,利用swank-clojure这个项目,加上clojure-mode,可以完美地运行slime。编译、运行、跳转、文档和引用查看甚至debug都可以搞定。具体配置恕不重复,看swank-clojure的文档即可自己安装起来,或者这篇中文博客windows上配置

    分享几个Tip,也期待大家分享你们的使用心得。

    首先是自动在打开clj后缀文件的时候启动执行clojure-jack-in与slime连接,可以在emacs配置里加上个callback:

(eval-after-load "clojure-mode"
  '(progn
     (require 'slime)
     (require 'clojure-mode)
     (unless (slime-connected-p)
       (save-excursion (clojure-jack-in)))))
    这样在打开clj为后缀的文件的时候,将自动启动clojure-mode执行clojure-jack-in函数并且连接slime。

    将clj后缀的文件自动关联到clojure-mode:
(setq auto-mode-alist (cons '("\\.clj$" . clojure-mode) auto-mode-alist))
    通常来说如果你是利用marmalade安装的,会自动关联的。

    另外,启动自动匹配括号、字符串引号等的paredit模式一定要启动:
(defun paredit-mode-enable () (paredit-mode 1))
(add-hook 'clojure-mode-hook 'paredit-mode-enable)
(add-hook 'clojure-test-mode-hook 'paredit-mode-enable)

   在使用clojure-mode或者clojure-test-mode的时候自动启用paredit模式,括号再也不是问题。括号匹配提示一般是开启的,如果没有,强制开启:

;;    显示括号匹配
(show-paren-mode t)
(setq show-paren-style 'parentheses)

    slime更多配置,启用IO重定向(多线程IO输出都定向到SLIME repl)以及设置通讯字符编码等:

(eval-after-load "slime"
  '(progn
     (slime-setup '(slime-repl slime-fuzzy))
     ;;(setq slime-truncate-lines t)
     (setq  swank:*globally-redirect-io*  t)
     ;; (setq slime-complete-symbol-function ' slime-fuzzy-complete-symbol)
     (setq slime-net-coding-system 'utf-8-unix)))

    细心的朋友可能注意到我注释了slime-fuzzy-complete的配置,这是一个支持更好的自动补全功能的SLIME插件(可以用缩写来自动补全),可惜在我机器上没有尝试配置成功,有兴趣你可以尝试下。

    在REPL里支持语法高亮,一定要配置上:

(add-hook 'slime-repl-mode-hook
          (defun clojure-mode-slime-font-lock ()
            (require 'clojure-mode)
            (let (font-lock-mode)
              (clojure-mode-font-lock-setup))))

    单独在clojure-mode(在其他mode里这些快捷键不会起作用)里配置快捷键可以这样:
(eval-after-load "clojure-mode"
  '(progn
     (require 'slime)
     (require 'clojure-mode)
     (define-key clojure-mode-map (kbd "M-/")  (quote slime-complete-symbol))
     (define-key clojure-mode-map (kbd "C-c s")  (quote slime-selector)))

   例如我这里将M-/作为自动补全的快捷键,因为meta键在我的Mac机器上设置为command键,因此自动补全的操作习惯就跟Eclipse类似。而slime-selector是一个非常有用的函数,用来跳转到slime的一系列buffer,因此我绑定了C-c s快捷键。

    额外一提,在Mac osx下,将command作为meta键:
;;; I prefer cmd key for meta
(setq mac-option-key-is-meta nil
      mac-command-key-is-meta t
      mac-command-modifier 'meta
      mac-option-modifier 'none)

    最后,期待大家不吝分享你的心得。
    

posted @ 2012-05-19 00:57 dennis 阅读(15400) | 评论 (11)编辑 收藏


    My weekend project clj.monitor is beta release,it's a clojure DSL for monitoring system and applications based on SSH.

Home:https://github.com/killme2008/clj.monitor

An example:
(ns clj.monitor.example
  (:use [clj.monitor.core]
        [control.core]
        [clj.monitor.tasks]))

;;define a mysql cluster
(defcluster mysql
  :clients [{:user "deploy" :host "mysql.app.com"}])

;;define a monitor for mysql cluster
(defmonitor mysql-monitor
  :tasks [(ping-mysql "root" "password")
            (system-load :5 3)]
  :clusters [:mysql])

;;start monitors
(start-monitors
 :cron "* 0/5 * * * ?"
 :alerts [(mail :from "alert@app.com" :to "yourname@app.com")]
 :monitors [mysql-monitor])

API document: http://fnil.net/clj.monitor

It is just a beta release,if you have any questions or find issues ,please let me know,thanks.

posted @ 2012-05-12 22:38 dennis 阅读(9383) | 评论 (5)编辑 收藏


    我们在维护的淘宝开源消息中间件的metaq的github分支,今天发布了1.4.2版本,主要做了如下改进:

    1.添加了大量的使用和原理文档,参见Wiki
    2.合并tools和server-wrapper工程,提供统一的脚本来管理Broker,管理Broker的工作变得非常容易,全部工作都可以通过metaServer.sh的脚本来执行。同时提供了bat启动脚本,用于在windows上启动Broker做测试。
    3.新功能:
   (1)新的客户端API用来获取topic的分区列表
   (2)新的客户端API用来获取Broker的统计信息
   (3)异步复制的Slave可以自动获取Master的配置变更,例如Master在配置文件中新增或者删除了topic并顺利reload热加载成功后,slave可自动复制或者移除变更的topic,无需重启。
   (4)新的统计项目,可以通过'stats config'协议获取Broker的配置文件。
    4.添加meta-python项目,一个python的客户端,暂时仅支持发送消息功能。
    5.其他小改进,如统计信息的优化、构建工具的整合等。

    更详细的发行日志请看RelaseNotes

    下载地址:  https://github.com/killme2008/Metamorphosis/downloads
    入门指南:  《如何开始
    更多文档请看Wiki

posted @ 2012-05-09 22:47 dennis 阅读(4740) | 评论 (1)编辑 收藏


    我发现很多人没办法高效地解决问题的关键原因是不熟悉工具,不熟悉工具也还罢了,甚至还不知道怎么去找工具,这个问题就大条了。我想列下我能想到的一个Java程序员会用到的常用工具。

一、编码工具

1.IDE:Eclipse或者IDEA,熟悉尽可能多的快捷键,《Eclipse常见快捷键列表
2.插件: 
(1) Findbugs,在release之前进行一次静态代码检查是必须的
(2) Clover,关心你的单元测试覆盖率
(3) Checkstyle 代码风格检查

3.构建和部署工具:ant或者maven,现在主流都是maven了吧,使用nexus搭建maven私服,再加上持续集成jenkins。代码质量不用愁。

4.版本管理工具: svn或者git

5.diff和patch

6.设置你的eclipse或者IDEA,如formatter,save actions以及code template等。代码风格,直接用google的也可以啊。《Google style guide

7.掌握一个文本编辑器,Emacs或者VIM,熟悉常用快捷键。这在你需要在线编辑代码,或者编写其他语言代码时候特别有用。《神器圣战

二、JDK相关

1.jstat : 观察GC情况,如:

jstat -gcutil pid 2000

2.jmap,查看heap情况,如查看存活对象列表:
jmap -histo:live pid |grep com.company |less 

或者dump内存用来分析:

jmap -dump:file=test.bin pid

3.分析dump的堆文件,可以用jhat:

jhat test.bin

  分析完成后可以用浏览器查看堆的情况。这个工具的分析结果还比较原始,你还可以用Eclipse MAT插件进行图形化分析,或者IBM的Heap Analyzer.

4.jvisualvm和jconsole: JVM自带的性能分析和监控工具,怎么用?请自己看文档。

5.jstack:分析线程堆栈,如

jstack pid > thread_dump

    查看CPU最高的线程在干什么的方法结合top和jstack:http://www.iteye.com/topic/1114219

6.更多JVM工具,参见官方文档:http://docs.oracle.com/javase/6/docs/technotes/tools/

7.学习使用btrace分析java运行时问题。《Btrace使用简介

8.GC日志分析工具:GC viewerGC-console或者自己挑吧。

9.性能分析工具,除了自带的jvisualvm外,还可以用商业的jprofiler

10.JVM参数大全

11.《JVM调优标准参数陷阱》,iteye神贴。

三、Linux工具

1.熟悉常用的shell命令,


3.使用htop替换top。

4.熟悉下strace,gdb甚至systemtap来分析问题。

5.熟悉vmstat,iostat,sar等性能统计工具。

5.自动化部署脚本,py-fabric或者自荐下我的clojure-control

四、其他

1.掌握一门脚本语言,Python或者Ruby,高效解决一些需要quick and dirty的任务:比如读写文件、导入导出数据库、网页爬虫等。注意不是python.com,咔咔。

2.使用Linux或者Mac os系统作为你的开发环境。

3.升级你的“硬件工具”,双屏大屏显示器、SSD、8G内存甚至更多。

4.你懂的:https://code.google.com/p/goagent/

五、如何查找工具?

1.搜索引擎,google或者baidu,《搜索技巧

2.万能的stack overflow:http://stackoverflow.com/

3.虚心问牛人。

六、最重要的是⋯⋯

一颗永不停止学习的心。

posted @ 2012-04-17 17:05 dennis 阅读(19343) | 评论 (17)编辑 收藏


    最近陆陆续续补充了不少metaq的文档,部分是直接从官方文档里摘抄出来,放在了github工程的wiki页,有兴趣了解甚至使用meta的可以仔细阅读下,一份目录:     
    后续还会继续补充。

posted @ 2012-04-13 22:43 dennis 阅读(47652) | 评论 (13)编辑 收藏

    Java世界里有findbugs这样的神器,可以让你避免很多“简单愚蠢”的bug。同样,Clojure世界里也有相应的替代品,这就是今天要介绍的kibit。不过kibit现在还比较年轻,判断的规则较少,但是已经可以使用起来做clojure代码的静态检查。

项目主页:https://github.com/jonase/kibit
使用:
1.安装lein插件:
lein plugin install jonase/kibit 0.0.2

2.在项目的根目录运行
lein kibit

kibit会分析项目里所有clojure源码,每个namespace分别分析,例如我分析clojure-control的输出:

== control.commands ==
== control.core ==
[186] Consider (zero? (:status (ssh host user cluster (str "test -e " file)))) instead of (= (:status (ssh host user cluster (str "test -e " file))) 0)
== control.main ==
== leiningen.control ==
[null] Consider Integer/parseInt instead of (fn* [p1__61444#] (Integer/parseInt p1__61444#))
[null] Consider Integer/parseInt instead of (fn* [p1__65254#] (Integer/parseInt p1__65254#))

    显然,kibit一个一个namespace分析过去,并且按照规则对它认为有问题的地方打印出来,并提出建议。例如这里它建议我用
(zero? (:status (ssh host user cluster (str "test -e " file))))
    替换control.core里186行的:
 (= (:status (ssh host user cluster (str "test -e " file))) 0)

    目前kibit大多数是这类代码风格上的检查,还没有做到类似findbugs那样更丰富的检查,例如NPE异常检查等。此外kibit还提供反射检查,任何有反射调用的地方都给出警告。
    kibit是基于core.logic实现的,它的规则都放在了这里,通过defrules宏来定义检查规则,源码中对算术运算的规则定义:
(defrules rules
  [(+ ?x 1) (inc ?x)]
  [(+ 1 ?x) (inc ?x)]
  [(- ?x 1) (dec ?x)]

  [(* ?x (* . ?xs)) (* ?x . ?xs)]
  [(+ ?x (+ . ?xs)) (+ ?x . ?xs)])
   
    第一个规则,任何对类似(+ 1 x)的代码,都建议替换成(inc x),后面的与此类似。理论上你也可以自定义规则,并提交给官方。总体上说kibit仍然是比不上findbugs的,期待未来发展的更好。

posted @ 2012-03-23 21:28 dennis 阅读(4624) | 评论 (0)编辑 收藏


    我们经常需要在程序中测量某段代码的性能,或者某个函数的性能,在Java中,我们可能简单地循环调用某个方法多少次,然后利用System.currentTimeMillis()方法测量下时间。在Ruby中,一般都是用Benchmark module做测试,提供了更详细的报告信息。

    同样,在Clojure里你可以做这些事情,你仍然可以使用System.currentTimeMillis()来测量运行时间,例如:

user=> (defn sum1 [& args] (reduce + 0 args))
#'user/sum1

user=> (defn sum2 [& args]
             (loop [rt 0
                    args args]
                 (if args
                   (recur (+ rt (first args)) (next args))
                   rt)))
#'user/sum2

user=> (defn bench [sum n]
         (let [start (System/currentTimeMillis)
               nums (range 0 (+ n 1))]
           (dotimes [_ n] (apply sum nums))
           (println (- (System/currentTimeMillis) start))))

user=> (bench sum1 10000)
1818
nil
user=> (bench sum2 10000)
4220
nil
   
    定义两个求和函数sum1和sum2,一个是利用reduce,一个是自己写loop,然后写了个bench函数循环一定次数执行sum函数并给出执行时间,利用System.currentTimeMillis()方法。显然sum1比sum2快了一倍多。为什么更快?这不是我们的话题,有兴趣可以自己看reduce函数的实现。

    除了用System.currentTimeMillis()这样的java方式测量运行时间外,clojure还提供了time宏来包装这一切:
user=> (doc time)
-------------------------
clojure.core/time
([expr])
Macro
  Evaluates expr and prints the time it took.  Returns the value of
 expr.
nil

    time宏用的不是currentTimeMillis方法,而是JDK5引入的nanoTime方法更精确。重写bench函数:
user=> (defn bench [sum n]
             (time (dotimes [_ n] (apply sum (range 0 (+ n 1))))))
#'user/bench

user=> (bench sum1 10000)
"Elapsed time: 5425.074 msecs"
nil
user=> (bench sum2 10000)
"Elapsed time: 7893.412 msecs"
nil
     尽管精度不一致,仍然可以看出来sum1比sum2快。
    
     这样的测试仍然是比较粗糙的,真正的性能测试需要考虑到JVM JIT、warm up以及gc带来的影响,例如我们可能需要预先执行函数多少次来让JVM“预热”这些代码。庆幸的是clojure世界里有一个开源库Criterium帮你自动搞定这一切,它的项目主页也在github上:https://github.com/hugoduncan/criterium

     首先在你的项目里添加criterium依赖:
:dependencies [[org.clojure/clojure "1.3.0"]
                        [criterium "0.2.0"]])
   
     接下来引用criterium.core这个ns,因为criterium主要宏也叫bench,因此我们原来的bench函数不能用了,换个名字叫bench-sum:
user=> (use 'criterium.core)
nil
user=> (defn bench-sum [sum n]
              (with-progress-reporting (bench (apply sum (range 0 (+ 1 n))) :verbose)))
#'user/bench-sum

     调用criterium的bench宏执行测试,使用with-progress-reporting宏包装测试代码并汇报测试进展,测试进展会打印在标准输出上。请注意,我这里并没有利用dotimes做循环测试,因为criterium会自己计算应该运行的循环次数,我们并不需要明确指定,测试下结果:
user=> (bench-sum sum1 10000)
Cleaning JVM allocations 
Warming up for JIT optimisations 
Estimating execution count 
Running with sample-count 60 exec-count 1417 
Checking GC
Cleaning JVM allocations 
Finding outliers 
Bootstrapping 
Checking outlier significance
x86_64 Mac OS X 10.7.3 4 cpu(s)
Java HotSpot(TM) 64-Bit Server VM 20.4-b02-402
Runtime arguments: -Dclojure.compile.path=/Users/apple/programming/avos/logdashboard/test/classes -Dtest.version=1.0.0-SNAPSHOT -Dclojure.debug=false
Evaluation count             : 85020
             Execution time mean : 722.730169 us  95.0% CI: (722.552670 us, 722.957586 us)
    Execution time std-deviation : 1.042966 ms  95.0% CI: (1.034972 ms, 1.054015 ms)
         Execution time lower ci : 692.122089 us  95.0% CI: (692.122089 us, 692.260198 us)
         Execution time upper ci : 768.239944 us  95.0% CI: (768.239944 us, 768.305222 us)

Found 2 outliers in 60 samples (3.3333 %)
    low-severe     2 (3.3333 %)
 Variance from outliers : 25.4066 % Variance is moderately inflated by outliers
nil


user=> (bench-sum sum2 10000)
Cleaning JVM allocations 
Warming up for JIT optimisations 
Estimating execution count 
Running with sample-count 60 exec-count 917 
Checking GC
Cleaning JVM allocations 
Finding outliers 
Bootstrapping 
Checking outlier significance
x86_64 Mac OS X 10.7.3 4 cpu(s)
Java HotSpot(TM) 64-Bit Server VM 20.4-b02-402
Runtime arguments: -Dclojure.compile.path=/Users/apple/programming/avos/logdashboard/test/classes -Dtest.version=1.0.0-SNAPSHOT -Dclojure.debug=false
Evaluation count             : 55020
             Execution time mean : 1.070884 ms  95.0% CI: (1.070587 ms, 1.071136 ms)
    Execution time std-deviation : 1.057659 ms  95.0% CI: (1.050688 ms, 1.062877 ms)
         Execution time lower ci : 1.024195 ms  95.0% CI: (1.024164 ms, 1.024195 ms)
         Execution time upper ci : 1.145664 ms  95.0% CI: (1.145664 ms, 1.145741 ms)

Found 1 outliers in 60 samples (1.6667 %)
    low-severe     1 (1.6667 %)
 Variance from outliers : 19.0208 % Variance is moderately inflated by outliers
nil


    这个报告是不是相当专业?不是搞统计还不一定读的懂。大概解读下,sample-count是取样次数,默认是60次,exec-count是测试的执行次数(不包括前期warm up和JIT在内),CI是可信区间,这里取95%,Execution time mean是平均执行时间,而lower和upper是测试过程中执行时间的最小和最大值。而outliers是这一组测试中的异常值,比如执行sum1测试发现了2组异常结果。从结果来看,sum1的平均执行时间是722微秒,而sum2的平均执行时间是1.07毫秒,因此还是sum1更快一些。

    总结下,如果只是在开发过程中做一些小块代码的简单测试,可以直接利用内置的time宏,如果你希望做一次比较标准的性能测试,那么就应该利用criterium这个优秀的开源库。

posted @ 2012-03-22 21:14 dennis 阅读(4194) | 评论 (0)编辑 收藏


    继续Clojure世界之旅,介绍下我今天的探索成果,使用clojure生成clojure项目的API文档。在java里,我们是利用javadoc生成API文档,各种build工具都提供了集成,例如maven和ant都提供了javadoc插件或者task。在Clojure世界里,同样有一系列工具帮助你从源码中自动化生成API文档。今天主要介绍三个工具。不过我不会介绍怎么在clojure里写doc,具体怎么做请看一些开源项目,或者直接看clojure.core的源码。

    首先是codox,使用相当简单,我们这里都假设你使用Leiningen作为构建工具,在project.clj里添加codox依赖:
  :dev-dependencies    [[codox "0.5.0"]]

    解下执行lein doc命令即可生成文档,生成的文档放在doc目录,在浏览器里打开index.html即可观察生成的文档。我给clojure-control生成的codox文档可以看这个链接,效果还是不错的。

    第二个要介绍的工具是marginalia,使用方法类似codox,首先还是添加依赖:
:dev-dependencies [lein-marginalia "0.7.0"]
   
    执行lein deps处理依赖关系,然后执行lein marg命令即可在docs目录生成文档,与codox不同的是marginalia只生成一个html文件,没有带js和css,但是效果也不错,可以看我给clojure-control生成的marg文档链接marginalia生成的文档说明和源码左右对照,很利于阅读源码。

   最后要介绍的就是Clojure.org自己在使用的autodoc,如果你喜欢clojure.org上的API文档格式可以采用这个工具。并且autodoc可以跟github pages结合起来,生成完整的项目文档并展示在github上。例如以clojure-control为例来介绍整个过程。首先你需要配置你的github pages,参照这个链接
http://pages.github.com/

    第一步,仍然是在project.clj添加依赖:
:dev-dependencies [[lein-autodoc "0.9.0"]]
       第二步,在你的.gitignore里忽略autodoc目录:
autodoc/**
       将这些更改提交到github上,接下来在你的项目目录clone一份项目源码到<project>/autodoc目录:
 git clone git@github.com:<user name>/<project name>.git autodoc
       进入autodoc目录,执行下列命令创建一个gh-pages分支:
 $ cd autodoc
 $ git symbolic-ref HEAD refs/heads/gh-pages
 $ rm .git/index
 $ git clean -fdx
 $ cd ..
     回到项目根目录后,执行lein autodoc命令在autodoc目录生成文档:
lein autodoc
     接下来将生成的文档推送到github分支上:
 $cd autodoc 
 $ git add -A
 $ git commit -m"Documentation update"
 $ git push origin gh-pages
    等上几分钟,让github渲染你的文档,最终的效果看这个链接 http://killme2008.github.com/clojure-control     
    autodoc和marginalia都支持maven,具体使用请看他们的文档。

posted @ 2012-03-21 22:24 dennis 阅读(4304) | 评论 (0)编辑 收藏

    前面一篇博客介绍了我在github上的一个metaq分支,今天下午写了个metaq的python客户端,目前仅支持发送消息功能,不过麻雀虽小,五脏俱全,客户端和zookeeper的交互和连接管理之类都还具备,不出意外,我们会首先用上。第一次正儿八经地写python代码,写的不好的地方请尽管拍砖,多谢。
    项目叫meta-python,仍然放在github上:https://github.com/killme2008/meta-python

    使用需要先安装zkpython这个库,具体安装这篇博客,使用很简单,发送消息:
    from metamorphosis import Message,MessageProducer,SendResult
    p=MessageProducer("topic")
    message=Message("topic","message body")
    print p.send(message)
    p.close()

    
MessageProducer就是消息发送者,它的构造函数接受至少一个topic,默认的zk_servers为localhost:2181,可以通过zk_servers参数指定你的zookeeper集群:

p=MessageProducer("topic",zk_servers="192.168.1.100:2191,192.168.1.101:2181")

更多参数请直接看源码吧。一个本机的性能测试(meta和客户端都跑在我的机器上,机器是Mac MC700,osx 10.7,磁盘没有升级过):
from metamorphosis import Message,MessageProducer
from time import time
p=MessageProducer("avos-fetch-tasks")
message=Message("avos-fetch-tasks","http://www.taobao.com")
start=time()
for i in range(0,10000):
    sent=p.send(message)
    if not sent.success:
        print "send failed"
finish=time()
secs=finish-start
print "duration:%s seconds" % (secs)
print "tps:%s msgs/second" % (10000/secs)
p.close()

 结果:


duration:1.85962295532 seconds
tps:5377.43415749 msgs/second

posted @ 2012-03-21 19:08 dennis 阅读(5614) | 评论 (1)编辑 收藏

    开源的memcached Java客户端——xmemcached发布1.3.6版本。

    主要改进如下: 

1.  为MemcachedClientBuilder添加两个新方法用于配置:

public void setConnectTimeout(long connectTimeout);  
public void setSanitizeKeys(boolean sanitizeKeys);

 

2.  用于hibernate的XmemcachedClientFactoryd添加了connectTimeout属性,感谢网友 Boli.Jiang的贡献。

3.  添加新的枚举类型 net.rubyeye.xmemcached.transcoders.CompressionMode,用于指定Transcoder的压缩类型,默认是ZIP压缩,可选择GZIP压缩。Transcoder接口添加setCompressionMode方法。

4.  修改心跳规则,原来是在连接空闲的时候发起心跳,现在变成固定每隔5秒发起一次心跳检测连接。

5.  修改默认参数,默认禁用nagle算法,默认将批量get的合并因子下降到50。

6.  修复bug和改进,包括:161163165169172、173176179180

 

项目主页:http://code.google.com/p/xmemcached/

项目文档:http://code.google.com/p/xmemcached/w/list

下载:http://code.google.com/p/xmemcached/downloads/list

源码:https://github.com/killme2008/xmemcached

 

Maven依赖:

 <dependency>  

    <groupId>com.googlecode.xmemcached</groupId>  
    <artifactId>xmemcached</artifactId>  
    <version>1.3.6</version>  
</dependency> 

    最后感谢所有提出issue和改进意见的朋友们。

posted @ 2012-03-19 10:51 dennis 阅读(4959) | 评论 (3)编辑 收藏