Shanghai, China

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

升级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

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

cp src/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)

用Node.js结合RabbitMQ实现APN推送

最近刚开始看nodejs,于是想把之前写得稀烂的Python+RabbitMQ实现多线程APN推送推送重写一次。

实际完成后代码量很少,用到了两个库 node-apnnode-amqp,我要做的只是将两者结合起来打打酱油。

需要完善的部分:

rabbitmq的ack机制

现在仅仅是拿到消息调用推送函数后就返回ack了,实际上此时并不确定是否推送成功了(因为是异步),所以应该是在推送的transmitted事件触发后再返回ack。

准备工作:

安装rabbitmq
拿到推送证书 cert.pem和key.pem
修改receive.js中的队列名称

运行:

npm install
node receive.js

demo代码已发到https://github.com/henter/NodeRabbitMQAPN

APN推送峰值大概为2000条每秒,完爆之前python多线程的实现方式。

Fork me on GitHub