0%

需要将json的数据,按program属性的数组大小进行排序。Python实现特别轻松,主要在交换值方面轻松,以前用Delphi代码量起码是这个两倍。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#coding:utf-8
import json

jsonraw='[{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"19","orientation_name":"东","map_name":"格局三","start":"2017-05-09","floor_name":"东2楼","number":"2009","map_id":"16","days":"25","end":"2017-06-02","floor_id":"64","id":"104"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"21","orientation_name":"西","map_name":"格局三","start":"2017-05-09","floor_name":"东2楼","number":"2011","map_id":"16","days":"25","end":"2017-06-02","floor_id":"64","id":"105"}]},{"scores":0,"program":[{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局二","start":"2017-05-09","floor_name":"东3楼","number":"3009","map_id":"15","days":"25","end":"2017-06-02","floor_id":"65","id":"106"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"19","orientation_name":"东","map_name":"格局二","start":"2017-05-09","floor_name":"东5楼","number":"5005","map_id":"15","days":"25","end":"2017-06-02","floor_id":"67","id":"108"}]},{"scores":0,"program":[{"type_name":"普通房","type_id":"16","orientation_id":"22","orientation_name":"南","map_name":"格局二","start":"2017-05-09","floor_name":"东5楼","number":"5006","map_id":"15","days":"25","end":"2017-06-02","floor_id":"67","id":"109"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"21","orientation_name":"西","map_name":"格局二","start":"2017-05-09","floor_name":"东5楼","number":"5007","map_id":"15","days":"25","end":"2017-06-02","floor_id":"67","id":"110"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"23","orientation_name":"北","map_name":"格局三","start":"2017-05-09","floor_name":"东5楼","number":"5008","map_id":"16","days":"25","end":"2017-06-02","floor_id":"67","id":"111"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"21","orientation_name":"西","map_name":"格局二","start":"2017-05-09","floor_name":"东5楼","number":"5009","map_id":"15","days":"25","end":"2017-06-02","floor_id":"67","id":"112"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"19","orientation_name":"东","map_name":"格局三","start":"2017-05-09","floor_name":"东2楼","number":"2009","map_id":"16","days":"22","end":"2017-05-30","floor_id":"64","id":"104"},{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局11","start":"2017-05-31","floor_name":"东6楼","number":"6007","map_id":"14","days":"3","end":"2017-06-02","floor_id":"68","id":"114"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"21","orientation_name":"西","map_name":"格局三","start":"2017-05-09","floor_name":"东2楼","number":"2011","map_id":"16","days":"22","end":"2017-05-30","floor_id":"64","id":"105"},{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局11","start":"2017-05-31","floor_name":"东6楼","number":"6007","map_id":"14","days":"3","end":"2017-06-02","floor_id":"68","id":"114"}]},{"scores":0,"program":[{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局二","start":"2017-05-09","floor_name":"东3楼","number":"3009","map_id":"15","days":"22","end":"2017-05-30","floor_id":"65","id":"106"},{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局11","start":"2017-05-31","floor_name":"东6楼","number":"6007","map_id":"14","days":"3","end":"2017-06-02","floor_id":"68","id":"114"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"19","orientation_name":"东","map_name":"格局二","start":"2017-05-09","floor_name":"东5楼","number":"5005","map_id":"15","days":"22","end":"2017-05-30","floor_id":"67","id":"108"},{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局11","start":"2017-05-31","floor_name":"东6楼","number":"6007","map_id":"14","days":"3","end":"2017-06-02","floor_id":"68","id":"114"}]},{"scores":0,"program":[{"type_name":"普通房","type_id":"16","orientation_id":"22","orientation_name":"南","map_name":"格局二","start":"2017-05-09","floor_name":"东5楼","number":"5006","map_id":"15","days":"22","end":"2017-05-30","floor_id":"67","id":"109"},{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局11","start":"2017-05-31","floor_name":"东6楼","number":"6007","map_id":"14","days":"3","end":"2017-06-02","floor_id":"68","id":"114"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"21","orientation_name":"西","map_name":"格局二","start":"2017-05-09","floor_name":"东5楼","number":"5007","map_id":"15","days":"22","end":"2017-05-30","floor_id":"67","id":"110"},{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局11","start":"2017-05-31","floor_name":"东6楼","number":"6007","map_id":"14","days":"3","end":"2017-06-02","floor_id":"68","id":"114"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"23","orientation_name":"北","map_name":"格局三","start":"2017-05-09","floor_name":"东5楼","number":"5008","map_id":"16","days":"22","end":"2017-05-30","floor_id":"67","id":"111"},{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局11","start":"2017-05-31","floor_name":"东6楼","number":"6007","map_id":"14","days":"3","end":"2017-06-02","floor_id":"68","id":"114"}]},{"scores":0,"program":[{"type_name":"标准间","type_id":"15","orientation_id":"21","orientation_name":"西","map_name":"格局二","start":"2017-05-09","floor_name":"东5楼","number":"5009","map_id":"15","days":"22","end":"2017-05-30","floor_id":"67","id":"112"},{"type_name":"普通房","type_id":"16","orientation_id":"19","orientation_name":"东","map_name":"格局11","start":"2017-05-31","floor_name":"东6楼","number":"6007","map_id":"14","days":"3","end":"2017-06-02","floor_id":"68","id":"114"}]}]'

jsonarr=json.loads(jsonraw)

arrlen = len(jsonarr)

for i in range(0,arrlen):
for j in range(i+1,arrlen):
if len(jsonarr[i]['program']) > len(jsonarr[j]['program']):
jsonarr[i],jsonarr[j] = jsonarr[j], jsonarr[i]

print jsonarr

要实现对括号对的匹配。

假如不能双双配对,就是不合适的。举个例子

1
2
3
4
5
6
正确
'sd{sfsfs}[(s)]'
'[{()}]'

不正确
'[[]{()}'

思路就是用一个栈存储匹配到的左括号,匹配到右括号的话,就和栈顶的数据对比。因为两个括号的ASCII码,一般右括号总比左括号大1或者2.通过判断差值是否在这个区间,基本可以确定。匹配到一对括号,便消除一个。到最后如果栈为空,就是合格的。反之,不为空就是不合格的。

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/python
#coding:utf-8

leftChar=['{','[','(']
rightChar=['}',']',')']

def check_format(sPath):
s=[]
for x in sPath:
if x in leftChar:
s.append(x)
if x in rightChar:
if s:
if abs(ord(x)-ord(s[-1]))<=2:
s.pop()
else:
s.append(x)
else:
s.append(x)
if s:
print u"不合格"
else:
print u"合格"

结果如下

1
2
3
4
check_format('sd{sfsfs}[(s)]')
合格
check_format('[[]{()}')
不合格

很多数据都做了备份,而且全都用脚本完成。并定时下载一份回我们本地,等于多多点异地备份了,避免被一锅端了的情况。

像代码,这个是分发的,而且开发人手一份,这个就不备份了。用户静态数据也都定时做了rsync同步备份,压缩保存。

连测试环境的数据库都做了自动的MySQL逻辑备份。唯独线上环境,存在RDS中的MySQL数据库,虽然有RDS定时备份实例,但是不经常上去下载一份保存回本地你是不得安心的。

这万一被盗号了呢。。。岂不是辛辛苦苦几十年,一删回到解放前。

本来是可以用mysqldump -h 远程IP 这样备份的。但是RDS可能是防止脱裤,搞了限制。所以,往日都是我自己上阿里云官网,手动点击下载备份。

为了减少这一部分的重复劳动,我决定用阿里云的SDK搞一个自动下载实例。

以为会遇到很多麻烦,没想到SDK封装得还不错,使用异常简单。

步骤分为如下几步

  1. 安装阿里云API封装的Python SDK

    里面几乎有阿里云所有产品的SDK,这给我们定制自己的工具提供了很大的便利。
    这里我们要用的是RDS相关的功能,所以 sudo pip install aliyun-python-sdk-rds就行了。

  2. 申请访问API需要用的Access Key

    最好先新建RAM账号,再申请AK。

  3. 编码

整个备份逻辑很简单,就是查看指定日期区间备份列表,获取备份信息。然后下载备份。

主要用到SDK DescribeBackupsRequest 这个类。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from aliyunsdkcore import client
#from aliyunsdkrds.request.v20140815 import DescribeRegionsRequest
from aliyunsdkrds.request.v20140815 import DescribeBackupsRequest
import json
import requests
import datetime
#import time

saveRoot='/home/phan/backups/' #设置备份根目录

# 下载链接
def downLink(url,dateStr):
downFile = requests.get(url)
# 设置备份目录
savePath = '%s%s.tar.gz'%(saveRoot,dateStr)
with open(savePath,'wb') as backup_file:
backup_file.write(downFile.content)

# 获取今日和明日的日期
today=datetime.date.today();
yesterday=datetime.date.today() - datetime.timedelta(1)

clt = client.AcsClient('你的AK','你的ASK','cn-shenzhen') #这里的地区ID非必须的

request = DescribeBackupsRequest.DescribeBackupsRequest()
## 以下请求的参数都是必须的 尤其实例名和查询区间
request.set_accept_format('json')
request.set_action_name('DescribeBackups')
request.set_DBInstanceId('rm-xxxxxxxxxxx') # 你的实例ID
request.set_StartTime('%sT00:00Z'%(yesterday))
request.set_EndTime('%sT00:00Z'%(today))
result = clt.do_action_with_exception(request)
## 下载RDS备份
if result:
json_rds = json.loads(result)
if json_rds['Items']['Backup'][0]['BackupStatus']=='Success':
downLink(json_rds['Items']['Backup'][0]['BackupDownloadURL'],today)

print u"数据库备份完成"

至此,加入crontab,备份基本全自动了。

参考资料

Aliyun Python SDK
Aliyun RDS Help

有这么一个需求,需要根据svn生成的版本之间差异的文件列表,生成一个treeview。

数据样本如下:

1
2
3
4
5
6
7
8
9
10
11
$defaultStr = [
'Public/images/list/order.png',
'Public/images/list/reverse.png',
'Application/Crm/View/ReturnWork/visitRecord.html',
'Application/Crm/View/ReturnWork/myVisit.html',
'Application/Crm/View/ReturnWork/memberReturn.html',
'Application/Crm/View/ReturnWork/fitReturn.html',
'Application/Crm/View/ReturnWork/emergencReturn.html',
'Application/Crm/View/UserManagement/user_list.html',
'Application/Crm/View/UserManagement/add_maternal.html',
'ver.txt'];

思路如下,先根据 / 符号拆分路径成数组,拼凑tree数组。最后根据tree数组递归生成json数组。

生成数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$tree=array();

function pushNode($subtree,$name){
if(!empty($name)){
$tmpname=end($name);
if(!array_key_exists(end($name),$subtree)){
$subtree[$tmpname]=array();
}
array_pop($name);
$subtree[$tmpname]=pushNode($subtree[$tmpname],$name);
return $subtree;
}
}

for($i=0;$i<count($defaultStr);$i++){
$path_str=array_reverse(explode('/', $defaultStr[$i]));
$tree = pushNode($tree,$path_str);
}
var_dump($tree);

生成类似如下的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

array(3) {
["Public"]=>
array(1) {
["images"]=>
array(1) {
["list"]=>
array(2) {
["order.png"]=>
NULL
["reverse.png"]=>
NULL
}
}
}
["Application"]=>
array(1) {
["Crm"]=>
array(1) {
["View"]=>
array(2) {
["ReturnWork"]=>
array(5) {
["visitRecord.html"]=>
NULL
["myVisit.html"]=>
NULL
["memberReturn.html"]=>
NULL
["fitReturn.html"]=>
NULL
["emergencReturn.html"]=>
NULL
}
["UserManagement"]=>
array(2) {
["user_list.html"]=>
NULL
["add_maternal.html"]=>
NULL
}
}
}
}
["ver.txt"]=>
NULL
}

递归生成树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//递归生成树
function makeJsonTree($tree){
$json=[];
if(!empty($tree)){
foreach ($tree as $key => $value){
$jsonNode=[];
$jsonNode['text']=$key;
if($value!==null){
$jsonNode['nodes']=makeJsonTree($value);
}
array_push($json,$jsonNode);
}
}
return $json;
}

生成最终符合bootstrap treeview的格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
[
{
text: "Public",
nodes: [
{
text: "images",
nodes: [
{
text: "list",
nodes: [
{
text: "order.png"
},
{
text: "reverse.png"
}
]
}
]
}
]
},
{
text: "Application",
nodes: [
{
text: "Crm",
nodes: [
{
text: "View",
nodes: [
{
text: "ReturnWork",
nodes: [
{
text: "visitRecord.html"
},
{
text: "myVisit.html"
},
{
text: "memberReturn.html"
},
{
text: "fitReturn.html"
},
{
text: "emergencReturn.html"
}
]
},
{
text: "UserManagement",
nodes: [
{
text: "user_list.html"
},
{
text: "add_maternal.html"
}
]
}
]
}
]
}
]
},
{
text: "ver.txt"
}
]

未雨绸缪,考虑到以后可能服务器增多,如果日志不集中管理,单台日志查看,还是显得比较麻烦。

有常查看nginx日志的需求,所以,打算利用rsyslog对nginx日志做个集中管理。

这里假设日志中心为center,节点1为node1

服务器 IP
Center 192.168.1.2
Node1 192.168.1.3

配置都是基于Centos7 默认的rsyslog版本 7.4.7 V5、V7、V8配置差别似乎挺大的,所以尽量用同样的版本吧

Center配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# vim /etc/rsyslog.conf
# 开启端口给node传送数据。这里吧tcp和udp端口都打开了
# 配置前面加了$的都代表是全局配置
$ModLoad imudp
$UDPServerRun 514
$ModLoad imtcp
$InputTCPServerRun 514
# 允许特定IP段才能连接端口
$AllowedSender tcp, 192.168.1.0/24
# 新建一个模板
$template Remote,"/data/log/%fromhost-ip%/%fromhost-ip%_%$YEAR%-%$MONTH%-%$DAY%.log"
# 过滤本地消息
:fromhost-ip, !isequal, "127.0.0.1" ?Remote

配置好rsyslog,就可以重启一下服务了。

#systemctl restart rsyslog

如果没意外,netstat -antp | grep rsyslog 就能看到开放的514端口了。

Node1配置

1
2
3
4
## 将所有等于或者大于info级别的信息传到Center
*.info;mail.none;authpriv.none;cron.none @@192.168.1.2
$template myFormat,"%timestamp% %fromhost-ip%%msg%\n"

重启服务systemctl restart rsyslog,这个时候用logger "test"

在Center的/var/log/messages日志应该就能看到了。

配置nginx日志传输 同理也可以用到其它日志传输上。

新版本的nginx基本都支持在nginx配置中,直接配置syslog。

但是我这里还是用老办法。现在rsyslog配置读取nginx的日志文件,然后传送过去,这个办法也试用于其它服务的日志。

Node1 新建一个rsyslog配置nginx.conf

1
2
3
4
5
6
7
8
vim /etc/rsyslog.d/nginx.conf
# 关键是路径和tag、statefile
module(load="imfile" PollingInterval="5")
$InputFileName /var/log/nginx/access.log
$InputFileTag nginx-access-1:
$InputFileStateFile state-nginx-access-1
$InputRunFileMonitor

我在一台V5版本上是这样配的

1
2
3
4
5
6
7
8
$ModLoad imfile
$InputFileName /var/log/nginx/access.log
$InputFileTag nginx-access-1:
$InputFileStateFile state-nginx-access-1
$InputFileFacility local5
$InputFileSeverity info
$InputRunFileMonitor

Center

这个配置例子好匮乏,找了好多资料,终于谷爹那里找到了。见文末参考资料。根据配置的tag导出日志到特定文件。
同样新建一个rsyslog配置文件nginx.conf,使用omfile模块,用dynaFile参数实现动态文件名。这个很多国内文章基本都没提到,还是看帮助文档发现这个的。否则我就用logrotate了。

1
2
3
4
5
6
7
8
9
10
11
vim /etc/rsyslog.d/nginx.conf
template(name="OnlyMsg" type="string" string="%msg:2:$%\n")
template(name="dynaname" type="string" string="/logfiles/%fromhost-ip%/access-%$YEAR%%$MONTH%%$DAY%.log")
if( $syslogtag == 'nginx-access-1:') then {
# write to file
action(type="omfile" dynaFile="dynaname" template="OnlyMsg")
# forward over network
call sendToLogserver
stop
}

重启rsyslog,应该就没问题了。

参考资料

基于我们团队现在业务还在处于比较初期的阶段。访问量并不大,性能问题就成了一个重要但是不紧急的任务。

我而目前主要在发布、监控自动化、可视化。尽可能减少人为操作的问题上努力。

最近深圳那边同事说服务器有点问题,主要是网站访问缓慢,叫如果有空帮忙看看。

我09年左右就开始运营自己的小网站,可以说也是经历过很多这类问题的排查。

也遇过很多奇葩的问题导致网站访问过慢,甚至打不开的情况,但是从未记录下来。

所以,打算开一篇博文记录一下,一方面当做一种笔记,一方面也帮助自己理清一下整个流程。

是哪个环节导致的问题?

很多时候我们习惯访问一慢,就往服务器上看。看看是不是服务器出什么问题了。

其实,在访问网站的过程,任何一个环节都可能会出问题。

但是,最为常见的原因无非以下几种。

website slowness troubleshooting

往大来分类

自己电脑->网络运营商->DNS->主机服务商->服务器

而前面几个大多时候是我们不可控的。除了选择的时候 :D 但是,也得在我们考虑范围内,否则有时候可能白忙碌一场。

抛开不可控的因素,服务器上发生的所有一切才是我们最为关心的。

常见外在因素

1. 遭受攻击

这个问题,其实攻击都是有成本的,如果没有业务冲突,规模不大,很多时候是没人闲的蛋疼跑去攻击你。这个大多情况都是可以被排除的。

  • 常见攻击
    • DDoS、RDDoS 拒绝服务攻击
    • WebCC

这两个其实还是比较简单粗暴的。这个的防护,小规模的,现在很多大的云主机商多少都提供了这类的防护。节点少的,尽可能隐藏自己的源IP可以少很多麻烦

大规模的攻击,基本都是要上升到讨论成本的东西,软硬件防护,多节点,负载均衡等杂七杂八的,增加自己的成本,提高攻击者的攻击成本。

一些服务器上做的配置防护,在这类分布式多IP的大规模攻击下,效果微乎其微。

2.访问量过大

整个对于大多网站来说,是喜闻乐见的。不过这些访问,不单单包括自然访问,还有一些非自然访问量。

  • 自然访客
  • 搜索引擎/爬虫/采集器的蜘蛛 (一些网站被采集器爬到到整个网站访问缓慢也是挺常见的)
  • 一些无目的的攻击扫描等

常见内在因素

根据木桶原理,任何一个短板,都有可能导致整个服务流程出现缓慢。

1.基础服务-硬件

  • CPU
  • 内存
  • 网络IO
  • 硬盘IO

如果硬件到了瓶颈,那就该考虑,是不是该升级配置了?是不是该做CDN加速了?是不是该做负载均衡了?

如果排除前面提到的一些不可控和被攻击的问题,而硬件负载都很低,那基本问题就出现在服务和业务相关问题上了。

2.高级服务-软件服务

  • web服务 (Nginx/Apache/IIS等)
  • 数据库 (MySQL/Oracle/MSSQL/redis等)
  • 业务依赖的其它服务

服务的配置是否针对当前服务器的配置进行过针对性的优化?是否能发挥了当前硬件的潜能?

3.逻辑问题-业务问题

  • 代码效率问题 (比方算法,有些查询语句)
  • 业务逻辑问题 ( 比如优化资源访问方式,比如说分拆业务合并业务,减少请求数 )
  • 资源没有复用 ( 比方一些静态资源不缓存,一些连接没有复用,动态常用使用的资源不缓存 )

简而言之,哪个拖后腿整哪个。剩下的就是定位问题和解决问题,这也是最重要的部分。

先挖坑,以后再填。

报错:mysqldump: unknown variable ‘set-gtid-purged=OFF’

完整出错如下:

1
2
3
4
5
6
7
8
9
10
11
12
11:45:51 AM Dumping xxx_logview (gef_infos)
Running: mysqldump --defaults-file="/tmp/tmpePBoof/extraparams.cnf" --set-gtid-purged=OFF --user=root --host=119.23.xxx.xxx --protocol=tcp --port=3306 --default-character-set=utf8 --no-data --skip-triggers "xxx_logview" "gef_infos"
mysqldump: unknown variable 'set-gtid-purged=OFF'

Operation failed with exitcode 7
11:45:51 AM Dumping xxx_logview (gef_users)
Running: mysqldump --defaults-file="/tmp/tmpbh7vOY/extraparams.cnf" --set-gtid-purged=OFF --user=root --host=119.23.xxx.xxx --protocol=tcp --port=3306 --default-character-set=utf8 --no-data --skip-triggers "xxx_logview" "gef_users"
mysqldump: unknown variable 'set-gtid-purged=OFF'

Operation failed with exitcode 7
11:45:51 AM Export of /home/xxx/dumps/Dump20170422-1 has finished with 2 errors

出错很明显是mysqldump 不支持这个set-gtid-purged。我用的是MairaDB自带的mysqldump工具,版本如下。可能移除了这个特性。

mysqldump Ver 10.15 Distrib 10.0.29-MariaDB, for debian-linux-gnu (x86_64)

而MySQL Workbench在export data的时候,默认是加上–set-gtid-purged=OFF参数的。

所以,我能想到的方法如下:

  1. 理论上去掉它这个默认设置就可以了。但是很遗憾,我不知道workbench在哪里改这参数。。。找了半天。

  2. 安装一个MySQL数据库,然后在MySQL Workbench -> Edit -> Perferences -> Administration -> Path to mysqldump 指定你MySQL版本数据带的mysqldump工具的路径。

  3. 用其它数据库管理工具,比如我,最后用了DBeaver CE 算是Linux下一个很万能的数据库管理工具了。大部分场景下,和Navicat有得拼。关键还是免费!

GoAccess GoAccess Download 作为一个轻量的日志分析,还是不错的。之前一直都是用命令安装。直到前几天为了体验最新版的UI,自己尝试在Ubuntu 16.04上编译安装,比较轻松。

可能是我自己的机子用了段时间,很多依赖包都装好了。但是在生产环境CentOS7.2上还是遇到了几个小问题。

安装依赖包

1
yum install ncurses-devel geoip-devel tokyocabinet-devel openssl-devel

安装完GeoIP记得更新一下 # geoipupdate 把数据库下载回来,之前不知道这个,导致老是觉得数据库太简陋了。

我的话是Clone git的源码安装的,步骤如下:

  1. $ git clone https://github.com/allinurl/goaccess.git
  2. $ cd goaccess
  3. $ autoreconf -fiv

这一步你可能会遇到像我一样的错误。

  • 错误1

    1
    -bash: autoreconf: command not found

    这个安装 yum install -y autoconf 即可

  • 错误2

    1
    Can't exec "autopoint": No such file or directory 

安装gettext-tools

1
yum install gettext-common-devel gettext-devel gettext-libs
  • 错误3
    1
    2
    Can't exec "aclocal": No such file or directory at /usr/share/autoconf/Autom4te/FileUtils.pm line 326.

    安装automake即可
    1
    yum install -y automake
    或者干脆yum groupinstall "Development tools"直接大杂烩,一锅炖了。
  1. $ make

  2. $ make install

walle高级任务设置

所以试试发布自己的线上业务看看。

理论部署的前后,会触发四个任务。

pre_deploypost_deploypre_releasepost_release

名字来看,就比较好理解,部署前、部署后,发布前,发布后。

官方给出的流程如下:

walle flow

以下是上线任务的发布进度条

walle

很明显,触发的deploy任务是在宿主机。

我整理了如下表格:

任务 执行点 时间时间
pre_deploy 宿主机 代码检出前
post_deploy 宿主机 代码检出后,传送之前
pre_release 目标机 创建链接之前
post_release 目标机 创建链接之后

使用场景举例

我们拉取开发环境的代码部署至宿主机,乃至线上环境的时候。有一些配置是要替换的,比如数据库账户之类的。有一些文件是要删除,一些目录是需要合并的。

但是,怎么做呢?

这就用到这些任务了。大多数场景,我想就用pre-deploy或者pre release就够了。

用法基本都一样,差别就在执行顺序。

使用任务

宿主机要用web执行系统命令,所以需要额外添加一句到sudoers文件里面。

visudo

1
Defaults:www    !requiretty

www 是web执行PHP的用户。

我们是有几个东西要替换。一个是配置目录、另外两个是用户的上传目录。用户上传的静态内容,开发环境是没有了。

考虑到每次复制替换,一个是费系统资源。

所以,我打算在传输之前,先删除掉静态资源。再传去服务器的时候,打上线上的资源的链接。

如此就能实现动态更新了。

有个官方没有说明的是。每次执行任务之前,都是进去到当前目录的。所以删除移动项目目录之类的,都用相对路径即可。

walle test

接手管理某台ERP服务器的生产环境。主要配有LNMP环境,前两天发现,这些服务居然都没有配置自启动。

这完全不考虑服务器意外重启导致服务未能启动,从而引起长时间的服务中断么?

所以,今天就特地添加了自启动。

本来,添加自启动是很简单的。尤其是yum安装出来的服务。

CentOS 7

1
2
systemctl enable nginx php-fpm

CentOS 5/6

1
2
3
chkconfig nginx on
chkconfig php-fpm on

然而,这台是自己下载的软件包,还是自己编译的不得而知。反正/etc/init.d/ 完全没这两货的影子。

没有那就添加吧,两个bash脚本。

我从网上扒拉了两段自己修改了一下。代码如下:

nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/bash
# chkconfig: - 85 15
PATH=/app/nginx
DESC="nginx daemon"
NAME=nginx
DAEMON=$PATH/sbin/$NAME
CONFIGFILE=$PATH/conf/$NAME.conf
PIDFILE=$PATH/logs/$NAME.pid
scriptNAME=/etc/init.d/$NAME
set -e
[ -x "$DAEMON" ] || exit 0
do_start() {
$DAEMON -c $CONFIGFILE || echo -n "nginx already running"
}
do_stop() {
$DAEMON -s stop || echo -n "nginx not running"
}
do_reload() {
$DAEMON -s reload || echo -n "nginx can't reload"
}
case "$1" in
start)
echo -n "Starting $DESC: $NAME"
do_start
echo "."
;;
stop)
echo -n "Stopping $DESC: $NAME"
do_stop
echo "."
;;
reload|graceful)
echo -n "Reloading $DESC configuration..."
do_reload
echo "."
;;
restart)
echo -n "Restarting $DESC: $NAME"
do_stop
do_start
echo "."
;;
*)
echo "Usage: $scriptNAME {start|stop|reload|restart}" >&2
exit 3
;;
esac
exit 0

nginx 主程序一般自带了好多方法,比如好用的 stop、reload。所以nginx的代码显得比较简洁。

关于找nginx路径,一般其实用which nginx 或者 whereis nginx就够了。

但是想补充一个更加可靠的。

  • 先找到nginx的pid号,ps -aux | grep nginx
  • ll /proc/pid号

看到 exe 文件的链接路径就是我们要找的程序全路径了。

1
2
3
4
5
6
7
8
$ ps -aux | grep nginx
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
root 1147 0.0 0.0 46328 2488 ? Ss Mar14 0:00 nginx: master process nginx
root 3870 0.0 0.0 47280 3772 ? S Mar29 2:27 nginx: worker process

$ sudo ls -l /proc/1147/ | grep exe
lrwxrwxrwx 1 root root 0 Mar 14 10:17 exe -> /app/nginx/sbin/nginx

php-fpm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# pidfile: /var/run/php-fpm.pid
# config: /usr/local/php/etc/php-fpm.conf

php_command=/app/php56/sbin/php-fpm
php_config=/app/php56/etc/php-fpm.conf
php_pid=/app/php56/var/run/php-fpm.pid
RETVAL=0
prog="php-fpm"

#start function
php_fpm_start() {
/app/php56/sbin/php-fpm #-c /app/php56/lib/php.ini -y ${php_config}
}

start(){
if [ -e $php_pid ]
then
echo "php-fpm already start..."
exit 1
fi
php_fpm_start
}

stop(){
if [ -e $php_pid ]
then
parent_pid=`cat $php_pid`
all_pid=`ps -ef | grep php-fpm | awk '{if('$parent_pid' == $3){print $2}}'`
for pid in $all_pid
do
kill $pid
done
kill $parent_pid
fi
exit 1
}

restart(){
# stop
# start
kill -USR2 `cat /app/php56/var/run/php-fpm.pid`
}

# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
# stop
# start
restart
;;
status)
status $prog
RETVAL=$?
;;
*)
echo $"Usage: $prog {start|stop|restart|status}"
exit 1
esac
exit $RETVAL

这个代码就有点随意了,我做了少量修改,注意修改下执行程序的路径和PID文件的路径就行了,这里代码并没有用到配置文件路径其实。如果无法正确加载配置,可以把我加上的#-c /app/php56/lib/php.ini -y ${php_config} 这句代码注释去掉。

另外 php-fpm的配置路径在ps -aux | grep php-fpm 即可看到。记得修改,去掉pid路径的注释。

虽然代码在没找到pid文件的时候,会自己尝试提取pid,但是建议还是去掉注释。

添加服务,开启自启动

两个文件一个保存为 /etc/init.d/nginx 一个保存为 /etc/init.d/php-fpm 名字可以自己改,但是不推荐。

  • 添加进chkconfig chkconfig --add nginx && chkconfig -add php-fpm

  • 开启开机启动 chkconfig nginx on && chkconfig php-fpm on

可以直接在 /etc/rc.local 修改吗

可以的。

比如

1
2
3
/app/nginx/sbin/nginx -c /app/nginx/conf/nginx.conf
/app/php56/sbin/php-fpm

为何不这样做,为了以后管理服务,比如重启方便一点。

参考资料