Nov 23

记Yii2的一个坑 不指定

felix021 @ 2016-11-23 00:33 [IT » 网络] 评论(1) , 引用(0) , 阅读(12697) | 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)
Nov 16
与某供应商对接的时候,要求用他们的RSA公钥加密,抛过来一个 RSAUtil.java ,核心代码大概是这样的:

public byte[] encrypt(byte[] data, PublicKey pk) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
    cipher.init(Cipher.ENCRYPT_MODE, pk);
    byte[] raw = new byte[128]; //cipher.getBlockSize() = 128
    cipher.doFinal(data, 0, data.length, raw, 0);
    return raw;
}


RSA什么的,用php来做不要太简单,顺手就能写出来:

function rsa_encrypt($plain_text, $public_key_path)
{
    $public_key = openssl_pkey_get_public(file_get_contents($public_key_path));
    openssl_public_encrypt($plain_text, $encrypted, $public_key);
    return bin2hex($encrypted);
}

function rsa_decrypt($encrypted, $private_key_path)
{
    $private_key = openssl_pkey_get_private(file_get_contents($private_key_path));
    openssl_private_decrypt(hex2bin($encrypted), $plain_text, $private_key);
    return $plain_text;
}


结果果然不行,发过去以后,对方表示无法解密,而我没有对方的私钥,不好验证,没办法,只能自己动手,造一对rsa密钥:

引用
$ openssl genrsa -out test.pem 1024
$ openssl rsa -in test.pem -pubout -out test_public.pem


试了下,确实和 java 的结果不互通。不过在测试过程中发现一个现象:java生成的加密串总是一样的,而php生成的加密串总是不一样的。google搜了一下"php openssl_public_encrypt different everytime", Stack Overflow 的解释是,PHP的 openssl_public_encrypt 默认使用 PKCS#1 算法,引入随机数,用于防止流量探测(频率分析、密文匹配什么的,我就不懂了)。

所以很显然,Bouncy Castle 没有使用 PKCS#1 算法,放狗搜到官方文档说,Cipher.getInstance("RSA", "BC") ,第一个参数 RSA 相当于 "RSA/NONE/NoPadding" (当然也可以指定 RSA/NONE/PKCS1Padding )。

看了下 php的openssl_public_encrypt文档,可以给第四个参数“padding”指定不同的值,例如 OPENSSL_NO_PADDING ,但是试了下,发现直接失败了,只好再放狗,竟然搜到了 php的bugreport,还好第一个回复就说明了原因:需要手动用 ASCII 0 填充到 blocksize 才行(当然rsa并不禁止使用其他value,主要是加解密双方要约定好)。

验证了一下,用 OPENSSL_NO_PADDING  能够正常解密 java 生成的密文,并且在明文前面填充了若干 ASCII 0 ,补全到128字节,就此解决问题:

function rsa_encrypt($plain_text, $public_key_path)
{
    $public_key = openssl_pkey_get_public(file_get_contents($public_key_path));
    openssl_public_encrypt(str_pad($plain_text, 128, "\0", STR_PAD_LEFT), $encrypted, $public_key, OPENSSL_NO_PADDING);
    return bin2hex($encrypted);
}

function rsa_decrypt($encrypted, $private_key_path)
{
    $private_key = openssl_pkey_get_private(file_get_contents($private_key_path));
    openssl_private_decrypt(hex2bin($encrypted), $plain_text, $private_key, OPENSSL_NO_PADDING);
    return ltrim($plain_text, "\0");
}


Sep 21

Zabbix自定义通知 不指定

felix021 @ 2016-9-21 04:20 [IT » 软件] 评论(0) , 引用(0) , 阅读(4410) | Via 本站原创
  虽然我们使用的腾讯云自带了一份监控,但是实在太弱,不好用(上次因为crontab导致postfix的maildrop把inode用光,腾讯云没有任何报警信息),所以我们额外搭建了一套Zabbix用于线上服务的监控,用来发现更细粒度的系统问题,例如io过载、cpu过载、内存不足、磁盘空间不足、服务down了之类的事件。

  发现了事件需要及时报警,但是zabbix自带的那个邮件通知太弱(不支持需要用户名和密码的smtp服务器),所以需要自定义通知。zabbix文档在有对自定义通知的说明,但是太粗略了,折腾了好久终于搞明白,这里记录一下备查。

1. 编写通知发送脚本

参照Custom alertscripts,写一个脚本,接受三个命令行参数 "to subject body" (即收件人、标题、正文),把消息发送出去。例如我们的短信通知是这样的(注: message.test.com是我们内部的统一通知服务):

文件名: sms
引用
#!/usr/bin/python

import sys
import requests

to = sys.argv[1]
subject = sys.argv[2]
body = sys.argv[3]

message = '%s: %s' % (subject, body)

print requests.post('http://message.test.com/sms/send', {
    'request_source' : 'monitor',
    'mobile'        : to,
    'message'        : message
})


除此之外我们还配置了通过 email、sentry 报警的脚本。

2. 将通知脚本置于 zabbix_server.conf 配置的 AlertScriptsPath 目录下

注意:必须在这个目录下、zabbix进程有执行权限

3. 在zabbix控制台配置通知脚本

引用
Administration -> Media Types -> (右上角) Create Media Type

Name: sms
Type: Script
Script name: sms (这里写脚本的名字就行,不用全路径)
Enabled: 打勾

Save.


4. 配置用户支持的通知方法

引用
Administration -> Users -> 点击需要配置的用户(例如Admin) -> Media -> Add

Type: sms
Send to: 13800138000 (这就是脚本接受到第一个参数)
When Active: 1-7, 00:00-24:00 (7*24,运维的悲剧)
Use if severity: 根据情况勾选
Status: Enabled

Add.

Save. (别漏了点这里!)


4. 配置触发器

引用
Configuration -> Actions -> (右上角) create action

#Action
Name: send to sms

#Operations (以下是参考选择)
a) 点击New
b) 添加收件人:Send to Users 右边的 Add 选择 Admin
c) 选择通知方式:Send only to 选择 sms
d) 点击Add
e) 点击Save

Add和Save都得点,否则就又2了


5. 触发事件测试效果

根据情况启动若干个耗cpu的进程,例如:

$ cat /dev/urandom | md5sum
Aug 14

启用BitLocker 不指定

felix021 @ 2016-8-14 15:11 [IT » 操作系统] 评论(1) , 引用(0) , 阅读(12506) | Via 本站原创
  记得很早之前就想给自己的电脑启用BitLocker,但是因为主板上没有TPM芯片(用于存储密钥),不能给系统盘加密;而不能给系统盘加密的话,其他盘的解密就需要每次启动系统以后再输入一次密钥,然而我的文档、桌面等一般都存在其他盘,我估摸着会出现一些奇怪的现象,所以就放弃了。

  昨天兴起又研究了一下,发现其实并不是一定要有TPM才能给系统盘加密——这个限制是Vista第一次引入BitLocker时的要求,后来微软也意识到,由于大多数民用主板上没有集成TPM芯片,导致BitLocker略显鸡肋,于是在Win7开始做了个变通,允许把加密密钥保存在单独的启动分区里,通过一个密码(或启动U盘)来保证密钥的安全,但是需要在组策略编辑器里将“计算机配置-管理模板-Windows组件-BitLocker驱动器加密-操作系统驱动器-启动时需要附加身份验证”修改为已启用(注意左下角“没有兼容的TPM时允许BitLocker”被打上勾了),系统盘被加密后,用户在启动电脑时输入一次密码即可。

  对Win7系统安装略有了解的同学大概注意到了,从光盘启动安装的话,除了Windows系统盘之外,还会创建一个100M的启动分区和一个恢复分区,其中启动分区一方面是为了兼容UEFI,另一方面也一定程度上解决了TPM的问题。虽然可以不需要TPM了,但是必须要说一句,这样的安全性还是降低了,因为启动分区是不加密的,这意味着有心人可以替换启动器(启动分区的第一个扇区,或者BOOTMGR)收集到用户启动时输入的密码,从而获得加密密钥。我想这也是微软默认仍然需要tpm,除非用户主动修改组策略的原因吧。

  我以前一直很不喜欢Windows的这个小动作(凭什么一个系统要占据3个Primary Partition?要知道MBR方式的磁盘只支持4个主分区或者3主分区+1逻辑分区,这么搞真浪费),所以我通常是用Wim安装器来安装,只需要一个主分区,结果就把自己坑了……还好补救措施很简单,通过DiskPark或者DiskGenius或者diskmgmt.msc开一个100M的主分区(注意不能是逻辑分区),激活(设置为启动分区,此处应有55AA梗),格式化为NTFS,把系统盘的 boot目录、bootmgr文件 拷贝到新分区,再用管理员cmd执行 "bcdedit /export X:\boot\bcd" ,重启电脑后就能开启BitLocker了。

  开启了BitLocker以后感觉心里踏实了很多,毕竟Chrome保存了那么多密码,如果电脑被偷走还是挺头疼的。
Jul 28
  上篇对比了阿里云、腾讯云、UCloud的云数据库,没想到文章被 Linux.cn 推荐,并被今日头条收录,影响范围颇大,相关云数据库团队都有工程师跟我取得了联系,尤其是阿里云和腾讯云的同学表示内部自测的性能并不差,向我要了具体的测试参数做核对;此外Linux中国的微信号下也有好奇宝宝回复表示想知道具体的测试命令和参数等。因此抽了点时间写个续篇,补充一些上篇没有提到的内容。

复测



  腾讯云的同学在测试完以后反馈,TPS 达到了 750 左右,与上篇中的结果相差悬殊。收到邮件,我感到深深的忧虑,难道之前的测试有问题?由于阿里云的同学说有些客户反馈性能差是因为测试的主机离数据库比较远,于是我重新申请了和上次配置一致的云数据库和云主机(并且特意check了所在分区是否一致),再次执行测试,发现在腾讯云上达到了 733 TPS,确实远超之前的测试结果。稳妥起见,我在阿里云和UCloud也申请了新的主机和数据库,再跑了一遍测试,结果如下:

点击在新窗口中浏览此图片

分析



  两次的结果并不是很稳定(证明了上次的测试确实有点粗糙,至少应该测几次取均值),然而在腾讯云上测试的两次结果相差过于悬殊,远不是误差能够解释的。所以我仔细研究了一下各家云数据库的说明,发现了一点玄机:

点击在新窗口中浏览此图片

  如上图所示,针对测试选择的实例,腾讯云和阿里云明确说明了实例的标称QPS,而UCloud则没有明确说明。从使用方的角度,我认为这个标称值是实例能保证的QPS,测试结果也间接证明了这一点。

  可以看出,因为使用的是标准化测试工具,TPS和QPS是之间有着1:27的比例关系。参考腾讯云标称的 7200 QPS,那么第一次测试的 7500 QPS / 277.8 TPS 就可以解释得通了:可能是该实例所在的物理机上负载较高所以执行了严格的限制,也可能是腾讯云不同机器上部署的频控策略不同(AB测试?),总之不巧正好测到了限制比较严格的实例。

  从表中还可以看出一些其他比较有意思的地方:

  1. 阿里云之所以表现得垫底,是因为QPS限制在4000,虽然第一次测试达到了5700,但仍然低于另外两家。如果能够采用最高档次的实例(14000 QPS),那么至少也能达到 500+ TPS,并不差。

  2. 实测QPS最高的是UCloud,远高于另外两家同档次的实例;他们在文档中给出了一组测试结果,号称最高能达到5.8万QPS。但是UCloud给自己留了余地,没有标称值,没有做出承诺,显得不够严谨。此外,我觉得,不能基于低QPS就武断地认为技术实力弱(想想双十一的量,阿里都扛得住),可能只是具体的运营或开发策略不同。

总结



  两次测试和进一步的分析更完整地体现了三家云数据库的状况。但正如上篇所说,性能往往并不是唯一/最高标准:即使选择了限制最严格的阿里云,在千万级的数据量下仍然能达到 200+ TPS,已经远超大部分业务的实际需求了。

彩蛋



  具体的测试脚本我放到了 Gist 上,供参考,如有不妥之处,欢迎指教探讨:

#!/bin/bash

#注:很多发行版自带SysBench的是0.4.12,但有些参数(如oltp-tables-count)是0.5才支持的,建议从官方源获取源码编译运行
#注2:ubuntu 16.04 上面编译如果报错说没有 libmysqlclient_r.so ,自己建一个软链接就好了
#注3:需要先create database mysql, 并对用于测试的用户授予 sbtest 这个库的读写权限。

cmd="sysbench --test=/root/src/sysbench-0.5/sysbench/tests/db/oltp.lua \
--oltp-table-size=2000000 \
--oltp-tables-count=20 \
--oltp-range-size=100 \
--oltp-point-selects=10 \
--oltp-simple-ranges=6 \
--oltp-sum-ranges=2 \
--oltp-order-ranges=3 \
--oltp-distinct-ranges=2 \
--oltp-index-updates=1 \
--oltp-non-index-updates=1 \
--num-threads=32
--max-requests=0
--max-time=1800
--report-interval=10
--rand-init=on
--rand-type=special
--rand-spec-iter=12
--rand-spec-pct=10
--rand-spec-res=75
--verbosity=3
--mysql-host=10.0.0.1
--mysql-port=3306
--mysql-user=root
--mysql-password=123456
"
$cmd cleanup
$cmd prepare
$cmd run
Jul 17

重拾线段树 不指定

felix021 @ 2016-7-17 22:46 [IT » 程序设计] 评论(1) , 引用(0) , 阅读(12806) | Via 本站原创
  前几天讨论遇到一个涉及区间覆盖的数据统计,发现很适合使用线段树来解决,于是重新回顾了一下这个好几年前学过的东西,凭着残存的理解,好了好久才勉强写了出来,感觉自己确实是没有搞算法的天赋,在边界处理的时候磕磕碰碰的,需要改好几次才能写对,不够干净利落。

  不过能从繁杂的业务中抽出来写写纯粹的数据结构和算法,有点回到学校的状态,感觉也蛮不错。

  以前在学校折腾算法的时候,从yyt同学的分享的ppt学到了这个数据结构,印象比较深的是,ppt上说,对于一个长度是 x 的线段,使用数组(元素 i 的左右节点分别是 2*i 和 2*i+1 )来记录的话,需要大小约为 3*x 的数组,但是在实际做题的时候却发现越界了,后来仔细去挖这个地方,才发现,其实应该是找到一个 y = 2^n 满足 2^(n-1) < x <= 2^n,所需的数组长度为 2y 。

  这次是先写了一个C++的class(偷懒用的struct),配合一些c-style的函数,让python用ctypes载入使用。然后兴起写了个Python的版本对比,跑了个简单的case,发现性能居然相差200+倍,做了一些改进,才领悟到,对于python来说,其实用数组建树比起直接用对象指针关联建树并没有太大优势,而用对象建树的好处是,如果不是极端情况,可以lazy load左右子树(当然,数组也可以lazy initialization子节点,但不能减少内存占用)。改起来也不难,于是就验证了一下,效果相当好,甚至比C++还快(因为测试case太简单,几乎没有展开子树),此外这种方式节点的数量可以减少到2 * x - 1(但是相应地每个节点需要增加指针)。

  另外遇到一个问题是迭代,Python的迭代器如果使用Generator语法(yield),写起来和用起来都特别自然,可是到C++就完全不一样了,形式上想要达到类似的效果比较累,保存和还原现场比较辛苦(不过这个case还好),试着实现了一个版本,但是需要遍历所有的节点感觉不太好,后来还是改成了偷懒的写法(直接生成整个结果集,在结果集上迭代)。

  最后,不成熟的小代码放在了这里:https://github.com/felix021/mycodes/tree/master/segtree

[update] 到数据集上实际跑了一下,C++版还是跑赢了几倍的速度,这还是没有做lazy init的情况,回头抽空再写个版本验证一下吧~
Jul 11

墙内三大云数据库测试对比 不指定

felix021 @ 2016-7-11 22:26 [IT » ] 评论(3) , 引用(0) , 阅读(18156) | Via 本站原创
  我司CTO和技术总监都是腾讯系的,所以我们一开始就选用腾讯云的服务。他们家的云数据库提供了可视化的运维操作页面和自动备份的能力,降低了DB运维的门槛。同时云数据库还支持高可用架构,对数据的安全性和服务的可靠性更有保障。另外有的云数据库厂商还提供了诸如数据库审计、慢查询分析、数据回档等能力,大大减轻了数据库运维和DBA的工作量。

  其实我们就没有专门的DBA,都是开发自己上去折腾,通过把数据库的可靠性外包给云端,确实极大地降低了我们的工作量,这一点还是挺爽的。但是在具体的使用过程中,发现还是有些地方不够满意,比如MySQL最高版只有5.6,没法用上5.7.8+新增的JSON字段;建立数据库自带的只读从库门槛较高(要最高版本);binlog的备份不方便;数据库授权上的坑(没有FILE和SHUTDOWN,不能grant all on *.*)等。

  记得以前对比过阿里云和UCloud的云主机磁盘IO(那时候腾讯云好像才刚起步呢),这么久过去了,再来比比看,他们的云数据库怎么样。墙内目前就只有这三家还算比较能入眼吧,网易和百度的就先跳过,都没听说谁家在用。另外那个不要脸的X云就算了,期权都能黑下来的公司,估计也活不了多久。

  这次除了对比性能,顺便再看看价格。

  在测试开始之前先打个预防针:以下的测试可能比较粗糙,并不是针对实际业务进行的,所以结果仅供参考;而且实际的业务往往并不是以性能为唯一考量标准,公司的一整套业务需要多项云服务的支撑,最基础的主机、数据库、NoSQL、对象存储、负载均衡等服务这几家都比较完善了,但是在增值、附加服务上各有优劣、亮点,实际选型还是应该根据业务特点仔细考量。


性能


  首先最重要的,是云数据库的读写性能。我在规划实例的配置时,主要考虑下面2点:首先,云数据库要使用SSD硬盘,这样能够保证数据库服务器的IO能够尽量的快。其次,云数据库的内存要尽可能大,这样有尽可能多的数据能够被缓存,提高读写速度。

  因此我选择的数据库配置如下:硬盘300GB SSD,内存8GB左右。由于每个云平台提供的配置都不相同(腾讯云的内存和磁盘比例是限定的,UCloud的内存没有8G等等),我在三个云平台上分别申请了如下配置的云数据库进行性能测试:

点击在新窗口中浏览此图片

  由于实际情况下,云数据库一般是通过云服务器进行访问的,因此我在这3个平台分别申请了配置差不多的云主机,在上面运行性能测试。我申请的云主机的操作系统都是64位CentOS 6.5,具体的配置如下:

点击在新窗口中浏览此图片

  现在比较流行的测试数据库工具是sysbench,为了和实际使用的情况吻合,我对sysbench做了参数上的修改。

  一般来说,读操作要远远高于写操作,并且有很多操作是需要范围查找和排序的,所以我在测试中提高了读操作的比例,特别是提高了范围查找和排序的比重。

  同时,对于写操作,稍微提高了update操作的比例。最终运行的测试中,每一个事务的读操作和写操作的比例是6:1左右。为了模拟项目启动之后的场景,我的sysbench测试集的数据量是总共20张表,每张表200万行数据,开启32个线程,并行向DB发送事务请求,共运行30分钟。下面是我观察到的结果:

点击在新窗口中浏览此图片

  把这个测试结果做成图表是这个结果:

点击在新窗口中浏览此图片

  这个性能测试结果大大出乎我的意料,虽然UCloud的DB界面看上去和阿里云和腾讯云相比比较朴实,产品介绍中也介绍的相对简单,但是,性能上的优势让我吃惊。说实话,自己测试之前,我没想到UCloud有如此大幅领先的性能。从测试结果分析,UCloud比阿里云高了422%,比腾讯云高了297%。而且,这还是在UCloud云数据库的内存不如其它2家大的情况下的结果(UCloud:6G内存,阿里云和腾讯云:8G内存)。
这个数据让我对UCloud的SSD云数据库性能十分动心,要知道,这个是我没有做过任何调优,开箱即用的配置,完全符合我对于云数据库“快速部署,性能满意”的期望。
 
  以下是测试结果的截图,从上到下分别是UCloud的UDB,阿里云RDS和腾讯CDB:

UCloud的UDB↓

点击在新窗口中浏览此图片

阿里云RDS↓

点击在新窗口中浏览此图片

腾讯云CDB↓

点击在新窗口中浏览此图片

价格


  看过了性能之后,我又顺便分析了一下价格,貌似阿里云和UCloud的价格是线性关系的。阿里云根据内存,CPU和磁盘定价,而UCloud根据内存和磁盘定价,CPU免费。腾讯云的配置只有几档,每一档根据内存和磁盘来定价,内存和磁盘的排列是固定的,不是简单的线性关系。因此,我计算出了阿里云和UCloud的价格因素的计算常数,然后以腾讯云的配置为基础,分别推算了3个厂商在同等条件下的价格,可以得到三个厂商的定价趋势图如下:

点击在新窗口中浏览此图片

  其中,腾讯云的价格是最便宜的,只是磁盘和内存的比例是固定的;UCloud的价格比腾讯云略贵,而阿里云比其它两家要高出40%左右的价钱。这可能是因为阿里云主备架构的关系;不过,我发现UCloud的普通版UDB和高可用版UDB几乎是一个价钱,也就是说,如果考虑主备架构的高可用版云数据库,UCloud的高可用版实例的价格比阿里云要低40%。仔细看了下,貌似UCloud的高可用数据库最近在进行促销,所以才会这么划算,也就是说趁活动期间购买的话可以省一大笔钱。可惜我们用的是腾讯云……

总结


  阿里云的文档非常全,而且详细。很多信息都可以通过文档来获取。而且它的MySQL在线管理工具很强大,就是建立DB的时候比较麻烦,还是要去界面上申请建立DB,价格较贵。

  腾讯云虽然没有阿里云的RDS做的那么完善,也还算易用。他们使用PHPMyAdmin来管理数据库,对于熟练这套工具的开发人员可能比较容易上手。价格较便宜。

  UCloud云数据库测出的TPS和QPS性能远高于业内平均水平。而且产品易用性好,价格适中。

  综上所述,性价比最高的云数据库是UCloud的UDB。

  额外再提一句,对于名列在程序员最讨厌两件事之一的“写文档”,在查看3个厂商的DB产品介绍时,感觉阿里云和腾讯云的产品介绍做的很好,里面有各种解决方案的架构,非常贴心。而且云数据库和云主机自建DB的区别也讲得很直观;而UCloud的UDB介绍比较简单,还需要进一步提高。

  以上。本文仅代表个人观点,如有意见和建议,欢迎探讨。
Jul 5

WR1041N v2 安装openwrt 不指定

felix021 @ 2016-7-5 22:31 [IT » 硬件] 评论(0) , 引用(0) , 阅读(12176) | Via 本站原创
(懒得看细节的同学直接拉到末尾下载带有shadowsocks的镜像)


TP-Link很久没给我的路由器更新固件了,而且它运行一段时间以后就需要重启才能保证wifi正常,这对于家里有基于Wifi的智能设备来说是不能忍的,所以只能折腾了,上OpenWRT吧。

记得09年的时候折腾路由器,WL520GU,刷上了DD-WRT,然后再交叉编译弄了个锐捷的客户端,这样就可以把校园网分享出来用。心血来潮到珞珈山水去翻了一下,当年发的帖子竟然还在Linux版置顶……

回归正题,1041n的OpenWRT页面在这里: https://wiki.openwrt.org/toh/tp-link/tl-wr1041n

虽然可以从 这里 下载预编译好的镜像,但是不折腾一下感觉不带感,所以参考官方的教程,自定义镜像。

1. 下载 Image Generator

当年玩WL520gu的时候,好辛苦,先得用openwrt的buildroot脚本创建一个交叉编译环境,那个脚本特别不适合中国国情,用wget慢慢下载国外的源……等得我都快崩溃了,于是拉出它的下载连接直接用迅雷下好放进去再启动。现在可好,直接就有现成的环境直接下载,一分钟就搞定了。

具体参考这个页面:https://wiki.openwrt.org/doc/howto/obtain.firmware.generate

正好1041n是ar71xx架构的,照做就好了。

2. 生成image

可惜1041n只有4M的flash,否则我一定把php和python都打进去,这样还能跑起个shadowsocks server。

最后权衡了下,是这么生成的:

    make image PROFILE=TLWR1041 PACKAGES='luci luci-i18n-base-zh-cn luci-i18n-firewall-zh-cn libev libpolarssl curl'

之所以把 libev 也打进去,主要是想之后可以用shadowsocks-libev,但是编译了一下发现可执行文件竟然 1M+,strip以后也还有两三百KB,还是算了。

如果不用curl的话,可以把libpolarssl和curl去掉,不过好像也没什么其他特别有用的了(或者是pptp?)。curl我是用来请求DnsPod的,这样可以及时把家里的IP刷到某个域名上去。

如果没什么错的话,生成的 image 就是这个了:

    bin/ar71xx/openwrt-15.05.1-ar71xx-generic-tl-wr1041n-v2-squashfs-factory.bin

3. 升级配置

tp不校验固件,所以直接通过路由器管理页面升级就行了。

升级了以后  telnet 192.168.1.1 登上去,用 passwd 修改root密码,然后就可以ssh登录,或者访问luci  http://192.168.1.1

image里面带了中文语言包,登录以后可以在 System -> Language 里面修改成简体中文。

配置PPPoE:打开  网络->接口,点击 WAN 接口的修改,看着配就好了。就那么点东西。

剩下的就是瞎折腾了

4. 创建用户

没什么卵用,弄着玩,这样就可以用另一个用户登录路由器,而且还是通过publickey authentication。

引用

echo 'felix021:*:103:100::/overlay/felix021:/bin/ash' >> /etc/passwd
echo 'felix021:*:16831:0:99999:7:::' >> /etc/shadow
mkdir -p /overlay/felix021/.ssh
cd /overlay/felix021/.ssh
echo $PUBKEY >> authorized_keys
chmod 600 authorized_keys
chown -R felix021:users /overlay/felix021/




==== UPDATE ====


还是花了一个晚上折腾了shadowsocks

1 安装SDK

参考:https://wiki.openwrt.org/doc/howto/obtain.firmware.sdk

下载SDK: https://downloads.openwrt.org/chaos_calmer/15.05.1/ar71xx/generic/OpenWrt-SDK-15.05.1-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64.tar.bz2

$ tar jxf Openwrt-SDK*

$ mv Openwrt-SDK* sdk

$ cd sdk

2 编译

$ git clone https://github.com/shadowsocks/openwrt-feeds.git package/feeds

$ git clone https://github.com/shadowsocks/shadowsocks-libev.git package/shadowsocks-libev
# 注@20180524,最新版本会出现编译错误,和libsodium貌似不兼容,建议用 v2.4.7

$ make menuconfig #选中 network 下的 shadowsocks-libev-polarssl

$ make package/shadowsocks-libev/compile V=99

编译好的文件位于:./build_dir/target-mips_34kc_uClibc-0.9.33.2/shadowsocks-libev-polarssl/shadowsocks-libev-2.4.7/ipkg-install/usr/bin/

3 腾出空间(回到Image Builder)

对于4M flash的image来说,openwrt给的空间实在太小了,所以只好打起现有package的主意。翻了一下,ipv6似乎占了比较大的空间,考虑到国内确实用不上ipv6,所以就拿它开刀吧。

参考前面 https://wiki.openwrt.org/doc/howto/obtain.firmware.generate 的 Remove useless files from firmware

修改 Makefile,添加 FILES_REMOVE 选项,并在 files_remove 文件中添加:
引用

/etc/modules.d/20-ipv6
/etc/modules.d/42-ip6tables
/etc/modules.d/nf-conntrack6
/etc/modules.d/nf-ipt6
/lib/modules/3.18.23/ip6table_filter.ko
/lib/modules/3.18.23/ip6table_mangle.ko
/lib/modules/3.18.23/ip6table_raw.ko
/lib/modules/3.18.23/ip6_tables.ko
/lib/modules/3.18.23/ip6t_REJECT.ko
/lib/modules/3.18.23/ipv6.ko
/lib/modules/3.18.23/nf_conntrack_ipv6.ko
/lib/modules/3.18.23/nf_defrag_ipv6.ko
/lib/modules/3.18.23/nf_log_ipv6.ko
/lib/modules/3.18.23/nf_reject_ipv6.ko
/usr/lib/libip6tc.so
/usr/lib/libip6tc.so.0
/usr/lib/libip6tc.so.0.1.0
/usr/lib/opkg/info/ip6tables.control
/usr/lib/opkg/info/ip6tables.list
/usr/lib/opkg/info/ip6tables.prerm
/usr/lib/opkg/info/kmod-ip6tables.control
/usr/lib/opkg/info/kmod-ip6tables.list
/usr/lib/opkg/info/kmod-ip6tables.postinst-pkg
/usr/lib/opkg/info/kmod-ip6tables.prerm
/usr/lib/opkg/info/kmod-ipv6.control
/usr/lib/opkg/info/kmod-ipv6.list
/usr/lib/opkg/info/kmod-ipv6.postinst-pkg
/usr/lib/opkg/info/kmod-ipv6.prerm
/usr/lib/opkg/info/kmod-nf-ipt6.control
/usr/lib/opkg/info/kmod-nf-ipt6.list
/usr/lib/opkg/info/kmod-nf-ipt6.postinst-pkg
/usr/lib/opkg/info/kmod-nf-ipt6.prerm
/usr/lib/opkg/info/libip6tc.control
/usr/lib/opkg/info/libip6tc.list
/usr/lib/opkg/info/libip6tc.prerm
/usr/lib/opkg/info/luci-proto-ipv6.control
/usr/lib/opkg/info/luci-proto-ipv6.list
/usr/lib/opkg/info/luci-proto-ipv6.prerm
/usr/sbin/ip6tables
/usr/sbin/ip6tables-restore
/usr/sbin/ip6tables-save
/lib/netifd/dhcpv6.script
/usr/lib/libiptext6.so


4 加入shadowsocks

$ mkdir -p files/etc/init.d files/usr/bin

$ cp /path/to/ss-server /path/to/ss-local files/usr/bin

$ cp /path/to/shadowsocks-libev/openwrt/files/shadowsocks.json files/etc/ss-server

$ cp /path/to/shadowsocks-libev/openwrt/files/shadowsocks.init files/etc/init.d/ss-server #修改配置文件路径为 /etc/ss-server,去掉 -b 0.0.0.0

$ cp /path/to/shadowsocks-libev/openwrt/files/shadowsocks.json files/etc/ss-local

$ cp /path/to/shadowsocks-libev/openwrt/files/shadowsocks.init files/etc/init.d/ss-local #修改配置文件路径为 /etc/ss-local

$ chmod +x files/etc/init.d/*

5 重新生成image

make image PROFILE=TLWR1041 PACKAGES='luci luci-i18n-base-zh-cn luci-i18n-firewall-zh-cn libev libpolarssl curl libpthread -kmod-ip6tables -ip6tables -kmod-ipv6 -kmod-nf-ipt6 -luci-proto-ipv6 -libip6tc' FILES=files/ FILES_REMOVE="files_remove"

升级一下新的rom,然后就可以在路由器上启动shadowsocks的server/client了。

在路由器上启动server,配合surge,就变成一个简易VPN了,效果相当赞。


附1:带有shadowsocks的镜像下载地址为 http://pan.baidu.com/s/1bpl9jK7 密码: 7ugp (貌似不加密码很容易失效)

附2:编译好的shadowsocks
分页: 9/99 第一页 上页 4 5 6 7 8 9 10 11 12 13 下页 最后页 [ 显示模式: 摘要 | 列表 ]