slan's blog

有梦就去追,累了就休息

高强度运动缺氧

高强度运动缺氧

由于我有多次很久不骑车,突然骑车爬山吐了的经历,查了下原因:

身体在高强度运动后,血液循环速度加快,如果马上坐下、蹲下或躺下,高速运转的血液突然受阻,会使人体器官供血、供氧、供能不足。同时,血液循环不畅会使血压瞬间降低,造成大脑供血不足,使人出现恶心、呕吐、晕眩等症状。

此外,高强度训练后马上休息,呼吸频率骤减,可能导致人体短时间缺氧,出现头晕、头痛、耳鸣、眼花、四肢无力等症状。

解决方案:

应慢跑或慢走10分钟左右,同时深呼吸,等心跳放缓、呼吸均匀、热量排出后再休息。

2022-07-05 00:37:58 +0800 +0800

nginx request limit - Nginx 限流

nginx request limit - Nginx 限流

限流是一个有效的保护服务的手段,nginx限流是一个便宜,可靠的限流方式,但是nginx的限流配置如果配置不好,可能给生产带来灾难。

官方默认示例配置:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

    ...

    server {

        ...

        location /search/ {
            limit_req zone=one burst=5;
        }

建议详细参考这篇文档:https://www.nginx.com/blog/rate-limiting-nginx/ 然后详细测试再上生产。

一、限流不生效, return 的问题

如果你在location里直接返回了,然后左测右测限流都不生效,比如这样的配置:

server {
    location /search/ {
        limit_req zone=mylimit;
        
        return 200 'ok';
    }
}

那么小心了, return 是nginx直接返回,并不会进入到限流处理流程,所以限流不会生效。建议设置个root file来测试

二、速率控制

nginx限流速率的配置,无非是在rate burst delay 三个参数上配置,如何理解这三个参数,记住:

  • burst是缓冲区,请求进来先进入缓冲区,如果没设置,则可以当做缓冲区为0
  • rate是消费者,将请求以多少速度清出缓冲区,如果请求出缓冲区,则开始请求。
  • delay是控制缓冲区的请求是否其他发起。

举例1 rate=5 burst=0 delay不设置:

limit_req_zone global zone=mylimit:10m rate=5r/s;
limit_req zone=mylimit;

则:

  1. 假设1秒瞬间来了10个请求(1ms间隔)
  2. burst=0,所以没有缓冲,直接进rate消费
  3. rate=5/s=200ms/个,消费1个,剩下4个因为没到下个200ms,被丢弃,返回503

举例2 rate=5 burst=5 delay不设置:

limit_req_zone global zone=mylimit:10m rate=5r/s;
limit_req zone=mylimit burst=5;

则:

  1. 假设1秒瞬间来了10个请求(1ms间隔)
  2. burst=5,有5缓冲,5个请求进入队列,5个503
  3. rate=5/s=200ms/个,消费1个,等待200ms
  4. 200-400ms,从burst清出1个,剩下3个
  5. 400-600ms,从burst清出1个,剩下2个
  6. 600-800ms,从burst清出1个,剩下1个
  7. 800-1000ms,从burst清出1个,剩下0个

这样虽然满足了解决了瞬间来的请求被丢弃的问题,但是带来了平均时延的上升,部分请求得等到1s才开始请求,平均延时增加了非常多。

举例3 rate=5 burst=5 nodelay:

limit_req_zone global zone=mylimit:10m rate=5r/s;
limit_req zone=mylimit burst=5 nodelay;

则:

  1. 假设1秒瞬间来了10个请求(1ms间隔)
  2. burst=5,有5缓冲,5个请求进入队列,5个503
  3. delay开挂,在缓冲区直接开干了,5个队列中的直接请求
  4. 这个时候再来几个请求,依旧会503,因为队列没消下去。
  5. rate=5/s=200ms/个,消费1个,等待200ms
  6. 这个时候可以支持再来1个请求,被直接消费
  7. 200-400ms,从burst清出1个,剩下3个
  8. 400-600ms,从burst清出1个,剩下2个
  9. 600-800ms,从burst清出1个,剩下1个
  10. 800-1000ms,从burst清出1个,剩下0个
  11. 队列消完了,又可以支持瞬间来5个了。

这个配置可以支持:全局以x qps的速度,不影响时延的情况下进行限流,超过缓冲区大小的请求直接被503大部分的情况下建议用这个配置

举例4,限流时的并发保护,rate=5 burst=5 delay=2:

limit_req_zone global zone=mylimit:10m rate=5r/s;
limit_req zone=mylimit burst=5 delay=2;

则:

  1. 假设1秒瞬间来了10个请求(1ms间隔)
  2. burst=5,有5缓冲,5个请求进入队列,5个503
  3. delay开挂,在缓冲区直接开干了,2个队列中的直接请求
  4. 这个时候再来几个请求,依旧会503,因为队列没消下去。
  5. rate=5/s=200ms/个,消费1个,等待200ms
  6. 这个时候可以支持再来2个请求,被直接消费
  7. 200-400ms,从burst清出1个,剩下3个
  8. 400-600ms,从burst清出1个,剩下2个
  9. 600-800ms,从burst清出1个,剩下1个
  10. 800-1000ms,从burst清出1个,剩下0个
  11. 队列消完了,又可以支持瞬间来5个了。

这种请求虽然缓冲区还是一样,但是缓冲区不是瞬间同时请求,可以起到保护后端的作用,但是设置不好还是有时延问题。一般情况下时延有要求的应用不推荐用。

限流测试

可以用jemeter等工具来测试,结合观察access.log,我比较喜欢用vegeta

echo "GET http://slan.localhost/"|vegeta attack -duration=5s -keepalive=true -rate=0 -max-workers=5 |tee results.bin|vegeta report

限流日志

$limit_req_status 是限流状态的变量,可以放到日志log_format中(1.17.6以上版本)

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$limit_req_status" "$request_time"';
2022-06-02 18:15:41 +0800 +0800

从网络包分析性能瓶颈点

从网络包分析性能瓶颈点

在做系统性能分析的时候,压测tps上不去,怎么识别是压测机性能问题,还是接入层性能问题,还是应用性能问题,还是数据库性能问题?

无法用应用日志分析清楚的情况下,往往就需要网络层的抓包来分析堵点情况了。

拿一个经常遇到的典型场景分析, 用户->nginx->tomcat->外部服务

如图:

当性能出现瓶颈的时候,如何知道是哪里出问题了呢,一般情况下,先在backend service 服务器上抓包。

前置网络知识

tcp抓包大家应该不陌生,linux服务器上一般使用tcpdump工具,分析包有wireshake,有2个技巧,对分析海量抓包数据很有帮助。

1.快速分析网络包异常,wireshake->analyze->expert information:
上述包就存在非常严重的重传,达到7k+次。

2.怎么快速找到传输时间较长的包,通常情况下达到性能瓶颈在网络层的表现要么是有rst,重传等错误报,还有就是包的返回时间非常慢,用下面方法可以快速找到哪些数据包传输慢:

第一种情况,后端服务器本身性能瓶颈

这种情况下,在后端服务器抓包,现象是: server -> 往前端服务发包慢

上图情况是tomcat 新建连接达到瓶颈(syn ack是tcp握手的第二步,分析前端往后端服务采用了短连接),改成长连接之后问题解决。

第二种情况,前端->后端服务链路有瓶颈

通常限速,或限连接数的情况下,会看到大量重传(参考上面第一种查看方法)

还是syn ack, 大量重传:

81->前端服务器的 syn ack回包都被丢了,tcp连接建不起来,原因在前端服务器没有收到ack的包,可能是前端服务器,或者中间链路有问题

第三种情况,后端->数据库等外部服务有瓶颈

这种情况,类似第一种情况,但是方向是反过来的,会在后端服务器上看到:

  1. 后端服务器->数据库服务器 SYN发包后:
  2. 数据库服务器回syn ack慢
  3. 或者后端服务器不断在发重传报过去(数据库服务器没回应)

总结

熟悉tcp握手原理,根据wireshake rtt, 重传等分析工具,可以快速定位到全链路上的异常节点。

2022-04-17 16:48:00 +0000 UTC

间歇性DNS解析异常处理

间歇性DNS解析异常处理

故障现象:服务器内访问外部接口间歇性失败

现象是no such host, 报dns解析失败则是dns问题,从抓包看223.5.5.5的dns能返回正常,但是100的内网dns返回不正常:

分析应该是:内网dns有异常,导致没有返回正常的结果,那么怎么快速解决呢?

首先dns默认是按顺序请求的,所以将公网dns放到首位就好了,但是这个故障现场,明显不是按顺序请求的,而且随机使用的dns。查看resolve.conf的手册:options启用rotate参数会轮询使用dns服务器:

rotate       Sets RES_ROTATE in _res.options, 
            which causes round-robin selection of name servers from among those listed.  
            This has the effect of spreading the query load among all listed servers, 
            rather than having all clients try the first listed server first every time.

去掉这个参数之后,问题解决。

tips: 服务器上dns建议开启nscd服务,缓存dns记录,否则每次对外请求会都会增加一个dns请求的时延,导致性能下降。

总结

resolve.conf 中options 开启rotate参数,会让dns请求轮询走dns服务器列表。

2022-04-17 15:53:00 +0000 UTC

通用问题分析定位办法

通用问题分析定位办法

最近一年做了大量云环境的问题定位,各种层面,产品,环境,逐渐总结出了一套通用的分析问题办法。我把他叫做:"SRE通用问题解决办法"。这个方法直接说结论,很简单的一句话概述:问题的解决过程就是不断分层建模,提取信息的过程(套用柯P的话:如果你现在很难猜的时候,标准动作: collect more information)。

在云环境,因为无法知晓客户的数据、架构,并且客户的sre体系建设层次不齐,能拿到的关键信息也有限,这就让我们得在有限的信息下,做大量的推测与验证才能解决问题。通常我拿到一个问题,先建立一个顶层模型,然后基于这个模型,不断深入下层建模,把信息都挖掘出来,随着模型的完善,信息的逐步显露,问题原因经常就自己出来了。例如:

访问域名不通的诊断案例

某客户反馈域名有些地方ping有丢包,无法连接上。那么我们建立第一层模型,以及信息:

graph TD;
    网民A--不通-->域名;
    网民B--通-->域名;

信息:

  1. 验证能ping通
  2. 想办法验证ping不通,访问不通

一轮验证下来,没办法复现问题,继续和客户沟通,网民A不通的具体模型,是ping不通,还是网站访问不了,网站协议多少

graph TD;
    网民A_全国17ce多个节点--http网站打不开-->域名;
    网民B--通-->域名;

第二层精确很多,而且能验证了,验证发现,17ce正常的,都返回200,为什么客户反馈打不开呢,基于这个疑问继续问客户,后面发现打不开的内容返回是一个域名过期页面,虽然正常返回了200,于是问题定位到是客户域名过期,301到了dns服务商的过期页面。

这个案例虽然简单,也能简单说明,层层完善模型,在快速问题诊断验证上,有不错的效果。

2021-04-05 23:29:53 +0800 +0800

将个人数据中心搬到了do

将个人数据中心搬到了do

之前为了跑自己的博客,代码库,小项目等,自己搭建了一个k8s容器环境.因为人比较穷,所有没有采取大厂的托管集群方案,自己用最便宜的3台2核4G vps机器,手工搭了个环境。介绍下方案选型的心得。方案两个阶段,之前用3台vps自己搭了一个,最近看到do出了托管k8s,于是今天换了下方案。

vps厂商选择

知道比较靠谱的几家有digitalocean,vultr,linode。

厂商 优点 缺点 价格/月
digitalocean 之前没有托管k8s,现在有了,存储多20GB 访问速度,性能比不上vlutr 3x20美元
vultr 性能好,访问速度好 存储少20GB 3x20美元
linode 没试过 控制台不友好

综合对比,之前选了vultr,跑了一年多,挂过几次,总体还算稳定,但是digitalocean后面出了托管k8s,支持单节点容器,有负载均衡,有对象存储,而且价格感人,3台机器一年720美元,还是一笔非常大的开支的,所以就在今天迁移到了digitalocean,因为自己折腾的相对安全性还是不够高。如果服务器挂了就永久丢数据了,do现在支持挂永久存储,简单而且可靠性高。目前do七七八八大概25美元一个月,比之前60美元少多了。

如何自己从vps上搭建k8s,我之前参考的这里: https://github.com/hobby-kube/guide 穷人的k8s教程

自建个人数据中心的基础服务

个人的需求,博客,代码库,有时候跑一些乱七八糟的服务等,从开发到部署线上的一整套环境都有需求。

git代码库

git一开始用的比较熟悉和强大的gitlab,简直要把自己坑死了,耗费资源较大,4核8G都跑得不是很顺畅,如果用这个巨无霸,那么我的小破群就要彻底歇菜了。

后面去找了下,发现gogs好像不错,用go写的自托管git服务,功能也比较强大,后面翻了下,发现好像不怎么更新了,了解了下原委,原来gogs主要是一个人开发,后面社区实在受不了了,就分叉了一个项目gitea,gitea相对迭代速度,功能完善程度更高,于是就使用gitea作为代码服务。

gitea越用越顺,性能占用极其的低,基本功能都有(不,是要用的功能都有,不要用的也有一堆),强烈推荐自己搭建git服务的同学。

ci工具

ci对gitea支持比较好的就是drone了,虽然之前连重试功能都没有,但是短小精悍深的我心,现在有重试功能了,安装比较折腾,花了我一天一夜的时间,主要是文档不清晰,互相矛盾,要各种猜测尝试。对k8s还要比较熟,理解drone是怎么在里面跑runner的,这里就不展开了,有疑问可以email我。

对象存储

对象存储因为是穷人嘛,最好的办法是直接对接s3,但是服务器上有空闲不用挺肉疼,所以装了minio,do有对象存储storage,5美元250G存储,1T流量,支持cdn分发,还是不错的。

网站证书自动签发

k8s穷人教程里有,就是nginx ingress controller,安装certmanager

docker仓库

在容器里自己弄一个docker仓库,不用推到其他地方,速度非常快,docker仓库很简单,直接用helm安装就行了。

现状

经过今天一天的折腾,从vultr迁移到了do,因为do访问慢的原因,博客可能稍微有点慢,不过都是静态页,也没啥人访问,就不在乎了。

后面再折腾下多节点访问,提升国内访问速度,慢慢玩啦。

2019-03-10 00:02:00 +0000 UTC

webpack expose-loader exports-loader区别

webpack expose-loader exports-loader区别

按字面意思理解是很困难的,暴露导出,实际上区别很大,做下实验:

三个文件,require.html entry.js content.js

<!DOCTYPEhtml>

<htmllang="en">

<head>

<metacharset="UTF-8">

<title>testrequire</title>

<scriptsrc="bundle.js"></script>

</head>

<body>

 

</body>

</html>

entry.js:

document.write(require("./content.js"));

content.js:

module.exports="i'mthedatathatyouwanted!";

执行webpack entry.js bundle.js生成bundle.js

渲染效果如下:

很简单的一个基本环境,就引入entry,write content.js的内容。

先实验expose-loader

npm install expose-loader

修改content.js 引入一个内部变量 insideData

varinsideData="I'mhiding";

module.exports="I'mthedatathatyouwanted!";

修改entry.js,用expose导出insideData

document.write(require("expose?insideData!./content.js"));

console.log(insideData);

console.log(window.insideData);

效果如下

发生了什么? console出来的并不是我们的insideData,而是exports出来的变量。所以expose并不是暴露包内部的变量,而是把包的exports变量暴露到全局空间。参数是可以随便自定义的。

再实验exports-loader

安装exports-loader, npm install exports-loader

entry.js,跟之前差不多,不过把expose换成exports:

document.write(require("exports?insideData!./content.js"));

console.log(insideData);

console.log(window.insideData);

content.js不变,效果如下:

require返回的东西换成了insideData,而不是原先定义的内容,所以exports插件理解也很简单,就是把导出的变量换掉,不带修改作用域功能。

总结

expose插件将module的exports变量暴露到全局空间。

exports插件将module的exports变量换掉

2016-03-30 12:23:00 +0000 UTC

DNS优先查询ipv6地址导致curl延时

DNS优先查询ipv6地址导致curl延时

起因

昨天有大活动,服务器进行扩容,跑的新封装的docker镜像,系统ubuntu14,发现用curl调用微信支付接口出现延时。能正确返回结果,但一般有三秒以上的延时。由于线上服务在跑,没有机会详细调试,于是先忽略之。

复现

今天在本地复现,一个简单的tcp连接,没有其他业务操作,时间花了3秒+,肯定哪里出了问题:

故障

1和2之间花了4秒,代码只有golang的dial函数,测试完整代码如下:

package main

import (
    "net"
    "fmt"
    "log"
)

func main() {

    log.Println(1);

    conn,err:=net.Dial("tcp","api.mch.weixin.qq.com:443")

    log.Println(2);

    if err!=nil {

        fmt.Printf("error: %v",err);

    }

    log.Println(3);

   conn.Write([]byte("hello"));

   log.Printf("remote ip: %s",conn.RemoteAddr().String())

   log.Println(4);
}

反馈给微信技术

联系到昨天在阿里云的服务器上的现象,怀疑是不是微信支付服务器出现网络问题,于是发封邮件反馈之,不料马上被呛了回来,我只能说:从来没有见过如此理直气壮让我滚回去查代码的客服:

微信反馈

相比而言阿里的同学还是好多了,发工单基本能回,不是他们问题也能帮忙协助分析,给些建议。

继续分析

对这样的回复我只能表示无语,不过也只能无奈的继续分析,本来准备抓包把结果丢他们糊他们一脸,结果却发现了一些有意思的事情:

抓包

先查询A记录,然后被cname然后查询AAAA记录,AAAA记录就是ipv6版本的A记录,结果dns服务器返回了查询失败,几个来回几秒的时间就过去了。然后ipv6地址解析失败,继续解析ipv4的地址,能正常解析,访问了。

所以延时的原因应该就清楚了,curl先去解析ipv6的记录,然后再解析ipv4的记录,这样出现了延时

疑问

为什么同样是ubuntu14,我们其他的服务器没有出现,单单docker里面每次都延时?

答:其他服务器默认开启了nscd,缓存了dns查询记录,不会每次查询,所以对业务的影响就很小了。

如何解决

优先方案是开nscd缓存服务,因为不只是解析ipv6的问题,频繁解析dns有很大的时间开销。

然后换个好点的dns,优先ipv6策略越来越多的被使用,为什么影响还不大,因为即使是dns查询失败也可以很快时间的返回,试验过好几个dns,有的dnsAAAA记录即使没有,但是返回时间很短,阿里的dns就卡了数秒,可能跟dns策略有关系。

修改系统配置可能有用,有搜到/etc/gai.conf,但是试验是没效果的,有兴趣的可以研究下。参考:https://community.rackspace.com/products/f/25/t/5110

最次是写host文件,紧急情况下可以用,但一定只能作为临时救急手段。

2016-03-29 16:09:00 +0000 UTC

docker在高流量情况下出现丢包,网络超时

docker在高流量情况下出现丢包,网络超时

概要

docker在高流量情况下(频繁的进行tcp连接)可能导致网络丢包,超时等网络问题。原因是docker的NAT机制,启用了iptables进行网络中转,可能触发 nf_conntrack table爆掉的问题,导致网络丢包,超时。这本质来说不是docker问题,但是在应用docker时不得不注意这一机制。

现象

某次对一个web应用进行压测,nginx+php-fpm,典型的web环境,不过是跑在docker中。压测一开始很正常,逐步加量,到某个点出现瓶颈,无法访问504,502。系统负载正常,cpu内存占用都很低,网络带宽占用也不高。

分析过程

既然系统没到瓶颈,那么应该是某个配置问题,导致负载上不去,先看fpm的error日志,其中出现:

php_network_getaddresses: getaddrinfo failed: Name or service not known dsn:mysql:host=****;port=3306;dbname=****;charset=utf8

域名解析失败,于是写死host,再次压测,还是一样的情况,不过error中换了日志:

PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000] [2002] Connection timed

连接数据库出现超时

由此,基本怀疑是系统的网络出现问题,导致dns服务不正常,tcp连接也出现超时,查看netstat,有大量的time_wait,是不是频繁连接数据库,导致端口资源分配完了呢?

为了验证,调整了net.ipv4.tcp_tw_recycle,net.ipv4.tcp_tw_reuse等参数,time_wait是下去了,但网络还是一样不正常。

一时找不到原因,用strace跟踪了下,发现fpm中,同样的请求数,一分钟前是正常,无压力,一分钟左右后,就突然全部卡住,strace日志:

04:33:25.883957 socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 6 <0.000045>

04:33:25.884065 fcntl(6, F_GETFL)       = 0x2 (flags O_RDWR) <0.000027>

04:33:25.884202 fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000035>

04:33:25.884312 connect(6, {sa_family=AF_INET, sin_port=htons(3306), sin_addr=inet_addr("121.199.***.***")}, 16) = -1 EINPROGRESS (Operation now in progress) <0.000048>

04:33:25.884428 poll([{fd=6, events=POLLIN|POLLOUT|POLLERR|POLLHUP}], 1, 60000) = 0 (Timeout) <60.047186>

connect数据库出现60秒的超时,不能访问的原因于是就基本确定了,网络异常导致fpm连接数据库超时,堵塞了所有的fpm进程,导致后续访问完全timeout.这样cpu负载确实是不高的。出现这种情况,调整fpm的进程数是没用的,有多少会堵多少。

继续查看为什么网络出现原因,翻系统日志(/var/log/syslog),发现:

Mar 26 13:02:45 iZ23cf3dp1nZ kernel: [ 7548.212230] nf_conntrack: table full, dropping packet

Mar 26 13:02:45 iZ23cf3dp1nZ kernel: [ 7548.652083] nf_conntrack: table full, dropping packet

Mar 26 13:02:46 iZ23cf3dp1nZ kernel: [ 7549.276076] nf_conntrack: table full, dropping packet

Mar 26 13:02:46 iZ23cf3dp1nZ kernel: [ 7549.340070] nf_conntrack: table full, dropping packet

Mar 26 13:02:46 iZ23cf3dp1nZ kernel: [ 7549.340081] nf_conntrack: table full, dropping packet

Mar 26 13:02:46 iZ23cf3dp1nZ kernel: [ 7549.340089] nf_conntrack: table full, dropping packet

Mar 26 13:02:46 iZ23cf3dp1nZ kernel: [ 7549.340094] nf_conntrack: table full, dropping packet

Mar 26 13:02:47 iZ23cf3dp1nZ kernel: [ 7550.364029] net_ratelimit: 26 callbacks suppressed 

google下,发现是iptables的nf_conntrack table爆掉了,而这个一般是系统有做NAT中转服务才会用到,NAT???为什么会用到NAT???查看iptables -l恍然大悟,有host机对docker container的网络转发规则,于是定位到了原因,修改net.ipv4.netfilter.ip_conntrack_max为原来四倍,再压测一切正常,继续加量,真正的达到了系统瓶颈,cpu跑满,负载飙升。

解决方案

我是直接调整了nf_conntrack table size完事,因为我们的服务瓶颈不在网络,然而有时候不一定有效,有可能你们的流量更大,tcp连接更频繁,调整size也有极限,可能会带来内存占用很大的问题,需要权衡。

推荐google下nf_conntrack: table full, dropping packet,原因以及应对方案分析很多,不再重复。

2016-03-27 10:22:00 +0000 UTC

PHP消息队列业务解耦实践

PHP消息队列业务解耦实践

php是web开发的利器,在中小型web开发中,无人能撼动其地位。不过在大型应用中,对比java,python,由于相应生态的缺乏,在构建大型web服务的时候,经常会显得力不从心。

其中缺乏完善的消息中间件,是一个较大问题。

php作为一门偏web开发的语言,一般跑在apache,fastcgi上面,生存期从请求开始到结束,这就决定了他不太适合跑长时间运行的服务,假如需要跑一些例如发送邮件,统计分析,后计费类的业务,一般都是用crontab来定时请求。php的哲学是实用主义,够用就行,crontab在大多数情况下都能满足需求,简单的的后端处理需求,直接用crontab等解决就行,不用多想。

但是在大型web服务中,crontab就开始满足不了了,需要更高精度的业务控制,需要更高性能的异步业务处理。一般的解决方式是通过消息队列,将任务发送到后端应用中处理。前端负责从mysql,redis等取数据,展示。后端负责处理耗时较长,不需要立即返回的任务。而怎样消费消息队列,不用局限于php,python、golang、java等也可以去消费消息队列的任务,这样就实现了服务的解耦合。

php的消息队列,之前用过php-resque,这是基于redis的一款消息队列服务,phpresque被用的比较多,我们用起来也比较稳定,没出过大的坑,大多数情况下,都能很好的满足需求。后来随着业务的发展,我们把消息队列换成了nsq,原因是:

  • 单点故障:

虽然出问题的时候,非常少,但是因为没有很好的多节点机制,一旦出问题,整个系统都会挂掉,不能很快的切到备用节点。

  • 性能

后端消费者限定了php,在性能上面有一些慢。

  • 部署

部署上依赖的东西有点多,需要安装与维护redis,php拓展,配置上有点复杂

  • 拓展

消费者限定php,一些情况下,我们想用golang来处理对性能有要求的业务

nsq是一款非常优秀的消息队列服务,部署很简单,启动程序即可,无依赖,消费消息有python golang的sdk,我们想把对性能要求比较高的业务处理换成golang,所以就把消息服务替换成了nsq。目前我们的结构是这样的:

异步架构

前端是web服务器组,直接处理的数据是mysql,memcache,以及通过nsq往后端发送任务数据。

后端包括抽象的微服务,以及各种业务后处理。消息处理完成之后,结果一般会进入mysql数据库,供前端业务进行消费。

2016-02-02 16:30:00 +0000 UTC

Win10 search function not working

Win10 search function not working

the windows's firewall is a useless function for me ,so I stop and disable it in services management .

but it casue a lot of trouble .

first ,I can't install a fonts download from other place.it soved by temporary start the firewall service.

and, the Win+s shortcut not working ,even I click the search btn at taskbar ,it get stuck and respone nothing.

I have google this trouble ,there are many information but no one can solve this problem .

I have spend many times want to fix it ,finally i find the system log have many firewall error,

then I start the firewall service ,the problem goes away ,what the fuck!.......

what a amazing relationship between firewall and Search function and install fonts!

2015-09-04 20:13:00 +0000 UTC

golang 交叉编译在1.5版本变得非常简单了

golang 交叉编译在1.5版本变得非常简单了

golang1.5正式版如期发布了,带来了一大波激动人心的新东西。

其中有个比较方便的功能,就是直接支持了交叉编译,之前要用源码编译各个环境。

现在下载安装版也能直接编译了,比如我在windows上编译64位linux的命令:

env GOOS=linux GOARCH=amd64 go build

前面env是设置环境变量,GOOS是系统,可以为下面的值:

darwin freebsd linux windows

GOARCH是架构,可以是:

386 amd64 arm arm64

相比与之前实在是方便了太多太多

2015-08-21 22:56:00 +0000 UTC

hacking team 415G 被黑文件种子

hacking team 415G 被黑文件种子

没事研究下黑客是怎么黑别人可以涨下见识。

里面全是各自攻击内幕,交易细节,以及各种威力强大的0day漏洞利用工具。

实在太震撼了,不愧为国家队。看来以后不能惹黑客。

感想就是以前单打独斗的黑客已经军团化,武装组织化了,不知道这样的组织全球还有多少,国内好像也有,咳咳。个人在这种组织面前是毫无安全可言的,就算平常注意上网习惯,不装360,勤打补丁,每个网站一个密码,手机验证,证书一堆。然并卵!!!

种子文件因为之前存储在sae中,项目收费销毁就丢了,抱歉

2015-07-08 12:11:00 +0000 UTC

配置超级方便的windows命令行开发环境

配置超级方便的windows命令行开发环境

windows一向以图形界面著称,命令行环境就不怎么为人所知了,最多知道有个cmd,众所周知cmd并不好用,比linux mac下的命令行环境差一截,所以这也是经常用来说明mac的开发环境比windows好的一个证明,mac下提供了更加强大的命令行操作环境,不过windows真的这么不堪么,其实windows作为一个受众面如此之广的系统,可以用得很简单,也可以玩得非常高端。

本文的目的在教你怎么打造一套命令行环境,媲美linux,mac,并且比其他系统有更强的可定制型,方便。

先分析需求:

我想在每个我打开的目录下,直接右键打开命令行,路径在当前目录(linux,mac下基本功能)

我想提示能智能一点,能自动补全,不要cmd那傻傻的补全,要linux那种

我不要windows下傻傻的C: D: E: 切来切去,一个根目录才是正常人

我要能用git ls grep 等各种shell命令,linux下命令行已经把我惯坏了

很多人以为这样的需求,在windows下就是痴人做梦,这是linux才有的功能,windows玩不了这么高端,不是的,工具只是工具,在程序员的世界里,应该是一切皆有可能。

来看本文的方案,工具链:git(mingw) +ConEmu,来实现以上所有功能,并且还能实现在一个窗口管理多个命令行界面的功能。

conemu界面

这是conEmu的界面,开了2个命令行,在一个窗口tab栏显示,支持linux常见命令,ls grep 等等,同时还能用windows下的命令,很强大的组合。

右键菜单,在当前目录打开命令行:

conemu右键界面

这样就可以方便的在多个目录下进程命令行操作了,git 执行程序都很方便

那么下面是搭建这套环境的教程:

一、安装git

git是程序员常用的工具,打包了一个小的mingw环境,直接装mingw太麻烦了,git只有一个安装包,安装一下就行,环境变量也自动配好,下载地址:https://git-scm.com/

二、安装ConEmu

这是一个管理命令行程序的工具,可以支持自定义命令行环境,自定义配色方案等

下载地址:https://github.com/Maximus5/ConEmu

三、git和ConEmu都装好了之后,进入ConEmu 设置

conemu设置

注册右键菜单,用来在目录中右键打开环境,Menu Item是右键菜单的名称,Command是执行的命令,这里我们执行git bash环境(可以在开始菜单查询git bash 右键属性查看)的命令。然后点Register,随便在什么目录右键,你会发现可以打开了。

现在你会发现打开多个命令行,任然是在多个窗口打开,怎么在一个窗口打开呢?一样在ConEmu中配置:

conemu多窗口配置

勾上这里,就单用一个窗口打开多个命令行了。

ConEmu还有很多自定义的功能,非常强大,可以自己摸索。

2015-06-01 14:15:00 +0000 UTC

web app是一条好路

web app是一条好路

最近刷了美版google play之后,找应用更方便了,经常习惯性的去这里下国外的应用。

刷reddit比较多,想这么火的一个网站应该有app吧,这样我就可以在手机上看了,吃惊的是上play上一搜,居然没有官方app,于是只能用手机浏览器上看了,折腾来折腾去,无意间发现一个碉堡的chrome功能,添加到主屏幕 ,试了下把reddit直接放到了手机桌面。

顿时桌面出现了reddit的图标,点开之后是全屏的,没有浏览器的导航栏,没有任何浏览器的信息,就这样网页直接进入了应用级别,而且体验好到爆,不用安装,不用更新,不用担心乱七八糟的权限问题,而且reddit的网页做的很流畅,导航加载跟app一样的效果。完美

chrome这功能真心做得不错,直接解决了web app的入口问题,对于只用html5的应用,完全没有必要用hybird再打一个apk,把web app的轻量,优雅都给打没了。

web app无法取代native app,就像在pc平台上上演的一样,虽然浏览器的功能越来越强大,但是应用程序还是无法替代,但是这毫不影响网页的应用趋势。

2015-05-25 16:57:00 +0000 UTC

怎么强制使用google.com而不是google.com.hk

怎么强制使用google.com而不是google.com.hk

在墙内使用google.com 一般会被重定向到google.com.hk

有个简单的方法来禁止重定向

访问一次: http://www.google.com/ncr

带上ncr之后,google在之后就不会重定向你了

2015-05-22 12:28:00 +0000 UTC

多一点设计,少一点烦恼

多一点设计,少一点烦恼

这是团队发生的一起事故。

需求是这样的,在用户个人中心,有一个将本地的视频上传到第三方存储库的需求,需求不复杂,但是出了很多岔子。 前后经过大概三个多冲刺,时间跨度一个月,仍然有很多问题,没有发布。

验证阶段: 开发人员是进行了前期验证的,将市面上的上传控件测试了一次,并有相关的demo,然而这个demo在后来 并没有起到加快任务进度的作用,实际上,实验的demo,后端接口并不是需求相关的第三方api,开发人员并没有对 api作验证

构建阶段: 没有相关设计,直接开始编码,开发人员对整个需求,所需要做的东西不了解,没有技术方案,只凭感觉 开始了切图,编码,随后陷入了地狱,demo不能直接拿过来用,因为上传的地方并不是业务服务器,而是第三方存储。 而第三方存储也没有api,几经沟通,给出直接用iframe嵌套页面的方案,这个方案验证也不行,因为有各种跨域, 交互等问题,在多次调试,耗费时间快2周的时候,终于调出一个勉强能用的方案。不过地狱还是没有结束,仍然有 一些需求没有确认,没有解决。

需求是会变的,这是铁律,产品人员的设计会根据市场的反馈发生改变,就算市场不变,整个团队成员对产品的理解会 在不断的沉浸中加深理解,更加的适应需求,就像开发人员写一些功能的时候,经常会有重构重写的情况发生,这是 好事,能提高代码的复用性,性能等,所以需求的变更是必然,有时候也是必要的。但不断变化的需求对开发人员来说, 是一个极度讨厌的恶魔,它延误了进度,增加了成本,消磨了耐心,降低了成就。这就需要一个平衡,尽量少的需求 变更来迭代更适应需求的产品。

敏捷是一个好方法,给了开发人员一个固定的冲刺时间,无打扰,无变更,所有的需求变更都在冲刺开始前确定, 在冲刺结束之前,无反悔机会。除非结束冲刺,重新制定冲刺计划。但这条护城河不是咒语,喊一声敏捷就抑制了 变更,这对开发提出了更高的要求:主动提前确认需求,提前做好架构,验证。否则,当发现技术方案有问题, 无法实现,实现成本过大等需要变更的时候,主动打破计划的就不是产品,而是研发人员。这同样对项目是危险。

在这个失控中学到什么:

  1. 对开发人员来说,在冲刺开始之前,需要明确这次冲刺的目标是什么,我如何实现这些目标,以及验证相应 技术方案,在方案仍然有疑问之前,绝不启动冲刺计划。在前期挣扎比在后期推倒对项目的影响要小得多。
  2. 对冲刺大师来说,大师是冲刺的守护,需要保证冲刺能正常顺利进行,尽量减少冲刺的风险,所以需要和开发 人员沟通,确认所有需求,技术方案细节,然后才能放行,开始冲刺。
  3. 对团队来说,需求变更的最小代价是需求刚开始的时候,越往后成本越大,所以尽量保持目标的简短很重要, 将大的需求,分解成小的冲刺目标,就算在冲刺周期内,也要保持持续交付,持续测试,继续验证的节奏。 如有条件,可以做到每日发布,每日验证。
  4. 沟通在冲刺过程中是非常非常重要的,在冲刺遇到障碍时,大师要及时发现问题,协助成员解决,当发现项目 失控,要想办法将冲刺拉到轨道上来,如有必要,重新评估任务,确认方案细节再重新开始冲刺。
2015-04-17 15:32:00 +0000 UTC

使用tita插件quicktable对单表进行快速增删改查

使用tita插件quicktable对单表进行快速增删改查

简介

quicktable 是tita框架的辅助插件,用来快速完成对单个表进行增删改差的功能。

示例

如何对这张表money_subject 进行增删改查

[抱歉上次服务器没续费图挂了]

最终的查询界面

[抱歉上次服务器没续费图挂了]

实现上面的功能,只需要2个控制器方法,一个显示页面,一个提供数据、操作接口,另外一个view页面

控制器方法

public function actionSubject()
{
    $table=new SearchTable('money_subject');
 
    $table->set_edit_able('parent_type',true);
    $table->set_edit_able('child_type',true);
    $table->set_edit_able('trade_type',true);
    $table->set_edit_able('parent_name',true);
    $table->set_edit_able('child_name',true);
    $table->set_edit_able('desc',true);
    $table->set_edit_able('group',true);
    $table->set_edit_able('status',true);
 
    $table->set_enum('group',['kmoney'=>'现金','coupon'=>'代金券']);
    $table->set_enum('status',['0'=>'关闭','1'=>'正常']);
    $table->set_enum('trade_type',['1'=>'划入','2'=>'划出']);
 
    $this->assign('fields',$table->fields());
    $this->display();
}

quicktable 有两个类,一个SearchTable,用来设置表单的属性,参数。

还有一个是SearchField用来表示单个列的属性,上面的方法将SearchTable中所有的SearchField传递到了fields变量

增删改查ajax接口

public function actionAjaxSubject()
{
    $action = $_GET['action'];
    if (empty($action)) {
        echo json_encode(['Flag' => 101, 'status' => 'n', 'info' => '参数错误','hasError'=>true,'error'=>'参数错误']);
        return;
    }
 
    $table = new SearchTable('money_subject');
 
    if ($action == 'list') {
        $ret = $table->search_from_post();
    } elseif ($action == 'add') {
        $ret = $table->add_from_post();
        if ($ret['hasError']) {
            $ret['error'] = '添加失败';
        }
    } elseif ($action == 'modify') {
        $ret = $table->modify_from_post();
        if ($ret['hasError']) {
            $ret['error'] = '修改失败';
        }
    } elseif ($action == 'del') {
        $ret = $table->del_from_post();
    } else {
        $ret['hasError'] = true;
        $ret['error']    = '非法参数';
    }
    echo json_encode($ret);
}

这个是用来bui来请求数据的接口,基本除了表名,都可以复制粘贴

视图文件

<div class="row">
    <form id="searchForm" class="form-horizontal span24">
        <div class="row">
 
            <?php foreach ($fields as $field): ?>
                <?php if(!$field->search) continue;?>
                <div class="control-group span4">
                    <label class="control-label"><?php echo $field->name;?>:</label>
                    <div class="controls">
                        <input type="text" class="control-text" name="<?php echo $field->field;?>">
                    </div>
                </div>
            <?php endforeach;?>
            <div class="span3 offset2">
                <button  type="button" id="btnSearch" class="button button-primary">搜索</button>
            </div>
        </div>
    </form>
</div>
 
<div id="grid"></div>
 
<script type="text/javascript">
    BUI.use(['common/search','bui/calendar'],function (Search,Calendar) {
 
        var <?php foreach ($fields as $field): ?>
            <?php if(!$field->display) continue;?>
            <?php if(!empty($field->enum)):?>
            <?php echo $field->field;?>EnumObj= <?php echo json_encode($field->enum,JSON_FORCE_OBJECT);?>,
            <?php endif;?>
            <?php endforeach;?>
            editing = new BUI.Grid.Plugins.RowEditing({
                triggerCls : 'btn-edit', //触发编辑的时候不选中行
                autoSave:true
            }),
            columns = [
                <?php foreach ($fields as $field): ?>
                <?php if(!$field->display) continue;?>
                {title:'<?php echo $field->name;?>',dataIndex:'<?php echo $field->field;?>',<?php if($field->width!=0):?>
                    width:<?php echo $field->width;?>,
                    <?php endif;?><?php if($field->editAble):?>
                    <?php if(!empty($field->enum)):?>
                    editor : {xtype :'select',items : <?php echo $field->field;?>EnumObj},
                    <?php else:?>
                    editor : {xtype : 'text',rules:{required:true}},
                    <?php endif;?>
                    <?php endif;?><?php if(!empty($field->enum)):?>
                    renderer:BUI.Grid.Format.enumRenderer(<?php echo $field->field;?>EnumObj),
                    <?php endif;?><?php if($field->renderer!=''):?>
                    renderer:<?php echo $field->renderer;?>,
                    <?php endif;?>},
                <?php endforeach;?>
            ],
            store = Search.createStore('<?php echo \core\Tita::entry();?>?a=AjaxSubject&action=list&type=<?php echo $type;?>',{
                proxy : {
                    save : { //也可以是一个字符串,那么增删改,都会往那么路径提交数据,同时附加参数saveType
                        addUrl : '<?php echo \core\Tita::entry();?>?a=AjaxSubject&action=add&type=<?php echo $type;?>',
                        updateUrl : '<?php echo \core\Tita::entry();?>?a=AjaxSubject&action=modify&type=<?php echo $type;?>',
                        removeUrl : '<?php echo \core\Tita::entry();?>?a=AjaxSubject&action=del&type=<?php echo $type;?>'
                    },
                    method : 'POST'
                },
                remoteSort: true,  // 开启异步排序
                autoSync : true, //保存数据后,自动更新
                pageSize : 10
            }),
            gridCfg = Search.createGridCfg(columns,{
                forceFit: true,
                plugins : [editing,BUI.Grid.Plugins.AutoFit] // 插件形式引入多选表格
            });
 
        var  search = new Search({
                store : store,
                gridCfg : gridCfg
            }),
            grid = search.get('grid'),
            store=search.get('store');
 
 
        function addFunction(){
            var newData = {name : ''};
            store.addAt(newData,0);
            editing.edit(newData,'name'); //添加记录后,直接编辑
        }
 
        //删除操作
        function delFunction(){
            var selections = grid.getSelection();
            delItems(selections);
        }
 
        function delItems(items){
            var ids = [];
            BUI.each(items,function(item){
                ids.push(item.id);
            });
 
            if(ids.length){
                BUI.Message.Confirm('确认要删除选中的记录么?',function(){
                    store.save('remove',{ids : ids});
                },'question');
            }
        }
 
        //监听事件,删除一条记录
        grid.on('cellclick',function(ev){
            var sender = $(ev.domTarget); //点击的Dom
            if(sender.hasClass('btn-del')){
                var record = ev.record;
                delItems([record]);
            }
        });
 
        var datepicker = new Calendar.DatePicker({
            trigger:'.calendar-time',
            showTime:true,
            autoRender : true
        });
 
 
    });
 
 
</script>
</script>

这是一个bui表单搜索页面,前面的searchForm 用来搜索表中的字段

后面是表单的配置,需要该的就是增删改查对应的地址url。

主要变动的地方,就是对field的配置,需要设置字段的名次,是否搜索,是否显示,枚举数据等。

例如设置trade_type的显示,数据库里是1 2,界面里面需要显示划入划出,就在控制器里,加上配置:

$table->set_enum('trade_type',['1'=>'划入','2'=>'划出']);

完整的功能配置,可以直接查看源代码。需要配合tita框架,bui前端框架使用

2015-04-09 11:40:00 +0000 UTC

php-resque 长时间运行 redis cpu占用过高的问题

php-resque 长时间运行 redis cpu占用过高的问题

前些天,公司有台队列服务器,用的是php-resque方案,redis进程cpu占用居高不下,后来到一个核心跑满,系统负载报警多了起来。

本来以为是用这个队列服务的业务大增导致的性能问题,好在能水平拓展,加了一台服务器来一起扛。

神奇的是,新加的服务器cpu使用率在3%以内徘徊,老的服务器cpu使用率依然在50%左右,而访问已经平均分流了。

区别就是老的服务器redis服务跑了大半年了,新的是刚装的。

查看老的redis服务,发现数据居然有好几G,不太正常,因为redis只是用来做存队列数据的,队列完成之后数据应该是删除的,应该只有很小的数据,

进入redis-cli 执行keys * 发现不断跳出类似resque:job:2fee271769ffdca0715f3b45d6ec1005:status的key

这是php-resque用来保存队列任务状态的key,查看php-resque源代码

lib/Resque/Job/Status.php

/**
 * Update the status indicator for the current job with a new status.
 *
 * @param int The status of the job (see constants in Resque_Job_Status)
 */
public function update($status)
{
    if(!$this->isTracking()) {
        return;
    }
 
    $statusPacket = array(
        'status' => $status,
        'updated' => time(),
    );
    Resque::redis()->set((string)$this, json_encode($statusPacket));
 
    // Expire the status for completed jobs after 24 hours
    if(in_array($status, self::$completeStatuses)) {
        Resque::redis()->expire((string)$this, 86400);
    }
}

更新状态的方法中,设置了已经完成的key 1天以后删除

如果key真的是一天过期,那也没有什么问题,只是稍微存多一点,不过这好几g的数据,就有些异常了。

其实redis的过期删除机制是这样的:

第一种,如果有访问这个key,并且key过期,删除,我们队列的数据,程序不会去访问昨天的数据,所以这个机制没法保证我们的队列数据能删除

第二种,抽样过期,每秒十次随机查找20个key,发现过期删除,当找到大于25个key过期之后,继续执行这一过程,详细介绍见 http://redis.io/commands/expire

我们的数据量是非常大,新增非常快的,不知道第二种过期算法,在这样的数据场景下是不是有问题,结果就是运行快半年后,负载异常了。

其实在我们的业务场景中,已经完成的任务是可以直接删除的,所以将php-resque的代码做了修改,将expire直接改成del,这样就没有负载异常的问题了。

/**
 * Update the status indicator for the current job with a new status.
 *
 * @param int The status of the job (see constants in Resque_Job_Status)
 */
public function update($status)
{
    if(!$this->isTracking()) {
        return;
    }
 
    $statusPacket = array(
        'status' => $status,
        'updated' => time(),
    );
    Resque::redis()->set((string)$this, json_encode($statusPacket));
 
    // 直接删除已经完成的任务
    if(in_array($status, self::$completeStatuses)) {
        Resque::redis()->del((string)$this);
    }
}
2015-03-20 09:53:00 +0000 UTC

ripple馅饼最终挣了121块

ripple馅饼最终挣了121块

13年比特币很火的时候,很多山寨币也跟着火起来了。

当时github送ripple活动的时候,领了2000ripple币,一看市价70块。

对这种山寨币是没什么好感的,ripple没比特币那种不增发的物理保证,公司想发多少就发多少,肯定最终圈钱走人。

但是想看看这种东西最终会怎么样,70块也买不了几个鸭脖,于是留了下来没兑换人民币。

一年多过去了,偶然看到比特币的新闻,在政策的封杀下,币坚强最终还是的活了下来,还一千多块钱一个。

想到自己的ripple币,于是跑上去看看,ripple的论坛非常冷清,都没几个活人,感觉这东西应该不长远了,出乎意料的是,我的2000个币,居然还升值了,从70涨到了120,看来这种投机空间还是有的,年化70%,还是很牛的哈哈。

然后yy了自己在30块入比特币,3000块抛出的美好梦境,却被媳妇嘲笑了,就算捂了一年多,捡70%的馅饼,也没他十几天的余额宝挣得多。。。。。。。。。。。

得到的感触就是:信息真的是第一生产力,掌握更多的知识绝对有益处。

没有人不羡慕几块钱入,几千块抛出的那群人,我在接触比特币的时候是10年,试着挖了一下矿,有零点几个币,当时的价格是几十块钱,有很多送0.1个的活动。这应该是公众有小规模接触的时间点,新闻还是很火,那时候比特币有2个几百人的群,成天扯淡,我觉得吵就退了。

后来一路走高,到几百,最终到快7000的高峰,如果我60入,买100个,成本6000,保险点3000出,30w,粗算算好像人生经历一次就已经赚够,可是再想想,如果你对经济一窍不通,对货币政策毫不关注,你能相信小区门口推销保险的能给你挣多少钱回来?你会拿一个月工资去买?何况就算撞到这样的大运还是首付都不够。

在比特币的盛宴中,能挣钱的主要有三类人

  1. 撞大运,这种有,概率极小,试想经常理财买彩票,知识靠新闻的,会在比特币初期发现这个东西?
  2. 经济领域专业人士,在初期根据市场运作规律,做出专业判断,长期持有,后期利用比特币的自由市场,用经济学专业技能,自动交易等挣钱。
  3. 卖矿机的,也是有强大专业背景的人。

所以最终,在这个世界上,最重要的,不是能狠多大心,能走多大运,而是要具备别人没有的知识储备,进而拥有比别人更远的眼光。

keep learning!

2015-03-09 21:50:00 +0000 UTC

一次apache Segmentation fault (11) 崩溃问题定位

一次apache Segmentation fault (11) 崩溃问题定位

这些天出了一个很严重的问题。php接口,或者某个页面,会突然访问不了,查看apache错误日志,发现是不断的apache Segmentation fault (11),访问就会出现崩溃,之后所有的请求都会失败了。

之后运维重启apache解决,但是之后惨无人道的崩溃大幕开始了,每天晚上十点多的时候,某个站就会跑出来崩溃下。在分析最近更新内容后,发现没有明显能导致崩溃的更新或者写法。于是开发不行运维补,伟大的运维大神在连续几天晚上两三点被叫起来尿尿的情况下,终于想到了一个好办法,监控apache error日志的Segmentation fault (11) ,出现就重启,粗糙猛的办法临时解决了问题。

但是原因还是要找的,万一啥时候监控不准或者忘了加,或者掉了,那就出大事了。在这种找不到具体问题,也没法知道是更新了什么的情况下,只有分析apache的崩溃文件了。于是叫运维大神开启了apache崩溃产生core文件的配置。抓到了一堆core文件,gdb强势插入,bt观察,core文件大致分2种,

第一种崩在

崩溃1

remove_header,而且参数是 content-type ,大概估计是移除这个content-type的时候,崩溃了,看了下php代码,没有手动设置删除content-type,设置content-type倒很多,但都不是新增的,还是无法直接定位到具体的原因。

第二种是:

在销毁op_array的地方,完整堆栈: 崩溃2

注意下12-16层,是curl的调用,curl出了很多bug,于是猜测是新增了什么大量调用curl的功能,但是检查发现没有。

继续看类似几个core文件,发现除了curl,其他core文件还有mysql,file_get_contents等函数出现,然后就崩溃了,难道是阿里云网络最近不稳定,导致涉及网络调用的代码崩溃了?再仔细看看调用:

#7 0x004ea8ed in apr_pool_destroy (pool=0x9cbb920)

    at memory/unix/apr_pools.c:814

#8 0x001c06e5 in clean_child_exit (code=0) at prefork.c:218

#9 0x001c073d in just_die (sig=15) at prefork.c:344

#10

just_die (sig=15)应该是收到信号,然后apache退出,在释放资源的时候崩溃了,结合core文件生成的时候,能确定这种是产生Segmentation fault (11)后,监控脚本重启apache,然后apache进程在退出的时候,因为有网络调用的内容在跑,没处理好导致了崩溃,这种崩溃只在重启的时候才会发生,于是略过不管,真正致命的应该是第一种。

继续分析第一种core,完整堆栈信息

(gdb) bt

#0 sapi_remove_header (l=0x85e20e8, name=0xb7fc21a0 "Content-Type", len=12) at /usr/local/src/lamp/php-5.4.15/main/SAPI.c:601

#1 0x01426236 in sapi_header_add_op (op=, sapi_header=0xbff94460, tsrm_ls=0x85e2f60)

    at /usr/local/src/lamp/php-5.4.15/main/SAPI.c:650

#2 0x01426e8b in sapi_header_op (op=SAPI_HEADER_REPLACE, arg=0xbff944a0, tsrm_ls=0x85e2f60)

    at /usr/local/src/lamp/php-5.4.15/main/SAPI.c:842

#3 0x013b9415 in zif_header (ht=1, return_value=0xb7fc213c, return_value_ptr=0x0, this_ptr=0x0, return_value_used=0, tsrm_ls=0x85e2f60)

    at /usr/local/src/lamp/php-5.4.15/ext/standard/head.c:48

#4 0x014b21a4 in zend_do_fcall_common_helper_SPEC (execute_data=0xb7fb210c, tsrm_ls=0x85e2f60)

    at /usr/local/src/lamp/php-5.4.15/Zend/zend_vm_execute.h:643

#5 0x014b8d98 in execute (op_array=0xb7fb2070, tsrm_ls=0x85e2f60) at /usr/local/src/lamp/php-5.4.15/Zend/zend_vm_execute.h:410

#6 0x01480c7c in zend_execute_scripts (type=2, tsrm_ls=0x85e2f60, retval=0x0, file_count=1)

    at /usr/local/src/lamp/php-5.4.15/Zend/zend.c:1315

#7 0x015364fb in php_handler (r=0x8801058) at /usr/local/src/lamp/php-5.4.15/sapi/apache2handler/sapi_apache2.c:669

#8 0x0808541b in ap_run_handler (r=0x8801058) at config.c:168

#9 0x08088fbb in ap_invoke_handler (r=0x8801058) at config.c:432

#10 0x0809a707 in ap_process_async_request (r=0x8801058) at http_request.c:317

#11 0x0809a83d in ap_process_request (r=0x8801058) at http_request.c:363

#12 0x08097230 in ap_process_http_sync_connection (c=0x87f4b20) at http_core.c:190

#13 ap_process_http_connection (c=0x87f4b20) at http_core.c:231

#14 0x0808f32b in ap_run_process_connection (c=0x87f4b20) at connection.c:41

#15 0x00fb6c59 in child_main (child_num_arg=) at prefork.c:704

#16 0x00fb6ffd in make_child (s=0x8570d48, slot=15) at prefork.c:800

#17 0x00fb7b13 in prefork_run (_pconf=0x854c0a8, plog=0x85729a0, s=0x8570d48) at prefork.c:902

#18 0x0806ddc9 in ap_run_mpm (pconf=0x854c0a8, plog=0x85729a0, s=0x8570d48) at mpm_common.c:96

#19 0x0806886a in main (argc=139763872, argv=0x87f2940) at main.c:777

调用层比较少,说明还没跑什么就崩溃了。

这里有两个地方能看到php信息,第5层的 execute 以及第4层zend_do_fcall_common_helper_SPEC 。看下op_array中的信息:

找到了崩溃的php文件main.php,但是这是一个入口文件。基本所有功能都走这进入,要定位的范围还是很大。

继续看下zend_do_fcall_common_helper_SPEC,在gdb中,可以手动打印出函数的变量,可以查看下源码,看传递进去的变量是什么结构,然后构造个指针查看。

不过php调试有简单方法。php有个gdb调试脚本,可以较快的将php执行信息输出来,不用盯着c的结构体看细节内容,

来执行一下:source /usr/local/src/lamp/php-5.4.15/.gdbinit

然后切到zend_do_fcall_common_helper_SPEC的上下文中,命令是frame 层数 缩写是f,执行f 4

dump_bt类似bt,是php的gdb调试脚本提供的方法,能看到当前执行环境的调用层次,我们这就看到一层,在header设置content-type的时候就崩掉了。

main.php源代码如下:

date_default_timezone_set('Asia/Shanghai');
header('Content-Type: text/html;charset=utf-8');
define('APP_PATH',dirname(__FILE__)."/aae");//tita base dir
define('ROOT_PATH',dirname(__FILE__));
 
require APP_PATH . '/core/Core.php';//加载框架核心
 
$app=\core\Tita::App();
$app->run();

根据dump_bt提示,崩在16行设置header的地方,也就是这里的第二行,这比较头痛,刚到入口apache就崩了,应该不是php语句写错了导致的。这样子也不好绕过,继续往上看:

#0 sapi_remove_header (l=0x85e20e8, name=0xb7fc21a0 "Content-Type", len=12) at /usr/local/src/lamp/php-5.4.15/main/SAPI.c:601
#1 0x01426236 in sapi_header_add_op (op=, sapi_header=0xbff94460, tsrm_ls=0x85e2f60)
    at /usr/local/src/lamp/php-5.4.15/main/SAPI.c:650
#2 0x01426e8b in sapi_header_op (op=SAPI_HEADER_REPLACE, arg=0xbff944a0, tsrm_ls=0x85e2f60)
    at /usr/local/src/lamp/php-5.4.15/main/SAPI.c:842

最终是崩在remove_header,我们是设置header,为什么会remove header呢, 看源码。

sapi.c 601 行
/*
 * since zend_llist_del_element only remove one matched item once,
 * we should remove them by ourself
 */
static void sapi_remove_header(zend_llist *l, char *name, uint len) {
 sapi_header_struct *header;
 zend_llist_element *next;
 zend_llist_element *current=l->head;
 while (current) {
  header = (sapi_header_struct *)(current->data);
  next = current->next;
  if (header->header_len > len && header->header[len] == ':'
    && !strncasecmp(header->header, name, len)) {
   if (current->prev) {
    current->prev->next = next;
   } else {
    l->head = next;
   }
   if (next) {
    next->prev = current->prev;
   } else {
    l->tail = current->prev;
   }
   sapi_free_header(header);
   efree(current);
   --l->count;
  }
  current = next;
 }
}

这里对current这个结构体进行操作,l->head是个链表,在next = current->next;崩溃,当时的current是个已经损坏的zend_llist_element结构,导致继续遍历下个节点的时候崩溃了。注意这句:

zend_llist_element *current=l->head;

current最初是从l来的,幸好l还没损坏,

检查下链表指向

查看到第二个节点的时候,就发现异常了0x1d明显不像是个指针,也就是应该只有一个节点,第一个节点的next本来应该为null,这里却是一个不知道啥的值。在->next的时候就崩溃了

至此,大致情况清楚了:

我们的php程序在调用header('Content-Type: text/html;charset=utf-8');的时候,apache进入了sapi_remove_header,然后这个函数里,遍历header列表的时候,使用了一个未初始化或者已经释放或者不知道怎么被损坏的一个header链表。如何解决这个问题呢?既然我们不需要remove_header,那是不是不进入这里就可以了,再看下#1层的函数sapi_header_add_op,这应该是我们要操作的内容,设置header content-type

static void sapi_header_add_op(sapi_header_op_enum op, sapi_header_struct *sapi_header TSRMLS_DC)
{
 if (!sapi_module.header_handler ||
  (SAPI_HEADER_ADD & sapi_module.header_handler(sapi_header, op, &SG(sapi_headers) TSRMLS_CC))) {
  if (op == SAPI_HEADER_REPLACE) {
   char *colon_offset = strchr(sapi_header->header, ':');
   if (colon_offset) {
    char sav = *colon_offset;
    *colon_offset = 0;
          sapi_remove_header(&SG(sapi_headers).headers, sapi_header->header, strlen(sapi_header->header));
    *colon_offset = sav;
   }
  }
  zend_llist_add_element(&SG(sapi_headers).headers, (void *) sapi_header);
 } else {
  sapi_free_header(sapi_header);
 }
}

源代码显示op == SAPI_HEADER_REPLACE时才会进入sapi_remove_header,也就是我们传进来就是replace,看手册:

void header ( string $string [, bool $replace = true [, int $http_response_code ]] )

原来默认设置成replace,这个参数的意思是当有同名header的时候替换较旧设置的那个header,我们这因为是入口,代码保证了这个header是第一个,所以,直接设置成false,来避免进入崩溃的函数sapi_remove_header。改成:header('Content-Type: text/html;charset=utf-8',false);

虽然崩溃问题解决了,但是为啥链表会是个坏的,因为崩溃几率小,压测设置header也不崩溃,所以还无法复现。

2014-09-18 05:38:00 +0000 UTC

遭遇每天40w次攻击

遭遇每天40w次攻击

一个头像服务的网站,居然也被墙,真的很无奈

最近不知道抽什么风,墙越垒越高,sublime插件网站,stackflow这种跟敏感词毫无关系的站也经常访问不了。

前段时间,出于被墙的担心去阿里云备了案,准备把博客放到国内,却迟迟下不了决心。虽然迁回来可能看的人速度更快,更稳定了,但是自己却要付出额外的自由。且不说要挂上良民证,在备案的过程中,你必须说谎说自己做的不是博客,你必须关站,你必须上传你的照片。

出于对审查,以及随时因为被查到是博客而被取消备案的恐惧,以及心底的不情愿,备案这么久了还是没有迁到阿里云,国外虽慢,但是这地方也基本就我一个人在看吧。

有时候很愤怒,大多数时候是无奈。多希望环境越来越好,越来越自由,越来越开放,越来越尊重人。现实却是你得掌握各种莫名其妙的技能,比如翻墙。

现实越是难过,越是要心存美好,因为你要更加努力才能避开这艰难的世界,去欣赏那围不住的美好。

2014-09-13 17:46:00 +0000 UTC

写了一个项目时间倒计时工具

写了一个项目时间倒计时工具

最近迷上了GTD,一直使用doit.im管理要做的事情。

不过,执行不是很强,总感觉自己的安排和自己真正怎么做的,契合度不高。

GTD最关键的是时间安排,决定好最优先级的事情。但是在网站上排了马上就做之后,往往还是会因为各种事情,没按这个执行。

我想如果能有一个工具,让我只处理这一件最优先级的事情,并且能实时看到我的预估剩余时间,耗费了多少时间。

我可能会去反思怎么去改进事情的安排,所以我做了这个工具,如图:

[图没了见谅.....]

打开工具,设置我要做的事情,我预估要多久。然后开始计时。

这工具会跑在所有窗口上面,保证我干任何事情,都会看到我现在要干嘛。。。这样应该走神也能马上拉回来。

默认出现在右上角下来一点点。

[图没了见谅.....]

然后就开始计时了,经过一天的试用,发现实际耗费的时间,往往比预估的时间要长,这是在doit.im上没反思的事情。

too simple too young啊!

国际惯例:

github开源,地址:https://github.com/qhgongzi/out_of_time

环境需求:win7无压力运行,xp需要.net 3.5

2013-09-08 22:24:00 +0000 UTC

boost shared_ptr 使用时的一个内存泄漏注意事项

boost shared_ptr 使用时的一个内存泄漏注意事项

自从用了智能指针之后,内存泄漏这种问题就很少发生了,不过今天运行的时候,程序狂吃内存,就是不知道哪儿出了问题。

最后虽然知道是一个低级错误,不过也值得注意下。

情况是这样的,一个拥有异步函数的类。构造代码如下:

boost::shared_ptr httpClient(new client(*m_ioServ,task,respone_));
 
httpClient->send(boost::bind(&http::MessageBack,this,_1,cb,httpClient));

send是一个异步发送函数,按理说,这个函数还没发送,因为异步原因马上返回了,httpClient就释放了,为了不让他释放,也是为了保存异步回调,这个boost::bind之后的回调函数被作为一个类的成员函数给保存了起来,如下:

class client
    {
    public:
        typedef boost::function result)> ClientCallBack;
        ClientCallBack mHttpBack;
    }

这样就产生了智能指针永远无法释放的问题,类似相互引用,成员回调对象没释放,类不会释放。类不先释放,成员更不会释放。

说到底boost bind是一个对象,保存了智能指针,导致了相互引用的问题。使用时应该注意下。

最后解决办法是client创建的时候,没有使用智能指针,直接new 一个对象,然后在MessageBack里delete,如下:

 client* httpClient=new client(*m_ioServ,task,respone_);
 httpClient->send(boost::bind(&http::MessageBack,this,_1,cb,httpClient));

虽然问题解决了,但是感觉不是很对,希望有人能指教。最好是用智能指针自动管理对象。

2013-09-02 22:50:00 +0000 UTC

安装mediawiki的时候,出现Catchable fatal error

安装mediawiki的时候,出现Catchable fatal error

今天准备装个公司内部wiki,前面都一气呵成。一切安装完成之后,出现错误:

Catchable fatal error: Argument 1 passed to ScopedCallback::__construct() must be an instance of Closure, unknown given, called in /usr/local/apache/wiki/includes/cache/MessageCache.php on line 346 and defined in /usr/local/apache/wiki/includes/ScopedCallback.php on line 33

简单的看了下,意思是传递给ScopedCallback这个类的参数必须是个闭包(匿名函数)。但是给的确实未知参数。这个unknown很少见过,一般都是提示错误的类型,NULL什么的。于是看了下代码,这段是这样的:

$cache = $this->mMemc;
$isc = new ScopedCallback( function() use ( $cache, $statusKey ) {
    $cache->delete( $statusKey );
} );

看样子是给这个回调类,传递删除key的匿名函数,但是这个匿名函数却无法识别,匿名函数是5.3开始支持的,公司用的5.4,应该不是不支持匿名函数的问题,那就看看写法有没有问题,但是找了一下,这写法也对的。

测试发现公司机器上最基本的匿名函数都不支持 如:

function() { return true; }

开始从环境找原因,最后对比本机与内网机器,因为本机是可以正常运行mediawiki的,看了下,相同的匿名函数本机都能运行。

最后对比发现,本机比内网机少装了eAccelerator,多装了xdebug,查了下eAccelerator closure。发现eAccelerator在5.4下确实不兼容closure。

换APC,搞定。

2013-07-02 19:56:00 +0000 UTC

php利用命名空间管理项目模块

php利用命名空间管理项目模块

缘由

之前Tita的model结构是这样的,model目录用自动加载机制来加载用到的类,运行久了就有一个问题,这一个目录的类越来越多,很不好管理,于是就想分目录来存放相关的类,将不同类别的类文件放到不同的目录只有,自动加载越来越不好弄了,因为要遍历model下的所有目录。如果子目录还要划分小类,那更是一场灾难,于是将目光转向命名空间,虽然觉得反斜杠来当分隔符比较的2,但是相对管理那么多复杂的类,还是很激动的将命名空间支持添加到了Tita。

在这里,我们利用命名空间和php自动加载机制,较为完美的实现了模块的清晰划分,目前对效果很满意。看反斜杠也不觉得反感了。

教程

如何理解命名空间,在命名空间出现以前,所有的函数,类变量都在一个空间。命名空间出来以后,这块空间就叫做了一个特殊的全局空间,对于没有划分自己空间的代码,都放在全局空间。

如果你新建一个空间,比如叫做admin,你就能通过new admin\User();语句来实例一个user类。如果你需要另一个全局的User类,你可以之前的老方法用new User()来创建,需要注意的是,在一个命名空间内不能不带命名空间前缀去访问另外一个空间的类,即使访问全局的类也不行。

你在admin空间内,如果要使用全局的User,则需要这样来创建:new User();带上全局空间的符号。这样我们就能根据不同的模块,来划分不同的功能了。

如何定义一个命名空间

很简单,在文件最开始的地方写上,namespace xxx;那么这个文件的内容,就都被放在了xxx空间下。如果要定义xxx的子空间,则可以写上namespace xxx\abc;

在代码中使用别名

定义好的命名空间,可以使用完全的地址去访问,比如space\child_space\mid_space\class(),但是每次用这么一长条来表示类不是太麻烦了。一个还好,当一个文件中多次使用这个空间的时候,那就相当难受了。

  • 导入命名空间,跟c++差不多。use space\child_space\mid_space;跟c++不一样的是,你导入了这个空间之后,不能直接去调用这个空间的类class(),php只能将最后的空间名当作别名来导入,在这里我们来使用mid_space\class();
  • 导入空间使用自定义别名,use space\child_space\mid_space as myspace;然后可以调用myspace\class();
  • 导入类,跟导入空间差不多,use space\child_space\mid_space\class;不过类被导入了之后,可以直接使用。调用class();也可以 use space\child_space\mid_space\class as myclass;来用一个新类名来调用。例如myclass();

如何方便的管理命名空间

如果定义了命名空间,还把文件放在一个目录,那就没多大意义了,我们可以将命名空间与目录绑定到一起。让空间与目录层次上保存一直,在Tita框架中,model是数据层目录,我们新建了一个pack目录,里面放了两个包,money和props。如图:props里面有个文件,叫做Cars.php,里面放了一个类叫Cars,声明命名空间 namespace pack\props;,我们在外部调用的时候,就可以使用pack\props\Cars()。

如何自动加载命名空间中的类

命名空间不会自动去包含相应的文件,我们使用的时候,依然需要去手动,或者自动去加载相应的文件,在之前是通过autoload或者spl_autoload_register来自动加载对应的类,其实命名空间同样能用这两个机制来加载,当你访问new pack\props\Cars();的时候,传递到autoload函数的类名将是 pack\props\Cars,我们根据自己定义的目录规则,来加载这个文件

$class=str_replace('', '/', $class);
$file=APP_PATH.'/model/'.$class.'.php';
if(is_file($file))
{
    include $file;
}

代码解释下,先将pack\props\Cars,拼成APP_PATH./model/pack/props/Cars.php 然后去包含。这样我们就自动加载了命名空间中的类。

2013-06-09 19:40:00 +0000 UTC

vs2012更新update2之后编译的程序无法在xp下运行

vs2012更新update2之后编译的程序无法在xp下运行

今天发布一个更新,用户反馈在xp下无法运行。

出现提示:无法定位程序输入点InitializeCriticalSectionEx 于动态链接库 KERNEL32.dll 上

程序是MFC程序采用静态链接的方式编译的。

之前一直没问题。于是google了下,找呀找,找呀找,找到了这个:http://support.microsoft.com/kb/2835600/en

783276: A fix for ATL & MFC XP targeting is included. This fix addresses the following scenario:

In Visual Studio 2012 Update 2, you create a C++ Win32 Console Application project.

In the application wizard, you select both ATL and MFC.

On the project's Property Pages screen, you specify the following options:

Platform Toolset: v110_xp

Use of MFC: Use MFC in a Static Library

Use of ATL: Static Link to ATL

You run the application on a computer that is running Windows XP or Windows Server 2003.

In this scenario, you receive the following error message:

Application Name - Entry Point Not Found

The procedure entry point InitializeCriticalSectionEx could not be located in the dynamic link library KERNEL32.dll

原来是这群混蛋更新update2造的bug。已经在update3修复了。于是果断下载之,重新编译之后,终于xp正常打开了。

vs2012 update3 下载地址:http://www.microsoft.com/zh-cn/download/details.aspx?id=39305

在这个页面点下载,然后选择vs2012.3 RC.exe 你看到这篇文章的时候,可能已经不是RC版了,选择类似的就ok

直接点下一步,然后运行,在线安装,我4m宽带,大概十几分钟就装好了。

2013-05-13 21:22:00 +0000 UTC

mysql无法启动排错

mysql无法启动排错

上班打开虚拟机,系统是ubuntu,在连接数据库的时候发现无法连接数据库

试着启动mysql:执行service mysql start 提示:Job failed to start

在终端执行mysql命令:提示:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

继续找日志,发现/var/log下的mysql都没有日志。。

只有系统日志,打开syslog这个文件,发现:

Apr 28 10:54:22 localhost kernel: [   31.015430] init: mysql main process (901) terminated with status 1

Apr 28 10:54:22 localhost kernel: [   31.015457] init: mysql main process ended, respawning

Apr 28 10:54:23 localhost kernel: [   31.930266] init: mysql post-start process (902) terminated with status 1

Apr 28 10:54:23 localhost kernel: [   31.939001] type=1400 audit(1367117663.768:12): apparmor="STATUS" operation="profile_replace" name="/usr/sbin/mysqld" pid=949 comm="apparmor_parser"

Apr 28 10:54:24 localhost kernel: [   33.060400] init: mysql main process (953) terminated with status 1

Apr 28 10:54:24 localhost kernel: [   33.060426] init: mysql respawning too fast, stopped

只提示mysql进程无法启动。还是没办法定位问题。

在修改了几个配置,仍然无法启动之后,决定重装mysql。。

先备份数据库文件,防止数据跟表都没了。cd到/var/lib/mysql/ 这个目录下,将整个目录拷走。然后重装mysql。

悲剧的是,重装之后依然无法启动。万幸的是mysql错误日志出来了,位置在\var\log\mysql\error.log

发现以下error:

130428 11:28:55 [ERROR] /usr/sbin/mysqld: Table './mysql/db' is marked as crashed and last (automatic?) repair failed

130428 11:28:55 [ERROR] Fatal error: Can't open and lock privilege tables: Table './mysql/db' is marked as crashed and last (automatic?) repair failed

130428 11:28:55 [ERROR] /usr/sbin/mysqld: File '/var/run/mysqld/mysqld.pid' not found (Errcode: 13)

130428 11:28:55 [ERROR] /usr/sbin/mysqld: Error reading file 'UNKNOWN' (Errcode: 9)

130428 11:28:55 [ERROR] /usr/sbin/mysqld: Error on close of 'UNKNOWN' (Errcode: 9)

发现是mysql库下db这个表有问题,导致启动失败,cd 到存表文件的目录/var/lib/mysql/mysql 然后执行修复文件命令:myisamchk -of db.MYI 重新启动。终于启动成功了。

还有怎么找存放mysql数据文件的路径,可以执行 find / -name *.MYD

2013-04-28 11:53:00 +0000 UTC

差点走火入魔

差点走火入魔

= = 最近数据统计遇到瓶颈,n多游戏服务器,n多广告,然后n多数据,查得很崩溃,打开一个页面几十秒是基本的。后来想到了弄redis,然后整linux,整完编译,整完安装拓展,又开始想弄c++。。。差点走火入魔啊。mysql+redis应该能搞定的。不要把时间浪费在没必要的技术上面。嗯哼!老老实实做好东西吧。

2012-07-17 10:31:00 +0000 UTC