庄周梦蝶

生活、程序、未来

Clojure的条件编译

| Comments

在C或者CPP中,条件编译很常见,比如最常见的防止重复包含头文件:

#ifndef FUNCA_H
#define FUNCA_H
//头文件内容
#endif

最近读Reducer库源码,看到一个很精彩的宏,做到类似这样的条件编译:

 (defmacro ^:private compile-if
  "Evaluate `exp` and if it returns logical true and doesn't error, expand to
  `then`.  Else expand to `else`.

  (compile-if (Class/forName \"java.util.concurrent.ForkJoinTask\")
    (do-cool-stuff-with-fork-join)
    (fall-back-to-executor-services))"
  [exp then else]
  (if (try (eval exp)
           (catch Throwable _ false))
    `(do ~then)
    `(do ~else)))

compile-if当(eval exp)的结果为true并且没有异常的时候,展开then表达式,否则展开else表达式,在Reducer库中使用这个宏来初始化Fork/Join框架,如果当前环境是JDK7+,直接使用Fork/Join的API,否则尝试JSR166的实现,判断当前JDK版本是通过尝试执行(Class/forName “java.util.concurrent.ForkJoinTask”)查找有没有ForkJoinTask这个类来做到:

(compile-if
 (Class/forName "java.util.concurrent.ForkJoinTask")
 ;; We're running a JDK 7+
 (do
   (def pool (delay (java.util.concurrent.ForkJoinPool.)))

   (defn fjtask [^Callable f]
     (java.util.concurrent.ForkJoinTask/adapt f))

   (defn- fjinvoke [f]
     (if (java.util.concurrent.ForkJoinTask/inForkJoinPool)
       (f)
       (.invoke ^java.util.concurrent.ForkJoinPool @pool ^java.util.concurrent.ForkJoinTask (fjtask f))))

   (defn- fjfork [task] (.fork ^java.util.concurrent.ForkJoinTask task))

   (defn- fjjoin [task] (.join ^java.util.concurrent.ForkJoinTask task)))
 ;; We're running a JDK <7
 (do
   (def pool (delay (jsr166y.ForkJoinPool.)))

   (defn fjtask [^Callable f]
     (jsr166y.ForkJoinTask/adapt f))

   (defn- fjinvoke [f]
     (if (jsr166y.ForkJoinTask/inForkJoinPool)
       (f)
       (.invoke ^jsr166y.ForkJoinPool @pool ^jsr166y.ForkJoinTask (fjtask f))))

   (defn- fjfork [task] (.fork ^jsr166y.ForkJoinTask task))

   (defn- fjjoin [task] (.join ^jsr166y.ForkJoinTask task))))

这个宏必须能到工具库里。

当LazySeq遇上closure

| Comments

LazySeq在Clojure里很关键,它解决了持久数据结构在多次操作中的性能问题,避免了多趟(pass)扫描数据结构,将性能的开销“分担”到一个一个的“实现”的步骤里去(当然,这一步可能比较大,一个chunk是32个元素)。

正因为有LazySeq,

(->> (get-titles)
     (map compute-checksum)
     (filter verify-ok?)
     count)

也只是需要扫描一趟。

但是,只要LazySeq的头元素(head)仍然被持有(专业点属于可能叫reachable),它就没办法被回收,并且是所有已经reliazed的元素都将持续保存在内存里面。 如果是个大的数据结构,那将占用相当多的内存,影响性能。

当LazySeq遇上Closure的时候,悲剧可能就发生了。看一个例子:

user=> (defn f [g] (g))
#'user/f 
user=> (defn t1 [c] (f (fn [] (dorun (map identity c)))))
#'user/t1
user=> (t1 (range 1e8))
OutOfMemoryError Java heap space  clojure.lang.ChunkBuffer.<init> (ChunkBuffer.java:20)

f函数只是执行传入的函数,t1方法是构造一个匿名函数并传给f执行(为什么这么做?这只是例子……),匿名函数中调用dorun强制map返回的LazySeq“实现”下,但是dorun不会持有LazySeq的head,因此理论上不应该会有内存溢出,比如我们直接这样跑是不会有内存溢出的:

user=> (dorun (map identity (range 1e8)))
nil

但是一放到匿名函数里,并让f来执行就出现内存溢出了。为什么呢?

———————– 我是分割线 ——————————-

问题在于匿名函数使用了非参数(没有在匿名函数的参数列表[]里出现)的外部参数c,这形成了一个closure,更专业点,我们叫closed-over closure。在SICP里,只有引用了“自由变量”的函数才可以称为closure,不过通常我们习惯性地将所有匿名函数也称为closure。这里的“自由变量”就是t1的参数c,它被匿名函数closed over了。我们接地气一些,就叫“保存”了这个变量c,并且这个匿名函数可以在多次调用中使用这个c。

因此原因很简单,LazySeq被dorun“实现”了之后,虽然dorun不持有head,但是匿名函数持有这个LazySeq“实现”后的集合,并且匿名函数无法在f调用完成之前被释放,导致内存被占满并溢出。

———————– 我是分割线 ——————————-

解决办法,古怪的once和fn*出场:

user=> (defn t2 [c] (f (^:once fn* [] (dorun (map identity c)))))
#'user/t2

这里用fn*而不是fn只是因为fn不可以传入metadata,而^:once就是一个metadata,告诉clojure的编译器说,这个匿名函数的Closure只会被调用一次,不要让Closure继续保存“自由变量”(或者clojure里叫LocalBinding)。让我们测试下(请确保在OOM之后重启过REPL,前面的REPL已经填满了垃圾元素):

user=> (t2 (range 1e8))
nil   

Cool! It works.问题顺利解决。虽然这里once的用法很怪异。不过这个用法在clojure.core里很普遍,比如future宏:

(defmacro future
  [& body] `(future-call (^{:once true} fn* [] ~@body)))    

这个问题的更多讨论请参考这篇博客和这个论坛帖子。上面的例子就来自那里。

———————– 我是分割线 ——————————-

没有结束,我们再稍微深入一些,看看Clojure是怎么特殊处理once元信息的。首先,请遵循我过去写的《Clojure Hacking Guide》来打印下t1和t2的字节码,我将打印出来的字节码存入文件并opendiff了一下,基本上两者生成的字节码是一致的,除了一个地方:

Snip20140119_1

左边是t1,右边是t2。

t2的匿名函数做了一个很关键的事情,就是通过PUTFIELD将自己内部的c的值设置为null(就是ACONST_NULL指令压入栈的null值),就这样“抛弃”了本该持有的c集合。

具体到Clojure编译器在Github的源码),关键代码是这样:

if(onceOnly && clear && lb.canBeCleared)
{
gen.loadThis();
gen.visitInsn(Opcodes.ACONST_NULL);
gen.putField(objtype, lb.name, OBJECT_TYPE);
} 

真相大白,水落石出,淫者见淫,智者见智……

Hello,Docker

| Comments

Docker is an open-source project to easily create lightweight, portable, self-sufficient containers from any application.

安装

(略),见官方文档

ubuntu安装很容易,不过要注意内核版本,至少3.8以上。安装成功后,使用lxc-checkconfig检查下LXC配置是否正常。

避免sudo docker

docker命令跟默认docker daemon创建的unix socket通讯都需要sudo权限,这可以通过创建docker组来解决。

sudo groupadd docker
#将当前用户加入docker组
sudo gpasswd -a ${USER} docker
#重启docker服务
sudo service docker restart

这样一来,docker命令的执行就不再需要sudo权限。如果没有生效,退出重新登陆即可。

hello world

跑个ubuntu的image试试看:

docker pull ubuntu
docker run ubuntu /bin/echo hello world

这将启动一个container加载ubuntu image并执行echo程序,打印hello world,然后退出。”docker ps”可查看container列表。

lxc-start: No cgroup mounted on the system

这个错误是因为cgroup没有mount,建议mount到/sys/fs/cgroup目录。首先编辑/etc/fstab文件,加入下面这行配置:

none        /sys/fs/cgroup        cgroup        defaults    0    0

接下来创建目录,并mount:

sudo mkdir -p /sys/fs/cgroup
sudo mount /sys/fs/cgroup

创建一个运行Node.js应用的ubuntu image

官方给的例子是创建centos上运行node.js应用,具体看这里

下面尝试用ubuntu创建下。

Node.js应用

创建一个src目录,存放node.js应用源码和配置

mkdir src 
touch src/package.json 
touch src/index.js

其中package.json内容:

 {
  "name": "docker-ubuntu-hello",
  "private": true,
  "version": "0.0.1",
  "description": "Node.js Hello World app on ubuntu using docker",
  "author": "Daniel Gasienica <daniel@gasienica.ch>, Dennis<killme2008@gmail.com>",
  "dependencies": {
    "express": "3.2.4"
     }
 }

index.js就是使用express框架渲染首页hello world:

var express = require('express');    
// Constants
var PORT = 8080;   
// App
var app = express();
app.get('/', function (req, res) {
    res.send('Hello World\n');
});    
app.listen(PORT)
console.log('Running on http://localhost:' + PORT);

创建Dockerfile

Dockerfile文件配置image众多参数,例如parent image是什么,执行哪些安装命令,拷贝应用文件,导出TCP端口等等。与src目录统计创建文件名为Dockerfile,内容如下:

FROM ubuntu:12.10
RUN apt-get update
RUN apt-get install -y python-software-properties python g++ make software-properties-common
RUN add-apt-repository ppa:chris-lea/node.js
RUN apt-get update
RUN apt-get install -y nodejs
ADD ./src /src
RUN cd /src; npm install
EXPOSE  8080
CMD ["node", "/src/index.js"]

我们采用官方提供的ubuntu 12.10的image做为os版本,接下来通过RUN指令安装node.js,然后用ADD指令将src目录拷贝到image的/src目录下,并在/src目录下执行npm install安装node.js依赖包,导出应用监听的8080端口到container外,最终通过CMD指令启动node.js应用。

关于Dockerfile指令参考这里

构建Image

执行下列指令:

docker build -t dennis/node-js .

通过-t选项为这个image打上一个tag,在构建完成后,通过docker images列出所有的image的时候,可以看到你刚创建的image: dennis/node-js

运行container并测试

有了image,就可以从这个image启动一个container来运行node.js应用:

docker run -p 47516:8080 -d dennis/node-js

我们将container内node.js应用监听的8080端口通过-p选项桥接到宿主机器(host machine)的47516端口,也就是说你可以通过47516端口能问到容器内的node.js应用。尝试下:

curl -X GET http://localhost:47516/

输出hello world。

本例子的源码已经放到Github

离开帝都

| Comments

《北漂一年》的博文里说过,预计希望能在2015年离开北京。为了这个目标,再好好奋斗两年。没想到要提前说告别了。在微博上说“2014年的目标是逃离帝都”。朋友问是不是要离职了,淘宝的老同事问想不想回去,非常感谢朋友们的关心。离开帝都是真,离职是假。

我们团队要去江苏昆山启用原来的老办公室,在那办公。我准备去那奋斗,并将家人都接过去。原来不想接家人在身边的原因就是不希望他们来北京,这空气和环境我一个人承受就好,舍不得让儿子也承受。如果去昆山,就没有这样的担心了。我也免了每一两个月的来回两地跑。

为什么突然要离开帝都?原因很简单。年底生了一场大病,上吐下泻,呼吸道感染一起来,查心电图,查出一点毛病。大惊一场跑去找专家,虽然最终结果还好,不过左思右想之下,加上孩子长大的教育问题(我长期不在身边,老婆不是有耐心的人,带不好他),最终跟老板提想回去福建,找个差不多的工作待上几年缓缓,充充电,陪陪家人,带带儿子。

老板提议说要不远程办公。虽然现在兴起这么一股潮流,但是从我自己的经历来看,在家办公的效果实在一般。原因是多方面,首先个人的时间在家很难不受干扰,并且在家办公很难将工作时间和家庭时间区分,反而更累,工作效率更差;其次,沟通是个大问题,开个视频会,受限于国内这个悲剧的网络状况,沟通交流的效果差很多。况且我们是一家创业公司,如果不能及时有效的沟通,高效的工作,那么是在耽误公司,我实在不愿意。

最终的决定是去重启我们在昆山的办公室,我们的AVOS Cloud团队愿意一起过去。我真的非常感激公司和同事。我对大城市都没有任何留恋的,不会玩,作为码农也没有夜生活的喜好,反而喜欢小城市的安逸生活,遥想中“退休”后的理想就是呆在老家的小村庄,每天读读书,晒晒太阳,愿意就写点代码,就非常知足了。

最后,再次打个广告,我们在招聘一位资深的Android工程师来帮助我们提升AVOS Cloud的Android SDK的质量,希望愿意来昆山加入我们,长三角或者想离开帝都的牛人都可以考虑下,感谢,有兴趣可以投递简历到 cn-jobs@avos.com。

工作所得之一二

| Comments

Clojure transient集合“陷阱”

掉进了一个坑两次,希望不会有第三次,下面这个代码,你认为最终的x会有几组元素?

(let [x (transient {})]
            (dotimes [n 30] (assoc! x n n))
            (count x))

30个?很遗憾,你跟我犯了同样的错误。Transient集合虽然是mutable的,但是要注意调用assoc!或者dissoc!等修改操作的时候仍然需要“收集并使用”返回值。也就是上面代码需要修改为:

(reduce #(assoc! %1 %2 %2) (transient {}) (range 0 30))

这样制造出来的集合才是30个元素,符合你的预期。

如果确实需要收集(比如跨越多个函数做收集,虽然是很坏的实践,为什么?),可以直接使用java的HashMap,我们可以编写几个辅助函数:

(defn jhash-map [ & opts] 
    (doto (java.util.HashMap.) (.putAll (apply hash-map opts))))

(defn put! [^java.util.Map m k v] (.put m k v))

(defn remove! [^java.util.Map m k] (.remove m k))

(defn immutable! [m] (into {} m))

使用:

user=> (jhash-map :a 1) 
{:a 1} 
user=> (jhash-map :a 1 :b 2) 
{:b 2, :a 1}
user=> (def m (jhash-map))
# 'user
user=> (dotimes [n 30] (put! m n n))
nil 
user=> (get m 29) 
29 
user=> (remove! m 29) 
29 
user=> (count m) 
29 
user=> (immutable! m) 
{0 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7, 8 8, 9 9, 10 10, 11 11, 12 12, 13 13, 14 14, 15 15, 16 16, 17 17, 18 18, 19 19, 20 20, 21 21, 22 22, 23 23, 24 24, 25 25, 26 26, 27 27, 28 28}

文件中的BOM字符

Unicode字符集有个BOM来标示字节序,所谓Byte Order Mark,具体可以看维基百科上的介绍。Windows记事本在保存为UTF-8格式的时候,会在文件开头插入这个字节序标示符:EF BB BF。Java在读取这样的文件的时候,会将这些不可见字符读出来,假如是一个格式化的文件(JSON或者XML之类),那么在解析的时候会造成不必要的麻烦。因此需要自动检测这样的文件并消除BOM字符。

简单的做法是使用commons-io的BOMInputStream

(ns test
 (:import [org.apache.commons.io.input BOMInputStream]
           [org.apache.commons.io ByteOrderMark]))

(defn slurp-file [f]
  (slurp (BOMInputStream.
          f
          false
          (into-array ByteOrderMark
                      [ByteOrderMark/UTF_8,
                       ByteOrderMark/UTF_16LE,
                       ByteOrderMark/UTF_16BE,
                       ByteOrderMark/UTF_32LE,
                       ByteOrderMark/UTF_32BE]))
         :encoding "utf-8"))

上面的代码也示范了怎么在clojure里调用Java的可变参数方法,使用into-array将可变参数组成数组传入即可。

CoffeeScript的==和!=

这个问题主要是对coffeescript没有那么熟悉,虽然也写了两千行的代码了,最近还是不小心掉进了坑。CoffeeScript的==和!=,以及is都是编译成JavaScript的===和!===,也就是所谓Identity比较,它希望两端比较的东西是完全一致的,不需要转型之类:

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

上面示范来自万能的stackoverflow

我犯的错误就是习惯性的用==来判断null或者undefined:

(x) ->
    y = 
        if x != null
            1
        else
            2

其实应该用x?来判断:

(x) ->
    y = 
        if x?
            1
        else
            2

x?会编译成:(typeof x !== "undefined" && x !== null)

节奏

| Comments

读过一些心理学或者谈工作效率书籍的朋友,可能都听说一个概念——“流态”。按照我的理解,就是工作处于全身心投入,无视时间流逝的状态。一般都会说,工作要尽量保持“流态”,尽量减少打断“流态”,这样才能提升工作效率。围绕种种保持“流态”的方式,提出很多方法。

如果我给自己挑选一个小优点,不得不说我的专注能力很强。不用耳机,不用音乐,哪怕外界很吵,当我全身心投入工作的时候,还是都可以将任何东西都忽略掉,全神贯注在工作上。过去我经常引以为豪,看到某本书上谈到这种状态,也洋洋自得。看,咱的工作效率多高呀。

去年还是前年,读到另一本书(《程序员的思维修炼》? 不确定),提出个相反的意见,认为这种“流态”不一定是有利的,反而有害。为何?因为在这种状态下,你可能很轻易地做出决策,只是为了效率,反而思虑不周,最终看起来工作的效率很高,但是后续反而会有太多的“无用功”——因为要“重构”你在“流态”下的“轻易”决策。

结合自身来说,我越来越感觉到这一点。在“流态”的状态下,我可以写出一打的代码(尝试过两天写一个表达式引擎,一周写出一个分布式MQ,都能符合生产质量要求),但是这些代码的很多“决策”——结构、模式、模块设计、API设计、类库选择等等,回头去看,并不是那么合理。因为在“流态”下,我的精神都关注在实现细节上,对整体的思考反而很缺乏。做到后来,我经常要推翻原来的设计,重构甚至重写代码。

后来,我慢慢有意思地控制“节奏”:在做一个东西之前,适当花一点时间画一个草图:大概几个模块,模块之间大概什么交互,模块的实现顺序和技术难点等等。我没有用什么UML,也不写任何文档,只是在白纸上列出一些点,画上几个图。这个工作也可以在大脑里进行。然后再动手写代码。每天将编码的工作分成了几段,每段都集中精力去写(一到两个小时),中间上上厕所,泡泡茶吃吃零食看看新闻,不搞连续几个小时甚至熬夜编程(好吧,其实是身体吃不消)。在每次push前,都去diff代码,review下阶段性成果,打断“流态”。总体来说,比较明显的减少了工作的“返工”。

招人,找人

| Comments

废话不说,我们在为AVOS Cloud这个项目招人。

技术方面,首先,我们需要一位资深的Android工程师负责Android SDK的开发。目前我和另一个同事在负责Android SDK,都不是专业的android工程师。需要一位大牛来促进这个项目。SDK对我们,对我们的用户都极端重要。除了要求对Android开发比较熟悉意外,希望能对API设计,多线程编程,性能优化上也有一定的经验,因为编写给其他人用的SDK,跟编写一个App还是有很大不同,需要考虑的更全面一些。

后端上,我们也想招聘一位资深的工程师,一起来改进AVOS Cloud的后端系统。我们的后端基本是基于Java和Clojure,部分Node.js和Erlang。我们还有很多工作需要做,例如虚拟化、高性能准实时的消息推送系统、云代码系统的改进等等。希望能找到一位相对资深一点的工程师,对后端系统和语言、分布式系统、性能优化都有很好的理解和研究,如果对Node.js或者Erlang甚至Golang有深入的了解,那更是非常欢迎。

非技术上,我们希望能招人志同道合的人,对技术有热情和冲劲,做事情认真靠谱,会采用最合适的办法去解决问题,一句话,我们需要有悟性,smart的人。什么叫悟性?简单来说,我们讨论一个东西,大家都知道相互之间在说什么,如果不知道,解释一遍之后能触类旁通,沟通不费劲,就是有悟性(个人观点哈)。

我们公司是AVOS.com在中国的分公司,有相当大的自主权,待遇和福利相比大多数创业公司还是比较有竞争力的,大老板是原youtube的创始人。AVOS Cloud本身是一个BaaS服务的创业项目,目前也积累了一些早期用户,并且BaaS本身在国内的接受程度慢慢在提升,我个人是非常看好。

有兴趣的朋友可以给我们投递简历,邮箱是cn-jobs@avos.com。

北漂一年

| Comments

写在前面:纯粹灌水的博文,可忽略,写完之后才发现标题错了,原来我来北京才一年3个月,怎么感觉过了好久好久。

博客基本长草,不是没有什么可写,挺多工作中解决的问题,学会的技巧,其实都可以写一写。有时候想起要写,但是回到家后就累的不想动了。又觉的这么小的东西,应该没什么人感兴趣吧。

离开杭州来北京两年了,偶尔回头看看,对这个选择还是没有后悔。因为我在杭州淘宝的时候,人已经不在状态,加上孩子的事情折腾的精疲力尽,来一个新环境,用一门新语言,跟一个新老板,做一些新东西,总体上讲这两年收获很多。

谈学习,越发觉的学习这种事情还是要看兴趣,兴趣在哪,就学到哪里,不要总想着用不用得上,因为说不定哪天就掉下来一个机会给你,恰好用到你学会的东西。所以说,学习其实是给自己选择的机会,干这个行当别的能力可以没有,但是自学的能力是万万不能没有。从我自己的经验来说,能加入avos,完全是因为我对clojure的热爱,纯属巧合地认识了现在的老板江宏,如果没有去学习这门语言,我也不可能加入这家公司,估计还在码Java。同样,对node.js的早早关注也让我现在能得心应手地写一些基于node.js的应用。但是这两年来,学习的时间减少了,工作没有那么空闲,周末也累的慌,最近开始看haskell,希望能坚持下去。为什么是Haskell?不是Go,不是scala?原因很简单,我一直坚持的观点是学习一门语言最重要的是能带来启发性的观点和思想,从这个角度出发,Go语言上的平庸和goroutine的模型实在不能吸引我(好吧,我也看过两本go的书了),scala就不提了,基本上没有列入考虑范围内。选择Haskell是因为这门语言的类型系统真是绝了,从clojure转换思维到haskell,带给我很多乐趣,还有智力上的挑战mond,这都能给我很多不一样的启发。

谈团队,我们是一家创业公司,但是跟一般的创业公司可能还不那么一样。首先大佬有成功的经验,其次,早早拿了融资,暂时都没有经济压力。第三,江宏也算是第一次创业,同事也几乎都是。所以经验缺缺,去年走了不少弯路,包括年底的裁员,人心慌慌。说实话,我自己都考虑过是不是应该走,相比去年,今年成长了不少,也许是人到30的缘故,回头看自己去年还是太不成熟了。做事情还是大公司那套的痕迹,态度上抱怨太多,改变的行动太少。一句话,牢骚太多,行动太少。今年立项了AVOS Cloud之后,专心写代码,不想东,也不想西,迸发了工作以来写代码的第二春,一个人几乎搞定了整个AVOS Cloud的后端和部分android sdk的开发工作,我自己对工作效率都很满意,但是写代码的质量似乎没有多大提升:) 。

创业公司,给我感觉最重要的还是找对人,跟对人,并且要有长期坚持的勇气。人不对,都在混日子,这公司长久不了。做项目不坚持,可能就输在最后一公里。团队一定要有核心人物去凝聚人心,一起奋斗。如果大家都是朝9晚5,那又何必在创业公司呢?大公司呆着舒舒服服,薪水拿的刚刚的,股票拿的多多的,何必来冒险?何必来北京这种污染极其严重的地方搏命?我不赞成加班,但是创业公司如果没有这种做事情的氛围,那比不加班还可怕。所谓做事情的氛围,就是你今天能做完的,不是故意拖到明天;可做可不做,大家都主动去做;细节,都主动去关心发现和解决。一个成长中的产品,没有这种尽心竭力,很难跑得过资源丰富,执行力超强的大小公司。

今年因为一个老同事要离开北京回家,我不得不开始带团队。带团队跟埋头写代码完全不同,一言难尽。只能说还在学习和适应,做到问心无愧。

帝都原本应该是很好的地方,厚重的历史,没有雾霾时特别晴朗的天空,可惜真的污染太严重了,去年有阵子都很担心自己是不是要早早挂了。来北京后,还有个收获是开始去户外爬山,看过了漫山绿野和黄叶,看过了雪中的古老长城,看过海驼和灵山之巅的美丽云海,从来没有想过原来爬山这么好玩。很享受那种呼吸达到极限的感觉,可以排解一周的压力和烦恼。今年还是争取一个月回家一趟,看看孩子和老婆,每次回家都不想来帝都了,想想房贷和后半生的日子,咱还要奋斗两年啊。不过两年是极限了,2015年,应该是我在北京的最后一年,希望是。

AVOSCloud简介——移动云平台

| Comments

最近半年来一直忙的项目,一个面向移动开发的云平台,提供数据管理、消息推送、SNS组件、网站托管、代码托管和跨平台SDK等方案和功能,我们称它为AVOSCloud,中文名定为万象,吉祥物是一只可爱的大象,感谢我们的美女设计师的设计。这个平台本身的想法来自于被Facebook收购的parse.com,API保持一致,如果你使用parse的服务,可以做到几乎零成本的迁移。当然,我们除了做本地化之外,还准备尝试添加一些更强大的功能。

下面这个PPT是一个简单的介绍,罗列下功能和show一些代码,感兴趣的朋友不妨看看。

Xmemcached和MetaQ发布新版本

| Comments

XMemcached发布1.4.2版本,主要更新如下:

  • 升级slf4j到1.7.5版本
  • 设置网络层线程为daemon线程,可随JVM终止而终止。
  • 二进制协议delete命令支持CAS参数,新增delete(key,cas,optimeout)方法,只删除cas值对应的item。
  • 改进KetamaMemcachedSessionLocator,获取链接socket地址的方式保持一致。
  • TextCommandFactory不再声明为final
  • 支持memcached命名空间功能,新增方法withNamespaceinvalidateNamespace用于在特定namespace内执行操作和失效整个namespace,例子:

    String ns = "user-id"; 
    this.memcachedClient.withNamespace(ns, new MemcachedClientCallable<Void>() {
    
            public Void call(MemcachedClient client)
                            throws MemcachedException, InterruptedException,
                            TimeoutException {
                            //username is in 'user-id' namespace.
                            client.set("username", username);
            }
    });
    //invalidate the namespace
    this.memcachedClient.invalidateNamespace(ns);
    
  • 修复bug,包括setEnableHeartBeat方法无法正常工作,delete方法在连接kestrel最新版本的时候协议错误等。

下载地址:

Maven引用:

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

如果你是clojure用户,请使用clj-xmemcached,也已经更新到0.2.3版本,添加了try-lockthrough宏。

MetaQ发布1.4.6.2,是在1.4.6.1版本之上的一个小改进,建议升级到1.4.6.1版本的朋友升级到此版本,改动如下:

1.服务器:

  • 修复dashboard的title显示错误的bug。

2.客户端,推荐已经升级到1.4.6.1的朋友升级到此版本,变更如下:

  • 修复Spring支持API无法运行在Spring 2.x版本的bug,感谢网友elvis.wangyi
  • 改进consumer负载均衡,减少产生重复消息的可能,降低重复性的重新负载均衡的次数。

下载地址:

客户端Maven引用:

<dependency>
  <groupId>com.taobao.metamorphosis</groupId>
  <artifactId>metamorphosis-client</artifactId>
  <version>1.4.6.2</version>
</dependency>