??xml version="1.0" encoding="utf-8" standalone="yes"?>BlogJava-聂永的博?/title><link>http://www.qpkxbc.shop/yongboy/</link><description>记录工作/学习的点Ҏ滴?/description><language>zh-cn</language><lastBuildDate>Sat, 24 Aug 2019 00:05:43 GMT</lastBuildDate><pubDate>Sat, 24 Aug 2019 00:05:43 GMT</pubDate><ttl>60</ttl><item><title>HTTP API设计W记http://www.qpkxbc.shop/yongboy/archive/2018/01/02/433000.htmlnieyongnieyongTue, 02 Jan 2018 12:53:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2018/01/02/433000.htmlhttp://www.qpkxbc.shop/yongboy/comments/433000.htmlhttp://www.qpkxbc.shop/yongboy/archive/2018/01/02/433000.html#Feedback0http://www.qpkxbc.shop/yongboy/comments/commentRss/433000.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/433000.html前言

最q一D|_要ؓ一个手机终端APPE序从零开始设计一整套HTTP APIQ因为面向的用户很固定,一个新的移动端APP。目前还是项目初期,自然要求一切快速、从Q实用性ؓ丅R?/p>

下面逐一我们是如何设计HTTP APIQ虽然相对大部分言Q没有什么新意,但对我来说很新鲜的。避免忘_着I闲快记录下来?/p>

技术堆栈的选择

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

JavaQ好几年没有过了,那么复杂的解x案,再加上团队内也没什么h?……

团队使用qLuaQ基于OpenResty构徏qTCP、HTTP|关{,对Lua + Nginxl合非常熟悉Q能够快速的应用在线上环境。再说Lua语法y、简单,一个新手半天就可以基本熟悉Q马上开工?/p>

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

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

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

HTTP API要设?/h2>

HTTP API路径和语?/h3>

每一具体业务逻辑Q直接在URL Path中体现出来。我们要的是单快速,数据l构之间的连接关p,可能的LE化。egQ?/p>

/resource/video/ID

比如用户反馈q一模块Q将使用下面比较固定的\径:

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

用户属性很多,用户늧只是其中一个部分,因此更新늧q一行ؓQHTTP?PATCH Ҏ可更_և的描q部分数据更新的业务需求:

/user/nickname
  • PATCHQ更新用hUͼ늧是用户属性之一Q可以用更轻量U的 PATCH 语义
    • curl -X PATCH http://localhost/user/nickname -d "nickname=hello2"

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

HTTP API的访问授?/h3>

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

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

另外是 OAUTH 认证了,有整套的认证Ҏq已工业化,很是成熟了,但对我们而言q是太重Q不太适合轻量U的HTTP APIQ不太可能花费太多的_֊d它的q维工作?/p>

最l选择了轻量?Json Web TokenQ非常紧凑,开即用?/p>

最佛_法是把JWT Token攑֜HTTPh头部中,不至于和其它参数hQ?/p>

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

下面是一副浏览器D늚一般认证流E,q与HTTP API认证大体一_

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

JWT和Lor的结?/h3>

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

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

不同于OAUTHQJWT协议?strong>自包?/strong>Ҏ,军_了后端可以将很多属性信息存攑֜payload负荷中,其token生成之后后端可以不用存储Q下ơ客L发送请求时会发送给服务器端Q后端获取之后,直接验证卛_Q验证通过Q可以直接读取原先保存其中的所有属性?/p>

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

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

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

    next()
end)
  • 针对具体路径q行讑֮权限拦截Q较_粒度;比如 /user 只允许已登陆授权用户讉K
app:use("/user", function(req, res, next)
    if not req.params.uid then
        -- 注意Q这里没有调用next()ҎQ请求到q里截止了Q不在匹配后面的路由
        res:status(403):send("not allowed reqeust")
    else
        next() -- 满以上条gQ那么l匹配下一个\?    end
end)
  • 一U是较细_度Q具体到每一个API接口Q因然URL一_但不同的HTTP Method有时h权限q是有区别的
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()

-- 假设查看是不需要用h限的
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加蝲q来
app:use("/user", user_router())

......

app:run()

Z么没有选择GraphQL API Q?/h2>

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

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

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

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

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

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

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



nieyong 2018-01-02 20:53 发表评论
]]>
TsungW记之IP地址和端口限制突破篇http://www.qpkxbc.shop/yongboy/archive/2016/08/16/431601.htmlnieyongnieyongTue, 16 Aug 2016 13:17:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2016/08/16/431601.htmlhttp://www.qpkxbc.shop/yongboy/comments/431601.htmlhttp://www.qpkxbc.shop/yongboy/archive/2016/08/16/431601.html#Feedback2http://www.qpkxbc.shop/yongboy/comments/commentRss/431601.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/431601.html前言

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

但现在我q想l箋深入一下,如何H破q个限制?Q?/p>

如何H破限制

q部分就是要从多个方面去讨论如何如何H破限制单个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
    ].

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

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

1. 增加IP地址

q是最为现实、最为方便的办法Q向q维的同事多甌若干个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可以有多U方式:

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

要是没有_的可用虚拟IP地址供你使用Q或怽需要关注一下后面的IP_TRANSPARENTҎ描q?:))

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

以被压测的一个TCP服务器ؓ例,l箋拿网l四元组说事?/p>

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

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

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

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

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

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

3. 透明代理模式支持

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

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

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

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

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

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

那么q些额外的IP地址如何讄呢?

  • 可以为client元素手动d多个可用的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. q些IP地址目前都没有被已有服务器在使用
  2. q且可以被正常绑定到物理/虚拟|卡上面
  3. 完全可用
3.3 配置路由规则支持

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

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

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

3.4 q阶Q我们用一个新的网D专用于试

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

压测端前面已l配|好了,现在需要ؓ被压的服务器添加\p则,q样在响应数据包的时候能够\由到压测端:

route add -net 10.10.10.0 netmask 255.255.255.0 gw 172.16.247.130

讄完成Q可以通过route -n命o查看当前所有\p则:

K?/p>

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

route del -net 10.10.10.0 netmask 255.255.255.0

梳理了以上所能够惛_的方式,以尽可能H破单机的限Ӟ核心q是可能找到够多可用的IP地址Q利用Linux内核Ҏ支持,E序层面l定可能多的IP地址Q徏立更多的对外q接。当然以上没有考虑cM于CPU、内存等资源限制Q实际操作时Q还是需要考虑q些资源的限制的?/p>

nieyong 2016-08-16 21:17 发表评论
]]>
TsungW记?00万用户压执行步骤篇http://www.qpkxbc.shop/yongboy/archive/2016/08/08/431498.htmlnieyongnieyongMon, 08 Aug 2016 13:31:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2016/08/08/431498.htmlhttp://www.qpkxbc.shop/yongboy/comments/431498.htmlhttp://www.qpkxbc.shop/yongboy/archive/2016/08/08/431498.html#Feedback1http://www.qpkxbc.shop/yongboy/comments/commentRss/431498.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/431498.html前言

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

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

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

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

K?/p>

看着步骤很多Q一旦熟悉ƈ掌握之后Q中间可以省却若qӀ?/p>

1. 阅读Tsung文档

K?/p>

大家在用Tsung之前Q花费一Ҏ间阅d整个用户手册Q虽然是英文的,阅读h也不复杂。读完之后,我们也就知道如何做测试了Q遇到的大部分问题,也能够在里面扑ֈ{案?/p>

  • 官网Q?a href="http://tsung.erlang-projects.org/">http://tsung.erlang-projects.org/
  • 在线手册Q?a href="http://tsung.readthedocs.io/en/latest/index.html">http://tsung.readthedocs.io/en/latest/index.html

2 定压测目标

K?/p>

  • 要对U上pȝ压测100万用PZ可能降低线上服务器负蝲压力Q这里设|每U?00个用P在60分钟内生完?/li>
  • 要压的服务器所填写|络讉K地址可以Ҏ需要填写多?/li>

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

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

  • TsungZ从模型,我们启动了主节点之后Q主节点会按需启动从节?/li>
  • 讑֮所用服务器可用内存大于3GQƈ且都只有一个IP地址
  • 一C机可用模?万用P需?7C?/li>
  • 若资源充I可以用几台服务器,配置多个IP地址
  • 扑ֈ所需要的压测用服务器Q在资源层面满试试集群需要,q个是关?/li>

4. 部vTsung

K?/p>

因ؓTsung依赖于ErlangQ因此需要首先安装:

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

然后再是安装TsungQ徏议直接用Tsung 1.6.0修改版,主要提供IP只连支持Q具体细节,可参考这?http://www.qpkxbc.shop/yongboy/archive/2016/07/28/431354.html Q:

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

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

K?/p>

Z么要替换掉SSHQ主要原因:

  • SSH在一般网l机房环境内服务器之间被止q接通信Q这会导致主节点无法启动从节点,无法建立分布式压集?/li>
  • q是SSH没被用Q主从之间需要设|免U钥SSHd方式Q十分麻?/li>

可进一步参考:TsungW记之分布式增强跛_SSH绊?/a>?/p>

6. ~写压测内容

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

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

其实Q演C所使用的是U有协议Q可以参?TsungW记之插件编写篇 ?/p>

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

7. q行Tsung

K?/p>

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

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

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

K?/p>

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

9. 压测l束Q生成Tsung报表

K?/p>

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

10. 回顾和ȝ

K?/p>

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

你若以ؓ仅仅只是谈论Tsung如何?M用户压测Q那错了,只要机器资源够,q个目标很Ҏ实现。我们更应该xQ我们压的目的是什么,我们应该x什么,q个应该形成一个完整可循环q程Q驱动着pȝ架构健康先前发展?/p>

nieyong 2016-08-08 21:31 发表评论
]]>
TsungW记之插件编写篇http://www.qpkxbc.shop/yongboy/archive/2016/07/30/431396.htmlnieyongnieyongSat, 30 Jul 2016 11:37:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2016/07/30/431396.htmlhttp://www.qpkxbc.shop/yongboy/comments/431396.htmlhttp://www.qpkxbc.shop/yongboy/archive/2016/07/30/431396.html#Feedback0http://www.qpkxbc.shop/yongboy/comments/commentRss/431396.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/431396.html前言

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

下面首先梳理一下当前Tsung 1.6.0所有内|插Ӟ然后Z个名UCؓQmsg的私有二q制协议~写插g, q行Qmsg服务器端E序Q执行压力测试,最后查看测试报告?/p>

已支持插件梳?/h3>

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

Tsung Controller  Support Plugins V2-1K?/p>

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

已经支持协议单说明:

  1. amqpQAdvanced Message Queuing Protocol~写Q只要支持高U消息队列协议的应用Q都可以用来做压,比如RabbitMQQActiveMQ{?/li>
  2. httpQ基本协议,构徏于HTTP协议之上的,q有cM于BOSHQWebDav{上层业务协?/li>
  3. jabberQ也UC为XMPPQ支持的相当丰富Q除了TCP/SSlQ还可以通过Websocektq行传?/li>
  4. rawQ针对原始类型消息,不做~解码处理,直接在TCP / UDP / SSL{传输层中传递,q个寚w分私有协议,比较友好Q不用写单独的编解码处理Q直接透传好了
  5. shellQ针对LInux/Unixl端命o调用q行压测Q这U场景比较小?/li>
  6. fsQfilesystem~写Q针Ҏ件系l的d性能q行压测
  7. jobQ针对Q务调度程序进行的压测Q比如PBS/torqueLSF、OAR{?/li>

Tsung插g工作机制

_一Ҏ看Tsung插g的工作流E(点击可以看大图)Q?/p>

tsung_qmsg_floK?/a>

攑֤一些(引用 hncscwc 博客囄Q相当赞Q)Q?/p>

Z么要~写插g

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

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

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

Qmsg协议定义

假设一个虚拟场景,打造一个新的协议QmsgQ二q制格式l成Q?/p>

qmsg_protocoK?/p>

q种随意假象出来的格式,不妨UC?strong>qmsgQQ可爱形式的messageQ协议,仅作为Demo演示而存在。简单场景:

  • 用户发言Q包含用户id和发a内容
    • User IDQ?2位自然数cd
    • 发言为文字内容,字符串Ş式,长度不固?/li>
    • l装后的h体ؓ二进制协议格?/li>
    • PocketLen:**##UserId + UserComment##**
  • 服务器端q回用户ID和一个幸q数?32位表C?
    • PocketLen:**##UserId + RandomCode##**

Z卡哇伊一些,多了一些点~的?*####**”符受?/p>

~写一个完整插?/h3>

q里ZTsung 1.6.0版本构徏一个Qmsg插gQ假定你懂一些Erlang代码Q以及熟悉Tsung一些基本概c?/p>

0. 创徏一个项?/h4>

要创建Tsung的一个Qmsg插g目Q虽没有固定规范Q但按照已有格式l织好代码层U也是有必要的?/p>

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

1. 创徏配置文g

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

<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_qmsgQ会话类型所依赖协议模拟客户端实?/li>
  • <qmsg uid="Number">Text</qmsg> 定义了qmsg会话可配|Ş式,内嵌?code>request元素?/li>
  • uid为属?/li>

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

2. 更新DTD文g

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

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

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

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

3. 头文?include/ts_qmsg.hrl

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

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

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

4. XML文g解析

ts_config_qmsg.erl文gQ用于解析和协议Qmsg兌的配|:
- 只需要实?code>parse_config/2唯一Ҏ
- 解析xml文g中所配置Qmsg协议h相关配置
- ?code>ts_config:parse/1在遇到Qmsg协议配置时调?/p>

备注Q?/p>

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

5. ts_qmsg.erl

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

首先需要实现接?code>ts_plugin规范定义的所有需要函敎ͼ定义了参数值和q回倹{?/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]).

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

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

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

6. 如何~译

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

Z节省体力Q用一个shell脚本 - build_plugin.shQ方便快速编译、部|Ԍ

# !/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

q里指定安装Tsung的指定目录ؓ/usr/localQ可以根据需要修?/p>

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

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

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

sh build_plugin.sh /your_path/tsung-1.6.0

后面嘛,q着自动~译和安装呗?/p>

启动Qmsg协议的压?/h3>

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

既然有压端Q就需要一个Qmsg协议处理的后端程?code>qmsg_server.erlQ用于接收客LhQ获得用户IDg后,生成一个随机数字,l装成二q制协议Q然后发l客LQ这是全部功能?/p>

q个E序Q简单一个文Ӟ?tsung_plugin_demo目录下面Q编译运? 默认监听5678端口Q?/p>

erlc qmsg_server.erl && erl -s qmsg_server start

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

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

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

源码见:tsung_plugin_demo/qmsg_server.erl

2. ~写Qmsg压测XML配置文g

因ؓ是演C示范,一台LinxuL上就可以q行了:

  • q接本机?127.0.0.1:5678
  • 最多?0个用P每秒产生1个,压力负蝲讄的很?/li>
  • 两个不同cd会话Q比?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>

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

3. 执行压力试

当Qmsg的压力测试配|文件写好之后,可以开始执行压力测试了Q?/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

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

4. 查看压测报告

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

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

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

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

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

以上代码已经攑օgithub仓库Q?a href="https://github.com/weibomobile/tsung_plugin_demo">https://github.com/weibomobile/tsung_plugin_demo?/p>

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

嗯,搞定了插Ӟ可以对pȝ愉快地进行压了 :))



nieyong 2016-07-30 19:37 发表评论
]]>
TsungW记之监控数据收集篇http://www.qpkxbc.shop/yongboy/archive/2016/07/29/431367.htmlnieyongnieyongFri, 29 Jul 2016 00:49:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2016/07/29/431367.htmlhttp://www.qpkxbc.shop/yongboy/comments/431367.htmlhttp://www.qpkxbc.shop/yongboy/archive/2016/07/29/431367.html#Feedback0http://www.qpkxbc.shop/yongboy/comments/commentRss/431367.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/431367.html前言

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

压测客户端的监控

压测端(指的是tsung clientQ会攉每一个具体模拟终端用P即ts_client模块Q行为数据,发送给主节点(tsung_controllerQ,供后面统计分析用?/p>

tsung_monitor_clientK?/p>

  1. ts_client模块调用ts_monQ而ts_mon又直接调用ts_mon_cacheQ有些绕Q不直观Q逻辑层面可忽略掉ts_monQ?/li>
  2. count数器Qsum表示各项累加|sample和sample_counter计算一ơ统计项的^均?amp;标准?/li>
  3. tsung.dump文g一般不会创?amp;写入Q除非你在tsung.xml文g中指定需要dump属性ؓtrueQ压数据量大时q个会媄响性能
  4. match.log仅仅针对HTTPhQ默认不会写入,除非在HTTP压测指定

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

  6. ts_mon_cacheQ接收到数据l计内存计算Q每500毫秒周期分发l后l模块,起到~冲作用

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

  8. ts_mon负责l计数据最?0U定时写入各统计数据到tsung.log文gQ非实时Q可避免盘IO开销q大问题

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

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

比如某次单机50万用户压tsung.log日志片段Q?/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日志文g可由tsung_stats.pl脚本提取、分析、整理成报表展示Q其报表的一个摘要截图:

K?/p>

异常行ؓ的收?/h4>

当模拟终端遇到网l连接超时、地址不可辄异常事gӞ最l也会发l主节点的ts_mon模块Q保存到tsung.log文g中?/p>

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

  • 比如ts_client模块遇到q接时会汇?code>error_connect_timeout错误
  • pȝ的可用端口不够用Ӟ创徏与压服务器q接数超出可用段限制Q上?code>error_connect_eaddrinuse错误

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

K?/p>

被压服务器的监?/h3>

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

  • erlang
  • snmp
  • Munin

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

tsung_server_monitoK?/p>

  • tsung_controller主节点会被强制启用监?/li>
  • SNMP方式Q客L作ؓ代理d注册q连接开放SNMP的服务器QSNMP安装针对新手来说比较复杂
  • Munin采用C/S模式Q自w要作ؓ客户端连接被压测服务器上能够安装Munin Server
  • erlang方式Q本w代理Ş式监控服务器资源占用Q满x件很单:
    • 需要能够自动登录连?/li>
    • q且安装有Erlangq行时环境,tsung_controller方便启动监控节点
    • 采用q程加蝲方式业务代码Q省去被监控端部|的ȝ
    • 现实情况下,我一般采用一个脚本搞定自动部|监控部|客LQ自动打包可UL的ErlangQ简单绿Ԍ部v方便
  • 提供监控采样数据包括 CPU/Memory/Load/Socket Sent&Recv
  • 所有监控数据都会被发送给ts_mon模块Qƈ定时写入到tsung.log文g?/li>

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

K?/p>

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

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

日志攉

和前面讲到的l端行ؓ数据采集和服务器端资源监控行为类|tsungq行q程中所产生日志被存储到主节炏V?/p>

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

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

Zerror_logger包装日志记录Q需要一个步骤:

  1. 讄输出到文件系l中 error_logger:tty(false)
  2. 讑֮输出的文件目?error_logger:logfile({open, LogFile})
  3. 包装日志输出接口 ?DEBUG/?DEBUGF/?LOG/?LOGF/
  4. 最l调用包装的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.

和大部分日志框架讑֮的日志等U一_emergency > critical > error > warning > notice (default) > info > debugQ从左到叻I依次递减?/p>

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

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

Tsungq行时诊?监控

Tsung在运行时Q我们可以remote shell方式q接dq去?/p>

Zq接方便Q我写了一个脚?connect_tsung.shQ只需要传入tsung节点名称卛_Q?/p>

# !/bin/bash
## 讉Kq程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

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

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

如何获得tsung节点名称

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

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

如何诊断/监控Tsungq行?/h4>

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

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

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

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

说一个用例,修改监控数据?0U写入tsung.log文g旉间隔|10U修改ؓ5U:

application:set_env(tsung_controller, dumpstats_interval, 5000).

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

ȝ了TsungM监控Q以及服务器端监控部分,以及q行时监控等。提供的被压服务器监控功能很粗Q仅攉CPU、内存、负载、接收数据等cd峰|h一般参考意义。但ZTsung构徏的、或cM商业产品Q一般会有提供专门数据收集服务器Q但对于开源的应用而言Q需要兼N用需求,也是能够理解的?/p>

nieyong 2016-07-29 08:49 发表评论
]]>
TsungW记之IP直连支持?/title><link>http://www.qpkxbc.shop/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>http://www.qpkxbc.shop/yongboy/archive/2016/07/28/431354.html</guid><wfw:comment>http://www.qpkxbc.shop/yongboy/comments/431354.html</wfw:comment><comments>http://www.qpkxbc.shop/yongboy/archive/2016/07/28/431354.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.qpkxbc.shop/yongboy/comments/commentRss/431354.html</wfw:commentRss><trackback:ping>http://www.qpkxbc.shop/yongboy/services/trackbacks/431354.html</trackback:ping><description><![CDATA[<h3 id="toc_0">前言</h3> <p>前面说到设计一个小型的C/Scdq程l端套g以替换SSHQƈ且已l应用到U上。这个问题,其实不是Tsung自n的问题,是外部连接依赖问题?/p> <p>Tsung在启动分布式压测Ӟ主节?code>tsung_controller</code>要连接的从机必须要填写主机名Q主机名没有内网DNS服务器支持解析的情况?我所l历互联|公司很有提供支持?Q只好费劲在<code>/etc/hosts</code>文g中填写主机名U和IP地址的映关p,颇ؓȝQ尤其是要添加一Ҏ的压从机或从机变动频率较大时?/p> <p>那么如何解决q些问题呢,让tsung在复杂的机房内网环境下,完全ZIPq行直连Q这是本文所讨论的内宏V?/p> <h3 id="toc_1">预备知识</h3> <h4 id="toc_2">完全限定域名</h4> <p>完全限定域名Q羃写ؓFQDN (fully qualified domain name)Q?a href="https://www.symantec.com/zh/cn/security_response/glossary/define.jsp?letter=f&word=fqdn-fully-qualified-domain-name">赛门铁克l出的中文定?/a>Q?/p> <blockquote> <p>一U用于指定计机在域层次l构中确切位|的明确域名?br/> 一台特定计机或主机的完整 Internet 域名。FQDN 包括两部分:L名和域名。例?mycomputer.mydomain.com?br/> 一U包含主机名和域名(包括域)?URL。例如,www.symantec.com 是完全限定域名。其?www 是主机,symantec 是二U域Q?com 是顶U域。FQDN L以主机名开始且以顶U域名结束,因此 <a href="http://www.sesa.symantec.com">www.sesa.symantec.com</a> 也是一?FQDN?/p> </blockquote> <p>若机器主机名为内|域名Ş式,q且支持DNS解析Q方便其它服务器可通过该主机名直接扑ֈ对应IP地址Q能?<code>ping -c 3 机器域名</code> 通,那么机器之间能够Ҏ扑ֈҎ?/p> <p>服务器hostname的命名,若不是域名Ş式,短名UŞ式,比如“yk_mobile_dianxin_001”,一般内|的DNS服务器不支持解析Q机器之间需要互相在/etc/hosts文g建立彼此IP地址映射关系才能够互相感知对斏V?/p> <h4 id="toc_3">Erlang节点名称的规?/h4> <p>因ؓTsung使用Erlang~写QErlang关于节点启动名称规定Q也是Tsung需要面对的问题?/p> <p>Erlang节点名称一般需要遵循两U格式:</p> <ol> <li>一般名Uͼ也称之ؓ短名Uͼ形式Q不包含?”字W,比如 <code>erl -name tsun_node</code></li> <li>完全限定域名形式 <ul> <li>域名形式Q比?code>erl -name tsun_node.youdomain.com</code></li> <li>IP形式Q比?code>erl -name 10.10.10.103</code></li> </ul></li> </ol> <p>Tsung处理方式Q?/p> <ul> <li>若非特别指定Q一般默认ؓ短名UŞ?/li> <li>启动时可以通过<code>-F</code>参数指定使用完全限定域名形式</li> </ul> <h4 id="toc_4">获得IP地址</h4> <p>L名称无论是完全限定域名Ş式,q是单的短名UŞ式,当别的主机需要通过L名访问时Q系l层面需要通过DNSpȝ解析成IP地址才能够进行网l连接。当内网DNS能够解析出来IP来,没有什么担心的Q(短名Uͼ解析不出来时Q多半会通过写入到系l的 <code>/etc/hosts</code> 文g中,q样也能够解析成功?/p> <p>一般机房内|环境,L名称大都是短名称形式Q若需分布式,每一个主Z间都要能够互相联通,最l济做法是直接使用IP地址Q可避免写入大量映射?hosts 文g中,也会避免一些隐患?/p> <h3 id="toc_5">主节点启动增加IP支持</h3> <p>默认情况下,Tsung Master主节点名U类g<code>tsung_controller@L?/code>Q?/p> <ul> <li>节点名称前缀默认为:<code>tsung_controller</code> Q除非在tsung启动旉过<code>-i</code>指定前缀Q?/li> <li>一般主机名都是字符串Ş式(<code>hostname</code>命o可设|主机名Q?/li> <li>可将L名称讄为本机IPQ但不符合hc认知惯?/li> </ul> <p>既然Tsung主节炚w认对IP节点名称支持不够Q改造一?code>tsung/tsung.sh.in</code>脚本?/p> <p>Tsung启动?code>-F</code>参数为指定?strong>完全限定域名(FQDN)</strong>形式Q不支持携带参数。若要直接传逺P地址Q类gQ?/p> <blockquote> <p>-F Your_IP</p> </blockquote> <p>修改<code>tsung.sh.in</code>Q可以传逺P地址Q手动组装节点名Uͼ</p> <pre><code class="language-bash">F) NAMETYPE="-name" SERVER_IP=$OPTARG if [ "$SERVER_IP" != "" ]; then CONTROLLER_EXTENDS="@$SERVER_IP" fi ;; </code></pre> <p>修改不复杂,更多l节请参考:<a href="https://github.com/weibomobile/tsung/blob/master/tsung.sh.in">https://github.com/weibomobile/tsung/blob/master/tsung.sh.in</a></p> <p>启动TsungӞ指定本地IPQ?/p> <pre><code class="language-bash">tsung -F 10.10.10.10 -f tsung.xml start </code></pre> <p>tsung_controller目前节点名称已经变ؓQ?/p> <blockquote> <p>-name tsung_controller@10.10.10.10</p> </blockquote> <p>嗯,目标达成?/p> <h3 id="toc_6">从节点主机增加IP配置</h3> <p>l出一个节点client50配置Q?/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惌问client50Q需要提前徏立client50与IP地址的映关p:</p> <pre><code class="language-bash">echo "10.10.10.50 client50" >> /etc/hosts </code></pre> <p><code>host</code>属性默认情况下只能填写长短名称Q无法填写IP地址Qؓ了兼容已有规则,修改<code>tsung-1.0.dtd</code>文g为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>文gQ增加处理逻辑Q只有当主节点主机名为IP时才会取<code>hostip</code>作ؓL名:</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>嗯,现在可以q样配置从节点了Q不用担心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>其实Q只要你定只用主节点L名ؓIP地址Q可以直接设|host属性gؓIP|可忽略hostip属性,但这以牺牲兼Ҏؓ代h的?/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>Z减少<code>/etc/hosts</code>大量映射写入Q还是推荐全部IP形式Q这UŞ式适合Tsung分布式集所依赖服务器的快速租赁模型?/p> <h3 id="toc_7">源码地址</h3> <p>针对Tsung最C码增加的IP直连Ҏ所有修改,已经攑֜github上:</p> <p><a href="https://github.com/weibomobile/tsung">https://github.com/weibomobile/tsung</a> ?/p> <p>q且已经递交<code>pull request</code>Q?<a href="https://github.com/processone/tsung/pull/189">https://github.com/processone/tsung/pull/189</a> ?/p> <p>比较有意思的是,有这样一条评论:</p> <p><img src="http://www.qpkxbc.shop/images/blogjava_net/yongboy/14696293372281.jpg" alt="" style="width:769px;"/>K?/p> <h4 id="toc_8">针对Tsung 1.6.0修改?/h4> <p>最q一ơ发行版是tsung 1.6.0Q这个版本比较稳定,我实际压所使用的就是在此版本上增加IP直连支持Q如上所qͼQ已l被单独攑օ到github上:</p> <p><a href="https://github.com/weibomobile/tsung-1.6.0">https://github.com/weibomobile/tsung-1.6.0</a></p> <p>至于如何安装Q?code>git clone</code>到本圎ͼ后面是如何~译tsung的步骤了Q不再篏q?/p> <h3 id="toc_9">结</h3> <p>若要让IP直连Ҏ生效,再次说明启用步骤一下:</p> <ol> <li>tsung.xml文g配置从机hostip属性,或host属性,填写正确IP</li> <li>tsung启动Ӟ指定本机可用IP地址Q?code>tsung -F Your_Available_IP -f tsung.xml ... start</code></li> </ol> <p>IP直连Q再配合前面所写SSH替换ҎQ可以让Tsung分布式集在复杂|络机房内网环境下适应性向前迈了一大步?/p> <blockquote> <p>2016-08-06 更新此文Q增加Tsung 1.6.0修改版描q?/p> </blockquote> <img src ="http://www.qpkxbc.shop/yongboy/aggbug/431354.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.qpkxbc.shop/yongboy/" target="_blank">nieyong</a> 2016-07-28 08:37 <a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/28/431354.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TsungW记之分布式增强跛_SSH绊?/title><link>http://www.qpkxbc.shop/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>http://www.qpkxbc.shop/yongboy/archive/2016/07/27/431340.html</guid><wfw:comment>http://www.qpkxbc.shop/yongboy/comments/431340.html</wfw:comment><comments>http://www.qpkxbc.shop/yongboy/archive/2016/07/27/431340.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.qpkxbc.shop/yongboy/comments/commentRss/431340.html</wfw:commentRss><trackback:ping>http://www.qpkxbc.shop/yongboy/services/trackbacks/431340.html</trackback:ping><description><![CDATA[<h3 id="toc_0">前言</h3> <p>Erlang天生支持分布式环境,Tsung框架的分布式压测受益于此Q简单轻松操控子节点生死存亡、派发Q务等不费吹灰之力?/p> <p>Tsung启动分布式压时Q主节点tsung_controller默认情况下需要通过SSH通道q接到远E机器上启动从节点,那么问题便来了,一般互联网公司Zx/堡垒?|关授权方式讉K机房服务器,那么SSH机制失效Qƈ且被明o止。SSH不通,TsungL启动不了从机Q分布式更无从谈赗?/p> <p>那么如何解决q个问题呢,让tsung在复杂的机房|络环境讑֮下更加如鱼得_是本文所讨论的内宏V?/p> <h3 id="toc_1">RSHQRemote Shell</h3> <p>RSHQremote shell~写Q维基百U上英文解释Q?a href="https://en.wikipedia.org/wiki/Remote_Shell">https://en.wikipedia.org/wiki/Remote_Shell</a>。作Z个终端工PLinux界鸟哥曾l写q?<a href="http://linux.vbird.org/linux_server/0310telnetssh/0310telnetssh-centos4.php#rsh">RSH客户端和服务器端搭徏教程</a>?/p> <p>在CentOS下安装也单:</p> <pre><code class="language-bash">yum install rsh </code></pre> <p>Erlang借助于rsh命o行工具通过SSH通道q接C节点启动Tsung应用Q下面可以看到rsh工具本n失去了原本的含义Q类g<code>exec</code>命o功效?/p> <p>比如Erlang主节点(假设q个服务器名UCؓ<code>node_master</code>Qƈ且已l在/etc/hosts文g建立了IP地址映射Q在启动时指定rsh的可选方式ؓSSHQ?/p> <pre><code class="language-bash">erl -rsh ssh -sname foo -setcookie mycookie </code></pre> <p>启动之后Q要启动q程L节点名称?code>node_slave</code>的子节点Q?/p> <pre><code class="language-erlang">slave:start(node_slave, bar, "-setcookie mycookie"). </code></pre> <p>上面Erlang启动从节点函敎ͼ最l被译为可执行的shell命oQ?/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>命oErlang的启动命令,要求L<code>node_slave</code>自n也要安装了Erlang的运行时环境才行?/p> </blockquote> <p>从节点的启动命o最l依赖于SSHq接q远E执行,光用一般格式ؓQ?/p> <pre><code class="language-bash">ssh HOSTNAME/IP Command </code></pre> <p>q就是基于Erlang构徏的Tsung操控从节点启动的最l实现机制?/p> <blockquote> <p>其它语言中,Master启动Slave也是如此机制</p> </blockquote> <h3 id="toc_2">SSH为通用ҎQ但不是最好的Ҏ</h3> <p>业界选用<a href="https://zh.wikipedia.org/wiki/Secure_Shell">SSH</a>机制q接q程Unix/Linux服务器主机,分布式环境下要能够自由免除密码方式启动远E主ZQ这里指的是内部Lan环境Q应用,一般需要设|公钥,需要传递公钥,需要保存到各自机器上,q有l常遇到权限问题Q很是麻烦,q是其一。若要取消某台服务器登陆授权Q则需要被动修改公钥,也是不够灉|?/p> <p>另外一般互联网公司处于安全考虑都会止公司内部人员直接通过SSH方式d到远E主行操作,q样DSSH通道失效QTsungL通过SSHq接C机ƈ执行命oQ也׃可能了?/p> <p>其实Q在Z分布式压环境下Q快速租赁、快速借用/归还的模型就很适合。一般公司很会存在专门用于压测的大量空闲机器,但是U上会运行着当前负蝲不高的服务器Q可以拿来用作压客L使用Q用完就归还。因为压不会是长时间运行的服务Q其为短旉行ؓ。这U模式下׃适合复杂的SSH公钥满天飞,后期忘记删除的情况,在压端多的情况下Q无疑也造成q维成本Ȁ增,安全性降低等问题?/p> <h3 id="toc_3">SSH替换ҎQ一U快速租赁模式远E终端方?/h3> <p>现在需要寻找一U新的代替方案,一U适应快速租赁的q程l端实现机制?/p> <h4 id="toc_4">替换Ҏ要求?/h4> <ol> <li>cM于SSH ServerQ监听某个端口,能够执行传递过来的命o</li> <li>能够ҎIP地址授权Q这样只有Tsung Master才能够访问从节点Q从节点之间无法直接对连</li> <li>需要接受一些操控指令,可以判断是否存活</li> <li>一C个脚?E序搞定Q尽量避免安装,开即?/li> <li>M配置、操作一定要单,实际q维成本一定要?/li> </ol> <p>没找到很轻量的实玎ͼ可以设计q实现这样一U方案?/p> <h4 id="toc_5">服务器端守护q程</h4> <p>轻量U服务端守护q程 = 一个监控端口的q程Q?code>rsh_daemon.sh</code>Q?+ 执行命oqo功能(rsh_filter)</p> <p><code>rsh_daemon.sh</code> 负责守护q程的管理:</p> <ul> <li>ZCentOS 6/7默认安装?code>ncat</code>E序</li> <li>主要用于理19999端口监听</li> <li>start/stop/restart 负责监控q程启动、关?/li> <li>status 查看q程状?/li> <li>kill 提供手动方式关闭q删除掉自n</li> <li><code>rsh_filter</code>用于远E传入命令ƈq行处理 <ul> <li>接收ping指oQ返回pong</li> <li>执行Erlang从节点命令,q返?done 字符?/li> <li>对不合法命oQ直接关?/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> <li>Z<code>ncat</code>监听19999端口提供bind shell机制Q但限制有限IP可访?/li> <li>动态生成命令过滤脚?code>rsh_filter.sh</code>Q执行Erlang从节点命?/li> </ul> <p>请参考:<a href="https://github.com/weibomobile/tsung_rsh/blob/master/rsh_daemon.sh">https://github.com/weibomobile/tsung_rsh/blob/master/rsh_daemon.sh</a></p> <h3 id="toc_6">客户端连接方?/h3> <p>服务器端已经提供了端口接入ƈ准备好了接收指oQ客LQ?code>rsh_client.sh</code>Q可以进行连接和交互了:</p> <ul> <li>cMSSH客户端接收方式:<code>rsh_client.sh Host/IP Command</code></li> <li>完全Z<code>nc</code>命oQ连接远E主?/li> <li>q接成功Q发送命?/li> <li>得到相应Q流E完?/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替换ҎQ那主节点就可以q样启动了:</p> <pre><code class="language-bash">erl -rsh ~/.tsung/rsh_client.sh -sname foo -setcookie mycookie </code></pre> <p>比如当Tsung需要连接到另外一台服务器上启动从节点Ӟ它最l会译成下面命令:</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>则最l需要执行连接到服务器、ƈ发送命的命令:</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>q样实C和SSH一L功能了,很简单吧?/p> <h3 id="toc_8">Tsung如何切换切换Q?/h3> <p>为tsung启动d<code>-r</code>参数指定卛_Q?/p> <pre><code class="language-bash">tsung -r ~/.tsung/rsh_client.sh -f tsung.xml start </code></pre> <h3 id="toc_9">q阶Q可指定q行命o路径</h3> <p><code>rsh_client.sh</code>脚本最后一行修改一下,指定目标服务器erlq行命oQ?/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>上面脚本所依赖的上下文环境可以是这LQ机房服务器操作pȝ和版本一_我们把Erlang 18.1整个q行时环境在一台机器上已经安装的目录(比如目录名ؓotp_18Q,拯到远E主?code>/root/.tsung/</code>目录Q相比于安装而言Q可以让Tsungq行依赖的Eralng环境完全可以UL化(PortableQ,一ơ安装,多次复制?/p> <h3 id="toc_10">代码托管地址</h3> <p>本文所谈及代码Q都已经托管在githubQ?br/> <a href="https://github.com/weibomobile/tsung_rsh">https://github.com/weibomobile/tsung_rsh</a></p> <p>后箋代码更新、BUG修复{,L接参考该仓库?/p> <h3 id="toc_11">结</h3> <p>单一套新的替换SSH通道无密钥登陆远E主机C/S模型Q虽然完整性上无法与SSH相比Q但胜在单够用,完全满了当前业务需要,q且其运l成本低Q无疑让Tsung在复杂服务器内网环境下适应性又朝前多走了半里\?/p> <p>下一将介绍为Tsung增加IP直连Ҏ支持,使其分布式网l环境下适应性更q泛一些?/p> <img src ="http://www.qpkxbc.shop/yongboy/aggbug/431340.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.qpkxbc.shop/yongboy/" target="_blank">nieyong</a> 2016-07-27 09:28 <a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/27/431340.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TsungW记之压端资源限制?/title><link>http://www.qpkxbc.shop/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>http://www.qpkxbc.shop/yongboy/archive/2016/07/26/431322.html</guid><wfw:comment>http://www.qpkxbc.shop/yongboy/comments/431322.html</wfw:comment><comments>http://www.qpkxbc.shop/yongboy/archive/2016/07/26/431322.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.qpkxbc.shop/yongboy/comments/commentRss/431322.html</wfw:commentRss><trackback:ping>http://www.qpkxbc.shop/yongboy/services/trackbacks/431322.html</trackback:ping><description><![CDATA[<h3 id="toc_0">前言</h3> <p>q里汇集一下媄响tsung client创徏用户数的各项因素。因为Tsung是IO密集型的应用QCPU占用一般不大,Z可能的生成更多的用P需要考虑内存相关事宜?/p> <h3 id="toc_1">IP & 端口的媄?/h3> <h4 id="toc_2">1. pȝ端口限制</h4> <p>Linuxpȝ端口为shortcd表示Q数g限ؓ65535。假讑ֈ配压业务可用端口范围ؓ1024 - 65535Q不考虑可能q运行着其它对外q接的服务,真正可用端口也就?4000左右Q实际上Q一般ؓ了方便计,一般直接设定ؓ50000Q。换a之,卛_一台机器上一个IPQ可用同时对外徏?4000|络q接?/p> <p>若是N个可用IPQ理Z 64000*NQ实际上q需要满I</p> <ul> <li>充内存支持 <ul> <li>tcp接收/发送缓冲区不要讄太大Qtsung默认分配32KQ可以修Ҏ16KQ一般够用了Q?/li> <li>一个粗略估一个连接占?0K内存Q那?0万用P占用约8G内存</li> </ul></li> <li>为多IP的压端分配适合的权重,以便承担更多的终端连?/li> </ul> <p>另外q需要考虑端口的快速回收等Q可以这样做Q?/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>若已l在 /etc/sysctl.conf 文g中有记录Q则需要手动修?/p> </blockquote> <p>作ؓ附加Q可讄端口重用Q?/p> <pre><code class="language-erlang"><option name="tcp_reuseaddr" value="true"/> </code></pre> <p>注意Q不要设|下面的可用端口范围Q?/p> <pre><code class="language-erlang"><option name="ports_range" min="1025" max="65535"/> </code></pre> <p>因ؓ操作pȝ会自动蟩q已l被占用本地端口Q而Tsung只能够被动通过错误q行可用端口+1l箋下一个连接,有些多余?/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从节点开始准备徏立网l连接会话时Q需要从tsung_controller主节点获取具体的会话信息Q其中就包含了客Lq接需要用到来源{LocalIPQ?LocalPort}二元l。由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"/> %% 因ؓq样存在端口冲突问题Q除非确实不存被占用情况 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>从节点徏立到压测服务器连接时Q就需要指定从主节点获取到的本机IP地址和端口两元组Q?/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>若从机单个网卡绑定了多个IPQ又懒于输入Q可以配|扫描特?</p> <pre><code class="language-xml"><ip scan="true" value="eth0"/> </code></pre> <p>本质上用shell方式获取IP地址Qƈ且支持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">Linuxpȝ打开文g句柄限制</h3> <p>pȝ打开文g句柄Q直接决定了可以同时打开的网l连接数量,q个需要设|大一些,否则Q你可能会在<a href="mailto:tsung_controller@IP.log">tsung_controller@IP.log</a>文g中看?code>error_connect_emfile</code>cM文g句柄不够使用的警告,此D大于 > 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会话启动脚本文g中明添加上<code>ulimit -n 300000</code>?/p> <h3 id="toc_6">内存的媄?/h3> <p>一个网lSocketq接占用不多Q但上万个或数十万等׃容小觑了Q设|不当会D内存直接成ؓ屏障?/p> <h4 id="toc_7">1. TCP接收、发送缓?/h4> <p>Tsung默认讄的网lSocket发送接收缓冲区?6KBQ一般够用了?/p> <p>以TCPZQ某ơ我手误为Tcp接收~存赋D?599967字节)Q这h一个网l了解至占用了0.6M内存Q直接导致在16G内存服务上网l连接数?万多Ӟ内存告急?/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>此g覆盖Linuxpȝ讄接收、发送缓冲大?/p> <p>_略的默认D,一个网l连接发送缓冲区 + 接收~冲区,再加上进E处理连接堆栈占用,U?0多K内存Qؓ卌方便,讑֮建立一个网l连接消?0K内存?/p> <p>先不考虑其它因素Q若我们惌从机模拟10W个用P那么当前可用内存臛_要剩余:50K * 100000 / 1000K = 5000M = 5G内存。针对一般服务器来讲Q完全可满要求Q剩下事情就是要有两个可用IP了)?/p> <h4 id="toc_8">2. Erlang函数堆栈内存占用</h4> <p>使用ErlangE序写的应用服务器,q程要存储堆栈调用信息,q程一多久会占用大量内存,惌服务更多|络q接/dQ需要将不活动的q程讄Z眠状态,以便节省内存QTsung的压会话信息若包含thinktime旉Q也要考虑启用hibernate休眠机制?/p> <pre><code class="language-xml"><option name="hibernate" value="5"></option> </code></pre> <p>值单位秒Q默认thinktime过10U后自动启动Q这里修改ؓ5U?/p> <h3 id="toc_9">XML文g讄需要注意部?/h3> <h4 id="toc_10">1. 日志{要调高一?/h4> <p>tsung使用error_logger记录日志Q其只适用于真正的异常情况Q若当一般业务调试类型日志量q多Ӟ不但耗费了大量内存,|络/盘写入速度跟不上生产速度Ӟ会导致进E堵塞,严重会拖累整个应用僵死,因此需要在tsung.xml文g中设|日志等U要高一些,臛_默认的notice很合适?/p> <h4 id="toc_11">2. 不要启用dump</h4> <p>dump是一个耗时的行为,因此默认为falseQ除非很的压测用户用于调试?/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>讑֮一个有状态的场景Q用户ID储存在文件中Q每一ơ会话请求都要从获取到用户IDQ压用户一旦达到百万别ƈ且用hU生速率q大Q比如每U?000个用PQ会l常遇到时错误Q?/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>q是因ؓQ当tsung client遇到<code>setdynvars</code>指oӞ会直接请求主机ts_file_server模块Q当一旉h量巨大,可能会造成单一模块处理~慢Q出现超旉题?/p> <p>怎么办:</p> <ol> <li>降低用户每秒产生速率Q比?00U用L?/li> <li>不用从文件中存储用户id{信息,采用别的方式</li> </ol> <h3 id="toc_13">如何限流/限?/h3> <p>某些时候,要避免tsung client压测端媄响所在服务器|络带宽IO太拥挤,需要限制流量,光用o牌桶法?/p> <pre><code class="language-xml"><option name="rate_limit" value="1024"></option> </code></pre> <ul> <li>gؓKB单位每秒</li> <li>目前仅对传入量生效</li> </ul> <p>阀D方式:</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>接收传入量数据Q需要计:</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>下面则是具体的o牌桶法Q?/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从机创徏用户的各因素,实际环境其实相当复杂Q需要一一对症下药才行?/p> <img src ="http://www.qpkxbc.shop/yongboy/aggbug/431322.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.qpkxbc.shop/yongboy/" target="_blank">nieyong</a> 2016-07-26 08:47 <a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/26/431322.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TsungW记之主从资源协调篇http://www.qpkxbc.shop/yongboy/archive/2016/07/25/431310.htmlnieyongnieyongMon, 25 Jul 2016 06:02:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2016/07/25/431310.htmlhttp://www.qpkxbc.shop/yongboy/comments/431310.htmlhttp://www.qpkxbc.shop/yongboy/archive/2016/07/25/431310.html#Feedback1http://www.qpkxbc.shop/yongboy/comments/commentRss/431310.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/431310.html前言

接着上文Qtsung一旦启动,M节点之间需要协调分配资源,完成分布式压Q务?/p>

如何启动Tsung压测从机

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

slave:start(Host, Node, Opts)

启动从机需要借助于免登陆形式q程l端Q比如SSHQ后l会讨论SSH存在不Q以及全新的替代品)Q需要自行配|?/p>

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

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

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

slave:stop(Node)

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

启动tsung client方式

TsungL启动从机成功Q从机和L可以Erlang节点q程之间q行Ҏ调用和消息传递。潜在要求是Qtsung~译后beam文g能够在Erlangq行时环境中能够讉K刎ͼq个和Java Classpath一致原理?/p>

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

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

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

压测目标

明白了主从启动方式,下面讨论压测目标Q比?0万用L量,Ҏl出的压从机列表,q行d分配?/p>

压测目标配置

tsung压测xml配置文gQload元素可以配置Md生成的信息?/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>
  • 定义一个最l压力生可以持l?0分钟压测场景Q?上限用户量ؓ50?/li>
  • arrivalphase duration属性持l时长表C生成压用户可消费M旉60分钟Q即为T1
  • users元素其属性表C单位时间内Q这里单位时间ؓU)产生用户Cؓ250?/li>
  • 50万用P在2000U?U?4分钟)内生成,耗时旉即ؓT2
  • T2于arrivalphase定义的用L成阶D|l时间T1
  • 若T2旉后(34分钟)后因Z生用h已经辑ֈ了上限,不再生新的用P知道整个压测l束
  • ?T1 于 T2Q则50万用户很难达刎ͼ因此T1旉要设|长一?/li>

从节点信息配|?/h4>

所说从节点也是压测客户端,需要配|clients元素Q?/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支持多个IPQ用于突破单个IP对外建立q接数的限制(后箋会讲刎ͼ
  2. xml所定义的一个cliet元素Q可能被分裂q从机实?即tsung client)Q? : N

ҎCPU数量分裂tsung client实例情况

在《Tsung Documentation》给ZQ一个CPU一个tsung client实例Q?/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对应一个CPUQ若不设|CPU属性,默认值就?
  • 一个cpu对应一个tsung clientQN个CPUQN个tsung client
  • 共同分担权重Q每一个分裂的tsung client权重 Weight/N
  • 一旦设|cpu属性,无论Tsung启动时是否携?code>-s参数讄׃nCPUQ都?
    • 自动分裂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实例׃n多个CPUQ此时不要设|cpu属性啦Q,需要在tsung启动时添?code>-s参数Qtsung client被启动时Qsmp属性被讄成autoQ?/p>

-smp auto +A 8

q样从机只有一个tsung client实例了,不会让h产生困扰。若是时租借从机,启动时?s参数Qƈ且要去除cpu属性设|,q样才能够自动共享所有CPU核心?/p>

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

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

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

tsung client分配的数量超q自w可服务上限用户Ӟq里讄的是1KQ时Q关闭自w?/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接收从节炚w出通知Q但分配L没有完成Q会启动新的tsung client实例Q一样先启动从节点,然后再启动tsung client实例Q。整个过E串行方式@环,直到10K用户数量完成Q?/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分配的用hQ可以理解ؓ会话d数。Tsung以终端可以模拟的用户为维度进行定义压?/p>

所有配|tsung client元素Q设|M1Q权重相加之和ؓL重TotalWeightQ用hL为MaxMemberQ一个tsung client实例QL设ؓM2Q分配的模拟用户数可能ؓQ?/p>

MaxMember*(Weight/TotalWeight)

需要注意:
- M2 >= M1
- 若压阶D?code><arrivalphase元素配置durationD,于最l用?0万用h照每U?50速率耗时旉Q最l分配用h小于期望?/p>

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

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

没有物理从机Q主从节炚w在一台机器上Q需要设|?code>use_controller_vm="true"。相比tsung集群Q单一节点tsung启动很单,M之间不需要SSH通信Q直接内部调用?/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 clientq行着一?code>ts_launch/ts_launch_static本地注册模块Q掌控终端模拟用L成和会话控制?/p>

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

L按照xml配置生成全局用户产生速率Q从机按照自w权重分配的速率q行单独控制Q这也是d分解的具体呈现?/p>

用户生成速度控制

在Tsung中用L成速度UC为强度,Ҏ所配置的load属性进行配|?/p>

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

关键属性:

  • interarrivalQ生成压用L旉间隔
  • arrivalrateQ单位时间内生成用户数量
  • 两者最l都会被转换为生成用户强度系数值是0.25
  • q个是ȝ强度|但需要被各个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 / TotalWeightQ?code>1000 * ClientIntensity是易读的每U生成用户速率倹{?/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被分配用h公式为:min(Duration * ClientIntensity, MaxNumber * Weight / TotalWeight)Q?/p>

  • 避免Mh数超出限?/li>
  • 阶段Phase持箋旉所产生用户数和tsung client分配用户C至于产生冲突Q一U协调策?/li>

再看一下launch加蝲一个终端用hQ会自动Ҏ当前分配用户生成压力pL获得ts_stats:exponential(Intensity)下一个模拟用户生等待生成的最长时_单位为毫U?/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.

l箋往下看吧,隐藏了部分无关代码:

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.

下一个用L成需要等?code>Wait - LaunchDuration毫秒旉?/p>

l出一个采h据,只有一个从机,q且用户产生速度1U一个,׃?0个用P

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

采集日志部分Q记录了Wait旉|其实M旉q需要加?code>LaunchDurationQ虽然这个值很)Q?/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

M来说Q每一个用L成间隔间不是固定|是一个大U|有偏差,但接q于目标讑֮Q?000毫秒生成一个用h准间隔)?/p>

执行模拟l端用户会话程

关于会话的说明:

  • 一个session元素中的定义一pdh-响应{交互行为称之ؓ一ơ完整会?/li>
  • 一个模拟用户需要执行一ơ完整会话,然后生命周期完成Q然后结?/li>

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

  • 所有下一步需要执行的会话指o都需要向L?code>ts_config_serverh
  • 执行会话指o
  • 具体协议调用相应协议插gQ比如ts_mqttl装会话消息
  • 建立|络Socketq接Q封装众多网l通道
  • 发送请求数据,处理响应
  • 记录q发送监控数据和日志

ts_clientK?/p>

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

nieyong 2016-07-25 14:02 发表评论
]]>
TsungW记之主从模型篇http://www.qpkxbc.shop/yongboy/archive/2016/07/23/431294.htmlnieyongnieyongSat, 23 Jul 2016 03:56:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2016/07/23/431294.htmlhttp://www.qpkxbc.shop/yongboy/comments/431294.htmlhttp://www.qpkxbc.shop/yongboy/archive/2016/07/23/431294.html#Feedback0http://www.qpkxbc.shop/yongboy/comments/commentRss/431294.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/431294.html前言

本篇讲解Tsung大致功能l成、结构,以及M模型Q以便M上掌握?/p>

Ml成

K?/p>

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

代码l成

从代码层ơ梳理一下tsung目功能l成l构Q便于一目了Ӟ方便直接索引?/p>

tsung_structK?/p>

M模型一?/h3>

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

tsung_master_slaveK?/p>

程大致说明Q?/p>

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

q种模型下:

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

M之间交互程

下面一张图单说明了M之间核心模块交互程Q虽然粗略,核心点也是涉及C?/p>

tsung_slave_flow_detaiK?/p>

后面会对具体协议部分有更l论q?/p>

一ơ压回话(ts_clientQ工作流E?/h4>

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

大致程囑֦下:

ts_client_structureK?/p>

会话什么时候结?/h4>
  • 针对从节点上Q(一个终端用LQ一ơ完整会话(sessionQ:
    • h主节点ts_config模块Q获取会话Session信息Q包含一ơ会话需要完成Q务LCount
    • 从节点ts_client 每执行一ơ事ӞdLCount?
    • 当Countgؓ0Ӟ说明d执行完毕Qts_client生命周期圆满Q一ơ完整会话结?/li>
  • 从节Ҏ分配的所有会话都l束了,表示从节点生命周期也会结?/li>
  • 主节Ҏ制的所有从节点都结束了Q即所有会话都一一完成Q那么整体压也l束了,整个压测程l束

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

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

nieyong 2016-07-23 11:56 发表评论
]]>
TsungW记之开?/title><link>http://www.qpkxbc.shop/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>http://www.qpkxbc.shop/yongboy/archive/2016/07/22/431291.html</guid><wfw:comment>http://www.qpkxbc.shop/yongboy/comments/431291.html</wfw:comment><comments>http://www.qpkxbc.shop/yongboy/archive/2016/07/22/431291.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.qpkxbc.shop/yongboy/comments/commentRss/431291.html</wfw:commentRss><trackback:ping>http://www.qpkxbc.shop/yongboy/services/trackbacks/431291.html</trackback:ping><description><![CDATA[<h3 id="toc_0">前言</h3> <p>有测试驱动的开发模式,目的在于保业务层面功能是准的Q每一ơ新增、修改等动作保都不会媄响到现有功能。功能开发完成了Q需要部|到U上Q系l能够承载多大的用户量呢Q这时候就需要借助于性能压测Q也UC为压力测试,界定pȝ能够承蝲具体定w上限Q从容应对业务的q营需要,扩容或羃容,心中有底?/p> <p>工欲善其事,必先利其器。掌握一U压工Pq切实应用到实践环境中,q以此不断P代,压力试驱动推动所开发后端应用处理性能逐渐完善?/p> <p>目前成熟的支持支持TCP、HTTP{连接通道的压工具不,以前接触qApache JMeterQ后面又接触q?a href="tsung.erlang-projects.org">Tsung</a>Q因为在实际环境下用比较多Q支持丰富的业务场景定义Qƈ且可扩展性强Q因此Tsung强力推荐之?/p> <h3 id="toc_1">Z么要选择Tsung</h3> <ul> <li>ZErlangQƈ发处理性能好,可以模拟_多v量用P只要你有_多的机器</li> <li>受益于ErlangQ天然支持分布式Q很Ƣ快的运行在一个集中</li> <li>支持协议众多 WebDAV/WebScoket/MQTT/MySQL/PGSQL/Shell/AQMP/JABBER/XMPP/LDAP {?/li> <li>传输通道支持 TCP/UDP/SSLQ更底层支持IPv4/IPv6</li> <li>支持单机l定多个IPQ无论是虚拟IPQ还是物理网卡绑定IPQ可以突破单机端?5535的限Ӟ扩展可能多的网l连接出口地址</li> <li>支持监控被压的服务器,通过Erlang Agent/SNMP/Munin </li> <li>压测l节XML可配|,q是一个完全基于情景的压力试行ؓ清单Q依赖于你的惌Q呈现完整业务的表达 <ul> <li>场景可以是动态的Q来自于文g、代码或者服务器响应可以构成下一个请求的参数Q这是可编E的h?/li> <li>行ؓ可以hQ回话可以在不同场景中,按照不同的行范各自^行进?/li> <li>休眠Q或暂停机制Q是可以随机的,?/li> <li>压测用户产生方式Q动态有序或随机</li> </ul></li> </ul> <p>MQTsung是一Ƒּ源的高性能分布式压力测试工P支持可编E的情景化测试方案,要向发挥它的Ҏ,依赖于h们的惌力和创造性?/p> <h3 id="toc_2">Z么要压力试驱动?Q?/h3> <p>软g/pȝ架构往往着gMl构Q这个可以是一个逐渐完善的过E。这U自我的不断完善的驱动往往来自于实c线上考验。而压力测试可以提供一U推动,心力暴露着架构在性能定w存在的一些不_~陷Q促使着向着更好的方向发展?/p> <p>pȝ的构Z赖于具体参与执行的hQ就是一资q工程师,业务上每一ơ功能的快速更q、Q何潜在局部修攚w会导致媄响、拖垮整体性能Q这是Z常说??a href="https://zh.wikipedia.org/zh/%E8%9D%B4%E8%9D%B6%E6%95%88%E5%BA%94">蝴蝶效应</a>“,牵一发而动全n?/p> <p>如何提早感知q且提早修复Q这需要压力测试的驱动Qƈ且压力测试应该成Z个常规化的例行行为,日常化的动作。在每一ơ修改之后,都要q一轮的压测的碾压之后,提供当前后端应用处理的性能、容量等具体指标Q用于指导后l业务上U业务的开展?/p> <h3 id="toc_3">实际操作上的</h3> <p>在一般互联网公司Q一般线上程序修改后之后Q需要经qQA团队/部门全部功能回归、校验之后才能够上线Q往往~少压测环节Q因Z/她们q不保证pȝ处理性能和容量是否恶化,pȝ的性能建立在系lM的功能上Q如何避免在性能上出现”牵一发而动全n“,有条件的QA同学/团队考虑增加性能压测环节Q功?+ 性能双重回归Q修改媄响点清晰、透明化?/p> <h3 id="toc_4">W记列表</h3> <p>本系列笔讎ͼZtsung-1.6.0源码基础上分析,q行环境为Linux Centos 6?/p> <p>W记列表Q?/p> <ol> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/23/431294.html">TsungW记之主从模型篇</a></li> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/25/431310.html">TsungW记之主从资源协调篇</a></li> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/26/431322.html">TsungW记之压端资源限制?/a></li> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/27/431340.html">TsungW记之分布式增强跛_SSH绊?/a></li> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/28/431354.html">TsungW记之IP直连支持?/a></li> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/29/431367.html">TsungW记之监控数据收集篇</a></li> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/30/431396.html">TsungW记之插件编写篇</a></li> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/08/08/431498.html">TsungW记?00万用户压执行步骤篇</a></li> <li><a href="http://www.qpkxbc.shop/yongboy/archive/2016/08/16/431601.html">TsungW记之IP地址和端口限制突破篇</a></li> </ol> <p>Z方便理解Q一些用词说明:</p> <ul> <li>主节点,也称之ؓMaster NodeQ指的是q行tsung_controller的应用服务实例,q行tsung启动应用自动产生“tsung_controller@机器?IP”节点名Uͼ一般用过Erlang的同学会很明?/li> <li>从节点,即tsung client应用实例Q对?tsung/src/tsung 目代码Q由tsung_controller主节Ҏ制启动、关闭、Q务分配等</li> </ul> <h3 id="toc_5">结</h3> <p>参与一个实时性交互强的项目,从一开始单机支撑不?万用戗^均请求响应时间约900毫秒Q到目前混合部v的单机支?0万用戗^均响应时间ؓ16毫秒Q这个过E中Tsung不断的压推动着架构逐渐E_、系l承载容量、QPS优化{完全达标。这是一个压力测试驱动性能改进的流E,每一步的改进能够得到正向反馈?/p> <p>q一pdW记Q所谈核心是TsungQ无论是认知q是改进Q最l都是ؓ了理解利器的Ҏ面面Q方便着手于实践环境中,压测所带来的能量能够驱动我们的E序/服务性能提升、稳定运行,q而更好方便我们进行容量规划、线上部|等?/p> <img src ="http://www.qpkxbc.shop/yongboy/aggbug/431291.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.qpkxbc.shop/yongboy/" target="_blank">nieyong</a> 2016-07-22 15:36 <a href="http://www.qpkxbc.shop/yongboy/archive/2016/07/22/431291.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TCP协议~陷不完全记?/title><link>http://www.qpkxbc.shop/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>http://www.qpkxbc.shop/yongboy/archive/2015/05/07/424917.html</guid><wfw:comment>http://www.qpkxbc.shop/yongboy/comments/424917.html</wfw:comment><comments>http://www.qpkxbc.shop/yongboy/archive/2015/05/07/424917.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.qpkxbc.shop/yongboy/comments/commentRss/424917.html</wfw:commentRss><trackback:ping>http://www.qpkxbc.shop/yongboy/services/trackbacks/424917.html</trackback:ping><description><![CDATA[<div class="wrap"> <h3 id="-">零。前a</h3> <p>TCP自从1974q被发明出来之后Q历l?0多年发展Q目前成为最重要的互联网基础协议。有U网l环境下QTCP表现的如虎添|但在Ud互联|和物联|环境下Q稍微表现得略有不?/p> <p>Ud互联|突出特性不E_Q信号不E_Q网l连接不E_。虽然目前发展到4GQ手机网l带宽有所增强Q但因其动Ҏ,信号也不是那么稳定:坐长途公交RQ或搭乘城铁Ӟ或周边上|密集时{环境,现实环境很复杂?/p> <p>以下讨论ZLinux服务器环境,假定环境为移动互联网环境。记录我目前所知TCP的一些不I有所偏差Q请l与指正?/p> <h3 id="-">一。三ơ握?/h3> <p>在确定传递数据之前需要三ơ握手,昄有些多余Q业界提ZTCP Fast Open (TFO)扩展机制Q两ơ握手之后就可以发送正怸务数据了。但q需要客L和服务器端内核层面都支持才行Q?Linux内核3.6客户端,3.7支持服务器端?/p> <p><img alt="" src="https://lwn.net/images/2012/tfo/foc_use.png" /></p> <p>q阶阅读Q?a href="https://lwn.net/Articles/508865/">TCP Fast Open: expediting web services</a></p> <h3 id="-">二。慢启动</h3> <p>一ơ的HTTPhQ应用层发送较大HTML面的数据,需要经q若q个往q@环时?Round-Trip Time)之后Q拥塞窗口才能够扩展到最大适合数|中间q程颇ؓ冗余。这个参数直接关pȝpȝ吞吐量,吞吐量大了,pȝ延迟了。但讄成多大,需要根据业务进行抉择?/p> <p>3.0内核之前初始化拥塞窗?initcwnd)大小?。一个已建立q接初始传输数据时可传?个MSSQ若1个MSS?400那么一ơ性可传?K的数据,若ؓ10Q一ơ性可传?3K的数据?/p> <p>hl过调研Q徏议移动互联网WEB环境下徏议initcwnd讄?0Qlinux内核3.0版本之后默认gؓ10。遇到较低内核,需要手动进行设|?/p> <p>若是局域网环境有类似大数据或文件的传输需求,可以考虑适当攑֮一些?/p> <p>若长q接建立之后传输的都是小消息Q每ơ传输二q制不到4KQ那么慢启动改动与否都是无关紧要的事情了?/p> <p>q阶阅读Q?/p> <ul> <li><a href="http://www.cdnplanet.com/blog/tune-tcp-initcwnd-for-optimum-performance/">Tuning initcwnd for optimum performance</a> </li><li><a href="https://blog.cloudflare.com/optimizing-the-linux-stack-for-mobile-web-per/">Optimizing Your Linux Stack for Maximum Mobile Web Performance</a> </li><li><a href="http://research.google.com/pubs/pub36640.html">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先进先出队列Q当前面数据传输丢失后,后箋数据单元只能{待Q除非已l丢q数据被重传ƈ认接收以后Q后l数据包才会被交付给客户端设备,q就是所谓的U头QHOLQhead-of-line blockingQ阻塞。比较浪Ҏ务器带宽又降低了pȝ性能Q不高效?/p> <p><img alt="" src="http://orm-chimera-prod.s3.amazonaws.com/1230000000545/images/hpbn_0208.png" /></p> <h4 id="1-">1. 多\复用不理?/h4> <p>HTTP/2提出的业务层面多路复用,虽然在一定程度上解决了HTTP/1.*单\传输问题Q但依然受制于所依赖的TCP本nU头d的缺陗构ZTCP上层协议的多路复用,一旦发生出现线头阻塞,需要小心对待多路的业务数据发送失败问题?/p> <h4 id="2-tcp-keepalive-">2. TCP Keepalive机制失效</h4> <p>理论上TCP的Keepalive保活扩展机制Q在出现U头d的时候,发送不出去被一直阻塞,完全失效?/p> <p>cM于NFS文gpȝQ一般采用双向的TCP Keepalive保活机制Q用以规避某一端因U头d出现DKeepalive无效的问题,及时感知一端存zLc?/p> <h4 id="3-">3. U头d时提示</h4> <p>数据包发送了Q启动接收确认定时器Q超时后会重发,重发依然无确认,后箋数据会一直堆U到待发送队列中Q这里会有一个阻塞超Ӟ法很复杂。上层应用会接收到来自内核协议栈的汇?No route to host"的错误信息,默认不大?6分钟旉。在服务器端Q没有业务心x持的情况下)发送数据前把终端强制断U,Zl合TCPDUMP截包Q等15分钟左右内核警告"EHOSTUNREACH"错误Q应用层面就可以看到"No route to host"的通知?/p> <h3 id="-">四。四ơ摆?/h3> <p>两端q接成功建立之后Q需要关闭时Q需要生四ơ交互,q在Ud互联|环境下Q显得有些多余。快速关闭,快速响应,冗余交互D|络带宽被占用?/p> <h3 id="-">五。确认机刉知C层应用?</h3> <p>q是一个比较美好的愿望Q上层应用在调用内核层接口发送大D|据,内核完成发送ƈ且收到对方完整确认,然后通知上层应用已经发送成功,那么在一些环境下Q可以节省不业务层面交互步骤?/p> <h3 id="-nat-">六。NAT|关时</h3> <p>IPV4有限Q局域网环境借助于NAT路由讑֤扩展了接入终端设备的数量。当建立一个TCP长连接时QNAT讑֤需要维护一个内部终端连接外部服务器所使用的内部IP:PORT与出ȝIP:PORT映射对应关系。这个关p需要维护,比较耗费内存资源Q有时定时器清理,否则会导致内存撑爆?/p> <p>不同NAT讑֤时g一P因此才需要心跌助,保l过NAT讑֤的连接一直保持,避免因过长的旉被踢掉。比如针对中国移动网l连接持久时间一般设|ؓ不超q?分钟。各U网l略有差异,引入心蟩机制比较合适?/p> <h3 id="-ip-">七。终端IP漫游</h3> <p>手机l端l常?G/3G/4G和WIFI之间切换Q导致IP地址频繁发生改变。这样造成的后果就是已有的|络h-响应被放弃和l止Q需要h工干预或重新发vhQ存在资源浪费现象?/p> <p>支持Multipath TCP的终端设备,可以同时利用 2G/3G/4G ?WiFi 建立Mutlpathq接Q通过多点优化|络下蝲Q且互ؓ备䆾。可以很好解军_个网l共存的情况下,一个网l中断不会导致全局h处理中断Q在讑֤的连接稳定和可靠性方面有所增强?/p> <p>当然Q服务器之间也可以利用Multipath TCP的多个网l增强网l吞吐量?/p> <p>现状是:</p> <ol> <li>目前只有IOS 7以及后箋版本支持 </li><li>Linux kernel 3.10实验分支上可以看到其支持w媄Q但何时合ƈC分支上,暂时未知 </li></ol> <p>q阶阅读Q?a href="http://blog.multipath-tcp.org/blog/html/index.html">A closer look at the scientific literature on Multipath TCP</a></p> <h3 id="-tcp-">八。TCP~存膨胀</h3> <p>当\由器接收到的数据包超其队列长度Ӟ一般会随机丢包Q以减少膨胀。针对上层应用程序而言Qgq增加,或误认ؓ数据丢失Q或q接丢失{?/p> <p>遇到q种情况Q一般徏议快速发包,以避免丢q数据部分。内核层面今早升U到最新版Q不低于3.6卛_?/p> <p>q阶阅读Q?a href="http://en.wikipedia.org/wiki/Bufferbloat">Bufferbloat</a></p> <h3 id="-tcp-">九。TCP不是l对可靠?/h3> <ol> <li>IP和TCP协议在头部都会有check sum错误校验和机Ӟ16位表C,反码相加Q结果求反,具体可参?<a href="http://blog.csdn.net/zhangskd/article/details/11770647">TCP校验和的原理和实?/a>。一般错误很L可检出来,但遇C?6位数字相加后l果不变的情况就一{莫展了 </li><li> <p>以太|CRC32校验一般情况下都很OKQ但可能遇到两端隔离多个路由器情况下Q就有可能出现问题,比如陈硕老师提供的一张图Q?<img alt="" src="http://hi.csdn.net/attachment/201106/6/0_13073208334BG8.gif" /></p> <blockquote> <p>上图中Client向Server发了一个TCP segmentQ这个segment先被装成一个IP packetQ再被封装成ethernet frameQ发送到路由器(图中消息aQ。Router收到ethernet frame (b)Q{发到另一个网D?c)Q最后Server收到dQ通知应用E序。Ethernet CRC能保证a和b相同Qc和d相同QTCP header check sum的强度不以保证收发payload的内容一栗另外,如果把Router换成NATQ那么NAT自己会构造cQ替换掉源地址Q,q时候a和d的payload不能用tcp header checksum校验?/p></blockquote> </li><li> <p>路由器可能偶然出现硬?内存故障D收发IP报文出现多bit/单bit的反转或双字节交换,q个反{如果发生在payload区,那么无法用链路层、网l层、传输层的check sum查出来,只能通过应用层的check sum来检。因此徏议应用层要设法添加校验数据功能?/p> </li><li> <p>大文件下载添加校验保证数据完整性,一般采用MD5Q也用于防止安全改</p></li></ol> <p>参考资料:</p> <ul> <li>Paper《When the CRC and TCP checksum disagree? </li><li><a href="http://noahdavids.org/self_published/CRC_and_checksum.html">The Limitations of the Ethernet CRC and TCP/IP checksums for error detection</a> </li><li><a href="http://status.aws.amazon.com/s3-20080720.html">Amazon S3遇到的单bit反{U上事故</a> </li></ul> <h3 id="-">十。小l?/h3> <p>在这个满世界都是TCP的环境下Q要惛_TCP动大手术Q这个是不太可能的,因ؓ它已l固化到已有的系l内核和Zg中。比如升U终端(比如Android/IOS{)pȝ/ZgQLinux服务器内核,中间讑֤/中介讑֤Q如路由器等Q,q是一个浩大工E,目前看也不现实?/p> <p>TCP位于pȝ内核层,内核I间的升U、修复,最为麻烦。服务器端升U还好说一些,用户l端pȝ的升U那叫一个难。用L?用户核的应用升、改造相Ҏ来说可控性强Q基于此Google专家们直接在UDP协议上进行构建、ƈ且运行在用户I间的QUIC协议Q综合了UDP的轻量和TCP的可靠性,是一个比较新颖的方向?/p> <p>若是对以后底层传输协议有所期望的话Q?/p> <ul> <li>在用L_用户核)出现可以定制的协议,cM于QUIC </li><li>传统的TCP/UDP可以q行在用L_直接略过内核 </li><li>完整协议栈以静态链接库形式提供l上层应? </li><li>上层应用可以在编译、打包的时包含其所依赖协议栈静态链接库so文g </li><li>dpdk/netmap{Packet IO框架 + 用户I间协议堆栈Q数据将从网卡直接送达上层应用 </li><li>Linux内核重要性降低,常规的SSHpȝl护 </li></ul> <p>虽然TCP存在q样、那L问题Q但目前q是无法l过的网l基设施Q但E微明白一些不的地方Q或怼Ҏ们当前用的现状有所帮助?/p></div><img src ="http://www.qpkxbc.shop/yongboy/aggbug/424917.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.qpkxbc.shop/yongboy/" target="_blank">nieyong</a> 2015-05-07 14:56 <a href="http://www.qpkxbc.shop/yongboy/archive/2015/05/07/424917.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>随手CTCP KeepaliveW记http://www.qpkxbc.shop/yongboy/archive/2015/04/14/424413.htmlnieyongnieyongTue, 14 Apr 2015 09:08:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2015/04/14/424413.htmlhttp://www.qpkxbc.shop/yongboy/comments/424413.htmlhttp://www.qpkxbc.shop/yongboy/archive/2015/04/14/424413.html#Feedback1http://www.qpkxbc.shop/yongboy/comments/commentRss/424413.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/424413.html

零。前a

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

一。TCP Keepalive解读

长连接的环境下,Z一般用业务层面或上层应用层协议(诸如MQTTQSOCKET.IO{)里面定义和用。一旦有热数据需要传递,若此时连接已l被中介讑֤断开Q应用程序没有及时感知的话,那么׃D在一个无效的数据链\层面发送业务数据,l果是发送失败?/p>

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

1. 交互q程

2. 协议解读

下面协议解读Q基?a href="https://tools.ietf.org/html/rfc1122#section-4.2.3.6">RFC1122#TCP Keep-Alives?/p>

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

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

二。Tcp keepalive 如何使用

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

1. pȝ内核参数配置

  1. tcp_keepalive_timeQ在TCP保活打开的情况下Q最后一ơ数据交换到TCP发送第一个保zL包的间隔,卛_许的持箋I闲旉Q或者说每次正常发送心跳的周期Q默认gؓ7200sQ?hQ?/li>
  2. tcp_keepalive_probes 在tcp_keepalive_time之后Q没有接收到Ҏ认Ql发送保zL包ơ数Q默认gؓ9Q次Q?/li>
  3. tcp_keepalive_intvlQ在tcp_keepalive_time之后Q没有接收到Ҏ认Ql发送保zL包的发送频率,默认gؓ75s?/li>

发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probesQ就得到了从开始探到攑ּ探测定q接断开的时?/p>

若设|,服务器在客户端连接空闲的时候,?0U发送一ơ保zL包到客LQ若没有及时收到客户端的TCP Keepalive ACK认Q将l箋{待15U?2=30U。M可以?0s+30s=120U(两分钟)旉内可到q接失效与否?/p>

以下改动Q需要写入到/etc/sysctl.conf文gQ?/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 命o一下是否已l生效?/p>

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

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

只需要在服务器端一方设|即可,客户端完全不用设|,比如Znetty 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();

JavaE序只能做到讄SO_KEEPALIVE选项Q至于TCP_KEEPCNTQTCP_KEEPIDLEQTCP_KEEPINTVL{参数配|,只能依赖于sysctl配置Q系l进行读取?/p>

3. C语言如何讄

下面代码摘取自libkeepalive源码QC语言可以讄更ؓ详细的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. 针对已有E序没有编码KTTCP EEPALIVE实现

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

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

q个工具q有一个比较方便的地方Q可以直接在E序q行前指定TCP保活详细参数Q可以省去配|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.cQ可以找到对应关p:

     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;

q里可以扑ֈ大部分处理逻辑Qnet/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++; // q里仅仅是计敎ͼq没有再ơ发送保zL包
               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 函数定义Q?/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的应用程序,一般可以捕获到下面几种cd错误

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

五。常见的使用模式

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

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

  • 不想折腾Q那弃用TCP Keepalive吧,完全依赖应用层心xӞ灉|可控性强
  • 除非可以很好把控TCP Keepalive机制Q那可以根据需要自׃用吧

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

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

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


nieyong 2015-04-14 17:08 发表评论
]]>UdAPP后端|络处理一些问题记?/title><link>http://www.qpkxbc.shop/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>http://www.qpkxbc.shop/yongboy/archive/2015/03/30/423963.html</guid><wfw:comment>http://www.qpkxbc.shop/yongboy/comments/423963.html</wfw:comment><comments>http://www.qpkxbc.shop/yongboy/archive/2015/03/30/423963.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.qpkxbc.shop/yongboy/comments/commentRss/423963.html</wfw:commentRss><trackback:ping>http://www.qpkxbc.shop/yongboy/services/trackbacks/423963.html</trackback:ping><description><![CDATA[<div class="wrap"> <h3 id="-">零。前a</h3> <p>q里讲的UdAPP主要指的是安卓^収ͼ大部分情况也适用于IOS{移动^収ͼ可能重点嘛会在后半部分呢?/p> <h3 id="-sdk-">一。嵌入多SDK存在的隐?/h3> <p>但凡一个常用的APP都会嵌入臛_一个SDKQ不同来源或同一来源Q有q告SDKQ有推送SDKQ有性能汇报SDKQ有用户跟踪SDKQ有l计量SDK{,有支付SDK{等。虽然带来了功能的复用和解耦,便于U向扩展Q但可能会存在:</p> <ol> <li>一个SDK可以看做一个后台ServiceQ多个SDK多个Service存在Q粗略上计算Q? </li><li>q行~慢的问题,多个SDK或多功能的模块(若不按需加蝲的话Q会Dq行~慢Q符合木桶理论,最慢的那个拖慢了M步伐 </li><li>每一个SDK都有自己核心诉求Q各自ؓ政,实现方式各异。M架构是否合适,带来了隐形的兼容成本 </li><li>彼此协调q作兼容性问题,一旦出现某个隐含BUGQ会不会Dq锁反应 </li><li>CPU、内存、网l等资源竞争问题Q想协调都很? </li><li>|络资源各自为政Q无法共享,更不用提q接复用 </li></ol> <p>若是同一来源的SDKQ当问题发生的时候还能够有所协作E作调整Q虽然这也是比较理想的情c外部的SDK可能问题反馈和BUG修正Q时效性就不太好说了?/p> <p>解耦虽然带来了功能的扩展性,带随之带来了资源利用斚w的重复和费Q愈多的SDK嵌入有可能DMq行~慢Q需要}慎用?/p> <h3 id="-app-service-">二。APP后台Service数量很多</h3> <p>一般安卓手Z提供后台q程的查看,一般APP会启动多个后台服务,比如爱奇艺APP很变态,5个服务存在(优酷APP也好不了哪去Q?-4个服务存在)Q?/p> <p><img alt="" src="http://www.qpkxbc.shop/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>一般较大公司,都会自己开发SDKQ但于KPI{业l考核Q很有Z认真从M上考虑把多个Serice服务合ƈZ个,节省一点用L资源占用Q不q那需要考验研发人员的架构能力了?/p> <p>虽然白用户不一定会查看后台服务Q但若干个后台服务,每一个都需要维护自w服务存zL,每一个都会占用若q内存,若合qؓ一个,自然减少了CPU占用和内存资源占用,以及l护成本Q还是有些小必要的?/p> <h3 id="-http-">三。HTTP短连接请求过于频J?/h3> <p>一般来Ԍ打开一个APP的时候,通过Wireshark抓包工具可以看到若干个HTTPq接瞬间建立Q诸如淘宝,天猫Q美团,优酷{APPQ满屏的都是HTTPhQ看着让h感觉有些恐怖,业务数据、设备信息上传、SDK信息抓取、埋点跟t、头像图片、HTML资源{,那叫一个庞大?/p> <p>但也没办法,业务展示那么丰富Q需要消耗过多的资源。大部分h都只专注于一部分数据。针对单一昄界面屏幕Q可以通过一个请求获得合q后的响应结果,减少|络资源消耗?/p> <p>手机淘宝和手机QQQ在HTTPh优化斚wQ已l在使用IP直连、HTTP长连接、SPDY{进行加速处理,值得学习?/p> <h3 id="-tcp-">四。TCP长连接利用率不高</h3> <p>q里所说长q接Q更多的是指TCP方式q接Q也包括HTTP方式的长q接Q但更多指的是具有双向通道的长q接。单通道的方式,协议所限,资源利用率不是那么高?/p> <p>当前TCP长连接在其存zL_大都只专注于传输一cd体业务内容,比如PUSH推送,一台手接一天,也接收不了几条消息,最频繁的就是心跳保zL据包传送了?/p> <p>一旦涉及到新的业务Q大安是要新徏一条全新TCPq接Q彼此业务不兌嘛,看上d耦合啊,但却造成了企业大量的重复资源费Q新的业务需要处理另外的q接接入Q资源重复投入,业务层面嘛,也会存在一半左右的重叠。当Ӟ若不在乎q些的话Q就另当别论了,但是你要是能够ؓ消费者手机的资源消耗考虑Q那便是用户的福音了?/p> <p><img alt="" src="http://www.qpkxbc.shop/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>长连接的业务层面多\复用Q支持类g摇一摇、抢U包、客服、客服咨询等Q同一个连接传输不同类型的数据Q即节省了服务器资源Q又提高了网l连接利用率Q两端的l护成本降低。针对客L而言Q多路业务复用在一条TCPq接上传输,需要业务\?+ 相应业务处理卛_。服务器端嘛Q接入不同的业务处理Q依靠业务\p行消息分发?/p> <p>题外话,举一个例子,qxM旉点约3000千万用户q接Q按每台?00万计,10-20台机器接入处理可,推送啊Q聊天啊Q客服咨询,q_再来一个好q摇一摇都行。年底了要扩容到1亿左右用h啊摇的抢东抢西抢U包Q还是同一个连接的方式Q不q增加了新的业务cd数据传递,服务器嘛Q扩展到50-80台就很OK了。用户量一旦降落,撤下多余机器p。或许,有空可以写一供一亿用户在U的pȝ架构的方?:))</p> <h3 id="-http-">五。HTTP 持久q接使用</h3> <p>针对HTTP/1.1可能很多人的思维方式Q还停留在浏览器环境Q一般浏览器有不支持或支持不够好Q但在非览器环境下Q针对APP应用的环境,了很多传统览器环境的限制Q但要完全发挥和释放HTTP/1.1协议所带来的持久特性,q便需要深度理解协议规范和具体的用环境等q行抉择?/p> <h4 id="1-http-1-1-keepalive-">1. HTTP/1.1 KeepAliveҎ?/h4> <p><img alt="" src="http://cdn.nginx.com/wp-content/uploads/2014/03/tcpka.png" height="120" width="762" /></p> <p>虽说HTTP/1.1 Keep-AliveҎ支持多个请求在同一个连接上排队发送,在浏览器端正常的HTML{资源请求,会带来线头阻塞弊端,后一个请求依赖于前一个请求完成,一旦出现阻塞,后箋h只能排队{待?/p> <p>但若针对非浏览器、业务模型不是很复杂的环境,比如日志采集/跟踪{常见业务,属于单@环的h-响应模型Q响应仅仅需要HTTP 200状态码卛_Q这要求服务器端接收之后异步处理直接q回200状态)Q后l的h只需要排队,q且不会对gq有苛刻要求Q那么Keep-AliveҎ就很适合。一般出现阻塞,那就意味着|络状况不容乐观Q关闭然后重键一个HTTP持久q接p?/p> <p>有些环境Q只需要发送数据,客户端不兛_有没有响应(或不接收响应Q,cM于UDP的方式,比如实时日志Q突出实时特性)Q但q需要客L异步处理响应?/p> <p>一般视频网站都会有实时跟踪用户正在播放中的视频播放状态,那就需要徏立一个持久的HTTP/1.1q接Q即时、持l传递视频观看事Ӟ自然避免了短连接多ơ创建、关闭的开销?/p> <h4 id="2-http-1-1-pipelining">2. HTTP/1.1 Pipelining</h4> <p>建立在Keep-Alive持久化基之上Q中文译为管U化Q支持连l的q等的GET/HEADҎhQ实际环境下Qƈ没有被浏览器所支持?/p> <p>同一个连接,处理同样的三ơ请?响应QKeep-Alive和Pipelining方式不同Q?/p> <p><img alt="" src="http://upload.wikimedia.org/wikipedia/commons/thumb/1/19/HTTP_pipelining2.svg/300px-HTTP_pipelining2.svg.png" /></p> <p>览器环境不支持的特性,在应用环境下或许可以扑ֈ光用I间。比如具有通过GET方式提交数据的情况(比如GET方式汇报地理位置QGET方式提交用户讑֤信息QGET方式汇报用户手机抖动情况{)Q多个请求批量发送?/p> <p>但换一个思维方式Q若能够合ƈ扚whZ个POST提交Q不走管U化方式Q可能会更合适一些?/p> <p>针对不太重要数据Q发送完毕,不用{待响应?/p> <h4 id="3-http-2">3. HTTP/2</h4> <p>若想了解HTTP/2的规范,可以参考本博客的其它文字。目前客L库和服务器端库,支持都不太好Q需要观察一D|_公司实力够,完全可以自主开发,或基于SPDY 2.0也可Q目前淘宝APP、腾讯手QQ{,也都在用?/p> <p>HTTP/1.1能够完全解决的问题,没有必要用HTTP/2Q后者造成了实现有些复杂,可能中介讑֤Q网兟뀁代理、CDN{)都还没有Z做好准备呢,但HTTP/2毕竟是趋ѝ?/p> <p>虽然规范只定义一个Hostname只允怸个连接,可能实际情况下会需?-3条长q接以争多的|络带宽资源?/p> <h3 id="-">六。小l?/h3> <p>怎么说呢Q在当前UdAPP环境下:</p> <ul> <li>TCP长连接的通道用途的传输潜力和利用率很低 </li><li>HTTP/1.1的持久特性很Ҏ被h忽略Q未能正? </li><li>HTTP/2多\复用值得期待 </li></ul> <p>无论哪一U方式,都需要熟悉协议和|络Q适合的环境用适合的协议特性,才能够发挥出潜在的性能出来?/p></div><img src ="http://www.qpkxbc.shop/yongboy/aggbug/423963.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.qpkxbc.shop/yongboy/" target="_blank">nieyong</a> 2015-03-30 17:11 <a href="http://www.qpkxbc.shop/yongboy/archive/2015/03/30/423963.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HTTP/2W记之错误处理和安全http://www.qpkxbc.shop/yongboy/archive/2015/03/24/423791.htmlnieyongnieyongTue, 24 Mar 2015 07:27:00 GMThttp://www.qpkxbc.shop/yongboy/archive/2015/03/24/423791.htmlhttp://www.qpkxbc.shop/yongboy/comments/423791.htmlhttp://www.qpkxbc.shop/yongboy/archive/2015/03/24/423791.html#Feedback0http://www.qpkxbc.shop/yongboy/comments/commentRss/423791.htmlhttp://www.qpkxbc.shop/yongboy/services/trackbacks/423791.html

零。前a

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

一。HTTP/2错误

1. 错误定义

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

  • D整个q接不可使用的错误ؓq接错误(connection error)
  • 单独出现在单个连接上的错误ؓ错?stream error)

2. 错误代码

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

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

3. q接错误处理

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

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

4. 错?/h4>

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

  • l端到错误,需要发送一个RST_STREAM帧,其包含了操作到错误流标识W?
  • RST_STREAM应当是发送错误流最后一个Q内含错误原因?
  • 发送方在发送之后,需要准备接收对端将要或卛_发送过来的帧数据,处理方式是忽略之,除非是可以修改连接状态
  • 一般来Ԍl端不应该发送多个RST_STREAM帧,但若在一个往q时间之后已关闭的流上能够l接收Q则需要发送再ơ发送一个RST_STREAM帧,处理q种行ؓ不端的实现?
  • l端在接收到RST_STREAM帧之后,不能响应一个RST_STREAM帧,避免d@?

5. q接l止

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

二。HTTP/2安全注意事项

1. 跨协议攻?/h4>

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

q里有一讲解跨协议d的文章:http://www.freebuf.com/articles/web/19622.html

TLS的加密机制得攻击者很难获得明文,另外TLS的ALPN协议扩展可以很轻村֤理请求是否需要作为HTTP/2hq行处理QM可有效阻止对ZTLS的其它协议攻凅R?/p>

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

MQ程序要可能的健壮Q容错,针对非法的请求,直接关闭Ҏq接?/p>

2. 中介端数据{换封装的d

中介所做的HTTP/1.1和HTTP/2之间转换Q会存在d点:

  1. HTTP/2头字D名U编码允怋用HTTP/1.1没有使用到的头字D名Uͼ中介在{换HTTP/2到HTTP/1.1时就Ҏ出现包含非法h头字DHTTP/1.1数据?
  2. HTTP/2允许头字D值可以是非法|诸如回R(CR, ASCII 0xd), 换行 (LF, ASCII 0xa), 零字W?(NUL, ASCII 0x0)Q这在逐字解析实现时是一个风险?

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

3. 推送内容的~存

推送内Ҏ保证的服务器提供Q是否缓存由头字DCache-Control控制?/p>

但若服务器上多租户Ş式(SAASQ,每一个租户用一部分URLI间Q比?tenant1.domain.comQtenant2.domain.comQ服务器需要确保没有授权的U户不能够推送超于预期的资源Q覆盖已有内宏V?/p>

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

4. 拒绝服务d注意事项

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

MQ诸如SETTINGS帧、小帧或IQ报头压~被合理滥用Ӟ表明上看W合逻辑Q会造成资源q度消耗。这需要服务器端监控跟t到此种行ؓQƈ且设|用数量的上限Q一旦发现直接报ENHANCE_YOUR_CALMcdq接错误?/p>

5. 报头块大限?/h4>

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

服务器一旦接收到过报头限制hQ需要响应一?31Q请求头q大Q?HTTP状态码Q客L呢可直接丢掉响应?/p>

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

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

一般来Ԍ填充可用来؜淆的真实负载长度,E加保护Q降低攻ȝ可能性。但若不当的填充{略Q固定填充数、可L推导出填充规则等情况都会降低保护的力度,都有可能会被d者破解?/p>

中介讑֤应该保留DATA帧的填充Q需要避免如上所qC些情况)Q但可丢弃HEADERS和PUSH_PROMISE帧的填充?/p>

三。TLS

HTTP/2加密建立在TLS基础Q关于TLSQ维基百U上有解释:http://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

摘取一张图Q可说明ZALPN协议扩展定义的协商流E:

其它要求Q?/p>

  • 只能ZTLS >= 1.2版本。目前TLS 1.3案版本,正式版本目前未可知。目前只有TLS 1.2可选?
  • 必须支持Server Name Indication (SNI) [TLS-EXT]扩展Q客L在连接协商阶D需要携带上域名
  • ZTLS 1.3或更高版本构建,仅需要支持SNI扩展。TLS 1.2要求较多
  • ZTLS 1.2构徏
    • 必须用压羃机制。不恰当压羃机制会导致信息外ԌHTTP/2报头有压~机?
    • 必须用重新协商机制。终端对待TLS 1.2重新协商作ؓPROTOCOL_ERRORcdq接错误对待Q密码套件加密次数限制导致连接一直挂L待不可用
    • l端可以通过重新协商提供对客L凭证保护功能在握手期_重新协商必须发生在发送连接序a之前q行。服务器当看到重新协商请求时应该h客户端证书在q接建立?
    • 当客Lh受保护的特定资源Ӟ服务器可以响应HTTP_1_1_REQUIRED错误Q可有效L重新协商机制

四。小l?/h3>

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

nieyong 2015-03-24 15:27 发表评论
]]> 1һ152ͼ òʖ ʱʱʺ ©ͳƱ ʶһ׬2018 㶫ʱʱqqȺ Ƹֻ̳36 IJѺ˫ СʱʤѺ ѡ淨 pk˹ƻ