Jim

Talk is cheap. Show me the code.


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

  • 搜索

通过 CeSi + Supervisor 可视化集中管理服务器节点进程

发表于 2018-07-21 | 分类于 Supervisor
  • 通过 CeSi + Supervisor 可视化集中管理服务器节点进程
    • 简介
    • Supervisor 的安装及基本使用
      • 1. 安装
      • 2. 基本使用
        • 2.1 启动 supervisor
        • 2.2 Supervisor 客户端 supervisorctl
    • 安装配置 CeSi
      • 1. 简介
      • 2. 安装
      • 3. 配置
      • 4. 启动
    • Supervisor 服务设置开机自启动
    • 参考链接

简介

Supervisor 是一个用 Python 写的进程管理工具,可以很方便的用来启动、重启、关闭进程。类似于 Linux 的 systemd 守护进程一样,通过统一的命令来管理系统的各个服务,当管理的服务挂掉时会自动重新拉起。Supervisor 还提供了很多第三方插件,比如后面会讲到的 CeSi,该工具是 Supervisor 的 WebUI,可以通过这个统一的 WebUI 集中化管理各个服务器节点的进程。

Supervisor 和 Docker 的架构类似,也是 C/S 架构,服务端是 supervisord,客户端是 supervisorctl 。客户端主要是用来控制服务端所管理的进程,比如控制服务的启动、关闭、重启、查看服务状态,还可以重启服务端、重载配置文件等。服务端管控各个服务的正常运行,当有服务异常退出时会自动拉起。

Supervisor 的安装及基本使用

1. 安装

Supervisor 的安装特别简单,由于是 Python 写的,因此可以通过 pip 一键安装:

1
pip install supervisor

在此我提供了一个 Sueprvisor 一键安装配置脚本,简化了 Supervisor 的初始配置。

2. 基本使用

安装完成后系统会多出如下三个命令:

supervisord :Supervisor 的服务端;
supervisorctl:Supervisor 的客户端;
echo_supervisord_conf:Supervisor 服务端默认配置文件生成工具;

2.1 启动 supervisor

首先通过如下命令将 supervisor 的默认配置生成到 /etc/supervisord.conf:

1
echo_supervisord_conf > /etc/supervisord.conf

Supervisor 配置文件格式是 INI 格式,因此看起来比较直观,很多配置项的含义已在上面生成的配置文件中以注释的形式说明,以下简要说明一下我在生产环境目前使用的配置,为了减少篇幅,在此只列出了非注释的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[unix_http_server]
file=/tmp/supervisor.sock ; 服务端套接字文件路径,supervisorctl 客户端会使用该文件和服务端通信
[inet_http_server] ; Supervisor 服务端提供的 http 服务,很多 Supervisor 的 WebUI
;都是通过访问该服务来实现统一管理的,比如后面要讲的 CeSi Web UI
port=0.0.0.0:9001 ; ip_address:port specifier, *:port for all iface
[supervisord] ; Supervisor 服务端配置
logfile=/tmp/supervisord.log ; 服务端日志文件路径
logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB
logfile_backups=10 ; # of main logfile backups; 0 means none, default 10
loglevel=debug ; log level; default info; others: debug,warn,trace
pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
nodaemon=false ; start in foreground if true; default false
minfds=1024 ; min. avail startup file descriptors; default 1024
minprocs=200 ; min. avail process descriptors;default 200
user=root
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl] ; Supervisor 客户端配置
serverurl=unix:///tmp/supervisor.sock ; 配置客户端和服务端的通信方式,默认 supervisorctl
;和 supervisor 通信是通过该套接字通信,也可以配成通过 http 方式通信。
[include] ; 在此我将 Supervisor 所管理的服务配置文件都放到了 /etc/supervisor/ 目录,然后通过 include 统一引入
files = /etc/supervisor/*.conf

接下来在 /etc/supervisor/ 放入需要 Supervisor 管理的各服务的配置文件,一般一个服务一个配置文件,当然也可以写到一起,比如逻辑上有关联的一组服务可以放到一个配置文件,这样方便管理,下面以一个实例来介绍下要通过 Supervisor 管理服务,相应的配置文件该如何编写(使用 Supervisor 管理 cesi 服务的配置):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; cesi.conf
[program:cesi-5000] ; program 表示 Supervisor 管理的服务实例,cesi-5000 是自己命名
;的服务名称,名字可以随便其,我为了方便管理统一命名为:服务名称-端口
directory = /home/ec2-user/cesi ; 程序的启动目录
command = python cesi/web.py ; 启动服务的命令
autostart = true ; 在 supervisord 启动的时候也自动启动
startsecs = 5 ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true ; 程序异常退出后自动重启
startretries = 3 ; 启动失败自动重试次数,默认是 3
user = ec2-user ; 用哪个用户启动
redirect_stderr = true ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 50MB ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 7 ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /home/ec2-user/cesi/stdout.log

将上述配置保存为 cesi.conf,放到 /etc/supervisor/。

前面已经对 echo_supervisord_conf 生成的默认配置文件做了微调,接下来启动 Supervisor 服务端(建议用 root 用户启动):

1
sudo supervisord -c /etc/supervisord.conf

如果不指定 -c 参数,会通过如下顺序来搜索配置文件:

1
2
3
4
$PWD/supervisord.conf
$PWD/etc/supervisord.conf
/etc/supervisord.conf
/etc/supervisor/supervisord.conf

2.2 Supervisor 客户端 supervisorctl

supervisorctl 有两种使用方式:
一种是直接执行 supervisorctl ,这样会进入交互式的 Shell, 然后在该交互式 Shell 中输入管理命令,举例:

1
2
3
4
5
6
[root@awsuw supervisor]# supervisorctl
cesi-5000 RUNNING pid 6538, uptime 1 day, 1:21:02
zipkinstage-9411 RUNNING pid 30919, uptime 1 day, 19:51:43
supervisor> status
cesi-5000 RUNNING pid 6538, uptime 1 day, 1:21:09
zipkinstage-9411 RUNNING pid 30919, uptime 1 day, 19:51:50

另一种是 supervisorctl [action] 的方式,这样不会陷入交互式 Shell,直接会返回命令的执行结果,其中 action 就是管理服务进程的各个命令,举例(查看目前所管理的服务的进程状态):

1
2
3
[root@awsuw supervisor]# supervisorctl status
cesi-5000 RUNNING pid 6538, uptime 1 day, 1:24:53
zipkinstage-9411 RUNNING pid 30919, uptime 1 day, 19:55:34

其中常用的 action 有如下(更多选项参数见 这里):

supervisorctl status : 查看所管理的服务状态;
supervisorctl start <program_name>:启动一个服务;
supervisorctl restart <program_name>:重启一个服务(注意:重启服务不会重新加载配置文件);
supervisorctl stop <program_name>:关闭一个服务;
supervisorctl update:重新加载配置文件,并重启配置有变动的服务;
supervisorctl reread:重新加载配置文件,但不会重启配置有变动的服务;
supervisorctl reload:重启 Supervisor 服务端;
supervisorctl clear <program_name>:清理一个服务的 stdout log;

安装配置 CeSi

1. 简介

CeSi 是 Supervisor 官方推荐的集中化管理 Supervisor 实例的 Web UI,该工具是用 Python 编写,基于 Flask Web 框架 。

Superviosr 自带的 Web UI 不支持跨机器管理Supervisor 进程,功能比较简单,通过 CeSi 可以集中管理各个服务器节点的进程,在 Web 界面就可以轻松管理各个服务的启动、关闭、重启等,很方便使用。

2. 安装

安装 CeSi 有三个依赖:Python,Flask,sqlite3
一般的 Linux 发行版都默认安装了 Python,所以 Python 不需要再次安装;
从 Python 2.5 开始 sqlite3 已经在标准库内置了,所以也不需要安装 sqlite3 模块了;
另外很多 Linux 发行版已经自带 sqlite3,所以无需另外安装;
只需要安装 flask web 框架即可;

CeSi 已经有了新的版本,在 GitHub 仓库的 v2_api 分支下,提供了比之前版本更加美观的界面,以下为 CeSi 一键安装配置脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# !/bin/bash
set -e

sudo pip install flask
git clone https://github.com/gamegos/cesi.git
cd cesi
# 使用最新版, 最新版的 Web UI 做了很大改动
git checkout -b v2_api origin/v2_api
sudo cp cesi.conf.sample /etc/cesi.conf
sudo ln -s /etc/cesi.conf cesi.conf
#创建用户信息表:
sqlite3 userinfo.db < userinfo.sql
#CeSi log 目录
sudo mkdir -p /var/logs/cesi
sudo chmod 777 -R /var/logs
exit 0

注意:CeSi 的配置文件路径必须是 /etc/cesi.conf ,否则启动会报错,简单看下 CeSi 的源码就知道为什么了。在这里我在仓库目录弄了个软连接指向了 /etc/cesi.conf,完全是为了编辑方便弄的。

3. 配置

CeSi 的配置非常简单,和 Supervisor 的配置文件类似,也是 INI 格式,关于配置文件的各项说明在 cesi.conf.sample 配置样例中已经通过注释的形式给了明确的说明,稍微看下就能明白,以下为我目前使用的配置(为了减小篇幅,去掉了注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[node:node1] ;各 Supervisor 节点的配置
username = ; 如果 Supervisor 节点没有设置账号密码,这里就保持为空,但不能不写
password =
host = 127.0.0.1
port = 9001
[node:node2]
username =
password =
host = node2.d.com
port = 9001
[node:node3]
username =
password =
host = node3.d.com
port = 9001

[cesi] ; CeSi 自身的配置
database = userinfo.db
activity_log = /var/logs/cesi/activity.log ;log目录没有的话需要提前建好
host = 0.0.0.0
port = 5000 ; CeSi 启动端口
name = CeSI
theme = superhero

4. 启动

CeSi 的启动非常简单,直接通过 Python 启动即可:

1
python cesi/web.py

为了方便管理,我把 CeSi 也通过 Supervisor 来管理,以下为对应的 Supervisor 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;cesi.conf
[program:cesi-5000]
directory = /home/ec2-user/cesi ; 程序的启动目录
command = python cesi/web.py
autostart = true ; 在 supervisord 启动的时候也自动启动
startsecs = 5 ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true ; 程序异常退出后自动重启
startretries = 3 ; 启动失败自动重试次数,默认是 3
user = ec2-user ; 用哪个用户启动
redirect_stderr = true ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 50MB ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 7 ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /home/ec2-user/cesi/stdout.log

启动完成后,做个 Nginx 反向代理即可通过浏览器访问,最终效果如下:
cesi-ui

Supervisor 服务设置开机自启动

以下为在 RedHat7下配置 Supervisor 开机自启动过程,编写 Unit 文件,使用 systemd 管理 Supervisor:

  1. 编写 Unit 文件:supervisord.service:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #supervisord.service

    [Unit]
    Description=Supervisor daemon

    [Service]
    Type=forking
    ExecStart=/bin/supervisord -c /etc/supervisord.conf
    ExecStop=/bin/supervisorctl shutdown
    ExecReload=/bin/supervisorctl -c /etc/supervisord.conf reload
    KillMode=process
    Restart=on-failure
    RestartSec=42s

    [Install]
    WantedBy=multi-user.target
  2. 将上述文件拷贝到 /usr/lib/systemd/system/ 目录下

  3. 将 supervisor.service 注册到系统中

    1
    2
    [root@awsuw ~]# systemctl enable supervisord.service
    Created symlink from /etc/systemd/system/multi-user.target.wants/supervisord.service to /usr/lib/systemd/system/supervisord.service.

    可以看出注册过程就是在 /etc/systemd/system/multi-user.target.wants/ 目录下创建一个软链接指向第二步中的中拷贝到 /usr/lib/systemd/system/ 的文件。

参考链接

http://supervisord.org/index.html
http://www.bjhee.com/supervisor.html
https://www.jianshu.com/p/03619bf7d7f5
http://liyangliang.me/posts/2015/06/using-supervisor

Redis 慢查询分析

发表于 2018-07-15 | 分类于 Redis

简介

和很多关系型数据库(例如:MySQL)一样, Redis 也提供了慢查询日志记录,Redis 会把命令执行时间超过 slowlog-log-slower-than 的都记录在 Reids 内部的一个列表(list)中,该列表的长度最大为 slowlog-max-len 。需要注意的是,慢查询记录的只是命令的执行时间,不包括网络传输和排队时间:

Alt text

慢查询分析配置

关于 Redis 慢查询的配置有两个,分别是 slowlog-log-slower-than 和 slowlog-max-len。

  1. slowlog-log-slower-than,用来控制慢查询的阈值,所有执行时间超过该值的命令都会被记录下来。该值的单位为微秒,默认值为 10000,如果设置为 0,那么所有的记录都会被记录下来,如果设置为小于 0 的值,那么对于任何命令都不会记录,即关闭了慢查询。可以通过在配置文件中设置,或者用 config set 命令来设置:

    1
    config set slowlog-log-slower-than 10000
  2. slowlog-max-len,用来设置存储慢查询记录列表的大小,默认值为 128,当该列表满了时,如果有新的记录进来,那么 Redis 会把队最旧的记录清理掉,然后存储新的记录。在生产环境我们可以适当调大,比如调成 1000,这样就可以缓冲更多的记录,方便故障的排查。配置方法和 slowlog-log-slower-than 类似,可以在配置文件中指定,也可以在命令行执行 config set 来设置:

    1
    config set slowlog-max-len 1000

查看慢查询日志

尽管 Redis 把慢查询日志记录到了内部的列表,但我们不能直接操作该列表,Redis 专门提供了一组命令来查询慢查询日志:

  1. 获取慢查询日志:
    slowlog get [n]
    下面操作返回当前 Redis 的所有慢查询记录,可以通过参数 n 指定查看条数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    127.0.0.1:6379> slowlog get
    1) 1) (integer) 456
    2) (integer) 1531632044
    3) (integer) 3
    4) 1) "get"
    2) "m"
    5) "127.0.0.1:50106"
    6) ""
    2) 1) (integer) 455
    2) (integer) 1531632037
    3) (integer) 14
    4) 1) "keys"
    2) "*"
    5) "127.0.0.1:50106"
    6) ""

    结果说明:
    1) 慢查询记录 id;
    2) 发起命令的时间戳;
    3) 命令耗时,单位为微秒;
    4) 该条记录的命令及参数;
    5) 客户端网络套接字(ip: port);
    6) “”

  2. 获取当前慢查询日志记录数
    slowlog len

    1
    2
    127.0.0.1:6379> slowlog len
    (integer) 458
  3. 慢查询日志重置
    slowlog reset
    实际上是对慢查询列表做清理操作:

    1
    2
    3
    4
    5
    6
    127.0.0.1:6379> slowlog len
    (integer) 461
    127.0.0.1:6379> slowlog reset
    OK
    127.0.0.1:6379> slowlog len
    (integer) 1

Redis 数据库管理

发表于 2018-07-14 | 分类于 Redis

Redis 数据库管理

概要

Redis 提供了几个面向数据库的操作,分别是 dbsize, select, flushdb/flushall。
其实在一个 Redis 实例内部也是有多个数据库的,与 MySQL 等其他关系型数据库不同的是,Redis 内部的数据库使用数字索引来标识,而不是像 MySQL 那样一个实例中的数据库是通过数据库名称来标识。
在 Redis 中数据库默认有 16 个,数据库标识分别是 0, 1, …, 15,我们默认使用的是 0 号数据库,不同数据库之间是隔离的,可以拥有同名的键。

各数据库管理命令介绍

1. dbsize 查看当前数据库 key 的个数

1
2
3
4
5
6
127.0.0.1:6379> set name tom
OK
127.0.0.1:6379> set score 99
OK
127.0.0.1:6379> dbsize
(integer) 2

2. select 切换数据

select 命令格式为:select index,index 为数据库的标识。举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> set name tom
OK
127.0.0.1:6379> set score 99
OK
127.0.0.1:6379> select 8 // 切换到 8 号数据库
OK
127.0.0.1:6379[8]> get name //可以看出不同 db 是隔离的
(nil)
127.0.0.1:6379[8]> set name tom
OK
127.0.0.1:6379[8]> get name
"tom"

3. flushdb/flushall 清理数据库

flushdb 和 flushall 的区别为:flushdb 清空当前数据库,而 flushall 清空所有数据库。举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6379> keys *
1) "score"
2) "name"
127.0.0.1:6379> select 8
OK
127.0.0.1:6379[8]> keys *
1) "score"
2) "name"
127.0.0.1:6379[8]> flushdb
OK
127.0.0.1:6379[8]> keys *
(empty list or set)
127.0.0.1:6379[8]> select 0
OK
127.0.0.1:6379> keys *
1) "score"
2) "name"
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)

总结

目前 Redis 对多数据库的支持开始弱化了,因为 Redis 是单线程架构,同一时间只有一个 CPU 为 Redis 服务,多个数据库同时存在不仅不会利用系统的多核优势,反而会由于单实例资源共享问题互相会有影响,导致出现问题时排错非常困难,Redis 实例如果一旦阻塞,那么所有的数据库都会受到影响。所以这是一个很鸡肋的功能,Redis 官方对其支持也在逐步弱化。
更合理的方式是一台机器启动多个 Redis 实例,互相隔离,充分利用 CPU 的多核优势。

Redis 基础

发表于 2018-07-11 | 分类于 Redis

简介

Redis 是一种基于键值对的 No-SQL 数据库,与很多键值对数据库不同的是,Redis 中的值可以由 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等多种数据结构和算法组成,因此 Redis 可以满足很多应用场景,而且因为 Redis 会将所有数据都存放在内存中,所以它的读写性能非常惊人。另外 Redis 提供了 RDB 和 AOF 两种持久化方式,使得即使发生断电或者机器故障,数据也可以持久化到磁盘上,防止了数据的意外丢失。

安装 Redis

Linux 安装软件一般由两种方式,第一种是通过各个操作系统的软件包管理器进行安装,比如 Ubuntu 使用 apt-get,RedHat 系列使用 yum 安装。但是由于 Redis 的更新速度比较快,而各大 Linux 发行版的相应软件源更新却比较慢,因此直接通过这种方式安装无法获取较新的版本。所以一般推荐第二种方式:源码方式安装。Redis 的源码安装特别简单,没有第三方依赖,直接下载源码编译安装即可。通过以下命令编译安装 Redis 最新稳定版:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装 gcc 相关编译工具
sudo apt-get install -y build-essential
# 安装 make 打包工具
sudo apt-get -y install make
# Use latest stable 下载最新稳定版源码
wget -q http://download.redis.io/redis-stable.tar.gz
tar zxvf redis-stable.tar.gz

cd redis-stable
# 编译源码
make
# 安装
sudo make install

安装完成后可以通过如下命令查看 Redis 版本:

1
2
vagrant@redis:~$ redis-cli -v
redis-cli 4.0.10

配置、启动、操作、关闭 Redis

Redis 安装之后,Redis 源码目录 src 和 /usr/local/bin 目录多了几个以 redis 开头的可执行文件,我们称之为 Redis Shell,这些文件包括 Redis server 和 client 以及其他操作 Redis 的实用工具:

可执行文件 作用
redis-server Redis 服务端
redis-cli Redis 命令行客户端
redis-benchmark Redis 基准测试工具
redis-check-rdb Redis AOF 持久化文件检测和修复工具
redis-check-aof Redis RDB 持久化文件检测和修复工具
redis-sentinel 启动 Redis Sentinel

启动 Redis

有三种方法启动 Redis:默认配置、运行配置、配置文件启动。

  1. 默认配置
    这种方式启动是直接执行 redis-server 来启动,后面没有任何参数,以默认的配置来启动。因为这种启动方式无法自定义配置,所以这种方式是不会在生产环境中使用。:

    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
    vagrant@redis:~$ redis-server
    13622:C 11 Jul 02:27:09.542 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
    13622:C 11 Jul 02:27:09.543 # Redis version=4.0.10, bits=64, commit=00000000, modified=0, pid=13622, just started
    13622:C 11 Jul 02:27:09.543 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
    13622:M 11 Jul 02:27:09.546 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
    13622:M 11 Jul 02:27:09.546 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
    13622:M 11 Jul 02:27:09.547 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
    _._
    _.-``__ ''-._
    _.-`` `. `_. ''-._ Redis 4.0.10 (00000000/0) 64 bit
    .-`` .-```. ```\/ _.,_ ''-._
    ( ' , .-` | `, ) Running in standalone mode
    |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
    | `-._ `._ / _.-' | PID: 13622
    `-._ `-._ `-./ _.-' _.-'
    |`-._`-._ `-.__.-' _.-'_.-'|
    | `-._`-._ _.-'_.-' | http://redis.io
    `-._ `-._`-.__.-'_.-' _.-'
    |`-._`-._ `-.__.-' _.-'_.-'|
    | `-._`-._ _.-'_.-' |
    `-._ `-._`-.__.-'_.-' _.-'
    `-._ `-.__.-' _.-'
    `-._ _.-'
    `-.__.-'

    13622:M 11 Jul 02:27:09.551 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
    13622:M 11 Jul 02:27:09.552 # Server initialized
  2. 运行启动
    这种方式是执行 redis-server 时把配置参数通过命令行指定,没有设置的配置将使用默认配置:

    1
    # redis-server --configKey1 configValue1 --configKey2 configValue2

    例如要用 6380 作为端口启动 Redis,那么执行:

    1
    # redis-server --port 6380

    虽然这种方式可以自定义配置,但是如果需要修改的配置较多或者希望将配置保存到文件中,不建议使用这种方式。

  3. 配置文件启动
    将配置写到文件里,例如我们将配置写到了 /opt/redis/redis.conf 中,那么只需执行如下命令即可启动 Redis:

    1
    # redis-server /opt/redis/redis.conf

    Redis 有 60 多种配置,这里给出一些基本的配置:

配置名 配置说明
port 端口
logfile 日志文件
dir Redis 工作目录(存放持久化文件和日志文件)
daemonize 是否以守护进程的方式启动 Redis

Redis 命令行客户端

现在我们已经启动了 Redis 服务,下面介绍如何使用 redis-cli 连接、操作 Redis 服务。redis-cli 可以使用两种方式连接 Redis 服务。

  1. 交互方式:通过 redis-cli -h {host} -p {port} 方式连接到 Redis 服务:

    1
    2
    3
    vagrant@redis:~$ redis-cli -h localhost -p 6379
    localhost:6379> keys *
    (empty list or set)
  2. 命令方式:通过 redis-cli -h {host} -p {port} {command} 就可以直接得到返回结果,不需要启动 Redis shell 来交互访问:

    1
    2
    vagrant@redis:~$ redis-cli -h localhost -p 6379 get name
    "haohao"

注意:如果 -h 参数没有指定,那么默认 host 是 127.0.0.1 ,如果没有 -p 参数,那么默认 6379 端口,也就是说 -h 和 -p 都没写,就是连接 127.0.0.1:6379 这个实例。

停止 Redis 服务

Redis 提供了 shutdown 命令来停止 Redis 服务,例如要停掉 127.0.0.1 上 6379 端口上的 Redis 服务,可以执行如下操作:

1
redis-cli shutdown

以这方式关闭 Redis 是一种优雅的方式,在关闭时会先将内存中的数据持久化到磁盘上(在配置文件中 dir 指定的目录中产生),然后关闭。如果直接 kill -9 强制杀掉不会产生持久化文件。
shutdown 还有一个参数,代表是否在关闭 Redis 前生产持久化文件:

1
redis-cli shutdown nosave|save

通过 Vagrantfile 安装配置 Redis

在此提供一个安装 Redis 的 vagrant 工程,通过 vagrant up 一键安装并配置 Redis,使用方式:

1
2
3
git clone git@github.com:qhh0205/infra-vagrant.git
cd infra-vagrant/redis
vagrant up

Redis 重大版本新增功能

Nginx upstream 失效转移机制研究

发表于 2018-07-08 | 分类于 Nginx

结论

经过多次模拟线上的环境测试,Nginx 负载均衡技术默认情况下已经对于 connect refused(状态码表现为 502)和 time out(状态码表现为 504)做了失效转移,使用的是 upstream 模块的 proxy_next_upstream 指令(这个选项默认是启动的)来实现。

对于 http GET 请求,当这个请求转发到上游服务器发生断路,或者读取响应超时则会将同样的请求转发到其他上游服务器来处理,如果所有服务器都超时或者断路,则会返回 502 或者 504 错误。

对于http POST 请求,当这个请求转发到上游服务器发生断路,则会将请求转发到其他上游服务器来处理,但是如果这个请求发生了读取超时,则不会做失效转移,会返回 504 错误,Nginx 之所以这么做应该是为了防止同一个请求发送两次,比如涉及到银行的充值等操作就会发生很严重的 bug。以下是模拟线上的场景测试得出的结论:

  1. 上游服务器有两台,一台处于 down 状态,另一台处于正常服务状态,那么来自客户端的 GET 和 POST 请求都会通过 Nginx 的失效转移机制路由到正常状态的机器,返回 200 状态码,并不会返回给客户端 502 错误;
  2. 上游服务器有两台,两台都 down 了,那么会不管是 GET 还是 POST 请求都会直接返回给客户端 502 错误;
  3. 上游服务器有两台,一台机器的 http GET 和 POST 接口都正常 return,另一台相同的接口死循环,模拟超时。
    这种情况下如果客户端的请求路由到了正常机器,那么直接返回 200。
    如果请求路由到了死循环的接口,并且是 GET 请求,那么会等待 Nginx 设置的超时时间过后,然后将请求转发到另一台机器的正常接口。
    如果请求路由到了死循环的接口,并且是 POST 请求,那么等待 nginx 设置的超时时间过后直接返回 504,没有进行失效转移,防止请求的重复发送;
  4. 上游服务器有两台,两台机器的 http GET 和 POST 接口都死循环,模拟超时,那么对于 GET 请求会进行请求转发到另一台尝试,对于 POST 请求直接返回 504,不会进行进一步尝试;

论证环境及工具

  • 一台前端 Nginx 服务器;
  • 两台上游服务器;
  • Nginx 配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 443;
server_name ngxfailover.xxx.me;
ssl on;
ssl_certificate xxx/xxx.crt;
ssl_certificate_key xxx/xxx.key;
location / {
proxy_pass http://py_web_upstream;
}
}

upstream py_web_upstream{
server upstream_server1:5000;
server upstream_server2:5000;
}
  • 第一台上游服务器正常代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import time
from flask import Flask
app = Flask(__name__)


@app.route('/a/<name>')
def failover_get_method(name):
print name
return '<h1>I am Server2, My name is %s </h1>' % name

@app.route('/b/<name>', methods=["POST"])
def failover_post_method(name):
print name
return '<h1>I am Server2, My name is %s </h1>' % name


if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
  • 第二台上游服务器超时代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
from flask import Flask
app = Flask(__name__)


@app.route('/a/<name>')
def failover_get_method(name):
print name
while True:
time.sleep(256)
return '<h1>I am Server2, My name is %s </h1>' % name

@app.route('/b/<name>', methods=["POST"])
def failover_post_method(name):
print name
while True:
time.sleep(256)
return '<h1>I am Server2, my name is %s </h1>' % name


if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')

论证过程

以下为前面 4 种案例的论证过程:

案例 1

上游服务器有两台,一台处于 down 状态,另一台处于正常服务状态。

在这种情况下,通过 curl 多次发送 GET 和 POST 请求,发现不管怎么请求,返回都是正常状态,如果 Nginx 发生了失败尝试操作,那么会在 Nginx access 日志中的 upstream 字段看到有两个服务器的地址。

发送 GET 和 POST 请求:

1
2
curl -XGET https://ngxfailover.xxx.me/a/hello
curl -XPOST https://ngxfailover.xxx.me/b/hello

观察日志:

可以看出所有请求都成功了,红框框圈起来的请求表示发生了失效转移,并且请求成功。

案例 2

上游服务器有两台,两台服务器都处于 down 状态。

在这种情况下不管是 GET 还是 POST 请求都会直接返回给客户端 502 错误。

发送 GET 和 POST 请求:

1
2
curl -XGET https://ngxfailover.xxx.me/a/hello
curl -XPOST https://ngxfailover.xxx.me/b/hello

观察日志:
可以看出所有请求全部返回 502 错误,红框框圈起来的请求表示发生了失效转移,但是还是失败了。

案例 3

上游服务器有两台,一台机器的 http GET 和 POST 接口都正常 return,另一台相同的接口死循环,模拟超时。

这种情况下如果客户端的请求路由到了正常机器,那么直接返回 200。

如果请求路由到了死循环的接口,并且是 GET 请求,那么会等待 Nginx 设置的超时时间过后,然后将请求转发到另一台机器的正常接口。

如果请求路由到了死循环的接口,并且是 POST 请求,那么等待 Nginx 设置的超时时间过后直接返回客户端 504 错误,没有进行失效转移,防止请求的重复发送。

发送 GET 请求:

1
curl -XGET https://ngxfailover.xxx.me/a/hello

观察日志:

可以看到对于 GET 请求全部成功,红框框圈起来的表示发生了失效转移,第一台超时后会是继续尝试第二台,最终成功。

发送 POST 请求:

1
curl -XPOST https://ngxfailover.xxx.me/b/hello

观察日志:

可以看到对于 POST 请求,如果 Nginx 等待上游服务器处理请求超时,并不会发生失效转移,直接返回给客户端 504 错误。

案例 4

上游服务器有两台,两台机器的 http GET 和 POST 接口都死循环,模拟超时。

这种情况下对于 GET 请求会将请求转发到另一台尝试,对于 POST 请求直接返回 504 错误,不会进行进一步尝试。

发送 GET 请求:

1
curl -XGET https://ngxfailover.xxx.me/a/hello

观察日志:

可以看出对于 GET 请求,Nginx 在等待超时会继续进行尝试,两台都尝试失败后返回了 504 错误。

发送 POST 请求:

1
curl -XPOST https://ngxfailover.xxx.me/b/hello

观察日志:

可以看出对于 POST 请求,Nginx 在等待超时会不继续进行尝试其他上游服务器,直接返回 504 错误。

总结

总体来看 Nginx 的失效转移技术已经非常成熟,Nginx 默认情况下对于 connect refused(状态码表现为 502)和 time out(状态码表现为 504)已经做了失效转移,并且 Nginx 根据请求的类型不同,对失效转移的策略也不同。对于服务器后台状态没有改变的请求(比如 GET 请求)会进行失效转移,对于服务后台状态有改变的请求(比如 POST 请求),有失效转移机制,这也符合 Rest API 的冪等性标准。如果要强行加其他状态码的失效转移,比如 500、503 等,需要考量下业务请求是否能容忍请求的重复发送。

Ubuntu 下添加开机启动脚本

发表于 2018-07-08 | 分类于 Linux

Ubuntu 下添加开机启动脚本

本文介绍在 Ubuntu 下添加开机启动脚本的两种方法:

  1. 编辑 /etc/rc.local 文件
    Ubuntu 会在启动时自动执行 /etc/rc.local 文件中的脚本,默认该文件中有效的脚本代码为空,把需要执行的脚本添加到该文件的 exit 0 之前即可,举例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #!/bin/sh -e
    #
    # rc.local
    #
    # This script is executed at the end of each multiuser runlevel.
    # Make sure that the script will "exit 0" on success or any other
    # value on error.
    #
    # In order to enable or disable this script just change the execution
    # bits.
    #
    # By default this script does nothing.
    cd /home/ubuntu
    echo 'hello,world' >> rc.local.log
    exit 0
  2. 通过 update-rc.d 命令添加开机自启动脚本
    Ubuntu 服务器在启动时会自动执行 /etc/init.d 目录下的脚本,所以我们可以将需要执行的脚本放到 /etc/init.d 目录下,或者在该目录下创建一个软件链接指向其他位置的脚本路径,然后通过 update-rc.d 将脚本添加到开机自启动。启动脚本必须以 #!/bin/bash 开头。举例如下:
    新建开机启动脚本 start_when_boot,放置到 /etc/init.d 目录

    1
    2
    3
    4
    #!/bin/bash
    cd /home/ubuntu
    date >> boot.log
    echo 'hello, world' >> boot.log

    执行 update-rc.d start_when_boot defaults 将上述脚本添加为开机启动;
    执行 update-rc.d -f start_when_boot remove 将上述开机启动脚本移除;

参考文章

https://wangheng.org/ubuntu-to-add-boot-script.html

使用 cli53 自动化管理 Aws Route53

发表于 2018-06-10 | 分类于 Aws

cli53 工具

cli53 是一个开源的命令行管理 Aws Route53 的工具,非常实用,可以通过命令行来进行域名及相关记录的创建、更新、删除、记录的导出备份、记录的导入恢复等。配置域名时无需在 Aws 界面控制台操作,只需用命令操作即可,能在一定程度上提高效率,将工作代码化。
工具地址(Go 语言版):https://github.com/barnybug/cli53
该工具还有个 Python 版本,是同一个作者,但是 Python 版的已不再维护,目前主要支持 Go 语言版的。

1. 安装

https://github.com/barnybug/cli53

Mac 下安装:

1
brew install cli53

2. 配置 AWS 访问密钥

  • 在控制台新建拥有 Route53 访问权限的 IAM 账号,获取 aws key

这里写图片描述

  • 将 aws key 添加到环境变量
    1
    2
    export AWS_ACCESS_KEY_ID="xxxx"
    export AWS_SECRET_ACCESS_KEY="xxxxx"

使用方法总结

1.创建一个域名托管(指定的域名必须是有效的,否则报错)

1
cli53 create example.com --comment 'my first zone'

2.列出 Rout53 当前所有域名

1
cli53 list

3.导入 BIND 区域文件(用来做域名迁移)

1
cli53 import --file zonefile.txt example.com

4.导出域名 BIND 区域文件(用来备份,防止误操作导致不可恢复)

1
2
3
4
5
# 导出非完全符合标准的 bind 文件
cli53 export example.com

# 导出完全符合标准的 bind 文件(一般使用该命令备份)
cli53 export --full example.com

5.创建一个 A 记录指向 192.168.0.1,并设置 TTL 为 60s

1
cli53 rrcreate example.com 'www 60 A 192.168.0.1'

6.更新上面创建的 A 记录,指向 192.168.0.2

1
cli53 rrcreate --replace example.com 'www 60 A 192.168.0.2'

7.删除一个 A 记录

1
cli53 rrdelete example.com www A

8.创建一个 MX 记录

1
cli53 rrcreate example.com '@ MX 10 mail1.' '@ MX 20 mail2.'

9.创建一个轮询的 A 记录

1
cli53 rrcreate example.com '@ A 127.0.0.1' '@ A 127.0.0.2'

10.创建 CNAME 记录

1
2
cli53 rrcreate example.com 'login CNAME www'
cli53 rrcreate example.com 'mail CNAME ghs.googlehosted.com.'

11.创建 ELB 别名记录

1
cli53 rrcreate example.com 'www AWS ALIAS A dns-name.elb.amazonaws.com. ABCDEFABCDE false'

12.删除一个域名(⚠️危险 删除时如果域名有记录则必须指定 --purge 选项)

1
cli53 delete --purge example.com

13.删除一个域名的所有记录(⚠️危险)

1
cli53 rrpurge example.com

域名导入注意事项

有的域名提供商,比如 GoDaddy 提供域名记录导出功能,但是导出来后的 BIND 域文件并不是符合标准的,CNAME
或者 MX 记录末尾没有圆点 .,这样在导入 Route53 后会出现问题,需要在导入之前用如下命令处理一下文件
(MX 和 CNAME 记录末尾添加圆点)再导入:

1
perl -pe 's/((CNAME|MX\s+\d+)\s+[-a-zA-Z0-9._]+)(?!.)$/$1./i' broken.txt > fixed.txt

Python 根据本地时间获取 UTC 偏移量

发表于 2018-06-09 | 分类于 Python

在 so 上查了一下,可以用如下代码获取本地时间相对于 UTC 时间的偏移量,代码实现思路比较简单,分别获取本地时间和和 UTC 时间,然后本地时间减去 UTC 时间即可得到相对于 UTC 的偏移小时,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/4/6 上午10:28
# @Author : qhh0205
# @Mail : qhh0205@gmail.com
# @File : utc_offset.py

import time
from datetime import datetime
ts = time.time()
utc_offset = int((datetime.fromtimestamp(ts) - datetime.utcfromtimestamp(ts)).total_seconds() / 3600)
print "UTC%+-d" % utc_offset

参考链接(so 讨论):
https://stackoverflow.com/questions/3168096/getting-computers-utc-offset-in-python?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa

Shadowsocks + Privoxy 搭建 http 代理服务

发表于 2018-05-20 | 分类于 Shadowsocks

目前很多软件都支持配置 http 代理来加速访问,或者绕过 GFW 来获取需要的资源。比如 docker、git、gcloud、curl 这些软件都支持 http 代理。那么我们该如何轻松地基于 shadowsocks 搭建一个 http 代理,其实很简单,使用 privoxy 代理软件将收到的 http 请求转发给 shadowsocks 客户端即可。

Shadowsocks 软件整体架构

如下图所示,shadowsocks 由两部分组成:客户端(SS Local),服务端(SS Server)。客户端就是用来做本地 Sock5 代理的,代理本地 PC 的请求和服务端通信,我们一般在手机、平板、PC 上安装的图形化 shadowsokcs 软件就是 SS 客户端软件,当然如果在 Linux 下,也有SS 客户端:sslocal,后面会介绍到如何配合 privoxy 来实现 http 代理服务。服务端就是在 Linux 服务器上安装的 shadowsocks 服务端软件,供客户端连接,我们一般说的搭建 shadowsocks 代理就是在服务器上安装并配置 SS Server。
Alt text

Shadowsocks + privoxy 搭建 http 代理服务步骤

整体架构如下图所示,我们需要找一台机器将 SS Server 搭建好,然后在局域网内的任何一台 Linux 服务器安装 SS Local 和 Privoxy,Privoxy 暴露 8118 端口作为 http 代理的端口:
Alt text

1. 安装配置 Shadowsocks Server 端(ssserver)

Shadowsocks 是用 Python 编写的,因此可以通过如下命令直接安装(sslocal 和 ssserver 均已安装):

1
sudo pip install shadowsocks

接下来编写 Shadowsocks Server 端的配置文件,配置监听端口,加密方式,密码等,新建 /etc/shadowsocks.json 文件,填入如下内容:

1
2
3
4
5
6
7
8
9
10
{
"server":"0.0.0.0",
"server_port":1851, # SS Server 端口
"local_address": "127.0.0.1", #SS Local 端配置,不影响Server端使用
"local_port":1080, #SS Local 端配置,不影响Server端使用
"password":"xxxxx",
"timeout":300,
"method":"aes-256-cfb",
"fast_open": false
}

使用配置文件启动 SS Server:

1
ssserver -c /etc/shadowsocks.json -d start

2. 安装配置 Shadowsocks 客户端(sslocal)

第一步已将 SS Server 安装并配置完成,服务端口为 1851 ,接下来在需要安装 http 代理的机器上安装配置 shadowsocks 客户端,安装方法和第一步一样:

1
sudo pip install shadowsocks

编写 SS Local 客户端配置文件,配置远程连接 SS Server 的 IP,端口,密码,加密方式等,新建 /etc/shadowsocks.json 文件,填入如下内容:

1
2
3
4
5
6
7
8
9
10
{
"server":"xxx.xxx.xxx.xxx", # SS Server 端服务器公网 IP
"server_port":1851, # SS Server 端口
"local_address": "127.0.0.1", # SS Local 本地监听 IP
"local_port:":1080, # SS Local 本地监听端口
"password":"xxxxxx",
"timeout":300,
"method":"aes-256-cfb",
"fast_open": false
}

使用配置文件启动 SS Local:

1
sslocal -c /etc/shadowsocks.json -d start

3. 安装并配置 Privoxy

Privoxy 是一款代理软件,我们这里用该代理软件实现 HTTP 到 Socks5 的转换,所有来自 Privoxy 的请求被转发到 SS Local,从而实现了一个 HTTP 代理服务,Privoxy 的安装非常简单,直接 yum 一键搞定:

1
sudo yum install privoxy

编辑 Privoxy 配置文件 /etc/privoxy/config,搜索关键字 listen-address 找到 listen-address 127.0.0.1:8118 这一句,改成 listen-address 0.0.0.0:8118,表示该代理可以对外访问。
接下来在该配置该文件末尾添加 HTTP 请求转发到 SS Local Socks5 的配置:

1
forward-socks5t / 127.0.0.1:1080 .

  • forward-socks5t: 表示 Privoxy 转发请求到 Socks5 协议;
  • 127.0.0.1: 第二步中启动 SS Local 本地绑定 IP;
  • 1080: 第二步中启动 SS Local 本地监听端口;

启动 Privoxy:

1
2
systemctl restart privoxy
systemctl enable privoxy

4. 测试代理是否可用

1
curl -x privoxy_ip:8118 https://www.google.com

参考文章

https://vc2tea.com/whats-shadowsocks
https://docs.lvrui.io/2016/12/12/Linux%E4%B8%AD%E4%BD%BF%E7%94%A8ShadowSocks-Privoxy%E4%BB%A3%E7%90%86

如何 dump jvm 内存及线程栈

发表于 2018-05-19 | 分类于 Java

目前很多企业的后台服务都是 java 服务,在故障出现时能及时 dump jvm 内存和线程栈对于故障的分析及定位是非常重要的。接下来介绍如何进行 dump 操作,并分享一个简单脚本实现服务器线程数超过一定阀值时自动 dump 线程数最高的 java 进程的内存及线程栈。

1. dump jvm 内存

命令格式:

1
jmap -dump:format=b,file=dump_file_name pid

举例:dump pid 为 4738 的 java 进程的内存到 app_mem_dump.bin 文件

1
jmap -dump:format=b,file=app_mem_dump.bin 4738

2. dump jvm 线程栈

命令格式:

1
jstack pid > dump_file_name

举例:dump pid 为 4738 的 java 进程的线程栈到 app_thread_dump.txt 文件

1
jstack 4738 > app_thread_dump.txt

脚本分享

当服务器线程数超过 2500 时自动 dump 线程数最高的 java 进程的内存及线程栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env bash
#
# 服务器线程数达到 2500 以上时 dump 线程数最多的 java 进程的线程及内存
#
source ~/.bashrc
cur_thread_num=`ps -efL | wc -l`
if [ $cur_thread_num -le 2500 ]; then
exit 0
fi

cur_date=`date +"%Y-%m-%d_%H-%M-%S"`
cd ./dumpfile
# 服务器当前线程 dump 到文件:按照线程数由大到小排序显示
ps -efL --sort -nlwp > server_thread_dump_$cur_date
# dump 线程数最多的 jvm 的线程及内存
most_thread_num_pid=`cat server_thread_dump_$cur_date | sed -n '2p' | awk '{print $2}'`
nohup jstack -l $most_thread_num_pid > java_app_thread_dump_${cur_date}_pid_${most_thread_num_pid} &
nohup jmap -dump:format=b,file=java_app_mem_dump_${cur_date}_pid_${most_thread_num_pid} $most_thread_num_pid &

exit 0

1…8910…14

haohao

Talk is cheap. Show me the code.

134 日志
35 分类
43 标签
GitHub CSDN 开源中国 E-Mail
© 2017 — 2021 haohao
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.3
访问人数 总访问量 次