??xml version="1.0" encoding="utf-8" standalone="yes"?>快乐12开奖直播:BlogJava-聂永的博?/title><link>//www.ot7t.com.cn/yongboy/</link><description>记录工作/学习的点点滴滴?/description><language>zh - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2018/01/02/433000.htmlnieyongnieyongTue, 02 Jan 2018 12:53:00 GMT//www.ot7t.com.cn/yongboy/archive/2018/01/02/433000.html//www.ot7t.com.cn/yongboy/comments/433000.html//www.ot7t.com.cn/yongboy/archive/2018/01/02/433000.html#Feedback0//www.ot7t.com.cn/yongboy/comments/commentRss/433000.html//www.ot7t.com.cn/yongboy/services/trackbacks/433000.html前言

最近一段时间,要为一个手机终端APP程序从零开始设计一整套HTTP API,因为面向的用户很固定,一个新的移动端APP。目前还是项目初期,自然要求一切快速、从简,实用性为主?/p>

下面将逐一论述我们是如何设计HTTP API,虽然相对大部分人而言,没有什么新意,但对我来说很新鲜的。避免忘却,趁着空闲尽快记录下来?/p>

技术堆栈的选择

PHP嘛?团队内也没几个人熟悉?/p>

Java?好几年没有碰过了,那么复杂的解决方案,再加上团队内也没什么人?……

团队使用过Lua,基于OpenResty构建过TCP、HTTP网关等,对Lua + Nginx组合非常熟悉,能够快速的应用在线上环境。再说Lua语法小巧、简单,一个新手半天就可以基本熟悉,马上开工?/p>

看来,Nginx + Lua是目前最为适合我们的了?/p>

HTTP API,需要充分利用HTTP具体操作语义,来应对具体的业务操作方法。基于此,没有闭门造车,我们选择?//lor.sumory.com/ 这么一个小巧的框架,用于辅助HTTP API的开发开发?/p>

嗯,OpenResty + Lua + Lor,就构成了我们简单技术堆栈?/p>

HTTP API简要设?/h2>

HTTP API路径和语?/h3>

每一具体业务逻辑,直接在URL Path中体现出来。我们要的是简单快速,数据结构之间的连接关系,尽可能的去淡化。eg?/p>

/resource/video/ID

比如用户反馈这一模块,将使用下面比较固定的路径:

/user/feedback
  • GET,以用户维度查询反馈的历史列表,可分?
    • curl -X GET //localhost/user/feedback?page=1
  • POST,提交一个反?
    • curl -X POST //localhost/user/feedback -d "content=hello"
  • DELETE,删除一个或多个反馈,参数附加在URL路径中?
    • curl -X DELETE //localhost/user/feedback?id=1001
  • PUT,更新评论内?
    • curl -X PUT //localhost/user/feedback/1234 -d "content=hello2"

用户属性很多,用户昵称只是其中一个部分,因此更新昵称这一行为,HTTP?PATCH 方法可更精准的描述部分数据更新的业务需求:

/user/nickname
  • PATCH,更新用户昵称,昵称是用户属性之一,可以使用更轻量级的 PATCH 语义
    • curl -X PATCH //localhost/user/nickname -d "nickname=hello2"

嗯,同一类的资源URL虽然固定了,但HTTP Method呈现了不同的业务逻辑需求?/p>

HTTP API的访问授?/h3>

实际业务HTTP API的访问是需要授权的?/p>

传统的Access Token解决方案,有session回话机制,一般需要结合Web浏览器,需要写入到Cookie中,或生产一个JSessionID用于标识等。这针对单纯面向移动终端的HTTP API后端来讲,并没有义务去做这一的兼容,略显冗余?/p>

另外就是 OAUTH 认证了,有整套的认证方案并已工业化,很是成熟了,但对我们而言还是太重,不太适合轻量级的HTTP API,不太可能花费太多的精力去做它的运维工作?/p>

最终选择了轻量级?Json Web Token,非常紧凑,开箱即用?/p>

最佳做法是把JWT Token放在HTTP请求头部中,不至于和其它参数混淆?/p>

curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiI2NyIsInV0eXBlIjoxfQ.LjkZYriurTqIpHSMvojNZZ60J0SZHpqN3TNQeEMSPO8" -X GET //localhost/user/info

下面是一副浏览器段的一般认证流程,这与HTTP API认证大体一致:

JWT的Lua实现,推? https://github.com/SkyLothar/lua-resty-jwt.git,简单够用?/p>

JWT和Lor的结?/h3>

jwt需要和业务进行绑定,结?lor 这个API开发框架提供的中间件机制,可在业务处理之前,在合适位置进行权限拦截?/p>

  • 用户需要请求进行授权接口,比如登陆?/li>
  • 服务器端会把用户标识符,比如用户id等,存入JWT的payload负荷中,然后生成Token字符串,发给客户?/li>
  • 客户端收到JWT生成的Token字符串,在后续的请求中需要附加在HTTP请求的Header?/li>
  • 完成认证过程

不同于OAUTH,JWT协议?strong>自包?/strong>特性,决定了后端可以将很多属性信息存放在payload负荷中,其token生成之后后端可以不用存储;下次客户端发送请求时会发送给服务器端,后端获取之后,直接验证即可,验证通过,可以直接读取原先保存其中的所有属性?/p>

下面梳理一下Jwt认证和Lor的结合?/p>

  • 全局拦截,针对所有PATH,所有HTTP Method,这里处理JWT认证,若认证成功,会直接把用户id注入到当前业务处理上下文中,后面的业务可以直接读取当前用户的id?/li>
app:use(function(req, res, next)
    local token = ngx.req.get_headers()["Authorization"]
    -- 校验失败,err为错误代码,比如 400
    local payload, err = verify_jwt(token)
    if err then
        res:status(err):send("bad access token reqeust")
        return
    end

    -- 注入进当前上下文中,避免每次从token中获?    req.params.uid = payload.uid

    next()
end)
  • 针对具体路径进行设定权限拦截,较粗粒度;比如 /user 只允许已登陆授权用户访问
app:use("/user", function(req, res, next)
    if not req.params.uid then
        -- 注意,这里没有调用next()方法,请求到这里就截止了,不在匹配后面的路由
        res:status(403):send("not allowed reqeust")
    else
        next() -- 满足以上条件,那么继续匹配下一个路?    end
end)
  • 一种是较细粒度,具体到每一个API接口,因为虽然URL一致,但不同的HTTP Method有时请求权限还是有区别的
local function check_token(req, res, next)
    if not req.params.uid then
        res:status(403):send("not allowed reqeust")
    else
        next()
    end
end

local function check_master(req, res, next)
    if not req.params.uid ~= master_uid then
        res:status(403):send("not allowed reqeust")
    else
        next()
    end
end

local lor = require("lor.index")
local app = lor()

-- 声明一个group router
local user_router = lor:Router()

-- 假设查看是不需要用户权限的
user_router:get("/feedback", function(req, res, next)
end)

user_router:put("/feedback", check_token, function(req, res, next)
end)

user_router:post("/feedback", check_token, function(req, res, next)
end)

-- 只有管理员才有权限删?user_router:delete("/feedback", check_master, function(req, res, next)
end)

-- 以middleware的形式将该group router加载进来
app:use("/user", user_router())

......

app:run()

为什么没有选择GraphQL API ?/h2>

我们在上一个项目中对外提供了GraphQL API,其(在测试环境下)自身提供文输出自托管机制,再结合方便的调试客户端,确实让后端开发和前端APP开发大大降低了频繁交流的频率,节省了若干流量,但前期还是需要较多的培训投入?/p>

但在新项目中,一度想提供GraphQL API,遇到的问题如下?/p>

  • 全新的项目数据结构属性变动太频繁
  • 普遍求快,业务模型快速开发、调?/li>
  • 大家普遍对GraphQL API有些抵触,使用JSON输出格式的HTTP API是约定俗成的习惯选择

毫无疑问,以最低成本快速构建较为完整的APP功能,HTTP API + JSON格式是最为舒服的选择?/p>

虽然有些担心服务器端的输出,很多时候还是会浪费掉一些流量,客户端并不能够有效的利用返回数据的所有字段属性。但和进度以及人们已经习惯的HTTP API调用方式相比,又微乎其微了?/p>

小结

当前这一套HTTP API技术堆栈运行的还不错,希望能给有同样需要的同学提供一点点的参考价? :))

当然没有一成不变的架构模型,随着业务的逐渐发展,后面相信会有很多的变动。但这是以后的事情了,谁知道呢,后面有空再次记录吧~



nieyong 2018-01-02 20:53 发表评论
]]>
Tsung笔记之IP地址和端口限制突破篇 - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2016/08/16/431601.htmlnieyongnieyongTue, 16 Aug 2016 13:17:00 GMT//www.ot7t.com.cn/yongboy/archive/2016/08/16/431601.html//www.ot7t.com.cn/yongboy/comments/431601.html//www.ot7t.com.cn/yongboy/archive/2016/08/16/431601.html#Feedback2//www.ot7t.com.cn/yongboy/comments/commentRss/431601.html//www.ot7t.com.cn/yongboy/services/trackbacks/431601.html前言

?a href="//www.ot7t.com.cn/yongboy/archive/2016/07/26/431322.html">Tsung笔记之压测端资源限制?/a>中说到单一IP地址的服务器最多能够向外发?4K个连接,这个已算是极限了?/p>

但现在我还想继续深入一下,如何突破这个限制??/p>

如何突破限制

这部分就是要从多个方面去讨论如何如何突破限制单个IP的限制?/p>

0. Tsung支持TCP情况

在Tsung 1.6.0 中支持的TCP属性有限,全部特性如下:

protocol_options(#proto_opts{tcp_rcv_size = Rcv, tcp_snd_size = Snd,
                             tcp_reuseaddr = Reuseaddr}) ->
    [binary,
     {active, once},
     {reuseaddr, Reuseaddr},
     {recbuf, Rcv},
     {sndbuf, Snd},
     {keepalive, true} %% FIXME: should be an option
    ].

比如可以配置地址重用?/p>

<option name="tcp_reuseaddr" value="true" />

1. 增加IP地址

这是最为现实、最为方便的办法,向运维的同事多申请若干个IP地址就好。在不考虑其它因素前提下,一个IP地址可以对外建立64K个连接,多个IP就是N * 64K了。这个在Tsung中支持的很好?/p>

<client host="client_99" maxusers="120000" weight="2" cpu="8">
    <ip value="10.10.10.99"></ip>
    <ip value="10.10.10.11"></ip>
</client>

增加IP可以有多种方式:

  • 增加物理网卡方式,一个网卡绑定一个IP地址
    • 代价?/li>
  • 一个网卡上绑定多个可用的虚拟IP地址
    • 比如 ifconfig eth0:2 10.10.10.102 netmask 255.255.255.0
    • 虚拟IP必须是真实可用,否则收不到回包数?/li>

要是没有足够的可用虚拟IP地址供你使用,或许你需要关注一下后面的IP_TRANSPARENT特性描?:))

2. 考虑Linux内核新增SO_REUSEPORT端口重用特?/h4>

以被压测的一个TCP服务器为例,继续拿网络四元组说事?/p>

{SrcIp, SrcPort, TargetIp, TargetPort}
  • 线上大部分服务器所使用的系统为CentOS 6系列,所使用系统内核低于3.9
    • {SrcIp, SrcPort} 确定了本地建立一个连接的唯一性,本地地址的唯一?/li>
    • {TargetIp, TargetPort}的无法确定唯一,仅仅标识了目的地址
  • Linux Kernel 3.9 支持 SO_REUSEPORT 端口重用特?- 网络四元组中,任何一个元素值的变化都会成为一个全新的连接
    • 真正让网络四元组一起组成了一个网络连接的唯一?/li>
    • 理论上可以对外建立的连接数依赖于四个元素可变数?/li>
    • Totalconnections = NSrcIp * NSrcPort * NTargetIp * NTargetPort

线上有部分服务器安装有CentOS 7,其内核?.10.0,很自然支持端口重用特性?/p>

针对只有一个IP地址的压测端服务器而言,端口范围也就确定了,只能从目标服务器连接地址上去考虑。有两种方式?/p>

  1. 目标服务器增加多个可用IP地址,服务程序绑定指定端口即?
    • N个IP地址,可用存?64K * N
  2. 服务程序绑定多个Port,这个针对程序而言难度不大
    • 针对单个IP,监听了M个端?/li>
    • 可用建立 64K * M 个连?/li>
  3. 可用这样梳理 , Total1 ip connections = 64K * N * M

啰嗦了半天,但目前Tsung还没有打算要提供支持呢,怎么办,自己动手丰足食吧:

https://github.com/weibomobile/tsung/commit/f81288539f8e6b6546cb9e239c36f05fc3e1b874

3. 透明代理模式支持

Linux Kernel 2.6.28提供IP_TRANSPARENT特性,支持可以绑定不是本机的IP地址。这种IP地址的绑定不需要显示的配置在物理网卡、虚拟网卡上面,避免了很多手动操作的麻烦。但是需要主动指定这种配置,比如下面的C语言版本代码

int opt =1;
setsockopt(server_socket, SOL_IP, IP_TRANSPARENT, &opt, sizeof(opt));

目前在最新即将打包的1.6.1版本中提供了对TCP的支持,也需要翻译成对应的选项,以便在建立网络连接时使用:
?/p>

说明一下:
- IP_TRANSPARENT没有对应专门的宏变量,其具体值为19
- SOL_IP定义宏对应值:0
- 添加Socket选项通用格式为:{raw, Protocol, OptionNum, ValueSpec}

那么如何让透明代理模式工作呢?

3.1 启用IP_TRANSPARENT特?/h5>
<options>
    ...
    <option name="ip_transparent" value="true" />
    ...
<options>
3.2 配置可用的额外IP地址

那么这些额外的IP地址如何设置呢?

  • 可以为client元素手动添加多个可用的IP地址

    <client host="tsung_client1" maxusers="500000" weight="1">
       <ip value="10.10.10.117"/>
       <ip value="10.10.10.118"/>
       ......
       <ip value="10.10.10.127"/>
    </client>
    
  • 可以使用新增?code>iprange特?/p>

    <client host="tsung_client1" maxusers="500000" weight="1">
        <ip value="10.10.10.117"/>
      <iprange version="v4" value="10.10.10-30.1-254"/>
    </client>
    

但是需要确保:

  1. 这些IP地址目前都没有被已有服务器在使用
  2. 并且可以被正常绑定到物理/虚拟网卡上面
  3. 完全可用
3.3 配置路由规则支持

假设我们?code>tsung_client1这台压测端服务器,绑定所有额外IP地址到物理网?code>eth1上,那么需要手动添加路由规则:

ip rule add iif eth1 tab 100
ip route add local 0.0.0.0/0 dev lo tab 100

这个支持压测端绑定同一网段的可用IP地址,比如压测端IP?72.16.247.130?72.16.247.201暂时空闲的话,那我们就可以使?72.16.89.201这个IP地址用于压测。此时不要求被压测的服务器配置什么?/p>

3.4 进阶,我们使用一个新的网段专用于测试

比如 10.10.10.0 这个段的IP机房暂时没有使用,那我们专用于压测使用,这样一台服务器就有?50多个可用的IP地址了?/p>

压测端前面已经配置好了,现在需要为被压测的服务器添加路由规则,这样在响应数据包的时候能够路由到压测端:

route add -net 10.10.10.0 netmask 255.255.255.0 gw 172.16.247.130

设置完成,可以通过route -n命令查看当前所有路由规则:

?/p>

在不需要时,可以删除掉?/p>

route del -net 10.10.10.0 netmask 255.255.255.0

小结

梳理了以上所能够想到的方式,以尽可能突破单机的限制,核心还是尽可能找到足够多可用的IP地址,利用Linux内核特性支持,程序层面绑定尽可能多的IP地址,建立更多的对外连接。当然以上没有考虑类似于CPU、内存等资源限制,实际操作时,还是需要考虑这些资源的限制的?/p>

nieyong 2016-08-16 21:17 发表评论
]]>
Tsung笔记?00万用户压测执行步骤篇 - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2016/08/08/431498.htmlnieyongnieyongMon, 08 Aug 2016 13:31:00 GMT//www.ot7t.com.cn/yongboy/archive/2016/08/08/431498.html//www.ot7t.com.cn/yongboy/comments/431498.html//www.ot7t.com.cn/yongboy/archive/2016/08/08/431498.html#Feedback1//www.ot7t.com.cn/yongboy/comments/commentRss/431498.html//www.ot7t.com.cn/yongboy/services/trackbacks/431498.html前言

总是说细节、理论,会让人不胜其烦。我们使用Tsung来一?00万用户压测的吧,或许能够引起好多人的兴趣 :))

下面,我根据在公司分享的PPT《分布式百万用户压测你的业务》,贴出其中的关键部分,说明进行一?00W(?M)用户压测的执行步骤?/p>

如何做分布式百万用户的压??/h3>

假定面向小白用户,因此才有了下面可执行的10个步骤用于开展分布式百万用户?/p>

?/p>

看着步骤很多,一旦熟悉并掌握之后,中间可以省却若干?/p>

1. 阅读Tsung文

?/p>

建议大家在使用Tsung之前,花费一点时间阅读完整个用户手册,虽然是英文的,阅读起来也不复杂。读完之后,我们也就知道如何做测试了,遇到的大部分问题,也能够在里面找到答案?/p>

  • 官网?a >//tsung.erlang-projects.org/
  • 在线手册?a >//tsung.readthedocs.io/en/latest/index.html

2 确定压测目标

?/p>

  • 要对线上系统压测100万用户,为了尽可能降低线上服务器负载压力,这里设置每秒产?00个用户,将在60分钟内产生完?/li>
  • 要压测的服务器所填写网络访问地址可以根据需要填写多?/li>

3. 计算所需要从机数?/h4>

?br/> ?br/> ?br/> ?br/> ?br/> ?/p>

  • Tsung为主从模型,我们启动了主节点之后,主节点会按需启动从节?/li>
  • 设定所用服务器可用内存大于3G,并且都只有一个IP地址
  • 一台从机可用模?万用户,需?7台从?/li>
  • 若资源充足,可以少用几台服务器,配置多个IP地址
  • 找到所需要的压测用服务器,在资源层面满足测试测试集群需要,这个是关?/li>

4. 部署Tsung

?/p>

因为Tsung依赖于Erlang,因此需要首先安装:

wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
sudo yum install erlang

然后再是安装Tsung,建议直接使用Tsung 1.6.0修改版,主要提供IP只连支持(具体细节,可参考这?//www.ot7t.com.cn/yongboy/archive/2016/07/28/431354.html ):

git clone https://github.com/weibomobile/tsung-1.6.0.git
./configure --prefix=/usr/local
make install

5. 下载SSH替代者-tsung—rsh

?/p>

为什么要替换掉SSH,主要原因:

  • SSH在一般网络机房环境内服务器之间被禁止连接通信,这会导致主节点无法启动从节点,无法建立分布式压测集?/li>
  • 就算是SSH没被禁用,主从之间需要设置免秘钥SSH登录方式,十分麻?/li>

可进一步参考:Tsung笔记之分布式增强跳出SSH羁绊?/a>?/p>

6. 编写压测内容

?br/> ?br/> ?/p>

要把业务定义的所有会话内容完整的整理映射成Tsung的会话内容,因为用户行为很复杂,也需要我们想法设法去模拟?/p>

其实,演示所使用的是私有协议,可以参?Tsung笔记之插件编写篇 ?/p>

当完成压测会话内容之后,users_100w.xml文件已经填写完毕,我们可以开始压测了?/p>

7. 运行Tsung

?/p>

  • -F 10.10.10.10 主节点IP地址,IP直连特?/li>
  • -rsh rsh_client.sh 远程终端,SSH通道被替?/li>
  • -s 压测端启用erlang smp特性,按需使用所有CPU核心

我们启动了从节点,然后从节点被启动,开始执行具体压测任务了?/p>

8. 压测过程中,我们该做什?/h4>

?/p>

紧密关注服务器服务状态、资源占用等情况就对了,最好还要作为一个终端用户参与到产品体验中去?/p>

9. 压测结束,生成Tsung报表

?/p>

Tsung压测结束之后,不会主动生成压测结果报表的,需要借助?tsung_stats.pl perl脚本生成,要查阅可借助python生成临Web站点,浏览器打开即可?/p>

10. 回顾和总结

?/p>

小结

其实,一旦熟悉并掌握Tsung之后,步?-6都可以节省了,循环执行步?-10?/p>

你若以为仅仅只是谈论Tsung如何?M用户压测,那就错了,只要机器资源够,这个目标就很容易实现。我们更应该关注,我们压测的目的是什么,我们应该关注什么,这个应该形成一个完整可循环过程,驱动着系统架构健康先前发展?/p>

nieyong 2016-08-08 21:31 发表评论
]]>
Tsung笔记之插件编写篇 - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2016/07/30/431396.htmlnieyongnieyongSat, 30 Jul 2016 11:37:00 GMT//www.ot7t.com.cn/yongboy/archive/2016/07/30/431396.html//www.ot7t.com.cn/yongboy/comments/431396.html//www.ot7t.com.cn/yongboy/archive/2016/07/30/431396.html#Feedback0//www.ot7t.com.cn/yongboy/comments/commentRss/431396.html//www.ot7t.com.cn/yongboy/services/trackbacks/431396.html前言

Tsung对具体协议、通道的支持,一般以插件形式提供接口,接口不是很复杂,插件也很容易编写,支持协议多,也就不足为怪了?/p>

下面首先梳理一下当前Tsung 1.6.0所有内置插件,然后为一个名称为Qmsg的私有二进制协议编写插件, 运行Qmsg服务器端程序,执行压力测试,最后查看测试报告?/p>

已支持插件梳?/h3>

Tsung 1.6.0支持的协议很多,简单梳理一下:

Tsung Controller  Support Plugins V2-1?/p>

  • 压测的协议首先需要支持xml形式配置,配置内容需?tsung_config_protocolname 模块解析
    • 存放在tsung_controller目录?/li>
  • 其次是tsung client端也要插?ts_protocolname 模块支持数据操作
    • 存放在tsung目录?/li>
  • 同时在tsung项目examples目录下也给出了已支持协议配置简单示范xml文件

已经支持协议简单说明:

  1. amqp,Advanced Message Queuing Protocol缩写,只要支持高级消息队列协议的应用,都可以用来做压测,比如RabbitMQ,ActiveMQ?/li>
  2. http,基本协议,构建于HTTP协议之上的,还有类似于BOSH,WebDav等上层业务协?/li>
  3. jabber,也称之为XMPP,支持的相当丰富,除了TCP/SSl,还可以通过Websocekt进行传?/li>
  4. raw,针对原始类型消息,不做编解码处理,直接在TCP / UDP / SSL等传输层中传递,这个对部分私有协议,比较友好,不用写单独的编解码处理,直接透传好了
  5. shell,针对LInux/Unix终端命令调用进行压测,这种场景比较小?/li>
  6. fs,filesystem缩写,针对文件系统的读写性能进行压测
  7. job,针对任务调度程序进行的压测,比如PBS/torqueLSF、OAR?/li>

Tsung插件工作机制

粗一点来看Tsung插件的工作流程(点击可以看大图)?/p>

tsung_qmsg_flo?/a>

放大一些(引用 hncscwc 博客图片,相当赞!)?/p>

为什么要编写插件

Tsung针对通用协议有支持,若是私有或不那么通用的协议,就不会有专门的插件支持了,那么可选的有两条路子:

  • 使用raw模式发送原始消息,需要自行组?/li>
  • 自己编写插件,灵活处理编解码

既然谈到了插件,我们也编写一个插件也体验一下编写插件的过程?/p>

Qmsg协议定义

假设一个虚拟场景,打造一个新的协议Qmsg,二进制格式组成?/p>

qmsg_protoco?/p>

这种随意假象出来的格式,不妨称作?strong>qmsg(Q可爱形式的message)协议,仅作为Demo演示而存在。简单场景:

  • 用户发言,包含用户id和发言内容
    • User ID?2位自然数类型
    • 发言为文字内容,字符串形式,长度不固?/li>
    • 组装后的请求体为二进制协议格?/li>
    • PocketLen:**##UserId + UserComment##**
  • 服务器端返回用户ID和一个幸运数?32位表?
    • PocketLen:**##UserId + RandomCode##**

为了卡哇伊一些,多了一些点缀的?*####**”符号?/p>

编写一个完整插?/h3>

这里基于Tsung 1.6.0版本构建一个Qmsg插件,假定你懂一些Erlang代码,以及熟悉Tsung一些基本概念?/p>

0. 创建一个项?/h4>

要创建Tsung的一个Qmsg插件项目,虽没有固定规范,但按照已有格式组织好代码层级也是有必要的?/p>

├── include
│ ?└── ts_qmsg.hrl
├── src
│ ?├── tsung
│ ?│ ?└── ts_qmsg.erl
│ ?└── tsung_controller
│ ?    └── ts_config_qmsg.erl
└── tsung-1.0.dtd

1. 创建配置文件

Tsung的压测以xml文件驱动,因此需要界定一个Qmsg插件形式的完整会话的XML呈现,比如:

<session probability="100" name="qmsg-demo" type="ts_qmsg">
    <request>
      <qmsg uid="1001">Hello Tsung Plugin</qmsg>
    </request>

    <request>
      <qmsg uid="1002">This is a Tsung Plugin</qmsg>
    </request>
</session>
  • ts_qmsg,会话类型所依赖协议模拟客户端实?/li>
  • <qmsg uid="Number">Text</qmsg> 定义了qmsg会话可配置形式,内嵌?code>request元素?/li>
  • uid为属?/li>

此时,你若直接在xml文件中编辑,会遇到校验错误?/p>

2. 更新DTD文件

Tsung的xml文件依赖tsung-1.0.dtd文件进行校验配置是否有误,需要做对DTD文件做修改,以支持所添加新的协议?/p>

?code>tsung-1.0.dtd项目中,最小支持:

  1. session元素type属性中添加?ts_qmsg
  2. request元素处添?qmsg : <!ELEMENT request ( match*, dyn_variable*, ( http | jabber | raw | pgsql | ldap | mysql |fs | shell | job | websocket | amqp | mqtt | qmsg) )>
  3. 添加qmsg元素定义?/li>
<!ELEMENT qmsg (#PCDATA) >
<!ATTLIST qmsg
    uid         CDATA   "0"
    ack         (local | no_ack | parse) #REQUIRED
    >

完整内容,可参?code>tsung_plugin_demo/tsung-1.0.dtd文件?/p>

3. 头文?include/ts_qmsg.hrl

头文?code>include/ts_qmsg.hrl定义数据保存的结构(也称之为记录/record):

-record(qmsg_request, {
          uid,
          data
         }).

-record(qmsg_dyndata, {
          none
         }
       ).
  1. qmsg_request: 存储从xml文件解析的qmsg请求数据,用于生成压力请?/li>
  2. qmsg_dyndata: 存储动态参数(当前暂未使用到)

4. XML文件解析

ts_config_qmsg.erl文件,用于解析和协议Qmsg关联的配置:
- 只需要实?code>parse_config/2唯一方法
- 解析xml文件中所配置Qmsg协议请求相关配置
- ?code>ts_config:parse/1在遇到Qmsg协议配置时调?/p>

备注?/p>

  1. 若要支持动态替换,需要的字段以字符串形式读和存储

5. ts_qmsg.erl

ts_qmsg.erl模块主要提供Qmsg协议的编解码的完整动? 以及当前协议界定下的用户会话属性设定?/p>

首先需要实现接?code>ts_plugin规范定义的所有需要函数,定义了参数值和返回值?/p>

-behavior(ts_plugin).

...

-export([add_dynparams/4,
         get_message/2,
         session_defaults/0,
         subst/2,
         parse/2,
         parse_bidi/2,
         dump/2,
         parse_config/2,
         decode_buffer/2,
         new_session/0]).

相对来说,核心为协议的编解码功能?/p>

  • get_message/2,构造请求数据,编码成二进制,上?code>ts_client模块通过Socket连接发送给目标服务?/li>
  • parse/2?当对响应作出校验?从原始Socket上返回的数据进行解码,取出协议定义业务内?/li>

这部分代码可以参?tsung_plugin_demo/src/tsung/ts_client.erl 文件?/p>

6. 如何编译

虽然理论上可以单独编,生成的beam文件直接拷贝到已经安装的tsung对应目录下面,但实际上插件编写过程中要依赖多个tsung的hrl文件,这造成了依赖路径问题。采用直接和tsung打包一起部署,实际操作上有些麻烦,

为了节省体力,使用一个shell脚本 - build_plugin.sh,方便快速编译、部署:

# !/bin/bash

cp tsung-1.0.dtd $1/
cp include/ts_qmsg.hrl $1/include/
cp src/tsung_controller/ts_config_qmsg.erl $1/src/tsung_controller/
cp src/tsung/ts_qmsg.erl $1/src/tsung/

cd $1/
make uninstall
./configure --prefix=/usr/local
make install

这里指定安装Tsung的指定目录为/usr/local,可以根据需要修?/p>

需要提前准备好tsung-1.6.0目录?/p>

wget //tsung.erlang-projects.org/dist/tsung-1.6.0.tar.gz
tar xf tsung-1.6.0.tar.gz

在编译Qmsg插件脚本? 指定一下tsung-1.6.0解压后的路径即可?/p>

sh build_plugin.sh /your_path/tsung-1.6.0

后面嘛,就等着自动编译和安装呗?/p>

启动Qmsg协议的压?/h3>

1. 首先启动Qmsg服务器端程序

既然有压测端,就需要一个Qmsg协议处理的后端程?code>qmsg_server.erl,用于接收客户端请求,获得用户ID值之后,生成一个随机数字,组装成二进制协议,然后发给客户端,这就是全部功能?/p>

这个程序,简单一个文件,?tsung_plugin_demo目录下面,编译运? 默认监听5678端口?/p>

erlc qmsg_server.erl && erl -s qmsg_server start

另外,还提供了一个手动调用接口,方便在Erlang Shell端调试:

%% 下面?qmsg_server:sendmsg(1001, "这里是用户发言").

启动之后,监听地址 *: 5678

源码见:tsung_plugin_demo/qmsg_server.erl

2. 编写Qmsg压测XML配置文件

因为是演示示范,一台Linxu主机上就可以进行了:

  • 连接本机?127.0.0.1:5678
  • 最多产?0个用户,每秒产生1个,压力负载设置的很?/li>
  • 两个不同类型会话,比?0% + 90% = 100%
  • qmsg-subst-example会话使用了用户ID个和用户发言内容自动生成机制
<tsung loglevel="debug" dumptraffic="false" version="1.0">
  <clients>
    <client host="localhost" use_controller_vm="true"/>
  </clients>

  <servers>
    <server host="127.0.0.1" port="5678" type="tcp"/>
  </servers>

  <load>
    <arrivalphase phase="1" duration="1" unit="minute">
      <users maxnumber="10" interarrival="1" unit="second"/>
    </arrivalphase>
  </load>

  <sessions>
    <session probability="10" name="qmsg-example" type="ts_qmsg">
      <request>
        <qmsg uid="1001" ack="parse">Hello Tsung Plugin Qmsg!</qmsg>
      </request>
    </session>
    <session probability="90" name="qmsg-subst-example" type="ts_qmsg">
      <setdynvars sourcetype="random_number" start="3" end="32">
        <var name="random_uid"/>
      </setdynvars>
      <setdynvars sourcetype="random_string" length="13">
        <var name="random_txt"/>
      </setdynvars>
      <request subst="true">
        <qmsg uid="%%_random_uid%%" ack="parse">Haha : %%_random_txt%%</qmsg>
      </request>
      <thinktime value="6"/>
      <request subst="true">
        <qmsg uid="%%_random_uid%%" ack="parse">This is a Tsung Plugin</qmsg>
      </request>
    </session>
  </sessions>
</tsung>

这部分内容,请参?tsung_plugin_demo/tsung_qmsg.xml 文件?/p>

3. 执行压力测试

当Qmsg的压力测试配置文件写好之后,可以开始执行压力测试了?/p>

tsung -f tsung_qmsg.xml start

其输出:

tarting Tsung
Log directory is: /root/.tsung/log/20160621-1334
[os_mon] memory supervisor port (memsup): Erlang has closed
[os_mon] cpu supervisor port (cpu_sup): Erlang has closed

其中, 其日志为?code>/root/.tsung/log/20160621-1334?/p>

4. 查看压测报告

进入其生成压测日志目录,然后生成报表,查看压测结果哈?/p>

cd /root/.tsung/log/20160621-1334

/usr/local/lib/tsung/bin/tsung_stats.pl

echo "open your browser (URL: //IP:8000/report.html) and vist the report now :))"
/usr/bin/python -m SimpleHTTPServer

嗯,打开你的浏览器,输出所在服务器的IP地址,就可以看到压测结果了?/p>

小结

以上代码已经放入github仓库?a >https://github.com/weibomobile/tsung_plugin_demo?/p>

实际业务的私有协议内容要比上面Demo出来的Qmsg复杂的多,但其私有协议插件编写,如上面所述几个步骤,按照规范编写,单机测试,然后延伸到分布式集群,完整流程都是一致的?/p>

嗯,搞定了插件,就可以对系统愉快地进行压测了 :))



nieyong 2016-07-30 19:37 发表评论
]]>
Tsung笔记之监控数据收集篇 - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2016/07/29/431367.htmlnieyongnieyongFri, 29 Jul 2016 00:49:00 GMT//www.ot7t.com.cn/yongboy/archive/2016/07/29/431367.html//www.ot7t.com.cn/yongboy/comments/431367.html//www.ot7t.com.cn/yongboy/archive/2016/07/29/431367.html#Feedback0//www.ot7t.com.cn/yongboy/comments/commentRss/431367.html//www.ot7t.com.cn/yongboy/services/trackbacks/431367.html前言

压力测试和监控分不开,监控能够记录压测过程中状态,方便问题跟踪、定位。本篇我们将讨论对压测客户端tsung client的监控,以及对被压测服务器的资源占用监控等。同时,也涉及到Tsung运行时的实时诊断方式,这也是对Tsung一些运行时状态的主动监控?/p>

压测客户端的监控

压测端(指的是tsung client)会收集每一个具体模拟终端用户(即ts_client模块)行为数据,发送给主节点(tsung_controller),供后面统计分析使用?/p>

tsung_monitor_client?/p>

  1. ts_client模块调用ts_mon,而ts_mon又直接调用ts_mon_cache,有些绕,不直观(逻辑层面可忽略掉ts_mon?/li>
  2. count为计数器,sum表示各项累加值,sample和sample_counter计算一次统计项的平均?amp;标准?/li>
  3. tsung.dump文件一般不会创?amp;写入,除非你在tsung.xml文件中指定需要dump属性为true,压测数据量大时这个会影响性能
  4. match.log仅仅针对HTTP请求,默认不会写入,除非在HTTP压测指定

        <http url="/" method="GET" version="1.1"/> 
        <match do=’log?when=’match?name=’http_match_200ok?gt;200OK</match> 
    
  5. 从节点tsung client所记录日志、需要dump的请?响应数据,都会交由tsung_controller处理

  6. ts_mon_cache,接收到数据统计内存计算,每500毫秒周期分发给后续模块,起到缓冲作用

  7. ts_stats_mon模块接收数据进行内存计算,结果写入由ts_mon触发

  8. ts_mon负责统计数据最?0秒定时写入各项统计数据到tsung.log文件,非实时,可避免磁盘IO开销过大问题

    • tsung/src/tsung_controller/tsung_controller.app.in 对应 {dumpstats_interval, 10000}
    • 可以在运行时修改
  9. tsung.log文件汇集了客户端连接、请求、完整会话、页面以及每一项的sum操作统计的完整记录,后续perl脚本报表分析基于?/p>

  10. ts_mon模块处理tsung.log的最核心模块,全局唯一进程,标识为{global, ts_mon}

比如某次单机50万用户压测tsung.log日志片段?/p>

# stats: dump at 1467620663
stats: users 7215 7215
stats: {freemem,"os_mon@yhg162"} 1 11212.35546875 0.0 11406.32421875 11212.35546875 11346.37109375 2
stats: {load,"tsung_controller@10.10.10.10"} 1 0.0 0.0 0.01171875 0.0 0.01171875 2                                                                                 17,1          Top
stats: {load,"os_mon@yhg162"} 1 2.3203125 0.0 3.96875 0.9609375 2.7558736313868613 411
stats: {recvpackets,"os_mon@yhg162"} 1 5874.0 0.0 604484 5874 319260.6024390246 410
stats: {sentpackets,"os_mon@yhg162"} 1 8134.0 0.0 593421 8134 293347.0707317074 410
stats: {cpu,"os_mon@yhg162"} 1 7.806645016237821 0.0 76.07377357701476 7.806645016237821 48.0447587419309 411
stats: {recvpackets,"tsung_controller@10.10.10.10"} 1 4164.0 0.0 45938 4164 24914.798543689314 412
stats: {sentpackets,"tsung_controller@10.10.10.10"} 1 4182.0 0.0 39888 4182 22939.191747572815 412
stats: {cpu,"tsung_controller@10.10.10.10"} 1 0.575191730576859 0.0 6.217097016796189 0.575191730576859 2.436491628709831 413
stats: session 137 2435928.551725737 197.4558174045777 2456320.3908691406 2435462.9838867188 2436053.875557659 499863
stats: users_count 0 500000
stats: finish_users_count 137 500000
stats: connect 0 0 0 1004.4912109375 0.278076171875 1.480528250488281 500000
stats: page 139 12.500138756182556 1.1243565417115737 2684.760009765625 0.43115234375 16.094989098940804 30499861
stats: request 139 12.500138756182556 1.1243565417115737 2684.760009765625 0.43115234375 16.094989098940804 30499861
stats: size_rcv 3336 3386044720
stats: size_sent 26132 6544251843
stats: connected -139 0
stats: error_connect_timeout 0 11

tsung.log日志文件可由tsung_stats.pl脚本提取、分析、整理成报表展示,其报表的一个摘要截图:

?/p>

异常行为的收?/h4>

当模拟终端遇到网络连接超时、地址不可达等异常事件时,最终也会发给主节点的ts_mon模块,保存到tsung.log文件中?/p>

这种异常记录,关键词前缀?**error_**?/p>

  • 比如ts_client模块遇到连接超时会汇?code>error_connect_timeout错误
  • 系统的可用端口不够用时(创建与压测服务器连接数超出可用段限制)上?code>error_connect_eaddrinuse错误

Errors报表好比客户端出现问题晴雨表,再加上tsung输出log日志文件,很清楚的呈现压测过程中出现的问题汇集,方便问题快速定位?/p>

?/p>

被压测服务器的监?/h3>

当前tsung提供?种方式进行监控目标服务器资源占用情况?/p>

  • erlang
  • snmp
  • Munin

大致交互功能,粗略使用一张图表示?/p>

tsung_server_monito?/p>

  • tsung_controller主节点会被强制启用监?/li>
  • SNMP方式,客户端作为代理主动注册并连接开放SNMP的服务器,SNMP安装针对新手来说比较复杂
  • Munin采用C/S模式,自身要作为客户端连接被压测服务器上能够安装Munin Server
  • erlang方式,本身代理形式监控服务器资源占用,满足条件很简单:
    • 需要能够自动登录连?/li>
    • 并且安装有Erlang运行时环境,tsung_controller方便启动监控节点
    • 采用远程加载方式业务代码,省去被监控端部署的麻烦
    • 现实情况下,我一般采用一个脚本搞定自动部署监控部署客户端,自动打包可移植的Erlang,简单绿色,部署方便
  • 提供监控采样数据包括 CPU/Memory/Load/Socket Sent&Recv
  • 所有监控数据都会被发送给ts_mon模块,并定时写入到tsung.log文件?/li>

看一个最终报表部分呈现吧?/p>

?/p>

tsung对服务器监控采样手机数据不是很丰富,因为它面向的更为通用的监控需求?/p>

更深层次、更细粒度资源监控,就需要自行采集、自行分析了,一般在商业产品在这方面会有更明确需求?/p>

日志收集

和前面讲到的终端行为数据采集和服务器端资源监控行为类似,tsung运行过程中所产生日志被存储到主节点?/p>

tsung使用error_logger记录日志,主节点tsung_controller启动之后,会并发启动tsung client从节点,换句话来说tsung client从节点是由主节点tsung_controller创建,这个特性决定了tsung client从节点使用error_logger记录的日志都会被重定向到主节点tsung_controller所在服务器上,这个是由Erlang自身独特机制决定?/p>

因此,你在主节点log目录下能够看到具体的日志输出文件,也就水到渠成了。因为Erlang天生分布式基因,从节点error_logger日志输出透明重定向到主节点,不费吹灰之力。这在其他语言看来,确实完全不可能轻易实现的?/p>

基于error_logger包装日志记录,需要一个步骤:

  1. 设置输出到文件系统中 error_logger:tty(false)
  2. 设定输出的文件目?error_logger:logfile({open, LogFile})
  3. 包装日志输出接口 ?DEBUG/?DEBUGF/?LOG/?LOGF/
  4. 最终调用包装的error_logger接口
debug(From, Message, Args, Level) ->
    Debug_level = ?config(debug_level),
    if
        Level =< Debug_level ->
            error_logger:info_msg("~20s:(~p:~p) "++ Message,
                                  [From, Level, self()] ++ Args);
        true ->
            nodebug
    end.

和大部分日志框架设定的日志等级一致,emergency > critical > error > warning > notice (default) > info > debug,从左到右,依次递减?/p>

需要注意事项,error_logger语义为记录错误日志,只适用于真正的异常情况,并不期望过多的消息量的处理?

若当一般业务调试类型日志量过多时,不但耗费了大量内存,网络/磁盘写入速度跟不上生产速度时,会导致进程堵塞,严重会拖累整个应用僵死,因此需要在tsung.xml文件中设置至少info级别,至少默认的notice就很合适?/p>

Tsung运行时诊?监控

Tsung在运行时,我们可以remote shell方式连接登录进去?/p>

为了连接方便,我写了一个脚?connect_tsung.sh,只需要传入tsung节点名称即可?/p>

# !/bin/bash
## 访问远程Tsung节点 sh connect\_tsung.sh tsung\_controller@10.10.10.10

HOST=`ifconfig | grep "inet " | grep -v "127.0.0.1" | head -1 | awk '{print $2}' | cut -d / -f 1`
if [ -z $HOST ]; then
    HOST = "127.0.0.1"
fi
erl -name tmp\_$RANDOM@$HOST -setcookie tsung -remsh $1

需要安装有Erlang运行时环境支?/p>

当然,要向运行脚本,你得知道Tsung所有节点名称?/p>

如何获得tsung节点名称

其实有两种方式获得Tsung节点名称?/p>

  • 直接连接tsung_controller节点获得
    • 若是IP形式?code>sh connect_tsung.sh tsung_controller@10.10.10.10
    • 若是hostname形式,可以这样:sh connect_tsung.sh tsung_controller@tsung_master_hostname
    • 成功进入之后,输?nodes(). 可以获得完整tsung client节点列表
  • 启动tsung时生成日志所在目录,可以看到类似日志文件?
    • tsung client端产生日志单独存放,格式?code>节点名称.log
    • eg: tsung15@10.10.10.113.log,那么节点名称为tsung15@10.10.10.113
    • 可以直接连接?code>sh connect_tsung.sh tsung15@10.10.10.ll3

如何诊断/监控Tsung运行?/h4>

其实,这里仅仅针对使用Erlang并且对Tsung感兴趣的同学,你都能够进来了,那么如何进行查看、调试运行时tsung系统运行情况,那么就很简单了。推荐使?recon 库,包括内存占用,函数运行堆栈,CPU资源分配等,一目了然?/p>

若问,tsung启动时如何添加recon依赖,也不复杂:

  1. 每一个运行tsung的服务器拷贝已经编译完成的recon项目到指定目?/li>
  2. tsung_controller主节点启动时,指定recon依赖库位?/p>

    tsung -X /Your_Save_Path/recon/ebin/ ...

说一个用例,修改监控数据?0秒写入tsung.log文件时间间隔值,10秒修改为5秒:

application:set_env(tsung_controller, dumpstats_interval, 5000).

执行之后,会立刻生效?/p>

小结

总结了Tsung主从监控,以及服务器端监控部分,以及运行时监控等。提供的被压测服务器监控功能很粗,仅收集CPU、内存、负载、接收数据等类型峰值,具有一般参考意义。但基于Tsung构建的、或类似商业产品,一般会有提供专门数据收集服务器,但对于开源的应用而言,需要兼顾通用需求,也是能够理解的?/p>

nieyong 2016-07-29 08:49 发表评论
]]>
Tsung笔记之IP直连支持?/title><link>//www.ot7t.com.cn/yongboy/archive/2016/07/28/431354.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Thu, 28 Jul 2016 00:37:00 GMT</pubDate><guid>//www.ot7t.com.cn/yongboy/archive/2016/07/28/431354.html</guid><wfw:comment>//www.ot7t.com.cn/yongboy/comments/431354.html</wfw:comment><comments>//www.ot7t.com.cn/yongboy/archive/2016/07/28/431354.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>//www.ot7t.com.cn/yongboy/comments/commentRss/431354.html</wfw:commentRss><trackback:ping>//www.ot7t.com.cn/yongboy/services/trackbacks/431354.html</trackback:ping><description><![CDATA[<h3 id="toc_0">前言</h3> <p>前面说到设计一个小型的C/S类型远程终端套件以替换SSH,并且已经应用到线上。这个问题,其实不是Tsung自身的问题,是外部连接依赖问题?/p> <p>Tsung在启动分布式压测时,主节?code>tsung_controller</code>要连接的从机必须要填写主机名,主机名没有内网DNS服务器支持解析的情况?我所经历互联网公司很少有提供支持?,只好费劲在<code>/etc/hosts</code>文件中填写主机名称和IP地址的映射关系,颇为麻烦,尤其是要添加一批新的压测从机或从机变动频率较大时?/p> <p>那么如何解决这些问题呢,让tsung在复杂的机房内网环境下,完全基于IP进行直连,这将是本文所讨论的内容?/p> <h3 id="toc_1">预备知识</h3> <h4 id="toc_2">完全限定域名</h4> <p>完全限定域名,缩写为FQDN (fully qualified domain name)?a >赛门铁克给出的中文定?/a>?/p> <blockquote> <p>一种用于指定计算机在域层次结构中确切位置的明确域名?br/> 一台特定计算机或主机的完整 Internet 域名。FQDN 包括两部分:主机名和域名。例?mycomputer.mydomain.com?br/> 一种包含主机名和域名(包括顶级域)?URL。例如,www.symantec.com 是完全限定域名。其?www 是主机,symantec 是二级域?com 是顶级域。FQDN 总是以主机名开始且以顶级域名结束,因此 <a >www.sesa.symantec.com</a> 也是一?FQDN?/p> </blockquote> <p>若机器主机名为内网域名形式,并且支持DNS解析,方便其它服务器可通过该主机名直接找到对应IP地址,能?<code>ping -c 3 机器域名</code> 通,那么机器之间能够容易找到对方?/p> <p>服务器hostname的命名,若不是域名形式,简短名称形式,比如“yk_mobile_dianxin_001”,一般内网的DNS服务器不支持解析,机器之间需要互相在/etc/hosts文件建立彼此IP地址映射关系才能够互相感知对方?/p> <h4 id="toc_3">Erlang节点名称的规?/h4> <p>因为Tsung使用Erlang编写,Erlang关于节点启动名称规定,也是Tsung需要面对的问题?/p> <p>Erlang节点名称一般需要遵循两种格式:</p> <ol> <li class="yibqv">一般名称(也称之为短名称)形式,不包含?”字符,比如 <code>erl -name tsun_node</code></li> <li class="yibqv">完全限定域名形式 <ul class="yibqv"> <li class="yibqv">域名形式,比?code>erl -name tsun_node.youdomain.com</code></li> <li class="yibqv">IP形式,比?code>erl -name 10.10.10.103</code></li> </ul></li> </ol> <p>Tsung处理方式?/p> <ul class="yibqv"> <li class="yibqv">若非特别指定,一般默认为短名称形?/li> <li class="yibqv">启动时可以通过<code>-F</code>参数指定使用完全限定域名形式</li> </ul> <h4 id="toc_4">获得IP地址</h4> <p>主机名称无论是完全限定域名形式,还是简单的短名称形式,当别的主机需要通过主机名访问时,系统层面需要通过DNS系统解析成IP地址才能够进行网络连接。当内网DNS能够解析出来IP来,没有什么担心的;(短名称)解析不出来时,多半会通过写入到系统的 <code>/etc/hosts</code> 文件中,这样也能够解析成功?/p> <p>一般机房内网环境,主机名称大都是短名称形式,若需分布式,每一个主机之间都要能够互相联通,最经济做法就是直接使用IP地址,可避免写入大量映射?hosts 文件中,也会避免一些隐患?/p> <h3 id="toc_5">主节点启动增加IP支持</h3> <p>默认情况下,Tsung Master主节点名称类似于<code>tsung_controller@主机?/code>?/p> <ul class="yibqv"> <li class="yibqv">节点名称前缀默认为:<code>tsung_controller</code> (除非在tsung启动时通过<code>-i</code>指定前缀?/li> <li class="yibqv">一般主机名都是字符串形式(<code>hostname</code>命令可设置主机名?/li> <li class="yibqv">可将主机名称设置为本机IP,但不符合人类认知惯?/li> </ul> <p>既然Tsung主节点默认对IP节点名称支持不够,改造一?code>tsung/tsung.sh.in</code>脚本?/p> <p>Tsung启动?code>-F</code>参数为指定使?strong>完全限定域名(FQDN)</strong>形式,不支持携带参数。若要直接传递IP地址,类似于?/p> <blockquote> <p>-F Your_IP</p> </blockquote> <p>修改<code>tsung.sh.in</code>,可以传递IP地址,手动组装节点名称:</p> <pre><code class="language-bash">F) NAMETYPE="-name" SERVER_IP=$OPTARG if [ "$SERVER_IP" != "" ]; then CONTROLLER_EXTENDS="@$SERVER_IP" fi ;; </code></pre> <p>修改不复杂,更多细节请参考:<a >https://github.com/weibomobile/tsung/blob/master/tsung.sh.in</a></p> <p>启动Tsung时,指定本地IP?/p> <pre><code class="language-bash">tsung -F 10.10.10.10 -f tsung.xml start </code></pre> <p>tsung_controller目前节点名称已经变为?/p> <blockquote> <p>-name tsung_controller@10.10.10.10</p> </blockquote> <p>嗯,目标达成?/p> <h3 id="toc_6">从节点主机增加IP配置</h3> <p>给出一个节点client50配置?/p> <pre><code class="language-xml"><client host="client50" maxusers="100000" cpu="7" weight="4"> <ip value="10.10.10.50"></ip> <ip value="10.10.10.51"></ip> </client> </code></pre> <p>Tsung Master想访问client50,需要提前建立client50与IP地址的映射关系:</p> <pre><code class="language-bash">echo "10.10.10.50 client50" >> /etc/hosts </code></pre> <p><code>host</code>属性默认情况下只能填写长短名称,无法填写IP地址,为了兼容已有规则,修改<code>tsung-1.0.dtd</code>文件为client元素新增一?code>hostip</code>属性:</p> <pre><code class="language-xml"><!ATTLIST client cpu NMTOKEN "1" type (machine | batch) "machine" host NMTOKEN #IMPLIED hostip CDATA "" batch (torque | pbs | lsf | oar) #IMPLIED scan_intf NMTOKEN #IMPLIED maxusers NMTOKEN "800" use_controller_vm (true | false) "false" weight NMTOKEN "1"> </code></pre> <p>修改<code>src/tsung_controller/ts_config.erl</code>文件,增加处理逻辑,只有当主节点主机名为IP时才会取<code>hostip</code>作为主机名:</p> <pre><code class="language-erlang">{ok, MasterHostname} = ts_utils:node_to_hostname(node()), case {ts_utils:is_ip(MasterHostname), ts_utils:is_ip(Host), ts_utils:is_ip(HostIP)} of %% must be hostname and not ip: {false, true, _} -> io:format(standard_error,"ERROR: client config: 'host' attribute must be a hostname, "++ "not an IP ! (was ~p)~n",[Host]), exit({error, badhostname}); {true, true, _} -> %% add a new client for each CPU lists:duplicate(CPU,#client{host = Host, weight = Weight/CPU, maxusers = MaxUsers}); {true, _, true} -> %% add a new client for each CPU lists:duplicate(CPU,#client{host = HostIP, weight = Weight/CPU, maxusers = MaxUsers}); {_, _, _} -> %% add a new client for each CPU lists:duplicate(CPU,#client{host = Host, weight = Weight/CPU, maxusers = MaxUsers}) end </code></pre> <p>嗯,现在可以这样配置从节点了,不用担心Tsung启动时是否附?code>-F</code>参数了:</p> <pre><code class="language-xml"><client host="client50" hostip="10.10.10.50" maxusers="100000" cpu="7" weight="4"> <ip value="10.10.10.50"></ip> <ip value="10.10.10.51"></ip> </client> </code></pre> <p>其实,只要你确定只使用主节点主机名为IP地址,可以直接设置host属性值为IP值,可忽略hostip属性,但这以牺牲兼容性为代价的?/p> <pre><code class="language-xml"><client host="10.10.10.50" maxusers="100000" cpu="7" weight="4"> <ip value="10.10.10.50"></ip> <ip value="10.10.10.51"></ip> </client> </code></pre> <p>为了减少<code>/etc/hosts</code>大量映射写入,还是推荐全部IP形式,这种形式适合Tsung分布式集群所依赖服务器的快速租赁模型?/p> <h3 id="toc_7">源码地址</h3> <p>针对Tsung最新代码增加的IP直连特性所有修改,已经放在github上:</p> <p><a >https://github.com/weibomobile/tsung</a> ?/p> <p>并且已经递交<code>pull request</code>?<a >https://github.com/processone/tsung/pull/189</a> ?/p> <p>比较有意思的是,有这样一条评论:</p> <p><img src="//www.ot7t.com.cn/images/blogjava_net/yongboy/14696293372281.jpg" alt="" style="width:769px;"/>?/p> <h4 id="toc_8">针对Tsung 1.6.0修改?/h4> <p>最近一次发行版是tsung 1.6.0,这个版本比较稳定,我实际压测所使用的就是在此版本上增加IP直连支持(如上所述),已经被单独放入到github上:</p> <p><a >https://github.com/weibomobile/tsung-1.6.0</a></p> <p>至于如何安装?code>git clone</code>到本地,后面就是如何编译tsung的步骤了,不再累述?/p> <h3 id="toc_9">小结</h3> <p>若要让IP直连特性生效,再次说明启用步骤一下:</p> <ol> <li class="yibqv">tsung.xml文件配置从机hostip属性,或host属性,填写正确IP</li> <li class="yibqv">tsung启动时,指定本机可用IP地址?code>tsung -F Your_Available_IP -f tsung.xml ... start</code></li> </ol> <p>IP直连,再配合前面所写SSH替换方案,可以让Tsung分布式集群在复杂网络机房内网环境下适应性向前迈了一大步?/p> <blockquote> <p>2016-08-06 更新此文,增加Tsung 1.6.0修改版描?/p> </blockquote> <img src ="//www.ot7t.com.cn/yongboy/aggbug/431354.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="//www.ot7t.com.cn/yongboy/" target="_blank">nieyong</a> 2016-07-28 08:37 <a href="//www.ot7t.com.cn/yongboy/archive/2016/07/28/431354.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tsung笔记之分布式增强跳出SSH羁绊?/title><link>//www.ot7t.com.cn/yongboy/archive/2016/07/27/431340.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Wed, 27 Jul 2016 01:28:00 GMT</pubDate><guid>//www.ot7t.com.cn/yongboy/archive/2016/07/27/431340.html</guid><wfw:comment>//www.ot7t.com.cn/yongboy/comments/431340.html</wfw:comment><comments>//www.ot7t.com.cn/yongboy/archive/2016/07/27/431340.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>//www.ot7t.com.cn/yongboy/comments/commentRss/431340.html</wfw:commentRss><trackback:ping>//www.ot7t.com.cn/yongboy/services/trackbacks/431340.html</trackback:ping><description><![CDATA[<h3 id="toc_0">前言</h3> <p>Erlang天生支持分布式环境,Tsung框架的分布式压测受益于此,简单轻松操控子节点生死存亡、派发任务等不费吹灰之力?/p> <p>Tsung启动分布式压测时,主节点tsung_controller默认情况下需要通过SSH通道连接到远程机器上启动从节点,那么问题便来了,一般互联网公司基于跳板/堡垒?网关授权方式访问机房服务器,那么SSH机制失效,并且被明令禁止。SSH不通,Tsung主机启动不了从机,分布式更无从谈起?/p> <p>那么如何解决这个问题呢,让tsung在复杂的机房网络环境设定下更加如鱼得水,将是本文所讨论的内容。?/p> <h3 id="toc_1">RSH:Remote Shell</h3> <p>RSH,remote shell缩写,维基百科上英文解释?a >https://en.wikipedia.org/wiki/Remote_Shell</a>。作为一个终端工具,Linux界鸟哥曾经写?<a >RSH客户端和服务器端搭建教程</a>?/p> <p>在CentOS下安装也简单:</p> <pre><code class="language-bash">yum install rsh </code></pre> <p>Erlang借助于rsh命令行工具通过SSH通道连接到从节点启动Tsung应用,下面可以看到rsh工具本身失去了原本的含义,类似于<code>exec</code>命令功效?/p> <p>比如Erlang主节点(假设这个服务器名称为<code>node_master</code>,并且已经在/etc/hosts文件建立了IP地址映射)在启动时指定rsh的可选方式为SSH?/p> <pre><code class="language-bash">erl -rsh ssh -sname foo -setcookie mycookie </code></pre> <p>启动之后,要启动远程主机节点名称?code>node_slave</code>的子节点?/p> <pre><code class="language-erlang">slave:start(node_slave, bar, "-setcookie mycookie"). </code></pre> <p>上面Erlang启动从节点函数,最终被翻译为可执行的shell命令?/p> <pre><code class="language-bash">ssh node_slave erl -detached -noinput -master foo@node_master -sname bar@node_slave -s slave slave_start foo@node_master slave_waiter_0 -setcookie mycookie </code></pre> <blockquote> <p><code>erl</code>命令Erlang的启动命令,要求主机<code>node_slave</code>自身也要安装了Erlang的运行时环境才行?/p> </blockquote> <p>从节点的启动命令最终依赖于SSH连接并远程执行,其通用一般格式为?/p> <pre><code class="language-bash">ssh HOSTNAME/IP Command </code></pre> <p>这就是基于Erlang构建的Tsung操控从节点启动的最终实现机制?/p> <blockquote> <p>其它语言中,Master启动Slave也是如此机制</p> </blockquote> <h3 id="toc_2">SSH为通用方案,但不是最好的方案</h3> <p>业界选用<a >SSH</a>机制连接远程Unix/Linux服务器主机,分布式环境下要能够自由免除密码方式启动远程主机上(这里指的是内部Lan环境)应用,一般需要设置公钥,需要传递公钥,需要保存到各自机器上,还有经常遇到权限问题,很是麻烦,这是其一。若要取消某台服务器登陆授权,则需要被动修改公钥,也是不够灵活?/p> <p>另外一般互联网公司处于安全考虑都会禁止公司内部人员直接通过SSH方式登录到远程主机进行操作,这样导致SSH通道失效,Tsung主机通过SSH连接到从机并执行命令,也就不可能了?/p> <p>其实,在基于分布式压测环境下,快速租赁、快速借用/归还的模型就很适合。一般公司很少会存在专门用于压测的大量空闲机器,但是线上会运行着当前负载不高的服务器,可以拿来用作压测客户端使用,用完就归还。因为压测不会是长时间运行的服务,其为短时间行为。这种模式下就不适合复杂的SSH公钥满天飞,后期忘记删除的情况,在压测端超多的情况下,无疑也将造成运维成本激增,安全性降低等问题?/p> <h3 id="toc_3">SSH替换方案:一种快速租赁模式远程终端方?/h3> <p>现在需要寻找一种新的代替方案,一种适应快速租赁的远程终端实现机制?/p> <h4 id="toc_4">替换方案要求?/h4> <ol> <li class="yibqv">类似于SSH Server,监听某个端口,能够执行传递过来的命令</li> <li class="yibqv">能够根据IP地址授权,这样只有Tsung Master才能够访问从节点,从节点之间无法直接对连</li> <li class="yibqv">需要接受一些操控指令,可以判断是否存活</li> <li class="yibqv">一到两个脚?程序搞定,尽量避免安装,开箱即?/li> <li class="yibqv">总之配置、操作一定要简单,实际运维成本一定要?/li> </ol> <p>没找到很轻量的实现,可以设计并实现这样一种方案?/p> <h4 id="toc_5">服务器端守护进程</h4> <p>轻量级服务端守护进程 = 一个监控端口的进程?code>rsh_daemon.sh</code>?+ 执行命令过滤功能(rsh_filter)</p> <p><code>rsh_daemon.sh</code> 负责守护进程的管理:</p> <ul class="yibqv"> <li class="yibqv">基于CentOS 6/7默认安装?code>ncat</code>程序</li> <li class="yibqv">主要用于管理19999端口监听</li> <li class="yibqv">start/stop/restart 负责监控进程启动、关?/li> <li class="yibqv">status 查看进程状?/li> <li class="yibqv">kill 提供手动方式关闭并删除掉自身</li> <li class="yibqv"><code>rsh_filter</code>用于检测远程传入命令并进行处理 <ul class="yibqv"> <li class="yibqv">接收ping指令,返回pong</li> <li class="yibqv">执行Erlang从节点命令,并返?done 字符?/li> <li class="yibqv">对不合法命令,直接关?/li> </ul></li> </ul> <p><code>rsh_daemon.sh</code>代码很简单:</p> <pre><code class="language-bash">#!/bin/bash # the script using for start/stop remote shell daemon server to replace the ssh server PORT=19999 FILTER=~/tmp/_tmp_rsh_filter.sh # the tsung master's hostname or ip tsung_controller=tsung_controller SPECIAL_PATH="" PROG=`basename $0` prepare() { cat << EOF > $FILTER #!/bin/bash ERL_PREFIX="erl" while true do read CMD case \$CMD in ping) echo "pong" exit 0 ;; *) if [[ \$CMD == *"\${ERL_PREFIX}"* ]]; then exec $SPECIAL_PATH\${CMD} fi exit 0 ;; esac done EOF chmod a+x $FILTER } start() { NUM=$(ps -ef|grep ncat | grep ${PORT} | grep -v grep | wc -l) if [ $NUM -gt 0 ];then echo "$PROG already running ..." exit 1 fi if [ -x "$(command -v ncat)" ]; then echo "$PROG starting now ..." ncat -4 -k -l $PORT -e $FILTER --allow $tsung_controller & else echo "no exists ncat command, please install it ..." fi } stop() { NUM=$(ps -ef|grep ncat | grep rsh | grep -v grep | wc -l) if [ $NUM -eq 0 ]; then echo "$PROG had already stoped ..." else echo "$PROG is stopping now ..." ps -ef|grep ncat | grep rsh | grep -v grep | awk '{print $2}' | xargs kill fi } status() { NUM=$(ps -ef|grep ncat | grep rsh | grep -v grep | wc -l) if [ $NUM -eq 0 ]; then echo "$PROG had already stoped ..." else echo "$PROG is running ..." fi } usage() { echo "Usage: $PROG <options> start|stop|status|restart" echo "Options:" echo " -a <hostname/ip> allow only given hosts to connect to the server (default is tsung_controller)" echo " -p <port> use the special port for listen (default is 19999)" echo " -s <the_erl_path> use the special erlang's erts bin path for running erlang (default is blank)" echo " -h display this help and exit" exit } while getopts "a:p:s:h" Option do case $Option in a) tsung_controller=$OPTARG;; p) PORT=$OPTARG;; s) TMP_ERL=$OPTARG if [ "$OPTARG" != "" ]; then if [[ "$OPTARG" == *"/" ]]; then SPECIAL_PATH=$OPTARG else SPECIAL_PATH=$OPTARG"/" fi fi ;; h) usage;; *) usage;; esac done shift $(($OPTIND - 1)) case $1 in start) prepare start ;; stop) stop ;; status) status ;; restart) stop start ;; *) usage ;; esac </code></pre> <p>总结一下:</p> <ul class="yibqv"> <li class="yibqv">基于<code>ncat</code>监听19999端口提供bind shell机制,但限制有限IP可访?/li> <li class="yibqv">动态生成命令过滤脚?code>rsh_filter.sh</code>,执行Erlang从节点命?/li> </ul> <p>请参考:<a >https://github.com/weibomobile/tsung_rsh/blob/master/rsh_daemon.sh</a></p> <h3 id="toc_6">客户端连接方?/h3> <p>服务器端已经提供了端口接入并准备好了接收指令,客户端?code>rsh_client.sh</code>)可以进行连接和交互了:</p> <ul class="yibqv"> <li class="yibqv">类似SSH客户端接收方式:<code>rsh_client.sh Host/IP Command</code></li> <li class="yibqv">完全基于<code>nc</code>命令,连接远程主?/li> <li class="yibqv">连接成功,发送命?/li> <li class="yibqv">得到相应,流程完?/li> </ul> <p>一样非常少的代码呈现?/p> <pre><code class="language-bash">#!/bin/sh PORT=19999 if [ $# -lt 2 ]; then echo "Invalid number of parameters" exit 1 fi REMOTEHOST="$1" COMMAND="$2" if [ "${COMMAND}" != "erl" ]; then echo "Invalid command ${COMMAND}" exit 1 fi shift 2 echo "${COMMAND} $*" | /usr/bin/nc ${REMOTEHOST} ${PORT} </code></pre> <h3 id="toc_7">Erlang主节点如何启?/h3> <p>有了SSH替换方案,那主节点就可以这样启动了:</p> <pre><code class="language-bash">erl -rsh ~/.tsung/rsh_client.sh -sname foo -setcookie mycookie </code></pre> <p>比如当Tsung需要连接到另外一台服务器上启动从节点时,它最终会翻译成下面命令:</p> <pre><code class="language-bash">/bin/sh /root/.tsung/rsh_client.sh node_slave erl -detached -noinput -master foo@node_master -sname bar@node_slave -s slave slave_start foo@node_master slave_waiter_0 -setcookie mycookie </code></pre> <p>客户端脚?code>rsh_client.sh</code>则最终需要执行连接到服务器、并发送命的命令:</p> <pre><code class="language-bash">echo "erl -detached -noinput -master foo@node_master -sname bar@node_slave -s slave slave_start foo@node_master slave_waiter_0 -setcookie mycookie" | /usr/bin/nc node_slave 19999 </code></pre> <p>这样就实现了和SSH一样的功能了,很简单吧?/p> <h3 id="toc_8">Tsung如何切换切换?/h3> <p>为tsung启动添加<code>-r</code>参数指定即可?/p> <pre><code class="language-bash">tsung -r ~/.tsung/rsh_client.sh -f tsung.xml start </code></pre> <h3 id="toc_9">进阶:可指定运行命令路径</h3> <p><code>rsh_client.sh</code>脚本最后一行修改一下,指定目标服务器erl运行命令?/p> <pre><code class="language-bash">#!/bin/sh PORT=19999 if [ $# -lt 2 ]; then echo "Invalid number of parameters" exit 1 fi REMOTEHOST="$1" COMMAND="$2" if [ "${COMMAND}" != "erl" ]; then echo "Invalid command ${COMMAND}" exit 1 fi shift 2 exec echo "/root/.tsung/otp_18/bin/erl $*" | /usr/bin/nc ${REMOTEHOST} 19999 </code></pre> <p>上面脚本所依赖的上下文环境可以是这样的,机房服务器操作系统和版本一致,我们把Erlang 18.1整个运行时环境在一台机器上已经安装的目录(比如目录名为otp_18),拷贝到远程主?code>/root/.tsung/</code>目录,相比于安装而言,可以让Tsung运行依赖的Eralng环境完全可以移植化(Portable),一次安装,多次复制?/p> <h3 id="toc_10">代码托管地址</h3> <p>本文所谈及代码,都已经托管在github?br/> <a >https://github.com/weibomobile/tsung_rsh</a></p> <p>后续代码更新、BUG修复等,请直接参考该仓库?/p> <h3 id="toc_11">小结</h3> <p>简单一套新的替换SSH通道无密钥登陆远程主机C/S模型,虽然完整性上无法与SSH相比,但胜在简单够用,完全满足了当前业务需要,并且其运维成本低,无疑让Tsung在复杂服务器内网环境下适应性又朝前多走了半里路?/p> <p>下一篇将介绍为Tsung增加IP直连特性支持,使其分布式网络环境下适应性更广泛一些?/p> <img src ="//www.ot7t.com.cn/yongboy/aggbug/431340.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="//www.ot7t.com.cn/yongboy/" target="_blank">nieyong</a> 2016-07-27 09:28 <a href="//www.ot7t.com.cn/yongboy/archive/2016/07/27/431340.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tsung笔记之压测端资源限制?/title><link>//www.ot7t.com.cn/yongboy/archive/2016/07/26/431322.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Tue, 26 Jul 2016 00:47:00 GMT</pubDate><guid>//www.ot7t.com.cn/yongboy/archive/2016/07/26/431322.html</guid><wfw:comment>//www.ot7t.com.cn/yongboy/comments/431322.html</wfw:comment><comments>//www.ot7t.com.cn/yongboy/archive/2016/07/26/431322.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>//www.ot7t.com.cn/yongboy/comments/commentRss/431322.html</wfw:commentRss><trackback:ping>//www.ot7t.com.cn/yongboy/services/trackbacks/431322.html</trackback:ping><description><![CDATA[<h3 id="toc_0">前言</h3> <p>这里汇集一下影响tsung client创建用户数的各项因素。因为Tsung是IO密集型的应用,CPU占用一般不大,为了尽可能的生成更多的用户,需要考虑内存相关事宜?/p> <h3 id="toc_1">IP & 端口的影?/h3> <h4 id="toc_2">1. 系统端口限制</h4> <p>Linux系统端口为short类型表示,数值上限为65535。假设分配压测业务可用端口范围为1024 - 65535,不考虑可能还运行着其它对外连接的服务,真正可用端口也就?4000左右(实际上,一般为了方便计算,一般直接设定为50000)。换言之,即在一台机器上一个IP,可用同时对外建?4000网络连接?/p> <p>若是N个可用IP,理论上 64000*N,实际上还需要满足:</p> <ul class="yibqv"> <li class="yibqv">充足内存支持 <ul class="yibqv"> <li class="yibqv">tcp接收/发送缓冲区不要设置太大,tsung默认分配32K(可以修改成16K,一般够用了?/li> <li class="yibqv">一个粗略估算一个连接占?0K内存,那?0万用户,将占用约8G内存</li> </ul></li> <li class="yibqv">为多IP的压测端分配适合的权重,以便承担更多的终端连?/li> </ul> <p>另外还需要考虑端口的快速回收等,可以这样做?/p> <pre><code class="language-bash">sysctl -w net.ipv4.tcp_syncookies=1 sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_tw_recycle=1 sysctl -w net.ipv4.tcp_fin_timeout=30 sysctl -w net.ipv4.ip_local_port_range="1024 65535" sysctl -p </code></pre> <blockquote> <p>若已经在 /etc/sysctl.conf 文件中有记录,则需要手动修?/p> </blockquote> <p>作为附加,可设置端口重用?/p> <pre><code class="language-erlang"><option name="tcp_reuseaddr" value="true"/> </code></pre> <p>注意,不要设置下面的可用端口范围?/p> <pre><code class="language-erlang"><option name="ports_range" min="1025" max="65535"/> </code></pre> <p>因为操作系统会自动跳过已经被占用本地端口,而Tsung只能够被动通过错误进行可用端口+1继续下一个连接,有些多余?/p> <h4 id="toc_3">2. IP和端口组?/h4> <p>每一个client支持多个可用IP地址列表</p> <pre><code class="language-xml"><client host="client_99" maxusers="120000" weight="2" cpu="8"> <ip value="10.10.10.99"></ip> <ip value="10.10.10.11"></ip> </client> </code></pre> <p>tsung client从节点开始准备建立网络连接会话时,需要从tsung_controller主节点获取具体的会话信息,其中就包含了客户端连接需要使用到来源{LocalIP?LocalPort}二元组。由tsung_controller主节点完成?/p> <pre><code class="language-erlang">get_user_param(Client,Config)-> {ok, IP} = choose_client_ip(Client), {ok, Server} = choose_server(Config#config.servers, Config#config.total_server_weights), CPort = choose_port(IP, Config#config.ports_range), {{IP, CPort}, Server}. choose_client_ip(#client{ip = IPList, host=Host}) -> choose_rr(IPList, Host, {0,0,0,0}). ...... choose_client_ip(#client{ip = IPList, host=Host}) -> choose_rr(IPList, Host, {0,0,0,0}). choose_rr(List, Key, _) -> I = case get({rr,Key}) of undefined -> 1 ; % first use of this key, init index to 1 Val when is_integer(Val) -> (Val rem length(List))+1 % round robin end, put({rr, Key},I), {ok, lists:nth(I, List)}. %% 默认不设?ports_range 会直接返? %% 不建议设?<option name="ports_range" min="1025" max="65535"/> %% 因为这样存在端口冲突问题,除非确实不存被占用情况 choose_port(_,_, undefined) -> {[],0}; choose_port(Client,undefined, Range) -> choose_port(Client,dict:new(), Range); choose_port(ClientIp,Ports, {Min, Max}) -> case dict:find(ClientIp,Ports) of {ok, Val} when Val =< Max -> NewPorts=dict:update_counter(ClientIp,1,Ports), {NewPorts,Val}; _ -> % Max Reached or new entry NewPorts=dict:store(ClientIp,Min+1,Ports), {NewPorts,Min} end. </code></pre> <p>从节点建立到压测服务器连接时,就需要指定从主节点获取到的本机IP地址和端口两元组?/p> <pre><code class="language-erlang">Opts = protocol_options(Protocol, Proto_opts) ++ [{ip, IP},{port,CPort}], ...... gen_tcp:connect(Server, Port, Opts, ConnectTimeout). </code></pre> <h4 id="toc_4">3. IP自动扫描特?/h4> <p>若从机单个网卡绑定了多个IP,又懒于输入,可以配置扫描特?</p> <pre><code class="language-xml"><ip scan="true" value="eth0"/> </code></pre> <p>本质上使用shell方式获取IP地址,并且支持CentOS 6/7?/p> <pre><code class="language-bash"> /sbin/ip -o -f inet addr show dev eth0 </code></pre> <blockquote> <p>因为扫描比较慢,Tsung 1.6.1推出?code>ip_range</code>特性支持?/p> </blockquote> <h3 id="toc_5">Linux系统打开文件句柄限制</h3> <p>系统打开文件句柄,直接决定了可以同时打开的网络连接数量,这个需要设置大一些,否则,你可能会在<a href="mailto:tsung_controller@IP.log">tsung_controller@IP.log</a>文件中看?code>error_connect_emfile</code>类似文件句柄不够使用的警告,建议此值要大于 > N * 64000?/p> <pre><code class="language-bash">echo "* soft nofile 300000" >> /etc/security/limits.conf echo "* hard nofile 300000" >> /etc/security/limits.conf </code></pre> <p>或者,在Tsung会话启动脚本文件中明确添加上<code>ulimit -n 300000</code>?/p> <h3 id="toc_6">内存的影?/h3> <p>一个网络Socket连接占用不多,但上万个或数十万等就不容小觑了,设置不当会导致内存直接成为屏障?/p> <h4 id="toc_7">1. TCP接收、发送缓?/h4> <p>Tsung默认设置的网络Socket发送接收缓冲区?6KB,一般够用了?/p> <p>以TCP为例,某次我手误为Tcp接收缓存赋值过?599967字节),这样每一个网络了解至少占用了0.6M内存,直接导致在16G内存服务上网络连接数?万多时,内存告急?/p> <pre><code class="language-xml"><option name="tcp_snd_buffer" value="16384"></option> <option name="tcp_rcv_buffer" value="16384"></option> </code></pre> <p>此值会覆盖Linux系统设置接收、发送缓冲大小?/p> <p>粗略的默认值计算,一个网络连接发送缓冲区 + 接收缓冲区,再加上进程处理连接堆栈占用,?0多K内存,为即计算方便,设定建立一个网络连接消?0K内存?/p> <p>先不考虑其它因素,若我们想要从机模拟10W个用户,那么当前可用内存至少要剩余:50K * 100000 / 1000K = 5000M = 5G内存。针对一般服务器来讲,完全可满足要求(剩下事情就是要有两个可用IP了)?/p> <h4 id="toc_8">2. Erlang函数堆栈内存占用</h4> <p>使用Erlang程序写的应用服务器,进程要存储堆栈调用信息,进程一多久会占用大量内存,想要服务更多网络连接/任务,需要将不活动的进程设置为休眠状态,以便节省内存,Tsung的压测会话信息若包含thinktime时间,也要考虑启用hibernate休眠机制?/p> <pre><code class="language-xml"><option name="hibernate" value="5"></option> </code></pre> <p>值单位秒,默认thinktime超过10秒后自动启动,这里修改为5秒?/p> <h3 id="toc_9">XML文件设置需要注意部?/h3> <h4 id="toc_10">1. 日志等级要调高一?/h4> <p>tsung使用error_logger记录日志,其只适用于真正的异常情况,若当一般业务调试类型日志量过多时,不但耗费了大量内存,网络/磁盘写入速度跟不上生产速度时,会导致进程堵塞,严重会拖累整个应用僵死,因此需要在tsung.xml文件中设置日志等级要高一些,至少默认的notice就很合适?/p> <h4 id="toc_11">2. 不要启用dump</h4> <p>dump是一个耗时的行为,因此默认为false,除非很少的压测用户用于调试?/p> <h4 id="toc_12">3. 动态属性太多,会导致请求超?/h4> <pre><code class="language-xml"><option name="file_server" id="userdb" value="/your_path/100w_users.csv"/> ... <setdynvars sourcetype="file" fileid="userdb" delimiter=";" order="iter"> <var name="userid" /> <var name="nickname" /> </setdynvars> ... <request subst="true"> <yourprotocol type="hello" uid="%%_userid%%" ack="local"> Hello, I'm %%_nickname%% </yourprotocol> </request> </code></pre> <p>设定一个有状态的场景,用户ID储存在文件中,每一次会话请求都要从获取到用户ID,压测用户一旦达到百万级别并且用户每秒产生速率过大(比如每?000个用户),会经常遇到超时错误?/p> <pre><code>=ERROR REPORT==== 25-Jul-2016::15:14:11 === ** Reason for termination = ** {timeout,{gen_server,call, [{global,ts_file_server},{get_next_line,userdb}]}} </code></pre> <p>这是因为,当tsung client遇到<code>setdynvars</code>指令时,会直接请求主机ts_file_server模块,当一时间请求量巨大,可能会造成单一模块处理缓慢,出现超时问题?/p> <p>怎么办:</p> <ol> <li class="yibqv">降低用户每秒产生速率,比?00秒用户生?/li> <li class="yibqv">不用从文件中存储用户id等信息,采用别的方式</li> </ol> <h3 id="toc_13">如何限流/限?/h3> <p>某些时候,要避免tsung client压测端影响所在服务器网络带宽IO太拥挤,需要限制流量,其采用令牌桶算法?/p> <pre><code class="language-xml"><option name="rate_limit" value="1024"></option> </code></pre> <ul class="yibqv"> <li class="yibqv">值为KB单位每秒</li> <li class="yibqv">目前仅对传入流量生效</li> </ul> <p>阀值计算方式:</p> <pre><code class="language-erlang">{RateConf,SizeThresh} = case RateLimit of Token=#token_bucket{} -> Thresh=lists:min([?size_mon_thresh,Token#token_bucket.burst]), {Token#token_bucket{last_packet_date=StartTime}, Thresh}; undefined -> {undefined, ?size_mon_thresh} end, </code></pre> <p>接收传入流量数据,需要计算:</p> <pre><code class="language-erlang">handle_info2({gen_ts_transport, _Socket, Data}, wait_ack, State=#state_rcv{rate_limit=TokenParam}) when is_binary(Data)-> ?DebugF("data received: size=~p ~n",[size(Data)]), NewTokenParam = case TokenParam of undefined -> undefined; #token_bucket{rate=R,burst=Burst,current_size=S0, last_packet_date=T0} -> {S1,_Wait}=token_bucket(R,Burst,S0,T0,size(Data),?NOW,true), TokenParam#token_bucket{current_size=S1, last_packet_date=?NOW} end, {NewState, Opts} = handle_data_msg(Data, State), NewSocket = (NewState#state_rcv.protocol):set_opts(NewState#state_rcv.socket, [{active, once} | Opts]), case NewState#state_rcv.ack_done of true -> handle_next_action(NewState#state_rcv{socket=NewSocket,rate_limit=NewTokenParam, ack_done=false}); false -> TimeOut = case (NewState#state_rcv.request)#ts_request.ack of global -> (NewState#state_rcv.proto_opts)#proto_opts.global_ack_timeout; _ -> (NewState#state_rcv.proto_opts)#proto_opts.idle_timeout end, {next_state, wait_ack, NewState#state_rcv{socket=NewSocket,rate_limit=NewTokenParam}, TimeOut} end; </code></pre> <p>下面则是具体的令牌桶算法?/p> <pre><code class="language-erlang">%% @spec token_bucket(R::integer(),Burst::integer(),S0::integer(),T0::tuple(),P1::integer(), %% Now::tuple(),Sleep::boolean()) -> {S1::integer(),Wait::integer()} %% @doc Implement a token bucket to rate limit the traffic: If the %% bucket is full, we wait (if asked) until we can fill the %% bucket with the incoming data %% R = limit rate in Bytes/millisec, Burst = max burst size in Bytes %% T0 arrival date of last packet, %% P1 size in bytes of the packet just received %% S1: new size of the bucket %% Wait: Time to wait %% @end token_bucket(R,Burst,S0,T0,P1,Now,Sleep) -> S1 = lists:min([S0+R*round(ts_utils:elapsed(T0, Now)),Burst]), case P1 < S1 of true -> % no need to wait {S1-P1,0}; false -> % the bucket is full, must wait Wait=(P1-S1) div R, case Sleep of true -> timer:sleep(Wait), {0,Wait}; false-> {0,Wait} end end. </code></pre> <h3 id="toc_14">小结</h3> <p>以上简单梳理一下影响tsung从机创建用户的各项因素,实际环境其实相当复杂,需要一一对症下药才行?/p> <img src ="//www.ot7t.com.cn/yongboy/aggbug/431322.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="//www.ot7t.com.cn/yongboy/" target="_blank">nieyong</a> 2016-07-26 08:47 <a href="//www.ot7t.com.cn/yongboy/archive/2016/07/26/431322.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tsung笔记之主从资源协调篇 - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2016/07/25/431310.htmlnieyongnieyongMon, 25 Jul 2016 06:02:00 GMT//www.ot7t.com.cn/yongboy/archive/2016/07/25/431310.html//www.ot7t.com.cn/yongboy/comments/431310.html//www.ot7t.com.cn/yongboy/archive/2016/07/25/431310.html#Feedback1//www.ot7t.com.cn/yongboy/comments/commentRss/431310.html//www.ot7t.com.cn/yongboy/services/trackbacks/431310.html前言

接着上文,tsung一旦启动,主从节点之间需要协调分配资源,完成分布式压测任务?/p>

如何启动Tsung压测从机

Erlang SDK提供了从机启动方式:

slave:start(Host, Node, Opts)

启动从机需要借助于免登陆形式远程终端,比如SSH(后续会讨论SSH存在不足,以及全新的替代品),需要自行配置?/p>

<client host="client_100" maxusers="60000" weight="1">
    <ip value="10.10.10.100"/>
</client>
  • host属性对应value为从机主机名称:client_100
  • Node节点名称由tsung_controller组装,类似于 tsung10@client_100
  • Opts表示相关参数
  • 一个物理机器,可以存在多个tsung从机实例
  • 一个tsung从机实例对应一个tsung client

简单翻译一下:slave:start(client_100, 'tsung10@client_100', Opts)

从机需要关闭时,就很简单了?/p>

slave:stop(Node)

当然若主机中途挂掉,从机也会自动自杀掉自身?/p>

启动tsung client方式

Tsung主机启动从机成功,从机和主机就可以Erlang节点进程之间进行方法调用和消息传递。潜在要求是,tsung编译后beam文件能够在Erlang运行时环境中能够访问到,这个和Java Classpath一致原理?/p>

rpc:multicall(RemoteNodes,tsung,start,[],?RPC_TIMEOUT)

到此为止,一个tsung client实例成功运行?/p>

  • tsung client实例生命周期结束,不会导致从机实例主动关?/li>
  • tsung slave提供了运行时环境,tsung client是业?/li>
  • tsung slave和tsung client关系? : 1关系,很多时候为了理解方便,不会进行严格区分

压测目标

明白了主从启动方式,下面讨论压测目标,比?0万用户的量,根据给出的压测从机列表,进行任务分配?/p>

压测目标配置

tsung压测xml配置文件,load元素可以配置总体任务生成的信息?/p>

<load>
    <arrivalphase phase="1" duration="60" unit="minute">
        <!--users maxnumber="500000" interarrival="0.004" unit="second"></users-->
        <users maxnumber="500000" arrivalrate="250" unit="second"></users>
    </arrivalphase>
</load>
  • 定义一个最终压力产生可以持?0分钟压测场景?上限用户量为50?/li>
  • arrivalphase duration属性持续时长表示生成压测用户可消费总体时间60分钟,即为T1
  • users元素其属性表示单位时间内(这里单位时间为秒)产生用户数为250?/li>
  • 50万用户,将在2000??4分钟)内生成,耗时时长即为T2
  • T2小于arrivalphase定义的用户生成阶段持续时间T1
  • 若T2时间后(34分钟)后因为产生用户数已经达到了上限,将不再产生新的用户,知道整个压测结束
  • ?T1 小于 T2,则50万用户很难达到,因此T1时间要设置长一?/li>

从节点信息配?/h4>

所说从节点也是压测客户端,需要配置clients元素?/p>

<clients>
    <client host="client_100" maxusers="60000" weight="1">
        <ip value="10.10.10.100"/>
    </client>

    ......

    <client host="client_109" maxusers="120000" weight="2">
        <ip value="10.10.10.109"></ip>
        <ip value="10.10.10.119"></ip>
    </client>
</clients>
  1. 单个client支持多个IP,用于突破单个IP对外建立连接数的限制(后续会讲到)
  2. xml所定义的一个cliet元素,可能被分裂出若干从机实?即tsung client)? : N

根据CPU数量分裂tsung client实例情况

在《Tsung Documentation》给出了建议,一个CPU一个tsung client实例?/p>

Note: Even if an Erlang VM is now able to handle several CPUs (erlang SMP), benchmarks shows that it’s more efficient to use one VM per CPU (with SMP disabled) for tsung clients. Only the controller node is using SMP erlang.
Therefore, cpu should be equal to the number of cores of your nodes. If you prefer to use erlang SMP, add the -s option when starting tsung (and don’t set cpu in the config file).

  • 默认策略, 一个tsung client对应一个CPU,若不设置CPU属性,默认值就?
  • 一个cpu对应一个tsung client,N个CPU,N个tsung client
  • 共同分担权重,每一个分裂的tsung client权重 Weight/N
  • 一旦设置cpu属性,无论Tsung启动时是否携?code>-s参数设置共享CPU,都?
    • 自动分裂CPU个tsung client实例
    • 每一个实例权重为Weight/CPU
%% add a new client for each CPU
lists:duplicate(CPU,#client{host     = Host,
                            weight   = Weight/CPU,
                            maxusers = MaxUsers})

若要设置单个tsung client实例共享多个CPU(此时不要设置cpu属性啦),需要在tsung启动时添?code>-s参数,tsung client被启动时,smp属性被设置成auto?/p>

-smp auto +A 8

这样从机就只有一个tsung client实例了,不会让人产生困扰。若是临时租借从机,建议启动时使?s参数,并且要去除cpu属性设置,这样才能够自动共享所有CPU核心?/p>

从机分配用户过多,一样会分裂新的tsung client实例

假设client元素配置maxusers数量?K,那么实际上被分配数量为10K(压测人数多,压测从机?时,那么tsung_controller会继续分裂新的tsung client实例,直?0K用户数量完成?/p>

<client host="client_98" maxusers="1000" weight="1">
    <ip value="10.10.10.98"></ip>
</client>

tsung client分配的数量超过自身可服务上限用户时(这里设置的是1K)时,关闭自身?/p>

launcher(_Event, State=#launcher{nusers = 0, phases = [] }) ->
    ?LOG("no more clients to start, stop  ~n",?INFO),
    {stop, normal, State};

launcher(timeout, State=#launcher{nusers        = Users,
                                  phase_nusers  = PhaseUsers,
                                  phases        = Phases,
                                  phase_id      = Id,
                                  started_users = Started,
                                  intensity     = Intensity}) ->
    BeforeLaunch = ?NOW,
    case do_launch({Intensity,State#launcher.myhostname,Id}) of
        {ok, Wait} ->
            case check_max_raised(State) of
                true ->
                    %% let the other beam starts and warns ts_mon
                    timer:sleep(?DIE_DELAY),
                    {stop, normal, State};
                false->
                    ......
            end;
        error ->
            % retry with the next user, wait randomly a few msec
            RndWait = random:uniform(?NEXT_AFTER_FAILED_TIMEOUT),
            {next_state,launcher,State#launcher{nusers = Users-1} , RndWait}
    end.

tsung_controller接收从节点退出通知,但分配总数没有完成,会启动新的tsung client实例(一样先启动从节点,然后再启动tsung client实例)。整个过程串行方式循环,直到10K用户数量完成?/p>

%% start a launcher on a new beam with slave module
handle_cast({newbeam, Host, Arrivals}, State=#state{last_beam_id = NodeId, config=Config, logdir = LogDir}) ->
    Args = set_remote_args(LogDir,Config#config.ports_range),
    Seed = Config#config.seed,
    Node = remote_launcher(Host, NodeId, Args),
    case rpc:call(Node,tsung,start,[],?RPC_TIMEOUT) of
        {badrpc, Reason} ->
            ?LOGF("Fail to start tsung on beam ~p, reason: ~p",[Node,Reason], ?ERR),
            slave:stop(Node),
            {noreply, State};
        _ ->
            ts_launcher_static:stop(Node), % no need for static launcher in this case (already have one)
            ts_launcher:launch({Node, Arrivals, Seed}),
            {noreply, State#state{last_beam_id = NodeId+1}}
    end;

tsung client分配用户?/h3>

一个tsung client分配的用户数,可以理解为会话任务数。Tsung以终端可以模拟的用户为维度进行定义压测?/p>

所有配置tsung client元素(设置M1)权重相加之和为总权重TotalWeight,用户总数为MaxMember,一个tsung client实例(总数设为M2)分配的模拟用户数可能为?/p>

MaxMember*(Weight/TotalWeight)

需要注意:
- M2 >= M1
- 若压测阶?code><arrivalphase元素配置duration值过小,小于最终用?0万用户按照每?50速率耗时时间,最终分配用户数将小于期望?/p>

只有一台物理机的tsung master启动方式

<clients>
  <client host="localhost" use_controller_vm="true"/>
</clients>

没有物理从机,主从节点都在一台机器上,需要设?code>use_controller_vm="true"。相比tsung集群,单一节点tsung启动就很简单,主从之间不需要SSH通信,直接内部调用?/p>

local_launcher([Host],LogDir,Config) ->
    ?LOGF("Start a launcher on the controller beam ~p~n", [Host], ?NOTICE),
    LogDirEnc = encode_filename(LogDir),
    %% set the application spec (read the app file and update some env. var.)
    {ok, {_,_,AppSpec}} = load_app(tsung),
    {value, {env, OldEnv}} = lists:keysearch(env, 1, AppSpec),
    NewEnv = [ {debug_level,?config(debug_level)}, {log_file,LogDirEnc}],
    RepKeyFun = fun(Tuple, List) ->  lists:keyreplace(element(1, Tuple), 1, List, Tuple) end,
    Env = lists:foldl(RepKeyFun, OldEnv, NewEnv),
    NewAppSpec = lists:keyreplace(env, 1, AppSpec, {env, Env}),

    ok = application:load({application, tsung, NewAppSpec}),
    case application:start(tsung) of
        ok ->
            ?LOG("Application started, activate launcher, ~n", ?INFO),
            application:set_env(tsung, debug_level, Config#config.loglevel),
            case Config#config.ports_range of
                {Min, Max} ->
                    application:set_env(tsung, cport_min, Min),
                    application:set_env(tsung, cport_max, Max);
                undefined ->
                    ""
            end,
            ts_launcher_static:launch({node(), Host, []}),
            ts_launcher:launch({node(), Host, [], Config#config.seed}),
            1 ;
        {error, Reason} ->
            ?LOGF("Can't start launcher application (reason: ~p) ! Aborting!~n",[Reason],?EMERG),
            {error, Reason}
    end.

用户生成控制

用户和会话控?/h4>

每一个tsung client运行着一?code>ts_launch/ts_launch_static本地注册模块,掌控终端模拟用户生成和会话控制?/p>

  • 向主节点ts_config_server请求隶属于当前从机节点的会话信息
  • 启动模拟终端用户ts_client
  • 控制下一个模拟终端用户ts_client需要等待时间,也是控制从机用户生成速度
  • 执行是否需要切换到新的阶段会话
  • 控制模拟终端用户是否已经达到了设置的maxusers上限
    • 到上限,自身使命完成,关闭自?/li>
  • 源码位于 tsung-1.6.0/src/tsung 目录?/li>

主机按照xml配置生成全局用户产生速率,从机按照自身权重分配的速率进行单独控制,这也是任务分解的具体呈现?/p>

用户生成速度控制

在Tsung中用户生成速度称之为强度,根据所配置的load属性进行配?/p>

<load>
    <arrivalphase phase="1" duration="60" unit="minute">
        <users maxnumber="500000" arrivalrate="250" unit="second"></users>
    </arrivalphase>
</load>

关键属性:

  • interarrival,生成压测用户的时间间隔
  • arrivalrate:单位时间内生成用户数量
  • 两者最终都会被转换为生成用户强度系数值是0.25
  • 这个是总的强度值,但需要被各个tsung client分解
parse(Element = #xmlElement{name=users, attributes=Attrs},
      Conf = #config{arrivalphases=[CurA | AList]}) ->

    Max = getAttr(integer,Attrs, maxnumber, infinity),
    ?LOGF("Maximum number of users ~p~n",[Max],?INFO),

    Unit  = getAttr(string,Attrs, unit, "second"),
    Intensity = case {getAttr(float_or_integer,Attrs, interarrival),
                      getAttr(float_or_integer,Attrs, arrivalrate)  } of
                    {[],[]} ->
                        exit({invalid_xml,"arrival or interarrival must be specified"});
                    {[], Rate}  when Rate > 0 ->
                        Rate / to_milliseconds(Unit,1);
                    {InterArrival,[]} when InterArrival > 0 ->
                        1/to_milliseconds(Unit,InterArrival);
                    {_Value, _Value2} ->
                        exit({invalid_xml,"arrivalrate and interarrival can't be defined simultaneously"})
                end,
    lists:foldl(fun parse/2,
        Conf#config{arrivalphases = [CurA#arrivalphase{maxnumber = Max,
                                                        intensity=Intensity}
                               |AList]},
                Element#xmlElement.content);

tsung_controller对每一个tsung client生成用户强度分解?ClientIntensity = PhaseIntensity * Weight / TotalWeight,?code>1000 * ClientIntensity就是易读的每秒生成用户速率值?/p>

get_client_cfg(Arrival=#arrivalphase{duration = Duration,
                                     intensity= PhaseIntensity,
                                     curnumber= CurNumber,
                                     maxnumber= MaxNumber },
               {TotalWeight,Client,IsLast} ) ->
    Weight = Client#client.weight,
    ClientIntensity = PhaseIntensity * Weight / TotalWeight,
    NUsers = round(case MaxNumber of
                       infinity -> %% only use the duration to set the number of users
                           Duration * ClientIntensity;
                       _ ->
                           TmpMax = case {IsLast,CurNumber == MaxNumber} of
                                        {true,_} ->
                                            MaxNumber-CurNumber;
                                        {false,true} ->
                                            0;
                                        {false,false} ->
                                            lists:max([1,trunc(MaxNumber * Weight / TotalWeight)])
                                    end,
                           lists:min([TmpMax, Duration*ClientIntensity])
                   end),
    ?LOGF("New arrival phase ~p for client ~p (last ? ~p): will start ~p users~n",
          [Arrival#arrivalphase.phase,Client#client.host, IsLast,NUsers],?NOTICE),
    {Arrival#arrivalphase{curnumber=CurNumber+NUsers}, {ClientIntensity, NUsers, Duration}}.

前面讲到每一个tsung client被分配用户数公式为:min(Duration * ClientIntensity, MaxNumber * Weight / TotalWeight)?/p>

  • 避免总人数超出限?/li>
  • 阶段Phase持续时长所产生用户数和tsung client分配用户数不至于产生冲突,一种协调策?/li>

再看一下launch加载一个终端用户时,会自动根据当前分配用户生成压力系数获得ts_stats:exponential(Intensity)下一个模拟用户产生等待生成的最长时间,单位为毫秒?/p>

do_launch({Intensity, MyHostName, PhaseId})->
    %%Get one client
    %%set the profile of the client
    case catch ts_config_server:get_next_session({MyHostName, PhaseId} ) of
        {'EXIT', {timeout, _ }} ->
            ?LOG("get_next_session failed (timeout), skip this session !~n", ?ERR),
            ts_mon:add({ count, error_next_session }),
            error;
        {ok, Session} ->
            ts_client_sup:start_child(Session),
            X = ts_stats:exponential(Intensity),
            ?DebugF("client launched, wait ~p ms before launching next client~n",[X]),
            {ok, X};
        Error ->
            ?LOGF("get_next_session failed for unexpected reason [~p], abort !~n", [Error],?ERR),
            ts_mon:add({ count, error_next_session }),
            exit(shutdown)
    end.

ts_stats:exponential逻辑引入了指数计算:

exponential(Param) ->
    -math:log(random:uniform())/Param.

继续往下看吧,隐藏了部分无关代码:

launcher(timeout, State=#launcher{nusers        = Users,
                                  phase_nusers  = PhaseUsers,
                                  phases        = Phases,
                                  phase_id      = Id,
                                  started_users = Started,
                                  intensity     = Intensity}) ->
    BeforeLaunch = ?NOW,
    case do_launch({Intensity,State#launcher.myhostname,Id}) of
        {ok, Wait} ->
                            ...
                        {continue} ->
                            Now=?NOW,
                            LaunchDuration = ts_utils:elapsed(BeforeLaunch, Now),
                            %% to keep the rate of new users as expected,
                            %% remove the time to launch a client to the next
                            %% wait.
                            NewWait = case Wait > LaunchDuration of
                                          true -> trunc(Wait - LaunchDuration);
                                          false -> 0
                                      end,
                            ?DebugF("Real Wait = ~p (was ~p)~n", [NewWait,Wait]),
                            {next_state,launcher,State#launcher{nusers = Users-1, started_users=Started+1} , NewWait}
                            ...
        error ->
            % retry with the next user, wait randomly a few msec
            RndWait = random:uniform(?NEXT_AFTER_FAILED_TIMEOUT),
            {next_state,launcher,State#launcher{nusers = Users-1} , RndWait}
    end.

下一个用户生成需要等?code>Wait - LaunchDuration毫秒时间?/p>

给出一个采样数据,只有一个从机,并且用户产生速度1秒一个,共产?0个用户:

<load>
    <arrivalphase phase="1" duration="50" unit="minute">
        <users maxnumber="10" interarrival="1" unit="second"/>
    </arrivalphase>
</load>

采集日志部分,记录了Wait时间值,其实总体时间还需要加?code>LaunchDuration(虽然这个值很小)?/p>

ts_launcher:(7:<0.63.0>) client launched, wait 678.5670934164623 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 810.2982455546687 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 1469.2208436232288 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 986.7202548184069 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 180.7484423006169 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 1018.9190235965457 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 1685.0156394273606 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 408.53992361334065 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 204.40900996137086 ms before launching next client
ts_launcher:(7:<0.63.0>) client launched, wait 804.6040921461512 ms before launching next client

总体来说,每一个用户生成间隔间不是固定值,是一个大约值,有偏差,但接近于目标设定?000毫秒生成一个用户标准间隔)?/p>

执行模拟终端用户会话流程

关于会话的说明:

  • 一个session元素中的定义一系列请求-响应等交互行为称之为一次完整会?/li>
  • 一个模拟用户需要执行一次完整会话,然后生命周期完成,然后结?/li>

模拟终端用户模块?code>ts_client(状态机),挂载?code>ts_client_sup下,?code>ts_launcher/ts_launcher_static调用ts_client_sup:start_child(Session)启动,是压测任务的最终执行者,承包了所有脏累差的活?/p>

  • 所有下一步需要执行的会话指令都需要向主机?code>ts_config_server请求
  • 执行会话指令
  • 具体协议调用相应协议插件,比如ts_mqtt组装会话消息
  • 建立网络Socket连接,封装众多网络通道
  • 发送请求数据,处理响应
  • 记录并发送监控数据和日志

ts_client?/p>

小结

简单梳理主从之间启动方式,从机数量分配策略,以具体压测任务如何在从机上分配和运行等内容?/p>

nieyong 2016-07-25 14:02 发表评论
]]>
Tsung笔记之主从模型篇 - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2016/07/23/431294.htmlnieyongnieyongSat, 23 Jul 2016 03:56:00 GMT//www.ot7t.com.cn/yongboy/archive/2016/07/23/431294.html//www.ot7t.com.cn/yongboy/comments/431294.html//www.ot7t.com.cn/yongboy/archive/2016/07/23/431294.html#Feedback0//www.ot7t.com.cn/yongboy/comments/commentRss/431294.html//www.ot7t.com.cn/yongboy/services/trackbacks/431294.html前言

本篇讲解Tsung大致功能组成、结构,以及主从模型,以便总体上掌握?/p>

总体组成

?/p>

tsung_controller ?tsung 这两个模块,负责分布式压测的核心功能?/p>

代码组成

从代码层次梳理一下tsung项目功能组成结构,便于一目了然,方便直接索引?/p>

tsung_struct?/p>

主从模型一?/h3>

设定环境为分布式环境下Tsung集群,下面简单梳理一下主、从节点启动流程?/p>

tsung_master_slave?/p>

流程大致说明?/p>

  • 主节点(tsung_controller)通过SSH或其它远程终端(后面会讲到操作更为轻量的完全替代SSH方式)连接到从服务器启动tsung从节点运行时环境
  • 主节点RPC批量启动tsung client进程
  • 主节点为每一个从节点启动会话监控,控制会话速度,开启ts_client模拟终端
  • 从节点请求主节点具体业务进程,获取会话指令以及会话具体内?/li>
  • 从节点建立到目标压测服务器的SOCKET网络连接,开始会?/li>
  • 主节点可以通过SSH/其它终端方式连接到目标压测服务器,启动从节点,然后收集数据(可选,具体细节,后续文字会讲到?/li>

这种模型下:

  • 全局严格控制模拟终端用户生成总量和生成速度
  • 主节点动态管理从节点生命周期,从生到死,并且掌握着所有会话细节,全局掌控
  • 从节点很轻,所有需要的会话指令,都必须请求主节点获?/li>

主从之间交互流程

下面一张图简单说明了主从之间核心模块交互流程,虽然粗略,核心点也算是涉及到了?/p>

tsung_slave_flow_detai?/p>

后面会对具体协议部分有更为详细论述?/p>

一次压测回话(ts_client)工作流?/h4>

其实是承接上一个流程图,已经启动了一个ts_client模块,即执行一个完整生命周期会话模拟终端。它的开启依赖于Tsung Controller启动ts_launch/ts_launch_static模块?/p>

大致流程图如下:

ts_client_structure?/p>

会话什么时候结?/h4>
  • 针对从节点上,(一个终端用户的)一次完整会话(session):
    • 请求主节点ts_config模块,获取会话Session信息,包含一次会话需要完成任务总数Count
    • 从节点ts_client 每执行一次事件,任务总数Count?
    • 当Count值为0时,说明任务执行完毕,ts_client生命周期圆满,一次完整会话结?/li>
  • 从节点所分配的所有会话都结束了,表示从节点生命周期也会结?/li>
  • 主节点控制的所有从节点都结束了,即所有会话都一一完成,那么整体压测也结束了,整个压测流程结束

小结

基于Erlang天生分布式基因支持,从节点的生死存亡完全受Tsung主节点的控制,按需创建,任务完成结束,主从协调行云流水般顺畅?/p>

嗯,后面将介绍主从实现的一些细节?/p>

nieyong 2016-07-23 11:56 发表评论
]]>
Tsung笔记之开?/title><link>//www.ot7t.com.cn/yongboy/archive/2016/07/22/431291.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Fri, 22 Jul 2016 07:36:00 GMT</pubDate><guid>//www.ot7t.com.cn/yongboy/archive/2016/07/22/431291.html</guid><wfw:comment>//www.ot7t.com.cn/yongboy/comments/431291.html</wfw:comment><comments>//www.ot7t.com.cn/yongboy/archive/2016/07/22/431291.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>//www.ot7t.com.cn/yongboy/comments/commentRss/431291.html</wfw:commentRss><trackback:ping>//www.ot7t.com.cn/yongboy/services/trackbacks/431291.html</trackback:ping><description><![CDATA[<h3 id="toc_0">前言</h3> <p>有测试驱动的开发模式,目的在于确保业务层面功能是准确的,每一次新增、修改等动作确保都不会影响到现有功能。功能开发完成了,需要部署到线上,系统能够承载多大的用户量呢,这时候就需要借助于性能压测,也称之为压力测试,界定系统能够承载具体容量上限,从容应对业务的运营需要,扩容或缩容,心中有底?/p> <p>工欲善其事,必先利其器。掌握一种压测工具,并切实应用到实践环境中,并以此不断迭代,压力测试驱动推动所开发后端应用处理性能逐渐完善?/p> <p>目前成熟的支持支持TCP、HTTP等连接通道的压测工具不少,以前接触过Apache JMeter,后面又接触?a href="tsung.erlang-projects.org">Tsung</a>,因为在实际环境下使用比较多,支持丰富的业务场景定义,并且可扩展性强,因此Tsung强力推荐之?/p> <h3 id="toc_1">为什么要选择Tsung</h3> <ul class="yibqv"> <li class="yibqv">基于Erlang,并发处理性能好,可以模拟足够多海量用户,只要你有足够多的机器</li> <li class="yibqv">受益于Erlang,天然支持分布式,很欢快的运行在一个集群中</li> <li class="yibqv">支持协议众多 WebDAV/WebScoket/MQTT/MySQL/PGSQL/Shell/AQMP/JABBER/XMPP/LDAP ?/li> <li class="yibqv">传输通道支持 TCP/UDP/SSL,更底层支持IPv4/IPv6</li> <li class="yibqv">支持单机绑定多个IP:无论是虚拟IP,还是物理网卡绑定IP,可以突破单机端?5535的限制,扩展尽可能多的网络连接出口地址</li> <li class="yibqv">支持监控被压测的服务器,通过Erlang Agent/SNMP/Munin </li> <li class="yibqv">压测细节XML可配置,这是一个完全基于情景的压力测试行为清单,依赖于你的想象,呈现完整业务的表达 <ul class="yibqv"> <li class="yibqv">场景可以是动态的,来自于文件、代码或者服务器响应可以构成下一个请求的参数,这就是可编程的请求?/li> <li class="yibqv">行为可以混搭,回话可以在不同场景中,按照不同的行为规范各自平行进?/li> <li class="yibqv">休眠,或暂停机制,是可以随机的,?/li> <li class="yibqv">压测用户产生方式,动态有序或随机</li> </ul></li> </ul> <p>总之,Tsung是一款开源的高性能分布式压力测试工具,支持可编程的情景化测试方案,要向发挥它的特性,依赖于人们的想象力和创造性?/p> <h3 id="toc_2">为什么要压力测试驱动??/h3> <p>软件/系统架构往往着眼于总体结构,这个可以是一个逐渐完善的过程。这种自我的不断完善的驱动往往来自于实践、线上考验。而压力测试可以提供一种推动,尽心尽力暴露着架构在性能容量存在的一些不足和缺陷,促使着向着更好的方向发展?/p> <p>系统的构建依赖于具体参与执行的人,就算是一群资深的工程师,业务上每一次功能的快速更迭、任何潜在局部修改都会导致影响、拖垮整体性能,这就是人们常说??a >蝴蝶效应</a>“,牵一发而动全身?/p> <p>如何提早感知并且提早修复,这就需要压力测试的驱动,并且压力测试应该成为一个常规化的例行行为,日常化的动作。在每一次修改之后,都要过一轮的压测的碾压之后,提供当前后端应用处理的性能、容量等具体指标,用于指导后续业务上线业务的开展?/p> <h3 id="toc_3">实际操作上的建议</h3> <p>在一般互联网公司,一般线上程序修改后之后,需要经过QA团队/部门全部功能回归、校验之后才能够上线,往往缺少压测环节,因为他/她们并不保证系统处理性能和容量是否恶化,系统的性能建立在系统总体的功能上,如何避免在性能上出现”牵一发而动全身“,建议有条件的QA同学/团队考虑增加性能压测环节,功?+ 性能双重回归,修改影响点清晰、透明化?/p> <h3 id="toc_4">笔记列表</h3> <p>本系列笔记,基于tsung-1.6.0源码基础上分析,运行环境为Linux Centos 6?/p> <p>笔记列表?/p> <ol> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/07/23/431294.html">Tsung笔记之主从模型篇</a></li> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/07/25/431310.html">Tsung笔记之主从资源协调篇</a></li> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/07/26/431322.html">Tsung笔记之压测端资源限制?/a></li> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/07/27/431340.html">Tsung笔记之分布式增强跳出SSH羁绊?/a></li> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/07/28/431354.html">Tsung笔记之IP直连支持?/a></li> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/07/29/431367.html">Tsung笔记之监控数据收集篇</a></li> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/07/30/431396.html">Tsung笔记之插件编写篇</a></li> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/08/08/431498.html">Tsung笔记?00万用户压测执行步骤篇</a></li> <li class="yibqv"><a href="//www.ot7t.com.cn/yongboy/archive/2016/08/16/431601.html">Tsung笔记之IP地址和端口限制突破篇</a></li> </ol> <p>为了方便理解,一些用词说明:</p> <ul class="yibqv"> <li class="yibqv">主节点,也称之为Master Node,指的是运行tsung_controller的应用服务实例,运行tsung启动应用自动产生“tsung_controller@机器?IP”节点名称,一般使用过Erlang的同学会很明?/li> <li class="yibqv">从节点,即tsung client应用实例,对?tsung/src/tsung 项目代码,由tsung_controller主节点控制启动、关闭、任务分配等</li> </ul> <h3 id="toc_5">小结</h3> <p>参与一个实时性交互强的项目,从一开始单机支撑不?万用户、平均请求响应时间约900毫秒,到目前混合部署的单机支?0万用户、平均响应时间为16毫秒,这个过程中Tsung不断的压测推动着架构逐渐稳定、系统承载容量、QPS优化等完全达标。这是一个压力测试驱动性能改进的流程,每一步的改进能够得到正向反馈?/p> <p>这一系列笔记,所谈核心是Tsung,无论是认知还是改进,最终都是为了理解利器的方方面面,方便着手于实践环境中,压测所带来的能量能够驱动我们的程序/服务性能提升、稳定运行,进而更好方便我们进行容量规划、线上部署等?/p> <img src ="//www.ot7t.com.cn/yongboy/aggbug/431291.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="//www.ot7t.com.cn/yongboy/" target="_blank">nieyong</a> 2016-07-22 15:36 <a href="//www.ot7t.com.cn/yongboy/archive/2016/07/22/431291.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TCP协议缺陷不完全记?/title><link>//www.ot7t.com.cn/yongboy/archive/2015/05/07/424917.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Thu, 07 May 2015 06:56:00 GMT</pubDate><guid>//www.ot7t.com.cn/yongboy/archive/2015/05/07/424917.html</guid><wfw:comment>//www.ot7t.com.cn/yongboy/comments/424917.html</wfw:comment><comments>//www.ot7t.com.cn/yongboy/archive/2015/05/07/424917.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>//www.ot7t.com.cn/yongboy/comments/commentRss/424917.html</wfw:commentRss><trackback:ping>//www.ot7t.com.cn/yongboy/services/trackbacks/424917.html</trackback:ping><description><![CDATA[<div class="wrap"> <h3 id="-">零。前言</h3> <p>TCP自从1974年被发明出来之后,历?0多年发展,目前成为最重要的互联网基础协议。有线网络环境下,TCP表现的如虎添翼,但在移动互联网和物联网环境下,稍微表现得略有不足?/p> <p>移动互联网突出特性不稳定:信号不稳定,网络连接不稳定。虽然目前发展到4G,手机网络带宽有所增强,但因其流动特性,信号也不是那么稳定:坐长途公交车,或搭乘城铁时,或周边上网密集时等环境,现实环境很复杂?/p> <p>以下讨论基于Linux服务器环境,假定环境为移动互联网环境。记录我目前所知TCP的一些不足,有所偏差,请给与指正?/p> <h3 id="-">一。三次握?/h3> <p>在确定传递数据之前需要三次握手,显然有些多余,业界提出了TCP Fast Open (TFO)扩展机制,两次握手之后就可以发送正常业务数据了。但这需要客户端和服务器端内核层面都支持才行?Linux内核3.6客户端,3.7支持服务器端?/p> <p><img alt="" src="https://lwn.net/images/2012/tfo/foc_use.png" /></p> <p>进阶阅读?a >TCP Fast Open: expediting web services</a></p> <h3 id="-">二。慢启动</h3> <p>一次的HTTP请求,应用层发送较大HTML页面的数据,需要经过若干个往返循环时?Round-Trip Time)之后,拥塞窗口才能够扩展到最大适合数值,中间过程颇为冗余。这个参数直接关系着系统吞吐量,吞吐量大了,系统延迟小了。但设置成多大,需要根据业务进行抉择?/p> <p>3.0内核之前初始化拥塞窗?initcwnd)大小?。一个已建立连接初始传输数据时可传?个MSS,若1个MSS?400那么一次性可传?K的数据,若为10,一次性可传?3K的数据?/p> <p>谷歌经过调研,建议移动互联网WEB环境下建议initcwnd设置?0,linux内核3.0版本之后默认值为10。遇到较低内核,需要手动进行设置?/p> <p>若是局域网环境有类似大数据或文件的传输需求,可以考虑适当放宽一些?/p> <p>若长连接建立之后传输的都是小消息,每次传输二进制不到4K,那么慢启动改动与否都是无关紧要的事情了?/p> <p>进阶阅读?/p> <ul class="yibqv"> <li class="yibqv"><a >Tuning initcwnd for optimum performance</a> </li><li class="yibqv"><a >Optimizing Your Linux Stack for Maximum Mobile Web Performance</a> </li><li class="yibqv"><a >An Argument for Increasing TCP's Initial Congestion Window</a> </li></ul> <h3 id="-head-of-line-blocking-hol-">三。线头阻?Head-of-line blocking, HOL)</h3> <p>TCP协议数据传输需要按序传输,可以理解为FIFO先进先出队列,当前面数据传输丢失后,后续数据单元只能等待,除非已经丢失的数据被重传并确认接收以后,后续数据包才会被交付给客户端设备,这就是所谓的线头(HOL,head-of-line blocking)阻塞。比较浪费服务器带宽又降低了系统性能,不高效?/p> <p><img alt="" src="//orm-chimera-prod.s3.amazonaws.com/1230000000545/images/hpbn_0208.png" /></p> <h4 id="1-">1. 多路复用不理?/h4> <p>HTTP/2提出的业务层面多路复用,虽然在一定程度上解决了HTTP/1.*单路传输问题,但依然受制于所依赖的TCP本身线头阻塞的缺陷。构建于TCP上层协议的多路复用,一旦发生出现线头阻塞,需要小心对待多路的业务数据发送失败问题?/p> <h4 id="2-tcp-keepalive-">2. TCP Keepalive机制失效</h4> <p>理论上TCP的Keepalive保活扩展机制,在出现线头阻塞的时候,发送不出去被一直阻塞,完全失效?/p> <p>类似于NFS文件系统,一般采用双向的TCP Keepalive保活机制,用以规避某一端因线头阻塞出现导致Keepalive无效的问题,及时感知一端存活情况?/p> <h4 id="3-">3. 线头阻塞超时提示</h4> <p>数据包发送了,启动接收确认定时器,超时后会重发,重发依然无确认,后续数据会一直堆积到待发送队列中,这里会有一个阻塞超时,算法很复杂。上层应用会接收到来自内核协议栈的汇?No route to host"的错误信息,默认不大?6分钟时间。在服务器端(没有业务心跳支持的情况下)发送数据前把终端强制断线,顺便结合TCPDUMP截包,等15分钟左右内核警告"EHOSTUNREACH"错误,应用层面就可以看到"No route to host"的通知?/p> <h3 id="-">四。四次摆?/h3> <p>两端连接成功建立之后,需要关闭时,需要产生四次交互,这在移动互联网环境下,显得有些多余。快速关闭,快速响应,冗余交互导致网络带宽被占用?/p> <h3 id="-">五。确认机制通知到上层应用?</h3> <p>这是一个比较美好的愿望,上层应用在调用内核层接口发送大段数据,内核完成发送并且收到对方完整确认,然后通知上层应用已经发送成功,那么在一些环境下,可以节省不少业务层面交互步骤?/p> <h3 id="-nat-">六。NAT网关超时</h3> <p>IPV4有限,局域网环境借助于NAT路由设备扩展了接入终端设备的数量。当建立一个TCP长连接时,NAT设备需要维护一个内部终端连接外部服务器所使用的内部IP:PORT与出去的IP:PORT映射对应关系。这个关系需要维护,比较耗费内存资源,有超时定时器清理,否则会导致内存撑爆?/p> <p>不同NAT设备超时值不一样,因此才需要心跳辅助,确保经过NAT设备的连接一直保持,避免因过长的时间被踢掉。比如针对中国移动网络连接持久时间一般设置为不超?分钟。各种网络略有差异,引入智能心跳机制比较合适?/p> <h3 id="-ip-">七。终端IP漫游</h3> <p>手机终端经常?G/3G/4G和WIFI之间切换,导致IP地址频繁发生改变。这样造成的后果就是已有的网络请求-响应被放弃和终止,需要人工干预或重新发起请求,存在资源浪费现象?/p> <p>支持Multipath TCP的终端设备,可以同时利用 2G/3G/4G ?WiFi 建立Mutlpath连接,通过多点优化网络下载,且互为备份。可以很好解决多个网络共存的情况下,一个网络中断不会导致全局请求处理中断,在设备的连接稳定和可靠性方面有所增强?/p> <p>当然,服务器之间也可以利用Multipath TCP的多个网络增强网络吞吐量?/p> <p>现状是:</p> <ol> <li class="yibqv">目前只有IOS 7以及后续版本支持 </li><li class="yibqv">Linux kernel 3.10实验分支上可以看到其支持身影,但何时合并到主分支上,暂时未知 </li></ol> <p>进阶阅读?a >A closer look at the scientific literature on Multipath TCP</a></p> <h3 id="-tcp-">八。TCP缓存膨胀</h3> <p>当路由器接收到的数据包超越其队列长度时,一般会随机丢包,以减少膨胀。针对上层应用程序而言,延迟增加,或误认为数据丢失,或连接丢失等?/p> <p>遇到这种情况,一般建议快速发包,以避免丢失的数据部分。内核层面今早升级到最新版,不低于3.6即可?/p> <p>进阶阅读?a >Bufferbloat</a></p> <h3 id="-tcp-">九。TCP不是绝对可靠?/h3> <ol> <li class="yibqv">IP和TCP协议在头部都会有check sum错误校验和机制,16位表示,反码相加,结果求反,具体可参?<a >TCP校验和的原理和实?/a>。一般错误很轻松可检测出来,但遇到两?6位数字相加后结果不变的情况就一筹莫展了 </li><li class="yibqv"> <p>以太网帧CRC32校验一般情况下都很OK,但可能遇到两端隔离多个路由器情况下,就有可能出现问题,比如陈硕老师提供的一张图?<img alt="" src="//hi.csdn.net/attachment/201106/6/0_13073208334BG8.gif" /></p> <blockquote> <p>上图中Client向Server发了一个TCP segment,这个segment先被封装成一个IP packet,再被封装成ethernet frame,发送到路由器(图中消息a)。Router收到ethernet frame (b),转发到另一个网?c),最后Server收到d,通知应用程序。Ethernet CRC能保证a和b相同,c和d相同;TCP header check sum的强度不足以保证收发payload的内容一样。另外,如果把Router换成NAT,那么NAT自己会构造c(替换掉源地址),这时候a和d的payload不能用tcp header checksum校验?/p></blockquote> </li><li class="yibqv"> <p>路由器可能偶然出现硬?内存故障导致收发IP报文出现多bit/单bit的反转或双字节交换,这个反转如果发生在payload区,那么无法用链路层、网络层、传输层的check sum查出来,只能通过应用层的check sum来检测。因此建议应用层要设法添加校验数据功能?/p> </li><li class="yibqv"> <p>大文件下载添加校验保证数据完整性,一般采用MD5,也用于防止安全篡改</p></li></ol> <p>参考资料:</p> <ul class="yibqv"> <li class="yibqv">Paper《When the CRC and TCP checksum disagree? </li><li class="yibqv"><a >The Limitations of the Ethernet CRC and TCP/IP checksums for error detection</a> </li><li class="yibqv"><a >Amazon S3遇到的单bit反转线上事故</a> </li></ul> <h3 id="-">十。小?/h3> <p>在这个满世界都是TCP的环境下,要想对TCP动大手术,这个是不太可能的,因为它已经固化到已有的系统内核和固件中。比如升级终端(比如Android/IOS等)系统/固件,Linux服务器内核,中间设备/中介设备(如路由器等),这是一个浩大工程,目前看也不现实?/p> <p>TCP位于系统内核层,内核空间的升级、修复,最为麻烦。服务器端升级还好说一些,用户终端系统的升级那叫一个难。用户空?用户核的应用升级、改造相对比来说可控性强,基于此Google专家们直接在UDP协议上进行构建、并且运行在用户空间的QUIC协议,综合了UDP的轻量和TCP的可靠性,是一个比较新颖的方向?/p> <p>若是对以后底层传输协议有所期望的话?/p> <ul class="yibqv"> <li class="yibqv">在用户空间(用户核)出现可以定制的协议,类似于QUIC </li><li class="yibqv">传统的TCP/UDP可以运行在用户空间,直接略过内核 </li><li class="yibqv">完整协议栈以静态链接库形式提供给上层应? </li><li class="yibqv">上层应用可以在编译、打包的时包含其所依赖协议栈静态链接库so文件 </li><li class="yibqv">dpdk/netmap等Packet IO框架 + 用户空间协议堆栈,数据将从网卡直接送达上层应用 </li><li class="yibqv">Linux内核重要性降低,常规的SSH系统维护 </li></ul> <p>虽然TCP存在这样、那样的问题,但目前还是无法绕过的网络基础设施,但稍微明白一些不足的地方,或许会对我们当前使用的现状有所帮助?/p></div><img src ="//www.ot7t.com.cn/yongboy/aggbug/424917.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="//www.ot7t.com.cn/yongboy/" target="_blank">nieyong</a> 2015-05-07 14:56 <a href="//www.ot7t.com.cn/yongboy/archive/2015/05/07/424917.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>随手记之TCP Keepalive笔记 - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2015/04/14/424413.htmlnieyongnieyongTue, 14 Apr 2015 09:08:00 GMT//www.ot7t.com.cn/yongboy/archive/2015/04/14/424413.html//www.ot7t.com.cn/yongboy/comments/424413.html//www.ot7t.com.cn/yongboy/archive/2015/04/14/424413.html#Feedback1//www.ot7t.com.cn/yongboy/comments/commentRss/424413.html//www.ot7t.com.cn/yongboy/services/trackbacks/424413.html

零。前言

TCP是无感知的虚拟连接,中间断开两端不会立刻得到通知。一般在使用长连接的环境下,需要心跳保活机制可以勉强感知其存活。业务层面有心跳机制,TCP协议也提供了心跳保活机制?/p>

一。TCP Keepalive解读

长连接的环境下,人们一般使用业务层面或上层应用层协议(诸如MQTT,SOCKET.IO等)里面定义和使用。一旦有热数据需要传递,若此时连接已经被中介设备断开,应用程序没有及时感知的话,那么就会导致在一个无效的数据链路层面发送业务数据,结果就是发送失败?/p>

无论是因为客户端意外断电、死机、崩溃、重启,还是中间路由网络无故断开、NAT超时等,服务器端要做到快速感知失败,减少无效链接操作?/p>

1. 交互过程

2. 协议解读

下面协议解读,基?a >RFC1122#TCP Keep-Alives?/p>

  1. TCP Keepalive虽不是标准规范,但操作系统一旦实现,默认情况下须为关闭,可以被上层应用开启和关闭?/li>
  2. TCP Keepalive必须在没有任何数据(包括ACK包)接收之后的周期内才会被发送,允许配置,默认值不能够小于2个小?/li>
  3. 不包含数据的ACK段在被TCP发送时没有可靠性保证,意即一旦发送,不确保一定发送成功。系统实现不能对任何特定探针包作死连接对?/li>
  4. 规范建议keepalive保活包不应该包含数据,但也可以包?个无意义的字节,比如0x0?/li>
  5. SEG.SEQ = SND.NXT-1,即TCP保活探测报文序列号将前一个TCP报文序列号减1。SND.NXT = RCV.NXT,即下一次发送正常报文序号等于ACK序列号;总之保活报文不在窗口控制范围?有一张图,可以很容易说明,但请仔细观察Tcp Keepalive部分?/li>

  1. 不太好的TCP堆栈实现,可能会要求保活报文必须携带?个字节的数据负载
  2. TCP Keepalive应该在服务器端启用,客户端不做任何改动;若单独在客户端启用,若客户端异常崩溃或出现连接故障,存在服务器无限期的为已打开的但已失效的文件描述符消耗资源的严重问题。但在特殊的NFS文件系统环境下,需要客户端和服务器端都要启用Tcp Keepalive机制?/li>
  3. TCP Keepalive不是TCP规范的一部分,有三点需要注意:
    • 在短暂的故障期间,它们可能引起一个良好连接(good connection)被释放(dropped?/li>
    • 它们消费了不必要的宽?/li>
    • 在以数据包计费的互联网消费(额外)花费金?/li>

二。Tcp keepalive 如何使用

以下环境是在Linux服务器上进行。应用程序若想使用,需要设置SO_KEEPALIVE套接口选项才能够生效?/p>

1. 系统内核参数配置

  1. tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s?h)?/li>
  2. tcp_keepalive_probes 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次?/li>
  3. tcp_keepalive_intvl,在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s?/li>

发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probes,就得到了从开始探测到放弃探测确定连接断开的时?/p>

若设置,服务器在客户端连接空闲的时候,?0秒发送一次保活探测包到客户端,若没有及时收到客户端的TCP Keepalive ACK确认,将继续等待15?2=30秒。总之可以?0s+30s=120秒(两分钟)时间内可检测到连接失效与否?/p>

以下改动,需要写入到/etc/sysctl.conf文件?/p>

net.ipv4.tcp_keepalive_time=90
net.ipv4.tcp_keepalive_intvl=15
net.ipv4.tcp_keepalive_probes=2

保存退出,然后执行sysctl -p生效。可通过 sysctl -a | grep keepalive 命令检测一下是否已经生效?/p>

针对已经设置SO_KEEPALIVE的套接字,应用程序不用重启,内核直接生效?/p>

2. Java/netty服务器如何使?/h4>

只需要在服务器端一方设置即可,客户端完全不用设置,比如基于netty 4服务器程序:

ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .childOption(ChannelOption.SO_KEEPALIVE, true)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(
                             new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(port).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();

Java程序只能做到设置SO_KEEPALIVE选项,至于TCP_KEEPCNT,TCP_KEEPIDLE,TCP_KEEPINTVL等参数配置,只能依赖于sysctl配置,系统进行读取?/p>

3. C语言如何设置

下面代码摘取自libkeepalive源码,C语言可以设置更为详细的TCP内核参数?/p>

int socket(int domain, int type, int protocol)
{
  int (*libc_socket)(int, int, int);
  int s, optval;
  char *env;

  *(void **)(&libc_socket) = dlsym(RTLD_NEXT, "socket");
  if(dlerror()) {
    errno = EACCES;
    return -1;
  }

  if((s = (*libc_socket)(domain, type, protocol)) != -1) {
    if((domain == PF_INET) && (type == SOCK_STREAM)) {
      if(!(env = getenv("KEEPALIVE")) || strcasecmp(env, "off")) {
        optval = 1;
      } else {
        optval = 0;
      }
      if(!(env = getenv("KEEPALIVE")) || strcasecmp(env, "skip")) {
        setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
      }
#ifdef TCP_KEEPCNT
      if((env = getenv("KEEPCNT")) && ((optval = atoi(env)) >= 0)) {
        setsockopt(s, SOL_TCP, TCP_KEEPCNT, &optval, sizeof(optval));
      }
#endif
#ifdef TCP_KEEPIDLE
      if((env = getenv("KEEPIDLE")) && ((optval = atoi(env)) >= 0)) {
        setsockopt(s, SOL_TCP, TCP_KEEPIDLE, &optval, sizeof(optval));
      }
#endif
#ifdef TCP_KEEPINTVL
      if((env = getenv("KEEPINTVL")) && ((optval = atoi(env)) >= 0)) {
        setsockopt(s, SOL_TCP, TCP_KEEPINTVL, &optval, sizeof(optval));
      }
#endif
    }
  }

   return s;
}

4. 针对已有程序没有硬编码KTTCP EEPALIVE实现

完全可以借助于第三方工具libkeepalive,通过LD_PRELOAD方式实现。比?/p>

LD_PRELOAD=/the/path/libkeepalive.so java -jar /your/path/yourapp.jar &

这个工具还有一个比较方便的地方,可以直接在程序运行前指定TCP保活详细参数,可以省去配置sysctl.conf的麻烦:

LD_PRELOAD=/the/path/libkeepalive.so \
  > KEEPCNT=20 \
  > KEEPIDLE=180 \
  > KEEPINTVL=60 \
  > java -jar /your/path/yourapp.jar &

针对较老很久不更新的程序,可以尝试一下嘛?/p>

三。Linux内核层面对keepalive处理

参数和定?/p>

#define MAX_TCP_KEEPIDLE     32767
#define MAX_TCP_KEEPINTVL     32767
#define MAX_TCP_KEEPCNT          127
#define MAX_TCP_SYNCNT          127

#define TCP_KEEPIDLE          4     /* Start keeplives after this period */
#define TCP_KEEPINTVL          5     /* Interval between keepalives */
#define TCP_KEEPCNT          6     /* Number of keepalives before death */

net/ipv4/Tcp.c,可以找到对应关系:

     case TCP_KEEPIDLE:
          val = (tp->keepalive_time ? : sysctl_tcp_keepalive_time) / HZ;
          break;
     case TCP_KEEPINTVL:
          val = (tp->keepalive_intvl ? : sysctl_tcp_keepalive_intvl) / HZ;
          break;
     case TCP_KEEPCNT:
          val = tp->keepalive_probes ? : sysctl_tcp_keepalive_probes;
          break;

初始化:

 case TCP_KEEPIDLE:
      if (val < 1 || val > MAX_TCP_KEEPIDLE)
           err = -EINVAL;
      else {
           tp->keepalive_time = val * HZ;
           if (sock_flag(sk, SOCK_KEEPOPEN) &&
               !((1 << sk->sk_state) &
                 (TCPF_CLOSE | TCPF_LISTEN))) {
                __u32 elapsed = tcp_time_stamp - tp->rcv_tstamp;
                if (tp->keepalive_time > elapsed)
                     elapsed = tp->keepalive_time - elapsed;
                else
                     elapsed = 0;
                inet_csk_reset_keepalive_timer(sk, elapsed);
           }
      }
      break;
 case TCP_KEEPINTVL:
      if (val < 1 || val > MAX_TCP_KEEPINTVL)
           err = -EINVAL;
      else
           tp->keepalive_intvl = val * HZ;
      break;
 case TCP_KEEPCNT:
      if (val < 1 || val > MAX_TCP_KEEPCNT)
           err = -EINVAL;
      else
           tp->keepalive_probes = val;
      break;

这里可以找到大部分处理逻辑,net/ipv4/Tcp_timer.c:

static void tcp_keepalive_timer (unsigned long data)
{
     struct sock *sk = (struct sock *) data;
     struct inet_connection_sock *icsk = inet_csk(sk);
     struct tcp_sock *tp = tcp_sk(sk);
     __u32 elapsed;

     /* Only process if socket is not in use. */
     bh_lock_sock(sk);
     if (sock_owned_by_user(sk)) {
          /* Try again later. */
          inet_csk_reset_keepalive_timer (sk, HZ/20);
          goto out;
     }

     if (sk->sk_state == TCP_LISTEN) {
          tcp_synack_timer(sk);
          goto out;
     }
    // 关闭状态的处理
     if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {
          if (tp->linger2 >= 0) {
               const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;

               if (tmo > 0) {
                    tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
                    goto out;
               }
          }
          tcp_send_active_reset(sk, GFP_ATOMIC);
          goto death;
     }

     if (!sock_flag(sk, SOCK_KEEPOPEN) || sk->sk_state == TCP_CLOSE)
          goto out;

     elapsed = keepalive_time_when(tp);

     /* It is alive without keepalive 8) */
     if (tp->packets_out || sk->sk_send_head)
          goto resched;

     elapsed = tcp_time_stamp - tp->rcv_tstamp;

     if (elapsed >= keepalive_time_when(tp)) {
          if ((!tp->keepalive_probes && icsk->icsk_probes_out >= sysctl_tcp_keepalive_probes) ||
               (tp->keepalive_probes && icsk->icsk_probes_out >= tp->keepalive_probes)) {
               tcp_send_active_reset(sk, GFP_ATOMIC);
               tcp_write_err(sk); // 向上层应用汇报连接异?/span>
               goto out;
          }
          if (tcp_write_wakeup(sk) <= 0) {
               icsk->icsk_probes_out++; // 这里仅仅是计数,并没有再次发送保活探测包
               elapsed = keepalive_intvl_when(tp);
          } else {
               /* If keepalive was lost due to local congestion,
               * try harder.
               */
               elapsed = TCP_RESOURCE_PROBE_INTERVAL;
          }
     } else {
          /* It is tp->rcv_tstamp + keepalive_time_when(tp) */
          elapsed = keepalive_time_when(tp) - elapsed;
     }

     TCP_CHECK_TIMER(sk);
     sk_stream_mem_reclaim(sk);

resched:
     inet_csk_reset_keepalive_timer (sk, elapsed);
     goto out;

death:    
     tcp_done(sk);

out:
     bh_unlock_sock(sk);
     sock_put(sk);
}

keepalive_intvl_when 函数定义?/p>

static inline int keepalive_intvl_when(const struct tcp_sock *tp)
{
    return tp->keepalive_intvl ? : sysctl_tcp_keepalive_intvl;
}

四。TCP Keepalive 引发的错?/h3>

启用TCP Keepalive的应用程序,一般可以捕获到下面几种类型错误

  1. ETIMEOUT 超时错误,在发送一个探测保护包经过(tcp_keepalive_time + tcp_keepalive_intvl * tcp_keepalive_probes)时间后仍然没有接收到ACK确认情况下触发的异常,套接字被关?pre>java.io.IOException: Connection timed out
  2. EHOSTUNREACH host unreachable(主机不可?错误,这个应该是ICMP汇报给上层应用的?pre>java.io.IOException: No route to host
  3. 链接被重置,终端可能崩溃死机重启之后,接收到来自服务器的报文,然物是人非,前朝往事,只能报以无奈重置宣告之?pre>java.io.IOException: Connection reset by peer

五。常见的使用模式

  1. 默认情况下使用keepalive周期?个小时,如不选择更改,属于误用范畴,造成资源浪费:内核会为每一个连接都打开一个保活计时器,N个连接会打开N个保活计时器? 优势很明显:
  • TCP协议层面保活探测机制,系统内核完全替上层应用自动给做好了
  • 内核层面计时器相比上层应用,更为高效
  • 上层应用只需要处理数据收发、连接异常通知即可
  • 数据包将更为紧凑
  1. 关闭TCP的keepalive,完全使用业务层面心跳保活机? 完全应用掌管心跳,灵活和可控,比如每一个连接心跳周期的可根据需要减少或延长
  2. 业务心跳 + TCP keepalive一起使用,互相作为补充,但TCP保活探测周期和应用的心跳周期要协调,以互补方可,不能够差距过大,否则将达不到设想的效果。朋友的公司所做IM平台业务心跳2-5分钟智能调整 + tcp keepalive 300秒,组合协作,据说效果也不错?/li>

虽然说没有固定的模式可遵循,那么有以下原则可以参考:

  • 不想折腾,那就弃用TCP Keepalive吧,完全依赖应用层心跳机制,灵活可控性强
  • 除非可以很好把控TCP Keepalive机制,那就可以根据需要自由使用吧

六。注意和 HTTP的Keep-Alive区别

  • HTTP协议的Keep-Alive意图在于连接复用,同一个连接上串行方式传递请?响应数据
  • TCP的keepalive机制意图在于保活、心跳,检测连接错误?/li>

七。引?/h3>
  1. 我来说说TCP保活
  2. TCP Keepalive HOWTO


nieyong 2015-04-14 17:08 发表评论
]]>移动APP后端网络处理一些问题记?/title><link>//www.ot7t.com.cn/yongboy/archive/2015/03/30/423963.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Mon, 30 Mar 2015 09:11:00 GMT</pubDate><guid>//www.ot7t.com.cn/yongboy/archive/2015/03/30/423963.html</guid><wfw:comment>//www.ot7t.com.cn/yongboy/comments/423963.html</wfw:comment><comments>//www.ot7t.com.cn/yongboy/archive/2015/03/30/423963.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>//www.ot7t.com.cn/yongboy/comments/commentRss/423963.html</wfw:commentRss><trackback:ping>//www.ot7t.com.cn/yongboy/services/trackbacks/423963.html</trackback:ping><description><![CDATA[<div class="wrap"> <h3 id="-">零。前言</h3> <p>这里讲的移动APP主要指的是安卓平台,大部分情况也适用于IOS等移动平台,可能重点嘛会在后半部分呢?/p> <h3 id="-sdk-">一。嵌入多SDK存在的隐?/h3> <p>但凡一个常用的APP都会嵌入至少一个SDK,不同来源或同一来源,有广告SDK,有推送SDK,有性能汇报SDK,有用户跟踪SDK,有统计流量SDK等,有支付SDK等等。虽然带来了功能的复用和解耦,便于纵向扩展,但可能会存在:</p> <ol> <li class="yibqv">一个SDK可以看做一个后台Service,多个SDK多个Service存在(粗略上计算? </li><li class="yibqv">运行缓慢的问题,多个SDK或多功能的模块(若不按需加载的话)会导致运行缓慢,符合木桶理论,最慢的那个拖慢了总体步伐 </li><li class="yibqv">每一个SDK都有自己核心诉求,各自为政,实现方式各异。总体架构是否合适,带来了隐形的兼容成本 </li><li class="yibqv">彼此协调运作兼容性问题,一旦出现某个隐含BUG,会不会导致连锁反应 </li><li class="yibqv">CPU、内存、网络等资源竞争问题,想协调都很? </li><li class="yibqv">网络资源各自为政,无法共享,更不用提连接复用 </li></ol> <p>若是同一来源的SDK,当问题发生的时候还能够有所协作稍作调整,虽然这也是比较理想的情况。外部的SDK可能问题反馈和BUG修正,时效性就不太好说了?/p> <p>解耦虽然带来了功能的扩展性,带随之带来了资源利用方面的重复和浪费,愈多的SDK嵌入越有可能导致总体运行缓慢,需要谨慎使用?/p> <h3 id="-app-service-">二。APP后台Service数量很多</h3> <p>一般安卓手机会提供后台进程的查看,一般APP会启动多个后台服务,比如爱奇艺APP就很变态,5个服务存在(优酷APP也好不了哪去?-4个服务存在)?/p> <p><img alt="" src="//www.ot7t.com.cn/images/blogjava_net/yongboy/Windows-Live-Writer/5264ea818301_D4ED/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20150327111706_2.png" /></p> <p>一般较大公司,都会自己开发SDK,但碍于KPI等业绩考核,很少有人会认真从总体上考虑把多个Serice服务合并为一个,节省一点用户的资源占用,不过那需要考验研发人员的架构能力了?/p> <p>虽然小白用户不一定会查看后台服务,但若干个后台服务,每一个都需要维护自身服务存活检测,每一个都会占用若干内存,若合并为一个,自然减少了CPU占用和内存资源占用,以及维护成本,还是有些小必要的?/p> <h3 id="-http-">三。HTTP短连接请求过于频?/h3> <p>一般来讲,打开一个APP的时候,通过Wireshark抓包工具可以看到若干个HTTP连接瞬间建立,诸如淘宝,天猫,美团,优酷等APP,满屏的都是HTTP请求,看着让人感觉有些小恐怖,业务数据、设备信息上传、SDK信息抓取、埋点跟踪、头像图片、HTML资源等,那叫一个庞大?/p> <p>但也没办法,业务展示那么丰富,需要消耗过多的资源。大部分请求都只专注于一部分数据。针对单一显示界面屏幕,可以通过一个请求获得合并后的响应结果,减少网络资源消耗?/p> <p>手机淘宝和手机QQ,在HTTP请求优化方面,已经在使用IP直连、HTTP长连接、SPDY等进行加速处理,值得学习?/p> <h3 id="-tcp-">四。TCP长连接利用率不高</h3> <p>这里所说长连接,更多的是指TCP方式连接,也包括HTTP方式的长连接,但更多指的是具有双向通道的长连接。单通道的方式,协议所限,资源利用率不是那么高?/p> <p>当前TCP长连接在其存活期间,大都只专注于传输一类具体业务内容,比如PUSH推送,一台手机连接一天,也接收不了几条消息,最频繁的就是心跳保活数据包传送了?/p> <p>一旦涉及到新的业务,大家都是要新建一条全新TCP连接,彼此业务不关联嘛,看上去很耦合啊,但却造成了企业大量的重复资源浪费:新的业务需要处理另外的连接接入,资源重复投入,业务层面嘛,也会存在一半左右的重叠。当然,若不在乎这些的话,就另当别论了,但是你要是能够为消费者手机的资源消耗考虑,那便是用户的福音了?/p> <p><img alt="" src="//www.ot7t.com.cn/images/blogjava_net/yongboy/Windows-Live-Writer/5264ea818301_D4ED/tcp_%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8_thumb.png" /></p> <p>长连接的业务层面多路复用,支持类似于摇一摇、抢红包、客服、客服咨询等,同一个连接传输不同类型的数据,即节省了服务器资源,又提高了网络连接利用率,两端的维护成本降低。针对客户端而言,多路业务复用在一条TCP连接上传输,需要业务路?+ 相应业务处理即可。服务器端嘛,接入不同的业务处理,依靠业务路由进行消息分发?/p> <p>题外话,举一个例子,平时任一时间点约3000千万用户连接,按每台?00万计算,10-20台机器接入处理足可,推送啊,聊天啊,客服咨询,平常再来一个好运摇一摇都行。年底了要扩容到1亿左右用户摇啊摇的抢东抢西抢红包,还是同一个连接的方式,不过增加了新的业务类型数据传递,服务器嘛,扩展到50-80台就很OK了。用户量一旦降落,撤下多余机器就行。或许,有空可以写一篇供一亿用户在线的系统架构的方?:))</p> <h3 id="-http-">五。HTTP 持久连接使用</h3> <p>针对HTTP/1.1可能很多人的思维方式,还停留在浏览器环境,一般浏览器有不支持或支持不够好,但在非浏览器环境下,针对APP应用的环境,少了很多传统浏览器环境的限制,但要完全发挥和释放HTTP/1.1协议所带来的持久特性,这便需要深度理解协议规范和具体的使用环境等进行抉择?/p> <h4 id="1-http-1-1-keepalive-">1. HTTP/1.1 KeepAlive特性使?/h4> <p><img alt="" src="//cdn.nginx.com/wp-content/uploads/2014/03/tcpka.png" height="120" width="762" /></p> <p>虽说HTTP/1.1 Keep-Alive特性支持多个请求在同一个连接上排队发送,在浏览器端正常的HTML等资源请求,会带来线头阻塞弊端,后一个请求依赖于前一个请求完成,一旦出现阻塞,后续请求只能排队等待?/p> <p>但若针对非浏览器、业务模型不是很复杂的环境,比如日志采集/跟踪等常见业务,属于简单循环的请求-响应模型,响应仅仅需要HTTP 200状态码即可(这要求服务器端接收之后异步处理直接返回200状态),后续的请求只需要排队,并且不会对延迟有苛刻要求,那么Keep-Alive特性就很适合。一般出现阻塞,那就意味着网络状况不容乐观,关闭然后重键一个HTTP持久连接就行?/p> <p>有些环境,只需要发送数据,客户端不关心有没有响应(或不接收响应),类似于UDP的方式,比如实时日志(突出实时特性),但这需要客户端异步处理响应?/p> <p>一般视频网站都会有实时跟踪用户正在播放中的视频播放状态,那就需要建立一个持久的HTTP/1.1连接,即时、持续传递视频观看事件,自然就避免了短连接多次创建、关闭的开销?/p> <h4 id="2-http-1-1-pipelining">2. HTTP/1.1 Pipelining</h4> <p>建立在Keep-Alive持久化基础之上,中文译为管线化,支持连续的幂等的GET/HEAD方法请求,实际环境下,并没有被浏览器所支持?/p> <p>同一个连接,处理同样的三次请?响应,Keep-Alive和Pipelining方式不同?/p> <p><img alt="" src="//upload.wikimedia.org/wikipedia/commons/thumb/1/19/HTTP_pipelining2.svg/300px-HTTP_pipelining2.svg.png" /></p> <p>浏览器环境不支持的特性,在应用环境下或许可以找到其适用空间。比如具有通过GET方式提交数据的情况(比如GET方式汇报地理位置,GET方式提交用户设备信息,GET方式汇报用户手机抖动情况等),多个请求批量发送?/p> <p>但换一个思维方式,若能够合并批量请求为一个POST提交,不走管线化方式,可能会更合适一些?/p> <p>针对不太重要数据,发送完毕,不用等待响应?/p> <h4 id="3-http-2">3. HTTP/2</h4> <p>若想了解HTTP/2的规范,可以参考本博客的其它文字。目前客户端库和服务器端库,支持都不太好,需要观察一段时间,公司实力够,完全可以自主开发,或基于SPDY 2.0也可,目前淘宝APP、腾讯手QQ等,也都在使用?/p> <p>HTTP/1.1能够完全解决的问题,就没有必要使用HTTP/2,后者造成了实现有些复杂,可能中介设备(网关、代理、CDN等)都还没有为之做好准备呢,但HTTP/2毕竟是趋势?/p> <p>虽然规范只定义一个Hostname只允许一个连接,可能实际情况下会需?-3条长连接以争夺较多的网络带宽资源?/p> <h3 id="-">六。小?/h3> <p>怎么说呢,在当前移动APP环境下:</p> <ul class="yibqv"> <li class="yibqv">TCP长连接的通道用途的传输潜力和利用率很低 </li><li class="yibqv">HTTP/1.1的持久特性很容易被人忽略,未能正确使? </li><li class="yibqv">HTTP/2多路复用值得期待 </li></ul> <p>无论哪一种方式,都需要熟悉协议和网络,适合的环境使用适合的协议特性,才能够发挥出潜在的性能出来?/p></div><img src ="//www.ot7t.com.cn/yongboy/aggbug/423963.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="//www.ot7t.com.cn/yongboy/" target="_blank">nieyong</a> 2015-03-30 17:11 <a href="//www.ot7t.com.cn/yongboy/archive/2015/03/30/423963.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HTTP/2笔记之错误处理和安全 - 四川福利彩票快乐12快乐12开奖直播快乐12开奖辽宁福彩快乐12快乐彩12选5走势图//www.ot7t.com.cn/yongboy/archive/2015/03/24/423791.htmlnieyongnieyongTue, 24 Mar 2015 07:27:00 GMT//www.ot7t.com.cn/yongboy/archive/2015/03/24/423791.html//www.ot7t.com.cn/yongboy/comments/423791.html//www.ot7t.com.cn/yongboy/archive/2015/03/24/423791.html#Feedback0//www.ot7t.com.cn/yongboy/comments/commentRss/423791.html//www.ot7t.com.cn/yongboy/services/trackbacks/423791.html

零。前言

这里整理了一下错误和安全相关部分简单记录?/p>

一。HTTP/2错误

1. 错误定义

HTTP/2定义了两种类型错误:

  • 导致整个连接不可使用的错误为连接错误(connection error)
  • 单独出现在单个连接上的错误为流错?stream error)

2. 错误代码

错误代码?2位正整数表示错误原因,RST_STREAM和GOAWAY帧中包含?/p>

未知或不支持的错误代码可以选择忽略,或作为INTERNAL_ERROR错误对待都可以?/p>

3. 连接错误处理

一般来讲连接错误很严重,会导致处理进程无法进行下去,或影响到整个连接的状态?/p>

  • 终端一旦遇上连接错误,需第一时间在最后一个可用流上发送包含错误原因GOAWAY帧过去,然后关闭连接
  • GOAWAY有可能不被对端成功接收到,若成功接收可获得连接被终止的原?
  • 终端可在任何时间终止连接,也可以把流错误作为连接错误对待。但都应该在关闭连接之前发送一个GOAWAY帧告知对?

4. 流错?/h4>

一般来讲具体流上的流错误不会影响到其它流的处理?/p>

  • 终端检测到流错误,需要发送一个RST_STREAM帧,其包含了操作到错误流标识?
  • RST_STREAM应当是发送错误流最后一个帧,内含错误原因?
  • 发送方在发送之后,需要准备接收对端将要或即将发送过来的帧数据,处理方式就是忽略之,除非是可以修改连接状态帧
  • 一般来讲,终端不应该发送多个RST_STREAM帧,但若在一个往返时间之后已关闭的流上能够继续接收帧,则需要发送再次发送一个RST_STREAM帧,处理这种行为不端的实现?
  • 终端在接收到RST_STREAM帧之后,不能响应一个RST_STREAM帧,避免死循?

5. 连接终止

TCP连接被关闭或重置时仍有处?open"?half closed"的流将不能自动重试?/p>

二。HTTP/2安全注意事项

1. 跨协议攻?/h4>

跨协议攻击,字面上理解就很简单,比如攻击者构建HTTP/1.1请求直接转发给仅仅支持HTTP/2的服务器,以期待获取攻击效果?/p>

这里有一篇讲解跨协议攻击的文章://www.freebuf.com/articles/web/19622.html

TLS的加密机制使得攻击者很难获得明文,另外TLS的ALPN协议扩展可以很轻松处理请求是否需要作为HTTP/2请求进行处理,总之可有效阻止对基于TLS的其它协议攻击?/p>

基于标准版TCP没有TLS和ALPN的帮忙,客户端所发送连接序言前缀为PRI字符串用来混淆HTTP/1.1服务器,但对其它协议没有提供保护,仅限于此。但在处理时,若接收到HTTP/1.1的请求,没有包含Upgrade升级字段,则需要认为是一个跨协议攻击?/p>

总之,程序要尽可能的健壮,容错,针对非法的请求,直接关闭对方连接?/p>

2. 中介端数据转换封装的攻击

中介所做的HTTP/1.1和HTTP/2之间转换,会存在攻击点:

  1. HTTP/2头字段名称编码允许使用HTTP/1.1没有使用到的头字段名称,中介在转换HTTP/2到HTTP/1.1时就容易出现包含非法请求头字段HTTP/1.1数据?
  2. HTTP/2允许头字段值可以是非法值,诸如回车(CR, ASCII 0xd), 换行 (LF, ASCII 0xa), 零字?(NUL, ASCII 0x0),这在逐字解析实现时是一个风险?

解决方式,一旦发现非法头字段名称,以及非法头字段值,都作为不完整、残缺数据对待,或丢弃,或忽略?/p>

3. 推送内容的缓存

推送内容有保证的服务器提供,是否缓存由头字段Cache-Control控制?/p>

但若服务器上多租户形式(SAAS),每一个租户使用一小部分URL空间,比?tenant1.domain.com,tenant2.domain.com,服务器需要确保没有授权的租户不能够推送超于预期的资源,覆盖已有内容?/p>

原始服务器没有被授权使用推送,既不能够违规发送推送,也不能够被缓存?/p>

4. 拒绝服务攻击注意事项

  • HTTP/2因为要为流、报头压缩、流量控制等特性占用资源较多,因此针对每一个连接的内存分配要设置限额,否则很少的连接占满内存,无法正常服务
  • 针对单个连接,规范对PUSH_PROMISE帧数量没有约束,但客户端需要设置一个上限值,这也是确定需要维护的"reserved (remote)"状态的数量,超出限额需要报ENHANCE_YOUR_CALM类型流错?
  • SETTINGS帧有可能会被滥用导致对端需要花费时间解析处理设置限制等,滥用情况包括包含未定义的参数,以及同一个参数多次出现等,类似于WINDOW_UPDATE和PRIORITY帧都会存在滥用的情况;这些帧被滥用导致资源耗费情况严重
  • 大量小帧或空帧一样会被滥用,但又符合逻辑,耗费服务器资源在处理报文头部上面。比如空负载DATA帧,以及用于携带报文头部数据的CONTINUATION帧,都属于安全隐?
  • 报头压缩存在潜在风险,也会被滥用,详情可参考HPACK协议第七章://http2.github.io/http2-spec/compression.html#Security
  • 终端中途发送的SETTINGS帧所定义参数不是立即可以生效的,这会导致对端在实际操作时可能会超过最新的限制。建议直接在连接建立时在连接序言内包含设置值,就算如此,客户端也会存在超出服务器端连接序言中所设置的最新限定值?

总之,诸如SETTINGS帧、小帧或空帧,报头压缩被合理滥用时,表明上看符合逻辑,会造成资源过度消耗。这需要服务器端监控跟踪到此种行为,并且设置使用数量的上限,一旦发现直接报ENHANCE_YOUR_CALM类型连接错误?/p>

5. 报头块大小限?/h4>

报头块过大导致实现需要维护大量的状态开销。另外,根据报头字段进行路由的情况,若此报头字段出现在一系列报头块帧的最后一个帧里面,可能会导致无法正常路由到目的地。若被缓存会导致耗费大量的内存。这需要设置SETTINGS_MAX_HEADER_LIST_SIZE参数限制报头最大值,以尽可能的避免出现以上情况?/p>

服务器一旦接收到超过报头限制请求,需要响应一?31(请求头过大?HTTP状态码,客户端呢可直接丢掉响应?/p>

6. 压缩使用的安全隐?/h4>
  • 针对安全通道,不能使用同一个压缩字典压缩保密的关键数据和易受攻击者控制的数据
  • 来源数据不能确定为完全可靠,就不应该使用压缩机制
  • 通用流的压缩不能在基于TLS的HTTP/2上使用这一部分,可参?//http2.github.io/http2-spec/compression.html#Security

7. 填充使用的安全隐?/h4>

一般来讲,填充可用来混淆帧的真实负载长度,稍加保护,降低攻击的可能性。但若不当的填充策略:固定填充数、可轻松推导出填充规则等情况都会降低保护的力度,都有可能会被攻击者破解?/p>

中介设备应该保留DATA帧的填充(需要避免如上所述一些情况),但可丢弃HEADERS和PUSH_PROMISE帧的填充?/p>

三。TLS

HTTP/2加密建立在TLS基础,关于TLS,维基百科上有解释://zh.wikipedia.org/wiki/%E5%82%B3%E8%BC%B8%E5%B1%A4%E5%AE%89%E5%85%A8%E5%8D%94%E8%AD%B0

摘取一张图,可说明基于ALPN协议扩展定义的协商流程:

其它要求?/p>

  • 只能基于TLS >= 1.2版本。目前TLS 1.3为草案版本,正式版本目前尚未可知。目前只有TLS 1.2可选?
  • 必须支持Server Name Indication (SNI) [TLS-EXT]扩展,客户端在连接协商阶段需要携带上域名
  • 基于TLS 1.3或更高版本构建,仅需要支持SNI扩展。TLS 1.2要求较多
  • 基于TLS 1.2构建
    • 必须禁用压缩机制。不恰当压缩机制会导致信息外露,HTTP/2报头有压缩机?
    • 必须禁用重新协商机制。终端对待TLS 1.2重新协商作为PROTOCOL_ERROR类型连接错误对待;密码套件加密次数限制导致连接一直挂起等待不可用
    • 终端可以通过重新协商提供对客户端凭证保护功能在握手期间,重新协商必须发生在发送连接序言之前进行。服务器当看到重新协商请求时应该请求客户端证书在连接建立?
    • 当客户端请求受保护的特定资源时,服务器可以响应HTTP_1_1_REQUIRED错误,可有效阻止重新协商机制

四。小?/h3>

这里简单记录HTTP/2错误和安全相关事项,本系列规范学习到此告一段落?/p>

]]>