Feb 1

墙外最老牌云数据库测试 不指定

felix021 @ 2018-2-1 00:03 [IT » ] 评论(0) , 引用(0) , 阅读(2181) | Via 本站原创
平台:AWS Aurora (MySQL 5.6.10a 兼容版本)

测试软件:sysbench 0.5 --test=oltp.lua (与之前一样的测试脚本

DB配置:db.r4.large (2核,约16GB RAM),Aurora Shared Disk

sysbench配置:m4.xlarge(4核,16GB RAM),20GB+100GB EBS(300/3000 IOPS)

测试结果:

tps: 276 tps
reads: 6400 tps
writes: 1100 tps
response time: 130ms (95%)

测试结果:
引用

[  10s] threads: 32, tps: 174.89, reads: 4077.58, writes: 705.68, response time: 305.44ms (95%), errors: 0.00, reconnects:  0.00
[  20s] threads: 32, tps: 230.00, reads: 5302.10, writes: 916.00, response time: 236.40ms (95%), errors: 0.00, reconnects:  0.00
[  30s] threads: 32, tps: 248.90, reads: 5680.20, writes: 994.70, response time: 219.35ms (95%), errors: 0.00, reconnects:  0.00
[  40s] threads: 32, tps: 250.10, reads: 5776.60, writes: 1000.00, response time: 219.16ms (95%), errors: 0.00, reconnects:  0.00
[  50s] threads: 32, tps: 253.20, reads: 5811.50, writes: 1012.00, response time: 205.62ms (95%), errors: 0.00, reconnects:  0.00
[  60s] threads: 32, tps: 252.30, reads: 5832.70, writes: 1009.60, response time: 197.89ms (95%), errors: 0.00, reconnects:  0.00
...
...
[1780s] threads: 32, tps: 279.00, reads: 6403.60, writes: 1115.30, response time: 135.88ms (95%), errors: 0.00, reconnects:  0.00
[1790s] threads: 32, tps: 276.30, reads: 6391.30, writes: 1106.80, response time: 155.70ms (95%), errors: 0.00, reconnects:  0.00
[1800s] threads: 32, tps: 276.90, reads: 6343.60, writes: 1106.80, response time: 131.83ms (95%), errors: 0.00, reconnects:  0.00
OLTP test statistics:
    queries performed:
        read:                            11371476
        write:                          1977648
        other:                          988824
        total:                          14337948
    transactions:                        494412 (274.66 per sec.)
    read/write requests:                13349124 (7415.78 per sec.)
    other operations:                    988824 (549.32 per sec.)
    ignored errors:                      0      (0.00 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          1800.0982s
    total number of events:              494412
    total time taken by event execution: 57593.8148s
    response time:
        min:                                24.28ms
        avg:                                116.49ms
        max:                                505.90ms
        approx.  95 percentile:            142.84ms

Threads fairness:
    events (avg/stddev):          15450.3750/15.24
    execution time (avg/stddev):  1799.8067/0.02
Jan 27
AWS的http/https负载均衡挺好用的,但是有一点比较麻烦,因为是应用层协议,所以在后端的nginx(以及下面挂的php-fpm)看到的 REMOTE_ADDR是负载均衡的IP,而直连LB的IP,则是保存在了 HTTP_X_FORWARD_FOR 这个header里面。

当然,在应用里面增加一小段代码去解析这个header也不是什么难事,但是毕竟有些框架已经有一套解析的方案了,而且碰到客户端自己还用代理的时候,这个字段的value是一串IP列表(直连负载均衡的ip是最后一个),就变得更复杂了。比如:

$ curl https://test.com/ -H "X-FORWARDED-FOR: 8.8.8.8"

php记录下来的 $_SERVER 变量就长这样了:

array (
  'HTTP_X_FORWARDED_PORT' => '443',
  'HTTP_X_FORWARDED_PROTO' => 'https',
  'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 13.31.11.23',
  'SERVER_PORT' => '80',  'SERVER_ADDR' => '172.24.22.33',
  'REMOTE_PORT' => '56247',
  'REMOTE_ADDR' => '172.24.22.34',
)

还是有点头疼的。幸好nginx有提供一个 ngx_http_realip_module 模块,可以解决这个问题,只要在配置里加上这么两行:

set_real_ip_from 172.24.0.0/16; #注:这个ip端是负载均衡所处VPC的CIDR
real_ip_header X-Forwarded-For;

重启nginx以后,再次访问就可以看到,REMOTE_ADDR 变成了 HTTP_X_FORWARDED_FOR 的 IP列表里最后一个IP。
Sep 22
(瞅一眼才发现四个月没写了,确实是好久没写代码了,没啥心得,不过想想好像可以写个提纲凑个数)

我们的业务主要还是用 MySQL 存储业务数据。

MySQL 一个很麻烦的问题是,alter table 的时候往往要锁表,而业务在最初设计的时候,又没法为未来的所有改动预留合适的字段,结果就是,要么另外建一张表横向扩展,要么熬到半夜,忍受锁表带来的业务中断;不过在多次实践中还是有一些心得体会,可以简单列一下。

1. alter table 是否都会锁表?

不都会,有些情况可以不锁表,例如,修改默认值,或者对 enum 类型字段增加一个 value

2. 对 enum 类型字段加 value 就不会锁表吗?

不一定,如果新增的 value 是最后一个就不会锁表,但也要注意,还是有坑(不能超过当前的bit数能表示的最大值)(为什么?)

3. 有没办法即 alter table 但又不长时间锁表?

有,percona-toolkit 有个工具能做到,原理很简单,新建一个表A的副本A',在A'上加字段,并同步数据,最后用一个 alter 语句对换两张表,但据说有BUG

4. 安利一下与 MySQL 协议基本兼容的 TiDB ,可以直接在线不锁表 alter table
May 25
$ vi /config/environments/production.rb

注释掉 “config.action_mailer.delivery_method = :sendmail”,

并在下面添加
引用

  config.action_mailer.delivery_method = :smtp
  config.action_mailer.perform_deliveries = true
  config.action_mailer.raise_delivery_errors = true

  config.action_mailer.smtp_settings = {
      :address              => "smtp.exmail.qq.com",
      :port                => 465,
      :domain              => 'yourdomain.com',
      :user_name            => '发信帐号',
      :password            => '密码',
      :authentication      =>  'login',
      :enable_starttls_auto => true,
      :tls                  => true,
      :email_from          => '发信帐号'
  }
Apr 28
想要计算一个区域里对角线的和,但SUMIF里面的那个criteria实在太简陋了,只能用vba来实现,大概长这样:
引用
Function sum_diag(n As Integer, ParamArray args() As Variant) As Variant
    result = 0

    For i = LBound(args) To UBound(args)
        For Each elem In args(i)
            If elem.Row + elem.Column = n Then
                result = result + elem.Value
            End If
        Next elem
    Next i
    sum_diag = result
End Function


然后这么用:
引用
=sum_diag(ROW()+COLUMN(), $B$1:$D$3)


点击在新窗口中浏览此图片
Apr 23
两年前学会了用Google Authenticator(详见为SSH添加两步验证),远程服务就装上了,感觉放心了很多。

但当时只是简单加上了Google Authenticator,实际使用中既要输入验证又要输入密码,太繁琐了,所以在搭建我司跳板机的时候,选择了用 publickey + authenticator 的方案,只需要输入一次验证码即可。

具体的配置方案变化不大,主要是用上了 SSH 6.2+ 新增的 AuthenticationMethods 参数,可以指定一系列验证方法,具体配置如下:

引用
#默认需要先用publickey验证,再用验证码
AuthenticationMethods publickey,keyboard-interactive

#对于指定的IP,只需要publickey验证
Match Address 10.0.0.4
    AuthenticationMethods publickey

#也可以指定用户只需要publickey验证
#Match User XXX
    #AuthenticationMethods publickey


顺便吐槽一下,Linux这套东西折腾起来真是要命,今天配置跳板机备份机的时候,完全相同的配置,复制一份就是不对,虽然配置里只指定了publickey,keyboard-interactive,但是每次输完验证码以后还是要求输入密码才行,折腾了几个小时才发现,不知道从什么时候开始,"auth required pam_google_authenticator.so" 已经不合适了,需要改成 "auth sufficient pam_google_authenticator.so",这样才会在输入验证码以后就结束认证过程(sufficient的实现里加了一个break?什么鬼。)(感谢 @ https://serverfault.com/a/740881/343388

以及,上篇也提到过的,另外一个坑是 ubuntu/debian 下面在自己编译完pam模块以后,需要手动拷贝到 /lib/security/ 目录下面才行,唉……
Apr 22

sudo玩弄小记 不指定

felix021 @ 2017-4-22 14:25 [IT » 操作系统] 评论(0) , 引用(0) , 阅读(10119) | Via 本站原创
在我司的运维实践中,sudo承担了一个很边缘,但是却很有意思的任务

最简单应用是,在web服务器上,在配置完nginx和php的log以后需要重启service,就这么玩(修改/etc/sudoers):
引用
nginx  ALL= NOPASSWD: /sbin/service nginx *, /sbin/service php5-fpm reload

如此一来,nginx用户可以执行 sudo service nginx reload 或者 sudo nginx configtest,也可以执行 sudo php5-fpm reload,但不能执行 sudo php5-fpm stop

不过被玩出花来的还是我司的跳板机。

对于管理员,我们这样配置:
引用
felix021 ALL=(ALL:ALL) ALL

通过密码验证切换到root用户

对于组长,我们这样配置:

  $ sudo groupadd master #添加组
  $ sudo usermod -a -G master felix021 #将用户添加到这个组
  $ vi /etc/sudoers
引用
%master ALL= NOPASSWD: /usr/bin/getpubkey *

这个 getpubkey 是一个shell脚本,只包含一句 cat "/home/$user/.ssh/id_rsa.pub" ,用来获取某个用户的公钥

然后配合 authroize_user 这个脚本,将公钥发送到组长有权限访问的机器上,通过这种方式实现一个简陋的二级授权:
引用
sudo getpubkey $1 | ssh nginx@$host bash -c "cat /dev/stdin >> ~/.ssh/authorized_keys"


另外顺便提一下sudo的debug,似乎相关资料很少。在配置group的时候,直接去测试,有时会发现好像总是不对,但是sudo默认没有log,很难排查问题。实际上只要在 /etc/ 下面添加一个 sudo.conf 就好,文件内容为:
引用
Debug sudo /var/log/sudo_debug all@warn,plugin@info

再次执行sudo的时候就会看到debug log文件里的信息:
引用
Apr 22 14:12:03 sudo[21786] user_info: user=felix021
Apr 22 14:12:03 sudo[21786] user_info: pid=21786
Apr 22 14:12:03 sudo[21786] user_info: ppid=21785
Apr 22 14:12:03 sudo[21786] user_info: pgid=21785
Apr 22 14:12:03 sudo[21786] user_info: tcpgid=21785
Apr 22 14:12:03 sudo[21786] user_info: sid=21522
Apr 22 14:12:03 sudo[21786] user_info: uid=1002
Apr 22 14:12:03 sudo[21786] user_info: euid=0
Apr 22 14:12:03 sudo[21786] user_info: gid=1000
Apr 22 14:12:03 sudo[21786] user_info: egid=1000
Apr 22 14:12:03 sudo[21786] user_info: groups=1000

从这里可以看到,虽然前面把 felix021 添加到了 master 这个group下,但是sudo并没有识别出来。

放狗搜了一下才知道,原来linux下需要退出所有的登录session重新登录,才能生效。
Nov 23

记Yii2的一个坑 不指定

felix021 @ 2016-11-23 00:33 [IT » 网络] 评论(1) , 引用(0) , 阅读(12540) | Via 本站原创
最近发现有些服务器会定期出现磁盘过载问题,这里记录一下追查过程,供参考。

11月10日,立山向我反映我们线上的某 service 出现了一小段时间的无响应,查看 error log,发现有几百条"208203204 connect() to unix:/var/run/php5-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream"错误,期间 zabbix 报警 server disk io overloaded,这让我想起确实每隔 3 ~ 4 天 zabbix 都会上报 server disk io overloaded(但出现的时间点并不固定在早上或晚上,也不一定是钱牛牛的访问高峰期),与 service 的error log时间也吻合,由于该 server 也是我们钱牛牛的两台 web 服务之一,因此在磁盘过载期间,钱牛牛对外提供的服务也收到了一定影响(error log也能证实这一点)。

用于 zabbix 的监控报警和 error log 的信息都太少,无法判断发生原因,因此没有继续追查下去;但是13日早晨这个问题又出现,因此决定重视起来。我在 server 上安装了 iotop 这个工具,使用 crontab 每分钟执行:
引用
$ /usr/sbin/iotop -btoqqqk --iter=5

每隔 1s 记录一次当前访问磁盘的进程及访问速度等信息,记录 5 次后退出。

在17号捕捉到又一次磁盘过载,通过 iotop 的输出:
引用
19:09:05  9663 be/4 nginx    31583.10 K/s 31551.68 K/s  0.00 % 93.86 % php-fpm: pool www

可以看到除了知道是 php-fpm 进程在写磁盘之外,并没有什么卵用,但至少还是指明了方向,只要找出 php 在写什么文件,就能离发现原因更近。

因此我写了另一个 monitor.py (后附),实时监控 iotop 的输出,筛选出磁盘 io 过大的进程,找出这些进程打开的文件(ls -lh /proc/$PID/fd),上报到sentry:
引用
$ /usr/sbin/iotop -btoqqqk | ./monitor.py


又等了5天,今天(22号)终于抓到罪魁祸首:

    server: PID(4252) IS USING TOO MUCH DISK IO
    {
        "iotop": "07:44:17  4252 be/4 nginx    288.76 K/s 94813.59 K/s  0.00 % 75.15 % php-fpm: pool www",
        "proc": "/proc/4252/fd:
    total 0
    lrwx------ 1 nginx users 64 Nov 22 07:44 0 -> socket:[1286391831]
    lrwx------ 1 nginx users 64 Nov 22 07:44 1 -> /dev/null
    lrwx------ 1 nginx users 64 Nov 22 07:44 2 -> /dev/null
    lrwx------ 1 nginx users 64 Nov 22 07:44 3 -> socket:[2138228391]
    lrwx------ 1 nginx users 64 Nov 22 07:44 4 -> socket:[2138229146]
    l-wx------ 1 nginx users 64 Nov 22 07:44 5 -> /data/www/xxx-service/runtime/logs/app.log
    lr-x------ 1 nginx users 64 Nov 22 07:44 6 -> /data/www/xxx-service/runtime/logs/app.log
    ",
        "time": "2016-11-21 07:44:17"
    }

从这里可以看出,php-fpm是在读写 service 的log。log文件内容有点琐碎,但是跟往常比起来确实没有什么异常,但是文件本身有点异常:

引用
nginx@server:logs$ ls -lah
total 5.0G
drwxrwxrwx 2 nginx users  4.0K Nov 22 10:54 .
drwxrwxrwx 3 nginx users  4.0K Jul 28 15:15 ..
-rwxrwxrwx 1 nginx users  55M Nov 22 12:04 app.log
-rw-r--r-- 1 nginx users 1001M Nov 22 07:44 app.log.1
-rw-r--r-- 1 nginx users 1001M Nov 22 07:43 app.log.2
-rw-r--r-- 1 nginx users 1001M Nov 22 07:42 app.log.3
-rw-r--r-- 1 nginx users 1001M Nov 22 07:41 app.log.4
-rw-r--r-- 1 nginx users 1001M Nov 22 07:40 app.log.5


可以看出,所有的log文件都是在磁盘负载特别高的时候修改的,可见,磁盘负载高的直接原因是 yii 框架的 logrotate 机制导致的。

以下是从 yii2/framework2/vendor/yiisoft/yii2/log/FileTarget.php 拷贝出来的内容:
public $rotateByCopy = true;
...
protected function rotateFiles()
{
    $file = $this->logFile;
    for ($i = $this->maxLogFiles; $i >= 0; --$i) {
        // $i == 0 is the original log file
        $rotateFile = $file . ($i === 0 ? '' : '.' . $i);
        if (is_file($rotateFile)) {
            // suppress errors because it's possible multiple processes enter into this section
            if ($i === $this->maxLogFiles) {
                @unlink($rotateFile);
            } else {
                if ($this->rotateByCopy) {
                    @copy($rotateFile, $file . '.' . ($i + 1));
                    if ($fp = @fopen($rotateFile, 'a')) {
                        @ftruncate($fp, 0);
                        @fclose($fp);
                    }
                } else {
                    @rename($rotateFile, $file . '.' . ($i + 1));
                }
            }
        }
    }
}


可以看出,罪魁祸首是 $rotateByCopy 默认值是 true ,而 yii2 之所以这么做,(根据框架的注释)是因为在 windows 下 log文件 很可能正被另一个文件打开,导致 rename 失败(吐槽:难道就不能多写一行代码根据检测到的os的type设置这个值吗???)。这也解释了为什么每个被 rotate 的 log 文件的修改时间间隔1分钟。

既然找到了问题的原因,解决方案就很简单了,把这个属性修改为false即可,当然,更完善的方案是能够根据OS的类型自动检测这个值。根据这个思路,我向 yii2 官方提交了一个pull requests:https://github.com/yiisoft/yii2/pull/13057,希望能被 merge 进去吧。

完。


monitor.py:

#!/usr/bin/python
#coding:utf-8

import sys
import re
import time
import datetime
import socket
try:
    import simplejson as json
except:
    import json

import subprocess
from raven import Client

import requests

last_sent = 0

dsn = '__SENTRY_DSN__'

#00 - '19:07:03'
#01 - '9663'
#02 - 'be/4'
#03 - 'nginx'
#04 - '10423.06'
#05 - 'K/s'
#06 - '10423.06'
#07 - 'K/s'
#08 - '0.00'
#09 - '%'
#10 - '99.99'
#11 - '%'
#12 - 'php-fpm: pool www'

def should_skip(program):
    if program == '[kjournald]':
        return True

    for prefix in ['gzip', 'rsync', 'ssh', 'logrotate', 'sap100', 'sar ', 'rpm ', 'updatedb', 'mysql', 'nginx', 'vim', 'cat']:
        if program.startswith(prefix):
            return True

    return False

def run_command(*cmd):
    try:
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        out, err = p.communicate()
        if err:
            raise Exception(err.strip())
        return out
    except Exception, e:
        return 'run %s failed: %s' % (str(cmd), e)

while True:
    try:
        line = sys.stdin.readline().strip()
    except KeyboardInterrupt:
        print >>sys.stderr, "user abort"
        sys.exit(0)

    fields = re.split(' +', line.strip(), 12)
    if len(fields) != 13:
        continue

    if should_skip(fields[12]):
        continue

    read_speed  = float(fields[4])
    write_speed = float(fields[6])
    if read_speed > 1000 or write_speed > 1000:
        date = time.strftime('%Y-%m-%d')
        pid = fields[1]
        client = Client(dsn)
        message = '%s: PID(%s) IS USING TOO MUCH DISK IO' % (socket.gethostname(), pid)
        args = {
            'time'  : date + ' ' + fields[0],
            'iotop' : line.strip(),
            'proc'  : run_command('ls', '-lhR', '/proc/%s/fd' % pid),
        }
        print >>sys.stderr, message
        print >>sys.stderr, json.dumps(args, indent=4)
        client.capture('raven.events.Message', message=message, extra=args)
分页: 8/99 第一页 上页 3 4 5 6 7 8 9 10 11 12 下页 最后页 [ 显示模式: 摘要 | 列表 ]