golang fasthttp优雅退出

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引言 使用Golang进行开发服务,我们会特别关心性能,fasthttp是标准库net/http性能的10倍。所以我们的web服务大量使用它。不过这个库并没有提供优雅退出方案, 查看了很多资料,我找到一个解决方案。 1.原理 对于web服务而言,优雅退出其实分为2步 1)关闭监听的端口,不再接受新的请求 2)对于正在处理的请求等待它们处理完成,如果全部处理完成,或者默认的超时时间已达,则退出 2. 实现 要达到这个目标需要实现 type Listener interface 下面展示一部分核心代码 grace_listener.go type GracefulListener struct { // inner listener ln net.Listener // maximum wait time for graceful shutdown maxWaitTime time.Duration // this channel is closed during graceful shutdown on zero open connections. done chan struct{} // the number of open connections connsCount uint64 // becomes non-zero when graceful shutdown starts shutdown uint64 } // Close closes the inner listener and waits until all the pending open connections // are closed before returning. func (ln *GracefulListener) Close() error { // 不再接受新的请求 err := ln.ln.Close() if err != nil { return nil } // 等待已经接到的请求处理完成 return ln.waitForZeroConns() } func (ln *GracefulListener) waitForZeroConns() error { atomic.AddUint64(&ln.shutdown, 1) fmt.Println("waitForZeroConns", atomic.LoadUint64(&ln.connsCount)) if atomic.LoadUint64(&ln.connsCount) == 0 { close(ln.done) return nil } select { case <-ln.done: return nil case <-time.After(ln.maxWaitTime): return fmt.Errorf("cannot complete graceful shutdown in %s", ln.maxWaitTime) } return nil } server.go 初始化&启动web服务 ...

January 18, 2018 · 1 min

Redis多实例-Proxy模式RPM包

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引子: 公司一个项目需要使用Redis,单实例的Redis性能不够,但出于成本考虑,不打算使用集群模式,因而选择了Redis的Proxy模式,为了方便部署,干脆打成了RPM包。这里分享出来,以飨众人。 RPM包下载地址: 链接: https://pan.baidu.com/s/1dP7Ilo 密码: p456 说明: 构建简单的Redis Cluster 基于 Redis 版本为4.0.1 Redis Proxy 为唯品会开发的多线程版本的twemproxy –twemproxies(版本1.2.1) 项目地址: https://github.com/vipshop/twemproxies 安装&&启动&&停止 1.安装 rpm -ivh redis_tp_cluster-0.0.1-4.x86_64.rpm 安装完成以后, 1.1 配置文件在 /etc/redis中 ## Redis共10个实例 /etc/redis/7001.conf /etc/redis/7002.conf /etc/redis/7003.conf /etc/redis/7004.conf /etc/redis/7005.conf /etc/redis/7006.conf /etc/redis/7007.conf /etc/redis/7008.conf /etc/redis/7009.conf /etc/redis/7010.conf # Redis Proxy 配置文件 /etc/redis/nutcrackers.yml 1.2 bin文件在 /usr/local/bin/redis-server /usr/local/bin/redis-cli /usr/local/bin/nutcrackers 1.3 启动脚本 /etc/init.d/redis_tp_cluster.sh 2.启动 /etc/init.d/redis_tp_cluster.sh start 3.停止 /etc/init.d/redis_tp_cluster.sh stop 重要 Redis Proxy默认 服务地址 127.0.0.1:22121 密码 b8@40fc02d52 如果需要调整请修改 /etc/redis/nutcrackers.yml 参考资料: 1.Redis rpm包下载地址

January 11, 2018 · 1 min

查看网站或域名价值的小工具

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 做了一个小工具,方便查看网站或域名价值,大家都来试下吧 点击链接 请我喝瓶饮料

January 10, 2018 · 1 min

程序hang住的问题的追踪

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引子: 我这里说的hang住,并不是指死锁,在真实的工作场景中,死锁的情况其实并不很多见。在我工作的几年中,有遇到好几次都是程序hang在某个IO请求上的。 1. 排查 排查hang住的最有用的命令是strace和lsof命令 比如我们有一个服务叫 atm-client 1.1 首先查出服务对应的进程 ps -ef| grep atm-client| grep -v grep 输出结果 root 5 1 0 2017 ? 01:05:39 atm-client 1.2 对于多线程的程序可以先列出进程的所有线程 ps -mp 5 -o THREAD,tid 输出结果 USER %CPU PRI SCNT WCHAN USER SYSTEM TID root 0.3 - - - - - - root 0.0 19 - poll_s - - 5 root 0.0 19 - sk_wai - - 11 root 0.0 19 - sk_wai - - 12 root 0.0 19 - sk_wai - - 13 root 0.0 19 - sk_wai - - 14 root 0.0 19 - sk_wai - - 15 root 0.0 19 - sk_wai - - 16 root 0.0 19 - sk_wai - - 17 root 0.0 19 - sk_wai - - 18 root 0.0 19 - sk_wai - - 19 root 0.0 19 - sk_wai - - 20 root 0.0 19 - sk_wai - - 21 1.3 strace 可以跟踪进程或者线程的系统调用 跟踪线程 ...

January 8, 2018 · 4 min

我的监控世界观(5)--如何在监控中反映业务场景

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 我在《我的监控世界观》1 ~ 4 中更多的阐述了对于某个监控点的监控、存储、展现。但是在现实世界中,整个世界的联系更像是一个图,每个点可以是某个监控点,而边是他们之间的调用关系或者数据流 举例: webserver –> mysql 对于一个最简单的web 服务, 它可能有两部分组成,webserver 和 mysql存储店铺、商品信息,webserver 服务直接和浏览器用户进行交互。在这样一个业务场景中,webserver 上有的监控点,可能包括单位时间内的UV、PV,而mysql 上的监控点可能有连接数,每秒请求数等等 这样,我们就把监控点和我们的自身的业务逻辑紧密的结合起来了。并且我们可以想到每一家公司的业务是完全不同的,所以如果我们把监控也分出层次的话,我们发现监控也是有层次的 3 业务层级的监控 2 监控点数据收集、存储、聚合 1 监控点数据采集 1)监控点数据的采集无法采取通行方案 除了基础类型的监控比如 CPU使用率、load、磁盘使用率 但是类似UV, PV 的业务指标很难统一起来 2)监控点数据收集、存储、聚合 是可以使用通行方案去解决的 最近我看到一篇文章提出用使用ES来做后聚合,可以无需做先聚合 3)业务层级的监控要反映业务流中各个应用,服务之间的调用关系,特别要反映数据的流向,流量的大小等等。数据流是贯穿各个模块的最核心的元素 后记: 这篇文章是我2014年的文章 现在看来,其中的很多观点还是具有指导意义 开源监控系统的重点在2,剩下的部分只要暴露良好的接口出来,让别人容易做二次开发即可。 请我喝瓶饮料

January 6, 2018 · 1 min

我的监控世界观(4) -- 监控数据的存储 RRD or RMDB OR Hbase

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 真正有必要存储的数据可能有两个 应用的历史状态信息(时序数据) 应用的监控项数据(元数据) 1. RRD 早期的开源监控系统,如cacti、nagios和ganglia,采用的是RRD,这种做法的好处是占用空间小,而且数据点的聚合是自动完成的,不需要监控系统的开发者自己开实现,另外出图也比较方便 但它的缺点也不少: 数据的提取和迁移非常的不方便 聚合点的调整不方便 监控数据的后期统计和分析十分不便 由于它的出图是直接保存为图片格式的导致传输成本更大 2. RMDB 这几年开始,不少监控系统如zabbix开始采用RMDB来存储监控项的数据,个人认为这种做法,应该是各大互联网公司的监控系统的主流做法,监控系统的数据存储具有以下特点: 大量的写操作,但是写操作相对均匀,突发现象较少 对时间的敏感度不是特别高,特别适合批量写的做法 读操作较少(相对于写操作),单次读取的数据量较大 且一般只做插入,不做变更 对于以mysql为代表的关系型数据库而言,经过调优以后,每秒至少可处理 10000+的写请求,再考虑读写分库的做法,对于一家拥有数万台机器的公司而言,我相信以关系型数据库作为存储,应该也是没有问题的 3.Hbase 以OpenTSDB作为代表,以Hbase 来存储数据 这个做法的好处是存储空间可以无限扩容,提取速度也比较快,因为笔者对Hbase 也不是特别了解,因此也不敢妄下结论,但是总是觉得用它来作为存储,有些杀鸡用牛刀的感觉。 后记 本文是我2014年的文章 这里做一点补充 其实所有的时序型数据库,比如ES,InfluxDB等等都可以用来存储监控数据 监控数据的典型特点是 高频写,低频读,而且一般比较平稳,没有太多的尖峰 对于RRD数据库好处是空间小,且可以自动聚合,不过扩容比较难,需要预先就设定好大小 对于RMDB的最大问题,数据需要经常清理,但是向MySQL这样的数据库,即使数据删除了,所占的磁盘空间也不能自动回收,这个是比较麻烦的 对于HBase Hadoop这套东西上手的难度太大,一般人玩不转。这也是让人都疼的东西 对于ES,搭建确实简单,如果数据按月分库,也确实能够支持历史数据清理的问题,不过ES比较吃内存。不过要是数据做前期聚合,就占用空间,后聚合,如果查询时间跨度大,速度有可能变慢 对于InfluxDB, 它没有集群模式

January 6, 2018 · 1 min

我的监控世界观(3)--监控领域的模型

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 根据我对这个领域的理解,监控系统中最重要的三个实体只有: 应用 主机 监控项 这三个概念都是高度抽象的,首先谈谈应用,应用可以理解为某个真实的服务,也可以理解为某个抽象的服务,比如前文提到的主机的所有状态,它们的组合就可以构成一个抽象意义上的应用,至于名字,你可以随便的指定。但不论是何种应用,它以下几个特点: 有一组可以采集的状态信息 “部署"在相应的主机上 有对应的报警接收人,报警接收人也是监控领域非常重要的实体,但是它与应用是强耦合的,一种应用往往只对应到一组固定的报警接收人,也就是一一对应 监控项,表示的是我们对应用状态信息选择性的关注,也就是对应用的关注点。应用有很多的状态,但是并非每种状态都是需要我们关注的,比如,一台运行中的主机,它的uptime,可能对我们来说就不是很重要,但它确确实实是一种状态,我们对监控项需要有一种判断,以决定应用的状态,可以采用阈值判断,也可以采用某种模型来进行判断。 最后来谈谈主机,主机的概念,非常复杂,通常意义上的主机,只得是可以部署应用的真实存在的物理机或者虚拟机,然而,在实际开发中我们发现,主机可以代表很多其它东西,比如某个路由器,甚至是某个作为其它应用负载均衡的nginx。 如果nginx 做为反向代理部署在机器A上,某网站, 为了负载均衡,部署在两台机器B, C上,那么通过访问nginx可以访问到 A和B, 如果把这个网站的服务当成一个应用,那么访问机器B, 机器C, 和直接访问机器A上的nginx ,几乎可以得到同样的状态信息。 主机的概念太多抽象,它可以有IP信息,也可以没有,它甚至可以只是一个域名。 后记 这是我2014年的文章,我看到open-falcon中引入了一个新的词汇 Endpoint来描述我文中的主机,我觉得还是很贴切的, 因为Endpoint可以没有实际的物理意义

January 6, 2018 · 1 min

我的监控世界观(2)-- 抽象的世界很精彩

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 抽象是我们认识世界的一种手段,它能把复杂的世界简单化 以主机监控为例,如果我们抛开网卡的数量不同,磁盘的数量不同,文件分区数量的不同,我们可以发现 剩余的部分几乎是相同的 cpu 使用率 磁盘使用率 内存使用率 … … 对于某种应用而言,比如某个网站在某台机器上的运行情况,我们可以发现大体可以得到以下状态数据 每分钟的PV数 每分钟的真实IP数 文件下载量 文章新增数等等 如果我们进一步抽象,就会发现主机监控除了状态信息不一样,几乎可以把它和其它的应用同等对待 当然抽象的结果就会失去精度,就好比,高度抽象的人这个概念,已经无法知道是男人还是女人,是 年轻人还是老人了。 后记: 这篇文章是我2014年写的文章。这里补充一点 其实简而言之,就是指标–时间–值 对于指标信息的描述是元数据,现在一般的实现是存储在关系型数据库里(比如MySQL) 时间-值都存储在时序型数据库里,比如 RRDTool (Nagios、Open-falcon) InfluxDB Hbase (OpenTSDB) ES

January 6, 2018 · 1 min

我的监控世界观(1)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 监控系统设计的目的是什么? 从哲学上,人所有的活动就是认识世界和改造世界。 IT意义上监控系统的目的是为了我们全面的了解我们的机器,系统的运行状态,以作出相应的判断和动作。它其实是更多的是侧重于认识世界。 监控系统设计的核心点是什么? 采集状态 状态判断 报警(或者触发某种动作) 图表展示(报表) 反馈 从经验来看,这4条的实现的难易程度,由难到易刚好是5 ->1,2 -> 3,4 监控系统的分类: 按紧急程度而言可分为2类 周期性监控 事件监控 (事件报警) 顾名思义,周期性监控一般是1分钟或5分钟来采集系统的状态,以cacti和nagios为代表 而事件监控一般是由被监控主体自主上报的某种事件,以sentry为代表(nagios的NRPE中也有trap) 它们适用于不同的场景,没有优劣之分 如果按照功能划分,又可分为: 安全监控 主机监控 应用监控 网络监控等等 有趣的规律: 以主机监控举例,早期的不少公司都是使用开源的监控软件作为工具,比如:cacati,nagios, ganglia,当物理机数量达到800~1000台左右时(我推测的),都逐步使用自己的开发的监控系统替换开源的监控软件 后记: 这篇文章其实是我2014年的文章,其实目前最火的应该是zabbix和open-falcon,其中我比较看好的是open-falcon,它在设计理念相较以前的监控系统是有一定很多创新和突破,且性能更好。

January 6, 2018 · 1 min

python 日志收集服务器

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 起因 python 的日志收集服务是线程安全的(对同一个文件的写入,使用了锁),但是对于多进程的情况,它是无法处理的。python 官方文档推荐的做法是,使用tcp 服务器专门用于日志的收集,以确保对的文件的写入是安全的。这里提供了日志收集服务器基于twisted的实现,可供参考,程序在centos上进行了测试,并可用于生产环境 当client 和server 在一台机器上时,每秒可以处理6000条日志记录 #!/usr/bin/env python # -*- coding:utf-8 -*- ################################## # logserver # 通过socket接收并存储日志信息 # 注:测试程序为test_logserver.py # 修改时间:2013-11-22 ################################### # standard lib import os, sys import time import twisted import logging import traceback import struct import cPickle # use cPickle for speed from logging.handlers import TimedRotatingFileHandler from twisted.internet.protocol import Protocol, Factory # our own code import settings from log_record import initlog # 根据logger name生成一个 def gen_log_name(name): name = name.replace('.', '_') return name + '.log' class ReceiveProtocol(Protocol): HEADER_LENGTH = 4 def __init__(self): self.buf = '' self.state = 'init' self.require_length = ReceiveProtocol.HEADER_LENGTH # 记录当前完成初始化的logger名称 self.logger_dict = {} def connectionMade(self): logger = logging.getLogger('log_server') logger.info('[makeConnection]与logger_client建立socket连接。') def dataReceived(self, data): logger = logging.getLogger('log_server') try: if len(self.buf + data) >= self.require_length: self.buf = self.buf + data # 数据就绪进行相应动作 data = self.buf[0:self.require_length] # 把数据从缓冲区中取走 self.buf = self.buf[self.require_length:] self.solve(data) # 可能一次读到了多条日志记录 if self.buf: self.dataReceived('') else: self.buf += data except BaseException, e: logger.error('处理记录失败' + str(e) + '\n' + traceback.format_exc()) def solve(self, data): logger = logging.getLogger('log_server') statehandler = None try: pto = 'proto_' + self.state statehandler = getattr(self,pto) except AttributeError: logger.error('callback',self.state,'not found') self.transport.loseConnection() statehandler(data) if self.state == 'done': self.transport.loseConnection() def connectionLost(self, reason): logger = logging.getLogger('log_server') logger.info('[connectionLost]与logger_client的socket连接关闭。') # 记录日志 def proto_record(self, data): logRecord = logging.makeLogRecord(cPickle.loads(data)) if logRecord.name not in self.logger_dict: logger = initlog(logRecord.name, settings.PATH, gen_log_name(logRecord.name), logLevel=logging.DEBUG) self.logger_dict[logRecord.name] = logger self.logger_dict[logRecord.name].handle(logRecord) # 修改下一步动作以及所需长度 self.state = 'init' self.require_length = ReceiveProtocol.HEADER_LENGTH # 处理头部信息 def proto_init(self, data): length = struct.unpack('!I', data[0:ReceiveProtocol.HEADER_LENGTH])[0] # 修改下一步动作以及所需长度 self.state = 'record' self.require_length = length if len(self.buf) >= self.require_length: data = self.buf[0:self.require_length] # 把数据从缓冲区中取走 self.buf = self.buf[self.require_length:] self.solve(data) class ReceiveFactory(Factory): def buildProtocol(self, addr): return ReceiveProtocol() def main(): print 'logserver has started.' logger = logging.getLogger('log_server') logger.info('logserver has started.') from twisted.internet import epollreactor epollreactor.install() reactor = twisted.internet.reactor reactor.listenTCP(settings.PORT, ReceiveFactory()) reactor.run() if __name__ == '__main__': main() 完整程序: https://github.com/vearne/log_server/tree/master ...

January 2, 2018 · 2 min