"#!/usr/bin/env ruby" vs. "#!/usr/bin/ruby"

2016 Dec 22

脚本文件(可执行的脚本文件)的第一行会有一个shebang,比如#!/bin/sh,表示用哪个程序来执行该脚本。 每种可执行文件的开头会有一个相应的magic number。 可执行的脚本文件对应的magic number是0x230x21,也就是ASCII码形式的#!

在shebang里使用env可以增加脚本的可移植性。 比如#!/usr/bin/env ruby相比于#!/usr/bin/ruby有着更好的可移植性。 因为在某个系统上,ruby不一定安装在/usr/bin/下,也有可能安装在/usr/local/bin/等其它目录里。 而应用#!/usr/bin/env ruby这种形式的shebang时,只要在执行脚本的用户的PATH里能搜索到ruby程序,就可以执行脚本。

当用rbenv来管理ruby版本时,ruby程序一般会安装在用户目录下,然后rbenv通过设置用户的PATH变量来找到需要的ruby版本。 这种情况下使用#!/usr/bin/env ruby会保证脚本在执行时可以找到正确的ruby程序。

在绝大部分的系统上,env都安装在/usr/bin/目录下,所以不用担心/usr/bin/env的可移植性。

参考

Shebang (Unix)

man env

env – set environment and execute command, or print environment

The env utility executes another utility after modifying the environment as specified on the command line.

Guava中的Utility Object Pattern

2016 Dec 20

在一篇Guava的presentation中,Google提到了一种叫“Utility Object pattern”的模式。

return Joiner.on(", ").skipNulls().join("one", null, "two", "three");
             |-- 1 ---|-- 2 ------|-- 3 ----------------------------|

return Splitter.on("|").omitEmptyStrings().split("|Harry||Ron|||Hermione  ||");
               |-- 1 --|-- 2 -------------|-- 3 ------------------------------|

通过1得到一个初始的、默认的utility对象,通过2可以得到utility对象的一个“变种”, 再通过3最后求值。

这种API看起来更简洁。用户不用知道不同变种对应的utility类。

实现上,2中的方法,比如skipNulls(),会返回一个新对象,或者修改对象的属性并返回它。

  public Joiner skipNulls() {
    return new Joiner(this) {//通过传入的this可以“继承”上一个Utility对象的某些特性
      @Override
      public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
      //...
      }
      //...
    }
  }
  public Splitter omitEmptyStrings() {
    //直接构造相应的Utility对象
    return new Splitter(strategy, true, trimmer, limit);
  }

ComparisonChain是另一个有趣的例子。

Comparator<Person> comparator = new Comparator<Person>() {
  public int compare(Person p1, Person p2) { 
    return ComparisonChain.start().compare(p1.exp, p2.exp).compare(p1.age, p2.age).result();
  }
};  

使用ComparisonChain时,不会创建新的ComparisonChain对象。 ComparisonChain也支持“short circuit(短路)”。 当p1.expp2.exp的比较有结果时(不等于0),就不会再去比较age

Bit Twiddling Hacks

2016 Dec 20

Java的Integer类有个highestOneBit方法,可以返回一个整数的二进制表示的最左边的1的位置。

public static int highestOneBit(int i) {
	// HD, Figure 3-1
	i |= (i >>  1);
	i |= (i >>  2);
	i |= (i >>  4);
	i |= (i >>  8);
	i |= (i >> 16);
	return i - (i >>> 1);
}

那么上面的实现到底是在干什么?!😨

Integer类的Javadoc上说highestOneBit的实现参考自书籍Hacker’s Delight

Implementation note: The implementations of the “bit twiddling” methods (such as highestOneBit and numberOfTrailingZeros) are based on material from Henry S. Warren, Jr.’s Hacker’s Delight, (Addison Wesley, 2002).

或许面试前可以看看这边书,让面试官惊讶一下。

另外,google一下“Bit Twiddling Hacks”,也可以发现很多参考资料。比如这篇pdf,以及这个网站

Java支持的位操作运算有:~&^|<<>>>>>

The unsigned right shift operator “»>” shifts a zero into the leftmost position, while the leftmost position after “»” depends on sign extension.

>>>会在最左边补0>>运算后,最左边是0还是1取决于整数的正负号。

JPA的Locking

2016 Dec 19

主要是为了在OneFeed里测试一下<table>

Lock Mode Description
OPTIMISTIC Obtain an optimistic read lock for all entities with version attributes.
OPTIMISTIC_FORCE_INCREMENT Obtain an optimistic read lock for all entities with version attributes, and increment the version attribute value.
PESSIMISTIC_READ Immediately obtain a long-term read lock on the data to prevent the data from being modified or deleted. Other transactions may read the data while the lock is maintained, but may not modify or delete the data. The persistence provider is permitted to obtain a database write lock when a read lock was requested, but not vice versa.
PESSIMISTIC_WRITE Immediately obtain a long-term write lock on the data to prevent the data from being read, modified, or deleted.
PESSIMISTIC_FORCE_INCREMENT Immediately obtain a long-term lock on the data to prevent the data from being modified or deleted, and increment the version attribute of versioned entities.
READ 来自JPA 1.0,OPTIMISTIC的同义词。
WRITE 来自JPA 1.0,OPTIMISTIC_FORCE_INCREMENT的同义词。
NONE 不加应用任何锁机制

可以通过多种途径设置锁,

jpa

JSON Web Token和Cookie的比较

2016 Dec 18

JSON Web Token (JWT)和基于cookie的认证机制的比较。

基于cookie的authentication需要在服务端记状态。 而JWT是stateless的,这样服务的水平扩展性更好(不用考虑类似session replication这样的问题)。 不像cookie,JWT可以cross domain。 使用JWT后,API的使用者可以不局限于浏览器,这样对mobile app更友好。 session机制需要一些服务端查找,相比而言可能JWT的性能会好点。

想起了Rails的EncryptedCookieStore。 EncryptedCookieStore和JWT一样也是完全存在于client端。 和JWT的一个不同点是EncryptedCookieStore会加密内容,JWT不会(JWT只是sign)。

基于cookie的session机制可以在服务端让session失效。 JWT则需要在客户端做失效。

Tomcat里的各种Class Loader

2016 Dec 18

这篇文档介绍了Tomcat的class loader hierarchy和加载类时目录的搜索次序。 Tomcat的各个web app会共用一些class loader,也有属于各自的class loader(为了隔离)。

      Bootstrap
          |
       System
          |
       Common
       /     \
  Webapp1   Webapp2 ...

当WebappX的class loader加载一个类时,会先在WebappX的目录里找类定义 (这和普通Java程序的class loader parent-delegation model略有不同)。 但是也有例外,比如加载JRE的基础类时还是会先委托父class loader去加载。

Tomcat的类加载行为也是可以配置的(通过<Loader delegate="true"/>)。

因为每个web app都有自己的class loader,要防止class loader的泄漏

Tomcat的文档还是比较详尽的,没事可以多看看。

OneFeed的通知推送功能

2016 Dec 14

实现了push notification功能。 现在当OneFeed上有新评论的时候,我的手机会收到通知。

手机通知推送

下面是简单的构架图,

通知推送的架构

生成新评论的时候,Rails app会触发一个job向RabbitMQ发送消息。 一个sneakers worker会处理RabbitMQ中的消息,然后调用Slack API向Slack发送消息。 Slack会帮忙把消息push到我的手机上。

用到的工具

RabbitMQ的ruby client用的是bunny。 Rails job用bunny来发送消息。 RabbitMQ的consumer用的是Sneakers。 通过Slack bot来向Slack发送消息。Slack的ruby client用的是slack-ruby-client

记一次失败的systemd尝试

2016 Dec 13

OneFeed的push notification实现借助了Sneakers。 在部署服务时,Ruby环境是通过rbenv管理的,环境变量(比如API Token等)则通过rbenv-vars管理。 为了不“污染”root用户以及系统的稳定,希望新建一个专门的用户来运行服务。

可以通过foreman把Sneakers项目导出成Upstart或者systemd service。 因为Ubuntu 16.04用systemd替换了Upstart,所以尝试用systemd来启动服务。

sudo foreman export systemd /etc/systemd/user -a notification-service -u hong

因为想以非root用户来启动服务,所以导出时加了-u选项指定用户,/etc/systemd/user是存放systemd user unit的目录。 导出的.service文件里会出现User=hong语句。

尝试启动服务,

su - hong
systemctl --user start notification-service.target

却始终报错,Failed to connect to bus: No such file or directory ubuntu。 Google后发现是su的问题。不用su,直接以用户hong登陆系统后再运行systemctl,上面的错误就消失了。 但是通过ps命令没有发现相关的ruby进程。再通过journalctl -r发现了如下错误信息,

Failed at step GROUP spawning /bin/bash: Operation not permitted

这可能是systemctl --user一个bug。 一个解决方案是删除User=hong语句。但是删除该语句后,systemctl会尝试用root用户的ruby来启动服务。 因为root用户的ruby没有安装服务所依赖的gem,所以会启动失败。 而且以root用户来启动服务也违背了初衷。

到此为止,这次对systemd的尝试以失败告终。而且前前后后花费了一两天的时间。 所以专门的DevOps还是必须的🤔。

Flyway,数据库升级工具

2016 Dec 3

Flyway是一种数据库升级工具,支持通过sql脚本或者java代码进行数据库升级。

来自Thoughtworks的技术雷达

2016 Dec 3

Thoughtworks对一些技术和工具(包括新的和老的)的评价和看法。 会推荐一些新的技术和工具,也会评估已存在的工具和技术是不是还能继续适用。 Technology Radar会定期更新,一般一年有两到三期。

没事可以看一看,起码可以了解一堆名词