织网

身体和灵魂,总有一个在路上

实时监控 Nginx Qps 的拓展

| Comments

用下班时间写了一个 ngx lua 的拓展, GitHub。可以:

  • [x] 实时监控域名的 qps
  • [x] 实时监控域名的 5xx 个数
  • [x] 实时监控域名的 响应时长
  • [x] 并附带 Ganglia 监控插件

使用

  • 配置 nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
http {
    ...
    ...

    lua_shared_dict statics_dict    1M; # 初始化变量
    lua_package_path "/etc/nginx/ngx_lua_reqstatus/?.lua";  #路径
    log_by_lua_file "/etc/nginx/ngx_lua_reqstatus/hook.lua"; # 添加此句

    server {
        listen 80;
        server_name  justforfun.com; 

        location /{
            ...
        }
    }
    # 监控服务
    server {
        listen 127.0.0.1:6080;
        location /{
            access_by_lua_file "/etc/nginx/ngx_lua_reqstatus/status.lua";
        }
    }
}
  • 效果
1
curl localhost:6080/?domain=justforfun.com
  • 输出
1
2
3
4
5
6
Server Name:    justforfun.com
Seconds SinceLast:   1.4399998188019 secs
Request Count:      1
Average Req Time:   0 secs
Requests Per Secs:  0.69444453182781
5xx num:    0

Nginx 日志利器 GoAccess

| Comments

我们经常需要从 Nginx 日志分析得出很多有价值的东西,分析的方法是各种 shell, awk, python, 现在 GoAccess 给你另外一种选择, 值得拥有。

  • 安装 用以下的方式安装,才能得到新版的 GoAccess, 功能更健全
1
2
3
4
$ echo "deb http://deb.goaccess.io $(lsb_release -cs) main"|sudo tee -a /etc/apt/sources.list
$ wget -O - http://deb.goaccess.io/gnugpg.key | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install goaccess
  • 推荐配置

安装完成之后,会生成一份 /etc/goaccess.conf 稍作编辑,这就是默认的配置,免去了后续每次都要定义格式

1
2
3
time-format %T   # 只有这种方式才能解决 0.0us 的显示问题
date-format %d/%b/%Y
log-format %h %^[%d:%t %^] "%r" %s %b "%R" "%u" %T
  • 使用

输出报表,报表中,我们可以看到最常访问的 IP, 接口,以及每个接口使用带宽,平均响应时长,状态码等,对业务分析有较好的便利性

1
2
3
4
5
6
7
8
9
10
11
终端显示
goaccess -f access.log 
 
输出报表,报表中,我们可以看到top ip, 接口,以及接口使用带宽,平均响应时长,状态码等
goaccess -f access.log > report.html
 
具体某段时间的输出
sed -n '/5\/Dec\/2015/,/10\/Dec\/2010/ p' access.log | goaccess -a
 
处理已经压缩的日志
zcat access.log.*.gz | goaccess

Gre 隧道与 Keepalived

| Comments

这一篇文章是做了不少功课的。

什么是 Gre 隧道

GRE 隧道是一种 IP-2-IP 的隧道,是通用路由封装协议,可以对某些网路层协议的数据报进行封装,使这些被封装的数据报能够在 IPv4/IPv6 网络中传输。Tunnel 是一个虚拟的点对点的连接,提供了一条通路使封装的数据报文能够在这个通路上传输,并且在一个Tunnel 的两端分别对数据报进行封装及解封装。Linux 上创建 GRE 隧道,需要 ip_gre 内核模块,它是Pv4 隧道的驱动程序。

假设我有2台服务器,想做这两台之间创建 GRE 隧道

  • $server_A_ip 表示服务器A的IP
  • $server_B_ip 服务器B 的内网IP
1
2
3
4
5
6
7
8
9
10
11
# 在 A 机器上执行
# 创建 GRE 协议的 虚拟网络设备, 指定本地和对端 IP
sudo ip link add gretap1 type gretap local $server_a_ip remote $server_b_ip 
sudo ip link set dev gretap1 up  # 启动该设备
sudo ip addr add dev gretap1 10.1.1.2/24 # 给该设备一个虚拟网络地址

# 在 B 机器上执行
# 创建 GRE 协议的 虚拟网络设备, 指定本地和对端 IP
sudo ip link add gretap1 type gretap local $server_b_ip remote $server_a_ip 
sudo ip link set dev gretap1 up  # 启动该设备
sudo ip addr add dev gretap1 10.1.1.3/24 # 给该设备一个虚拟网络地址

如果想停止或者删除上述网卡

1
2
ip link set gretap1 down
ip tunnel del gretap1

至此点到点得隧道建立。

什么是 vrrp 协议

VRRP(Virtual Router Redundancy Protocol), 即虚拟路由冗余协议。是实现路由器高可用的容错协议。

即将N台提供相同功能的路由器组成一个路由器组,这个组里面有一个 master 和多个 backup, 但在外界看来就像一台一样,构成虚拟路由器,拥有一个虚拟IP(vip),占有这个IP 的 master 实际负责 ARP 相应和转发 IP 数据包, 组中的其它路由器作为备份的角色处于待命状态。 master 会发组播消息,当 backup 在超时时间内收不到 vrrp 包时就认为 master 宕掉了,这时就需要根据VRRP的优先级来选举一个backup当master,保证路由器的高可用。

Keepalived 是什么

Keepalived 是一个基于 VRRP 协议来实现的服务高可用方案,可以利用其来避免IP单点故障。

  • 一个经典的案例

一个WEB服务至少会有2台服务器运行 Keepalived,一台为主服务器,一台为备份服务器, 但是对外表现为一个虚拟IP,主服务器会发送特定的消息给备份服务器,当备份服务器收不到这个消息的时候,即主服务器宕机的时候,备份服务器就会接管虚拟IP,继续提供服务,从而保证了高可用性。

怎么玩 Keepalived

  • 安装
1
sudo apt-get install keepalived

之前提到的,我们在 A, B 两台服务器建立起了 GRE 隧道了。 现在我们有一个虚拟的内网IP, 姑且叫做 $virtual_third_ip 接着在 A 服务器上

  • 配置

编辑服务器 A, B 的 /etc/keepalived/keepalived.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

global_defs {
    router_id LVS_DEVEL
}

vrrp_instance VI_1 {
    state MASTER
    interface gretap1 # 绑在建立起来的隧道上
    virtual_router_id 51
    # 优先级别,越高的设备会被弄成主设备, A,B 服务器要有所差别,其他都一样
    priority 100          advert_int 1      # 广播时间间隔
    authentication {  #  验证相关
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        $virtual_third_ip dev eth0
    }
}

我们将服务器 A 作为 Master, 服务器 B 当做 Backup, 当服务器 A 需要停机的时候,$virtual_third_ip 就会漂移到服务器 B 上面。 我们两台机器都有相同配置的 Nginx 服务,就可以保障机器出现故障的时候,Nginx 服务丝毫不受影响。

附录

给 Tengine 加上 Lua 拓展

| Comments

Tengine 能动态加载第三方模块,成为我们青睐的选择,我们可以编译动态链接文件,而不需要重新安装 Nginx, 这对在线增强 webservice 很有帮助. 感谢 agentzh, lua-nginx-module, 可以让我们使用 lua 增强nginx的功能, 不言而喻,我们必须现有 Lua 的环境,才能运行 ngx_lua;

编译 nginx_lua

官方推荐使用LuaJit,虽然也可以使用Lua,但是即时编译(Just-In-Time Compiler)混合了动态编译和静态编译,一句一句编译源代码,但是会将翻译过的代码缓存起来以降低性能损耗。

  • 下载安装
1
2
3
4
5
wget http://luajit.org/download/LuaJIT-2.0.4.tar.gz
tar zxvf LuaJIT-2.0.4.tar.gz
cd LuaJIT-2.0.4
make
make install
  • 设置环境变量
1
2
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0
  • 然后编译ngx-lua-module.so
1
/usr/local/share/dso_tool/ --path=/Path/To/Lua-Nginx-module
  • 设置动态库
1
2
> echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
> ldconfig

在 Tengine 中启用

nginx.conf 中先加载动态库

1
2
3
dso {
    load ngx_load_module;
}

在 nginx.conf 中添加

1
lua_code_cache on/off;

来开启是否将代码缓存,所以每次变更 “*.lua” 文件时,必须重启 nginx 才可生效.

使用 ngx_lua_waf

有了基础环境,我们要开始发挥 ngx lua 的优点了, 我用他安装了 waf (web application firework) ngx_lua_waf,这是一个通过 ngx_lua 编写的 web 应用防火墙, 在使用过程中也发现了 ngx_lua_waf 一个bug,给他提了一个Pull Request, 码农生涯第一个 PR.


注: 静态编译的程序在执行前全部被翻译为机器码,而动态直译执行的则是一句一句边运行边翻译。

记录使用 Flask 的点滴

| Comments

喜欢 Flask 经典的 RestFul 设计风格,以及它与 Gevent 的优雅结合,可以帮助我们轻松构建异步非阻塞应用,烂笔头记下一些较好的实践。

消息反馈

Flask 提供了一个非常简单的方法来使用闪现系统向用户反馈信息。 闪现系统使得在一个请求结束的时候记录一个信息,然后在且仅仅在下一个请求中访问这个数据。这通常配合一个布局模板实现

文档链接

1
2
3
4
5
6
# 视图函数需要调用
flash('your response message for user')

# 前端页面调用
for message in get_flashed_messages()
就可以输出反馈信息

Flask 上下文

有两种上下文:程序上下文,请求上下文

  • current_app: 程序级别上下文,当前激活程序的实例。
  • g: 请求级别的上下文
  • request: 是请求级别的上下文,封装了客户端发出的 Http 请求中的内容
  • session: 用户会话,用于存储请求之间需要记住的键值对

注册 JinJia 模板过滤

1
2
3
4
def reverse_filter(s):
    return s[::-1]

app.jinja_env.filters['reverse'] = reverse_filter

itsdangerous 生成过期时间 Json 签名

1
2
3
4
5
6
7
8
9
10
serialaizer = Serializer(SECRET_KEY, expires_in=EXPIRES)
info = {'id':'xxx'}
session = serialaizer.dumps(info)

# 判断 session 时间
info = None
try:
    info = serialaizer.loads(session)
except Exception:
    return jsonify(ERR_SESS_TIMEOUT)

用途:生成一个有时间限制的签名,用于API 访问的验证码,如果过期,提醒用户重新登录

一种较好的项目组织方式

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
▾ app/
    ▾ controlers/
        ▾ admin/                    #管理后台的蓝图控制器
            ▾ forms/                #表单限制文件
                __init__.py     
                xx.py            # blueprint 文件
            __init__.py
            administrator.py
        ▾ api/                      # API 控制器
            __init__.py
        ▾ site/                     # 站点控制器
            __init__.py             # blueprint 文件
            xx.py
        __init__.py
        error.py
    ▸ models/         # SQLAlchemy 的各类模型
    ▸ static/         # 需要的静态资源,css, js, imgs
    ▾ templates/   # 前端页面,跟 controller 分类一一对应
        ▸ admin/
        ▸ error/
        ▸ site/
    ▸ utilities/    #  功能函数
      __init__.py       # 初始化app需要的各种插件,比如 redis, db, 注册蓝图
run.py                  # 相当于 main 函数,创建 app, 执行app.run() 函数
settings.py             # 配置文件

BluePrint 的好

每个 Blueprint 就像独立的 Application, 可以管理自己的模板, 路由, 反向路由url_for, 甚至是静态文件,最后统一挂载到 Application 下。从头到尾都是 RestFul。

在创建 app (app/init.py) 的时候调用如下:

1
2
from app.controllers.site.console import console 
app.register_blueprint(console, url_prefix='/console')

视图文件 (app/controllers/site/console.py):

1
console = BLueprint('console', __name__)

Json 返回

1
2
from flask import jsonify
return jsonify({'code': 0})

自定义出错页面

1
2
3
4
5
6
7
@app.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def crash(error):
    return render_template('5xx.html'), 500

WTF 跨站脚本防御

Flask-WTF 能保护表单免受跨站请求伪造的攻击,恶意网站把请求发送到被攻击者已经登录的其他玩战会引发 CSRF 攻击

  • app config 文件中,开启 CSRF 开关并配置密钥
1
2
CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'
  • 表单的定义
1
2
3
4
5
from flask.ext.wtf import Form, TextField, BooleanField
from flask.ext.wtf import Required
class LoginForm(Form):
    openid = TextField('openid', validators = [Required()])
    remember_me = BooleanField('remember_me', default = False)
  • 页面渲染
1
2
3
4
5
6
<form action="" method="post" name="login">
    form.hidden_tag()
    <p> Please enter your OpenID:form.openid(size=80)<br></p>
    <p>form.remember_me </p>
    <p><input type="submit" value="Sign In"></p>
</form>
  • 控制器函数
1
2
3
4
5
6
7
@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="' + form.openid.data))
        return redirect('/index')
    return render_template('login.html', title = 'Sign In',form = form)

我们在配置中开启了CSRF(跨站伪造请求)功能,模板参数 form.hidden_tag() 会被替换成一个具有防止CSRF功能的隐藏表单字段。 在开启了CSRF功能后,所有模板的表单中都需要添加这个模板参数

Python 使用 LDAP

| Comments

写在开头

过去的两个多星期,几位小伙伴同心协力完成一个自研的,类似pushover的产品。感谢 Leader 的信任,让我在负责开发的同时也兼顾了一把项目经理。谢谢 IOS, Android 客户端的兄弟,设计师的支持,还有一位实习生,开心看到他的点滴成长。

提下背景,我们之前使用 pushover 来做报警推送,但是它对天朝用户不友好,Android 用户需要翻墙才能使用,有时候不稳定,体现为会收不到信息。pushove 需要付费。我们的受众不仅有程序汪,还有运营产品汪,他们需要一款更容易上手的推送软件,至少不需要番羽墙。于是自己开发一个很有必要。

为最大程度降低用户使用门槛,同时保证用户信息的安全,我们用了 LDAP 账户登陆,严格控制权限,以及 HTTPS 协议开发。下面提一提 LDAP 这个东西。

LDAP 是什么

LDAP维基百科

简单地讲,它是以目录树的方式存放账户信息。

这次项目中,我们不希望用户重新注册账户,而是采用原有的用户体系,这样对单点登录以及权限控制的好处不严而喻, LDAP 协议呼之欲出。

virtualenv 下安装 python-ldap

我们采用 Python 开发,这就需要 python-ldap 的帮助了, 记下安装笔记的好处是下次不用在此纠结太长时间。

1
2
apt-get install libsasl2-dev python-dev libldap2-dev libssl-dev
pip install python-ldap

身份验证

1
2
3
4
5
6
7
8
9
10
11
# LDAP 服务的域名和端口
SERVER_NAME = 'xxxx'
SERVER_PORT = xxx
try:
    conn = ldap.open(SERVER_NAME, SERVER_PORT)  
    conn.protocol_version = ldap.VERSION3 #设置ldap协议版本 
    username = "user=xxx,dc=company,dc=com" #身份信息
    password = "xxx" #访问密码
    conn.simple_bind_s(username,password) # 开始绑定,验证成功的话不会抛出异常
except ldap.LDAPError, e: #捕获出错信息
    print e

一点感悟

鄙人觉得,技术领导要带领项目成长, 除了有责任把控项目风险,推进项目进度。 还必须要花很多的心血在驾驭技术上, 身先士卒去调研可行性, 以及做技术攻关, 而非命令式地分配任务,让同事干活, 只问责结果。 否则很容易导致凝聚力不足,团队技术氛围不足,这样的团队易消极,也易滋生失败的项目。 然而在天朝, 很多人存在一个潜意识:写好代码是为了以后不写代码,这种阶级思想让我反感。

以前看过一个新闻, 硅谷在面试技术 VP ,仍然要求其在各位工程师面前手写代码,以此作为面试的重要环节, 不得不点赞。

Sysdig 值得拥有

| Comments

定位服务器问题时, 我们需要各式各样的武器, 诸如 iftop, ifstat, netstat, tcpdump, iostat。dstat 等, 因此工具箱需要装满很多工具, 在面对问题的时候才能显得不费吹灰之力, 迅速定位问题并解决, 保障服务稳定运行。Sysdig 的横空出世, 对我们而言, 就是一把瑞士军刀, 灵活小巧, 武艺多端.

安装

1
2
3
4
curl -s https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.public | apt-key add -
curl -s -o /etc/apt/sources.list.d/draios.list http://download.draios.com/stable/deb/draios.list
apt-get update
apt-get -y install sysdig

常用操作

sysdig 有很多 chisel, chisel 意为 铲子, 可以理解为定位某类问题的工具, sysdig 采用 Lua 编写的。

  • 查看 chisel 列表
1
sysdig -cl  
  • 查看具体某个 chisel 的提示
1
sysdig -i spy_logs
  • 使用某个 chisel
1
sysdig -c spy_logs
  • 过滤器可以帮助我们从各种输出信息中, 筛选出我们需要的, 比如 proc.name=foo , 如果你记住不了太多过滤器也无妨, 我们可以借助如下命令查看过滤器
1
sysdig -l
  • 记录定位信息到文本, 以及从文本读取信息
1
2
sysdig -w tracefile.cap
sysdig -r tracefile.dump proc.name=sshd

高效的实战

  • 服务器上经常需要查看哪个服务带宽占用使用较高, 特别是被 DDOS 的时候。
1
sudo sysdig -c topprocs_net
  • 查看某个IP的通讯数据,并以ASCII 码输出
1
sudo sysdig -s2000 -A -c echo_fds fd.cip=127.0.0.1
  • 查看非请求 redis-server 的其他请求进程和句柄
1
sudo sysdig -p"%proc.name %fd.name" "evt.type=accept and proc.name!=redis-server"
  • 查看访问该服务器的所有 GET 请求数据
1
sudo sysdig -s 2000 -A -c echo_fds fd.port=80 and evt.buffer contains GET
  • 查看访问该服务器的 SQL 语句
1
sudo sysdig -s 2000 -A -c echo_fds evt.buffer contains SELECT
  • 查看磁盘读写最高的进程
1
sysdig -c topprocs_file
  • 查看延迟最大的系统调用
1
sysdig -c topscalls_time
  • 查看具体文件的操作细节
1
sysdig -A -c echo_fds "fd.filename=syslog"
  • 查看 IO 延迟大于 1ms 的文件
1
sudo sysdig -c fileslower 1
  • 监视某个文件是否被操作, 从安全出发想象空间很大哦
1
sudo sysdig evt.type=open and fd.name contains /etc

Sysdig 官网

Ansible 使用经验

| Comments

当你只有一两台服务器的情况下,可以直接登上服务器,手敲命令完成软件部署,代码发布等工作。但假如你有10台,100台的时候,这种方式不仅浪费大量时间,而且给人为犯错带来了可能。于是我们选择 Ansible 来做自动化批量操作。

之前有记录一些 Ansible 入门的使用,请看这里, 这半年的积累, 总结一些实用的经验, 记录了一把。

更好地配置文件

我们会如下配置 /etc/ansible/host, 特意指明用户与 端口

1
2
3
[web-cluster]
<node-1-IP> ansible_ssh_port=<Your Port> ansible_ssh_user=zj
<node-2-IP> ansible_ssh_port=<Your Port> ansible_ssh_user=zj

在 /etc/ansible/ansible.cfg 文件里 我们特意提及了 ansible-role 的配置,未来我们会使用这个东西

1
roles_path    = /home/zj/my-ansible/roles

使用 ansible role 来区分业务

打开 ansible 部署脚本的文件夹, 目录树如下

1
2
3
4
5
6
7
8
9
cd /home/zj/my-ansible/
haproxy
     - entry.yaml
roles
     - haproxy
        - files
        - handlers
        - vars
        - tasks

我用一个管理 haproxy 的例子来讲解这种方式。 在 roles 目录下创建 haproxy, 如上所示,需要有四个目录;

  • files 目录下放置需要被传输到远端的文件;
  • vars 目录下有一个 main.yml 文件,可以定义一些通用的配置变量,可以在 ansbile 脚本中使用;
  • handlers 目录下有一个 main.yml, 可以定义一些通用的操作,比如重启服务等;
  • tasks 目录下是我们编写 main.yml 脚本,执行业务逻辑的地方;

那么 ansible role 的入口在哪呢?

在 ~/my-ansible/haproxy/entry.yml 中,指定了roles的角色,如此一来, ansible-playbook 就会去 /home/zj/my-ansible/roles/haproxy 准备执行 tasks/main.yml

1
2
3
- hosts: web-cluster
  roles:
    - haproxy

files 目录的路径定位

摘取 ~/my-ansible/roles/haproxy/tasks/main.yml

1
2
3
- name: copy haproxy conf
  copy: src=haproxy.cfg dest=/etc/haproxy/haproxy.cfg owner=root group=root
  sudo: yes

这里的 src=haproxy.cfg 意味着 ~/my-ansible/roles/haproxy/files/haproxy.cfg

使用 tags 区分不同操作

1
2
3
4
5
- name: install ppa
  shell: add-apt-repository -y ppa:vbernat/haproxy-1.5
  sudo: yes
  tags:
    - install-haproxy

以下命令,是使用 tags 参数区分操作的例子

1
2
cd ~/my-ansible/haproxy
ansible-playbook entry.yml -v -K --tags "install-haproxy"

规划 ansible roles 的 tasks 目录

tasks 目录有一个主执行文件 main.yml, 因为业务操作步骤太多,导致 main.yml 文件很长,那么可读性就下降了。为此,我们使用了 include 语法。

cat ~/my-ansible/roles/haproxy/tasks/main.yml

1
- include: 'install-haproxy.yml'

include 上述文件,这样 main.yml 就显得简洁,我们可以将相关的操作写在对应的 yml 文件里

cat ~/my-ansible/roles/haproxy/tasks/install-haproxy.yml

1
2
3
4
5
- name: copy haproxy conf
  copy: src=haproxy.cfg dest=/etc/haproxy/haproxy.cfg owner=root group=root
  sudo: yes
  tags:
     - install-haproxy

tags 最好也与该 yml 文件名一致,清晰分明

ansible-play-book 一些常用的选项

  • -K 需要 sudo 权限去客户机执行命令,会提示你输入密码
  • -v 可以输出冗余的执行过程
  • –check 可以测试脚本执行情况,但实际并未在远程机器执行
  • –tags 提示 ansible-play-book 调用哪些 tags 命令

使用过ansible roles 之后,最大的体会是操作调理化,甚至编程化,合理的利用 handler, vars, 能更加优雅抽象。


上述的例子在 Github 有代码, 结合本文阅读可能更容易上手 Link

Supervisor 监听器

| Comments

我们服务多是用 supervisor 启动的, 但监控多数是用 monit, 如果我们能通过监测 supervisor 事件变化来做监控,就可以写一套通用的监控程序。

庆幸的是,supervisor 的 eventListener 支持我的设想。

这个监控程序需要用 supervisor 启动,类型不再是program, 而是eventlistener,这里有几个比较耗时的地方需要记录下。

  • supervisor 有独特的通信协议,需要遵循,否则通讯不会被触发
1
2
3
4
5
6
def write_stdout(self, s):
    sys.stdout.write(s)
    sys.stdout.flush()

write_stdout('READY\n') //类似开始握手
write_stdout('RESULT 2\nOK') //结束通讯
  • 需要从标准输入端读取事件,而且他是个阻塞的事件模型
1
2
3
4
5
while 1:
    self.write_stdout('READY\n')
    line = sys.stdin.readline()
    do_some_thing()
    self.write_stdout('RESULT 2\nOK')
  • supervisor 配置文件需要订阅事件
1
2
3
4
5
6
7
8
[eventlistener:alarm]
user=zj
command=/usr/bin/python /home/ymserver/bin/alarm/main.py
events=PROCESS_STATE_EXITED,PROCESS_STATE_STOPPED,PROCESS_STATE_FATAL

# 记录控制台输出的日志位置
stderr_logfile=/home/zj/log/supervisor/alarm.err.log
stdout_logfile=/home/zj/log/supervisor/alarm.output.log

弄好 supervisor 配置,以及部署好代码之后,需要重启 supervisor 才会真正的订阅事件。 从此 supervisor 管理的程序一旦有 FATAL,EXIT 等状态就会触发程序,程序中就会触发自定义的报警。


代码

Happy Hack!

Twemproxy 一个 Redis 代理

| Comments

为解决线上 Redis 服务直连出现链接数爆棚而做的调研, 对 Twitter 开源的 twemproxy 做一些记录。 我们之所以放弃官方的 RedisCLuster 是因为不太满意其性能

初窥原理 * 安装与配置 * 不支持的操作 * 压力测试 * 摘自极光博客的评论

初窥原理

  • Twitter 出品的轻量级 Redis,memcached 代理,使用它可以减少缓存服务器的连接数,并且利用它来作分片。
  • 作是说最差情况下,性能损耗不会多于20%。背后是用了pipeline,redis是支持使用pipeline批处理的。
  • twemproxy 与每个 redis 服务器都会建立一个连接,每个连接实现了两个 FIFO 的队列, 通过这两个队列实现对 redis 的 pipeline 访问,将多个客户端的访问合并到一个连接,这样既减少了redis服务器的连接数,又提高了访问性能。

安装与配置

  • 安装
1
2
3
4
5
6
7
8
apt-get install automake
apt-get install libtool
git clone git://github.com/twitter/twemproxy.git
cd twemproxy
autoreconf -fvi
./configure
make
sudo make install

默认的可执行文件在 /usr/local/sbin/nutcracker

  • 配置文件 /etc/nutcracker/nutcracker.yml
1
2
3
4
5
6
7
8
9
10
11
alpha:
    listen: 127.0.0.1:8877
    hash: fnv1a_64
    distribution: ketama
    auto_eject_hosts: true
    redis: true
    server_retry_timeout: 30000
    server_failure_limit: 3
    servers:
        - 127.0.0.1:6379:1 master0  #后端的redis-server
        - 127.0.0.1:6380:1 master1

当 redis 做缓存的使用的时候应该启用 auto_eject_hosts, 如果某个节点失败的时候将该节点删除,虽然丧失了数据的一致性,但作为缓存使用,保证了这个集群的高可用性。当redis做存储的使用时为了保持数据的一致性,应该禁用 auto_eject_hosts,也就是当某个节点失败之后并不删除该节点。

不支持的操作

1
2
3
4
5
keys command: keys,migrate,move object,randomkey,rename,renamenx,
sort strings command: bitop,mset,msetnx
list command: blpop,brpop,brpoplpush
scripting command: script exists,script flush,script kill,script load
pub/sub command:(全部不支持)psubscribe,publish,punsubscribe,subscribe,unsubscribe

压测

感谢 redis 提供的 redis-benchmark 工具,用它来做压测挺好的。

  • n 表示多少个连接
  • r 表示多少个 key,
  • t 代表命令
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
zj@zheng-ji.info:~$ redis-benchmark -p 6700 -t smembers,hexists,get,hget,lrange,ltrim,zcard,setex,sadd -n 1000000 -r 100000000

====== GET ======
1000000 requests completed in 12.95 seconds
50 parallel clients
3 bytes payload
keep alive: 1

99.19% <= 1 milliseconds
99.93% <= 2 milliseconds
100.00% <= 2 milliseconds
77220.08 requests per second

====== SADD ======
1000000 requests completed in 10.74 seconds
50 parallel clients
3 bytes payload
keep alive: 1

99.88% <= 1 milliseconds
99.95% <= 2 milliseconds
99.97% <= 3 milliseconds
99.99% <= 4 milliseconds
100.00% <= 4 milliseconds
93144.56 requests per second

如作者所言, 性能几乎可以跟直连redis比拟,背后的数据也很均匀,使用twemproxy 观察连接数, 一直都保持在个位数左右。

摘自极光博客的评论

  • 前端使用 Twemproxy 做代理,后端的 Redis 数据能基本上根据 key 来进行比较均衡的分布。
  • 后端一台 Redis 挂掉后,Twemproxy 能够自动摘除。恢复后,Twemproxy 能够自动识别、恢复并重新加入到 Redis 组中重新使用。
  • Redis 挂掉后,后端数据是否丢失依据 Redis 本身的策略配置,与 Twemproxy 基本无关。
  • 如果要新增加一台 Redis,Twemproxy 需要重启才能生效;并且数据不会自动重新 Reblance,需要人工单独写脚本来实现。
  • 如同时部署多个 Twemproxy,配置文件一致(测试配置为distribution:ketama,modula),则可以从任意一个读取,都可以正确读取 key对应的值。
  • 多台 Twemproxy 配置一样,客户端分别连接多台 Twemproxy可以在一定条件下提高性能。根据 Server 数量,提高比例在 110-150%之间。
  • 如原来已经有 2 个节点 Redis,后续有增加 2 个 Redis,则数据分布计算与原来的 Redis 分布无关,现有数据如果需要分布均匀的话,需要人工单独处理。
  • 如果 Twemproxy 的后端节点数量发生变化,Twemproxy 相同算法的前提下,原来的数据必须重新处理分布,否则会存在找不到key值的情况。

参考链接

极光推送的博客