我在那里吃拉面

“终于找到一家和我身份地位品味收入都相符的拉面馆。” 这条本来要发到twitter上的,结果不料哪哪出了问题,没发出去,却被我吃面条的时候记下来了。自从有了饭否twitter,只言片语多了,真正坐下来搞创作的动力却没有了,咱虽然比不上极少部分搞艺术的男青年,但追求上进也不该含糊是吧。

纵然再有追求,也比不上清真拉面馆热卖鱼香肉丝面。上海郊区天高真主远,远亲不如近邻,再不创个新,小小的拉面馆恐怕连在这里匹配我的身份地位和品味的可能性也没有了。但是在首都的时候就不一样,同样是少数民族兄弟开的面馆,人家可不含糊,别说是买鱼香肉丝了,门都不让进。

宝盛里的半年,我发现最贴近我们民工生活的就要数西北拉面和成都小吃,碗大量足(当然也不会有拿着筷子在碗里一粒一粒拣肉末吃的男士)。就为了第一次在成都小吃发现盖浇饭里鸡丁可以比花生多,我在那家店里活活吃过了夏秋冬,后来我每每想起这事都难免情绪激动。而之所以没能熬到冬去春来,也是因为老板时间长了实在看不下去,问我要不要换点别的。我是受宠若惊,才不得不转战拉面店。那家叫不上名字店,名字不重要你知道它在哪就成,墙上还挂着清真寺的画,就像很多湘菜馆会挂毛主席像一样。你可以想象这样一家店是绝对做不出卖鱼香肉丝的勾当的。十三块一个的烤羊腿是他们冬天的热门;端菜的小孩最多也就初中年纪,解析几何他大概是不会清楚了,但是你不催他他也还是不会给你上菜。

另一家印象深刻的面馆是中关村软件园北门对面的,名字好像叫老马拉面,店的规模还不小,在那个鸟不那啥的地方算是鹤立鸡群了,是软件园民工,西北旺、西二旗一代租住户,应届大学毕业生理想的消费场所。我就在那吃过一次,印象深刻仅仅是因为一位服务员长得好看。那是北门最美好的两件事物之一,另一件是那里成排的杨树,我每次在那里等642路等会被这两件事物陶醉。

思绪万千了又。其实我就是感叹能找到一家10块钱以内打发晚饭晚上十点还不会饿的地方,抑制不住内心的喜悦,又回忆起以前了。

最后标题是借名记唐师曾的书名。

The post is brought to you by lekhonee v0.7

Perfomed my first commit to OpenStreetMap.org

每每看到OSM上南京的地图都一阵唏嘘,这么多年了几乎没有任何变化,仅有的一些数据也都是错误百出,身为一个南京人,又是一个GIS专业毕业的,深感压力巨大。

实际上编辑OSM没有大家想象的那么复杂。任何人都可以注册登录网站,用默认的Potlatch工具进行编辑,即使你手上没有数据也可以编辑一些基本的属性,比如把错误的中山路纠正为北京东路。我从MapMyTracks上导出GPX文件上传到OSM,GPS数据会在OSM上被显示出来,根据这些数据稍作加工就是道路信息了。浏览器里的编辑器功能并不强大,KDE桌面的Merkaator的编辑能力要强大得多,可以对道路进行split/join操作,这些操作对之前南京地图上各种错误的数据非常必要。

其实OpenStreetMap可以借助一些背景图进行数字化,但是在国内,目前只有分辨率非常低Yahoo卫星地图可以用。至于Google的卫星地图,由于License的原因,你是不能在编辑自由地图时使用的。

上海的OSM已经非常完善,数据完善的程度、渲染出地图的壮观已经让你难以想象,连去年开通的张江电车已经在地图上标出,还有各个站名。注册后可以发现,仅仅在浦东就有十几位OSM的贡献者。相比之下,南京在这方面一片空白,甚至还没有苏州的地图完善。考虑到南京有这么多学校这么多GIS专业学生,每年做那么多无用的、自以为是的数字化作业,真正有意义的工作却无人问津,并非没有人愿意去做,而恐怕是大部分人对OSM都闻所未闻。

今后每周我回抽出一定的时间来编辑南京的地图,也算是为家乡做一些贡献吧。

The post is brought to you by lekhonee v0.7

RPC, Serialization and Schema

The post is brought to you by lekhonee v0.7

糖果项目的后端用Java编写,我负责service gateway的开发(暂且叫sergent),服务以Java接口+Annotation的形式声明,与Spring集成使用,Java对象被序列化为JSON和XML(通过jackson和castor)与外部系统交互。专门的JSON Schema和XML Schema是可选的,系统交互通过简明的文档和人工确认。

RPC框架是跨进程、跨系统交互的重要工具,RPC框架中又包括远程调用、网络传输和序列化反序列化等等部分。流行的工具包括Facebook的thrift,Google的Protobuf和原先Hadoop项目下的avro。其中thrift包含远程调用、反序列化、网络等等全部的功能。Protobuf本身是一个序列化反序列化库,另有很多第三方RPC实现,avro目前除了序列化和反序列化的功能,也包含了ipc的HTTP Server和SocketServer等实现。在序列化的格式方面,Thrift支持JSON和二进制协议,Protobuf本身仅有二进制支持,但已经存在第三方的其他格式实现。 avro原生支持二进制和JSON格式。

从效率上来说,二进制方式的序列化要比文本方式的快。Google Code上(最近迁往了github)有一个tpc项目(thrift-protobuf-compare),根据这个项目的最新的比较结果(与原先不同):

protobuf成为了三者中耗时最少的框架,之后是thrift和avro,这次avro的耗时甚至超过了文本方式的jackson(主要在反序列化上)。

但是二进制协议通常都需要定义Schema,thrift / protobuf / avro三者各自定义了Schema的格式,没有类似XSD和Json Schema的统一标准,也就是说,当你需要传输一个对象,就要为它编写一个Schema文件。按照通常的习惯,都是先编写Schema,然后通过命令行工具或者自动构建工具来生成Java source。对于新系统还好说,对旧系统这个改造就比较麻烦了。另外,二进制协议不便于调试,所以各个thrift/protobuf/avro先后也都有JSON的实现,在文本的序列化格式上,JSON对XML的优势是全方位的。

所以综合起来,很难说有一种完美的解决方案。二进制协议的效率高,但是改造、编写Schema的代价并不小,还要面对核心Model被绑架到具体框架的风险。文本协议开发简便,不需要Schema,直接POJO就可以序列化和反序列化,但是在时间和空间上都不如二进制的方式。

补充
从tpc项目的结果上看,kryo在时间、空间上都击败了所有对手,而且,kryo的API非常简洁,不需要Schema文件就可以序列化POJO,听起来太完美了,看来以后sergent要借鉴一下的。

补充 2010-06-14
发现avro现在也有ReflectDatumReader和ReflectDatumWriter,可以通过反射内部自动映射生成Schema,可以尝试一下。

ibatis infinite loop when getFirstResultSet

前几天上线后老大发现几台负载非常高,dump线程状态后发现多个线程死循环在同一处,于是发现了ibatis的这个bug:

https://issues.apache.org/jira/browse/IBATIS-384

https://issues.apache.org/jira/browse/IBATIS-587

在mysql数据库上,没有结果集时,stmt.getUpdateCount()会返回0,而非-1.
ibatis 2.4.3

  private ResultSet getFirstResultSet(StatementScope scope, Statement stmt) throws SQLException {
    ResultSet rs = null;
    boolean hasMoreResults = true;
    while (hasMoreResults) {
      rs = stmt.getResultSet();
      if (rs != null) {
        break;
      }
      hasMoreResults = moveToNextResultsIfPresent(scope, stmt);
    }
    return rs;
  }

  private boolean moveToNextResultsIfPresent(StatementScope scope, Statement stmt) throws SQLException {
    boolean moreResults;
    // This is the messed up JDBC approach for determining if there are more results
    moreResults = !(((moveToNextResultsSafely(scope, stmt) == false) && (stmt.getUpdateCount() == -1)));
    return moreResults;
  }

  private boolean moveToNextResultsSafely(StatementScope scope, Statement stmt) throws SQLException {
    if (forceMultipleResultSetSupport(scope) || stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
      return stmt.getMoreResults();
    }
    return false;
  }

moreResults恒为真,程序出现死循环。

在mybatis 2.5的代码里,这部分已经修改为:

  private boolean moveToNextResultsIfPresent(StatementScope scope, Statement stmt) throws SQLException {
    boolean moreResults;
    // This is the messed up JDBC approach for determining if there are more results
    boolean movedToNextResultsSafely = moveToNextResultsSafely(scope, stmt);
    int updateCount = stmt.getUpdateCount();

    moreResults = !(!movedToNextResultsSafely && (updateCount == -1));

    //ibatis-384: workaround for mysql not returning -1 for stmt.getUpdateCount()
    if (moreResults == true){
        moreResults = !(!movedToNextResultsSafely && !isMultipleResultSetSupportPresent(scope, stmt));
    }

    return moreResults;
  }

  private boolean moveToNextResultsSafely(StatementScope scope, Statement stmt) throws SQLException {
    if (isMultipleResultSetSupportPresent(scope, stmt)) {
      return stmt.getMoreResults();
    }
    return false;
  }

  /**
   * checks whether multiple result set support is present - either by direct support of the database driver or by forcing it
   */

  private boolean isMultipleResultSetSupportPresent(StatementScope scope,
          Statement stmt) throws SQLException {
      return forceMultipleResultSetSupport(scope) || stmt.getConnection().getMetaData().supportsMultipleResultSets();
  }

这部分条件判断实在很极致了。

新的实现当getUpdateResult是0时,moreResults恒为真,这时再进行一个判断,如果是由于isMultipleResultSetSupportPresent为false导致了moveToNextResultsSafely为false,那么实际moreResults应是false

MyBatis 2.5还有一个issue没有解决,离发布还有一些时间,这个问题只好签出新版本代码自己build了

t.sdo.com上线

昨天搞到3点多,盛大糖果http://t.sdo.com/,昨天晚上终于开放IP给大家访问了。

留一些邀请码给大家

  • 2HlXyYKYz2
  • 2dKoCcQZYV
  • oHBYhO1JPw
  • -~YTMDYmN+
  • IXKYMq0kE@
  • Ub-C@xk8~r

我们后端服务从3月初开始正式开发,11个成员,加了三个多月的班。