Shanghai, China

E-mail: henter@henter.me
Weibo: @周攀Henter

Slack + Hubot 处理Android自动打包

Slack是一款企业内部协作平台,能将各种分散的沟通、协作工具整合到一起,基本上市面上所有的知名第三方服务都能很好的集成到Slack,其它没被官方集成的服务也可以通过Slack提供的API集成,比如整合内部任务系统、ERP系统等等。

Hubot是Github推出的开源聊天机器人,能整合各类IM工具。可以通过写hubot coffee脚本来实现各种好玩的小工具~

下面开始介绍如何利用Slack和Hubot处理Android自动打包。

Ubuntu环境处理Android打包

安装工具

下载ADT

$ wget https://dl.google.com/android/adt/adt-bundle-linux-x86_64-20140702.zip
$ unzip adt-bundle-linux-x86_64-20140702.zip
$ sudo cp -r adt-bundle-linux-x86_64-20140702/sdk /opt/adt-sdk

安装ant

$ sudo apt-get install ant

安装jdk

$ sudo apt-get install openjdk-7-jre openjdk-7-jdk openjdk-7-jre-lib

设置环境变量,在你的profile文件内加入下面代码:

export ANT_HOME=/usr/share/ant
export ANDROID_SDK_HOME=/opt/adt-sdk/
export PATH=$PATH:$ANDROID_SDK_HOME/tools:$ANDROID_SDK_HOME/build-tools/android-4.4W:$ANDORID_SDK_HOME/platforms/android-20/
export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64

注:根据平台不同,上面的java-7-openjdk-amd64有可能为java-7-openjdk-i386

检查环境

$ ant -version
Apache Ant(TM) version 1.8.2 compiled on December 3 2011

如果正常输出版本号则表示ok,如果提示报错,(如tools.jar找不到等),跟环境有关,请按上面步骤检查。

处理包签名

生成keystore(需设置密码),这里以kudong为例

$ keytool -genkey -v -keystore kudong.keystore -alias kudong -keyalg RSA --validity 10000

进入项目目录,新建文件ant.properties,输入以下内容:

key.store=上面保存的keystore文件路径
key.alias=kudong
key.store.password=上面设置的密码
key.alias.password=上面设置的密码

更新项目

$ android update project --name kudong -t 1 -p /path/to/android/project

这里的-t参数可能会报错,需要用下面的命令查看当前存在的targets

$ android list targets

开始打包

先跑一次测试

$ ant debug

如果报错,可能需要安装下面的库(android工具依赖库需要兼容32位)

$ sudo apt-get install libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5 lib32z1

再次ant debug提示成功后,就可以打包了

$ ant release

...
...
-post-build:

release:

BUILD SUCCESSFUL
Total time: 6 seconds

打好的包在项目的./bin目录下

安装Hubot

Hubot基于NodeJsCoffeescript,这里我们将用Hubot调用前面的ant打包工具。

安装并创建hubot项目

1. 安装npm

    $ wget http://nodejs.org/dist/v0.10.32/node-v0.10.32.tar.gz
    $ tar zxvf node-v0.10.32.tar.gz
    $ cd node-v0.10.32
    $ ./configure
    $ make
    $ sudo make install

2. 安装hubot和coffee

    $ sudo npm install -g hubot coffee-script

3. 创建Hubot项目

    $ hubot --create myhubot
    $ cd myhubot
    $ hubot
    Hubot>

用Hubot调用ant打包android项目

Hubot脚本需要用coffeescript编写,简化版如下:

module.exports = (robot) ->
  robot.respond /build/i, (msg) ->
    msg.send 'building, please wait...'

    command = 'cd /path/to/android/project; ant release'
    ret = require('child_process').exec(command)

    error_chunks = []
    ret.stdout.on 'data', (chunk) ->
      #msg.send chunk.toString()
    ret.stderr.on 'data', (chunk) ->
      error_chunks.push chunk.toString()
    ret.on 'exit', (code, signal) ->
      if code == 0
        msg.send 'build success'
      else
        msg.send error_chunks.pop()

Slack与Hubot通信

修改package.json文件,在dependencies中加入slack:

"hubot-slack": "~2.0.4"

然后运行

$ npm install

设置Slack相关环境变量(token,团队名,机器人名) 这里我们的机器人名字叫:kucat

export HUBOT_SLACK_TOKEN=你的Token
export HUBOT_SLACK_TEAM=kudong
export HUBOT_SLACK_BOTNAME=kucat

启动

nohup ./bin/hubot --adapter slack --name kucat &

然后在Slack增加Hubot集成 填入Hubot URL: http://你的服务器IP:8080/

这个时候在Slack任一频道内输入:

kucat build

就可以看到返回数据了。

最后

自动打包这个需求,其实完全可以通过Github的hook机制处理,还更方便快捷,不过这不是本文重点。

本文只是很简单的应用案例,Slack+Hubot还有更多更酷的玩法,后面慢慢讲~

升级Redis2.2到2.8.15稳定版

今天收到服务器报警邮件,是服务器硬盘快满了(超过90%),检查后发现redisaof文件居然达到9G,这才想起来没有定期rewrite aof文件,于是干脆一不做二不休,升级到新版,免得手动处理aof(redis 2.4以上的版本会自动rewrite aof)

大致过程比较简单

卸载旧版

apt-get remove redis-server

下载官方稳定版编译安装

wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make
sudo make install

拷贝文件到原来的位置(懒得改init脚本)

sudo cp /usr/local/bin/redis-* /usr/bin/

然后修改配置文件(aof bind daemon等等)。

另外,旧版的配置文件跟新版不兼容,比如vm-enabled等参数,新版中木有。

启动后,查看版本

$ redis-cli --version
redis-cli 2.8.15

然后回到开头的问题,aof文件过大的问题 只需执行BGREWRITEAOF命令即可:

redis> BGREWRITEAOF
Background append only file rewriting started

以上命令在旧版中也可以执行,但是新版中这个操作由Redis自行触发,就不用人为定期处理了。

可以通过以下参数来控制自动触发行为:

auto-aof-rewrite-percentage
auto-aof-rewrite-min-size

默认触发机制是当aof文件大小为基准大小(64mb)的两倍时,自动执行BGREWRITEAOF

完。

PHP调试工具ChromePHP简直太坑爹

昨天在开发一新接口时,遇到一问题,nginx时不时出现502错。

打开nginx错误日志,发现如下报错:

2014/09/15 14:34:28 [error] 5103#0: *114 upstream sent too big header while reading response header from upstream, client: 127.0.0.1, ...

这个属于比较常规的问题了,header长度超出,之前遇到过

    fastcgi_buffer_size 128k;
    fastcgi_buffers 4 256k;
    fastcgi_busy_buffers_size 256k;

于是按照之前的方法适当调整nginx配置,发现问题依旧。

程序逻辑比较简单,大致流程是:

  • 检查redis缓存看是否有数据,有则直接返回
  • 否则查询数据库
  • 写入redis缓存
  • 返回数据

调试发现,每次写入redis缓存都会导致nginx报502

开始觉得不可能,因为系统其它很多地方也用到redis但一直没遇到过这个问题.

于是怀疑是不是snc redis的bug? 经过一番调试发现,如果执行redis命令的时候不记录日志,则能正常工作,否则就报502错,代码为下面的$this->logger->logCommand这一行。

    public function __call($name, array $arguments)
    {
        $log = true;

        ...

        $startTime = microtime(true);
        $result = call_user_func_array(array($this->redis, $name), $arguments);
        $duration = (microtime(true) - $startTime) * 1000;

        if ($log && null !== $this->logger) {
            $this->logger->logCommand($this->getCommandString($name, $arguments), $duration, $this->alias, false);
        }

        return $result;
    }

进一步debug,发现这里调用到了Symfony内的Monolog的方法用于记录日志。

    public function addRecord($level, $message, array $context = array())
    {
        ...

        // check if any handler will handle this message
        $handlerKey = null;
        foreach ($this->handlers as $key => $handler) {
            if ($handler->isHandling($record)) {
                $handlerKey = $key;
                break;
            }
        }
        // none found
        if (null === $handlerKey) {
            return false;
        }

        // found at least one, process message and dispatch it
        foreach ($this->processors as $processor) {
            $record = call_user_func($processor, $record);
        }
        while (isset($this->handlers[$handlerKey]) &&
            false === $this->handlers[$handlerKey]->handle($record)) {
            $handlerKey++;
        }

        return true;
    }

问题就出在上面的 handlers里,打印出来发现这里面包含了4个handler,经过排查发现是ChromePhpHandler的问题,进一步抠代码:

    public function onKernelResponse(FilterResponseEvent $event)
    {
        ...

        $this->response = $event->getResponse();
        foreach ($this->headers as $header => $content) {
            $this->response->headers->set($header, $content);
        }
        $this->headers = array();
    }

打印出这里的header后发现问题了,header内的X-ChromeLogger-Data值长度达到165952字节,大约162K,超出了我本地nginx配置里设定的128K,于是出现502. 如图:

chromephp-headers

为弄清具体是什么数据,经过base64_decodejson_decode后,如图:

chromephp-headers-print

原来是snc_redis完整记录了redis操作数据,所以如果写入到redis的数据过大,可能超出web服务器设置的header最大长度,导致报错。

这尼玛。。这种方式简直反人类啊,绝逼不靠谱,于是准备跟symfony官方反馈这个问题,不过发现官方已经解决过了。。 解决办法是,去掉Symfony配置文件config_dev.yml内的chromephp相关配置。

到这里问题解决了,简直太坑。。

不过,调试过程中发现snc redis的另外一个坑,下次有空再写出来。

MySQL主从同步的坑

配置完slave后,show slave status\G;报错如下

Got fatal error 1236 from master when reading data from binary log: 'Slave can not handle replication events with the checksum that master is configured to log; the first event 'mysql-bin.000002' at 120, the last event read from './mysql-bin.000002' at 120, the last byte read from './mysql-bin.000002' at 120

后来发现是因为主库MySQL版本是5.6, 从库是5.5

5.6的版本中加入了replication event checksum,主从复制时间校验功能,所以需要把这个关掉才能正常同步到5.5的slave

修改主库 /etc/my.cnf

增加下一行

binlog_checksum=none

重启mysql

现在再看从库status就正常了~

mysql> show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.0.121
                  Master_User: replication
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000003
          Read_Master_Log_Pos: 120
               Relay_Log_File: mysqld-relay-bin.000002
                Relay_Log_Pos: 266
        Relay_Master_Log_File: mysql-bin.000003
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: kudong
          Replicate_Ignore_DB: mysql,information_schema
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 120
              Relay_Log_Space: 423
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File:
           Master_SSL_CA_Path:
              Master_SSL_Cert:
            Master_SSL_Cipher:
               Master_SSL_Key:
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 1
1 row in set (0.00 sec)
Fork me on GitHub