Feb
1
不知道为什么这些年写脚本一直没有解决这个小需求:把命令行参数传递给脚本中的某个命令继续执行
例如我想写一个 colored-echo 命令:
然后这么调用
但参数的数量是可变的,另一种实现是
但个实现也很奇怪,如果某个参数里面出现了引号或者空格,会因为bash奇葩的转义逻辑导致跟预期不一致。
可能因为痛感不强烈,所以拖了几年也没真正花心思去解决它,今天搜了一会,总算找到靠谱的解决方案了,而且超级简单:
例如我想写一个 colored-echo 命令:
引用
#!/bin/bash
color=$1
shift
echo -ne "\x1b[$color"
echo -n $1 $2 $3 $4 $5
echo -e "\x1b[0m"
color=$1
shift
echo -ne "\x1b[$color"
echo -n $1 $2 $3 $4 $5
echo -e "\x1b[0m"
然后这么调用
引用
colored-echo 41m hello world
但参数的数量是可变的,另一种实现是
引用
cmd=echo
for ((i=1; i<=$#; i++))
do
cmd="cmd ${@:i:1}"
done
$cmd
for ((i=1; i<=$#; i++))
do
cmd="cmd ${@:i:1}"
done
$cmd
但个实现也很奇怪,如果某个参数里面出现了引号或者空格,会因为bash奇葩的转义逻辑导致跟预期不一致。
可能因为痛感不强烈,所以拖了几年也没真正花心思去解决它,今天搜了一会,总算找到靠谱的解决方案了,而且超级简单:
引用
echo "$@"
Feb
1
平台: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
测试软件: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。
当然,在应用里面增加一小段代码去解析这个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
我们的业务主要还是用 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 => '发信帐号'
}
注释掉 “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
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 参数,可以指定一系列验证方法,具体配置如下:
顺便吐槽一下,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/ 目录下面才行,唉……
但当时只是简单加上了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
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承担了一个很边缘,但是却很有意思的任务
最简单应用是,在web服务器上,在配置完nginx和php的log以后需要重启service,就这么玩(修改/etc/sudoers):
如此一来,nginx用户可以执行 sudo service nginx reload 或者 sudo nginx configtest,也可以执行 sudo php5-fpm reload,但不能执行 sudo php5-fpm stop
不过被玩出花来的还是我司的跳板机。
对于管理员,我们这样配置:
通过密码验证切换到root用户
对于组长,我们这样配置:
$ sudo groupadd master #添加组
$ sudo usermod -a -G master felix021 #将用户添加到这个组
$ vi /etc/sudoers
这个 getpubkey 是一个shell脚本,只包含一句 cat "/home/$user/.ssh/id_rsa.pub" ,用来获取某个用户的公钥
然后配合 authroize_user 这个脚本,将公钥发送到组长有权限访问的机器上,通过这种方式实现一个简陋的二级授权:
另外顺便提一下sudo的debug,似乎相关资料很少。在配置group的时候,直接去测试,有时会发现好像总是不对,但是sudo默认没有log,很难排查问题。实际上只要在 /etc/ 下面添加一个 sudo.conf 就好,文件内容为:
再次执行sudo的时候就会看到debug log文件里的信息:
从这里可以看到,虽然前面把 felix021 添加到了 master 这个group下,但是sudo并没有识别出来。
放狗搜了一下才知道,原来linux下需要退出所有的登录session重新登录,才能生效。
最简单应用是,在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
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重新登录,才能生效。