??xml version="1.0" encoding="utf-8" standalone="yes"?>亚洲精品tv久久久久久久久久,亚洲不卡av不卡一区二区,亚洲av无码成h人动漫无遮挡http://www.tkk7.com/yongboy/archive/2023/09/13/451195.htmlnieyongnieyongWed, 13 Sep 2023 02:13:00 GMThttp://www.tkk7.com/yongboy/archive/2023/09/13/451195.htmlhttp://www.tkk7.com/yongboy/comments/451195.htmlhttp://www.tkk7.com/yongboy/archive/2023/09/13/451195.html#Feedback0http://www.tkk7.com/yongboy/comments/commentRss/451195.htmlhttp://www.tkk7.com/yongboy/services/trackbacks/451195.html前言

q里ZwhoamiC服务Q部|?个实例,分别一一验证各种cd的K8S Service服务范畴?/p>

大致逐一从下面列表逐一验证每种cd的Service讉K方式Q?/p>

  • Service Name
  • 域名解析l果{?/li>
  • CLUSTER-IP
  • EXTERNAL-IP

一些设定如下:

  • 试环境K8S版本号ؓv1.27.3
  • K8S集群Node节点IP地址D范_10.0.1.0/24
  • K8S集群自动生成Pod|段?code>10.43.0.0/24
  • 本书所列代码皆可拷贝直接粘贴到l端界面直接q行

首先Q部|whoami服务

先部|包?个实例的whoamiQ?/p>

# cat << 'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
  labels:
    app: whoami
spec:
  replicas: 3
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
      - name: whoami
        image: containous/whoami
        ports:
        - containerPort: 80
          name: web
EOF

查看一下:

# kubectl get all
NAME                                                      READY   STATUS      RESTARTS         AGE
pod/whoami-767d459f67-qffqw                               1/1     Running     0                23m
pod/whoami-767d459f67-xdv9p                               1/1     Running     0                23m
pod/whoami-767d459f67-gwpgx                               1/1     Running     0                23m

NAME                                                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/whoami                                3/3     3            3           23m

NAME                                                            DESIRED   CURRENT   READY   AGE
replicaset.apps/whoami-767d459f67                               3         3         3       23m

其次Q安装busyboxq行调试

安装一个包含有curl的busybox方便后箋调试Q?/p>

kubectl run busybox-curl --image=yauritux/busybox-curl --command -- sleep 3600

另v一个终端,输入下面命oq入Q?/p>

kubectl exec -ti busybox-curl -n default -- sh

环境准备好之后,下面逐一试各种cdQ?/p>

默认Cluster IP模式

K8S默认Service?code>Cluster IP模式Q面向内部Pod以及通过Ingress对外提供服务?/p>

下面一张图很清晰解释清楚了Port?code>TargetPort适用情景Q?code>Port为Service对外输出的端口,TargetPort为服务后端Pod的端口,两者之间有一个{换:port -> targetPort -> containerPort?br />

创徏一个ServiceQ?/p>

cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami-clusterip
  name: whoami-clusterip
spec:
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: whoami
EOF

部v后可以查看一下:

NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/whoami-clusterip      ClusterIP      10.43.247.74    <none>        80/TCP         57s

下面需要逐一试了?/p>

域名形式Q?/p>

# curl whoami-clusterip
Hostname: whoami-767d459f67-gwpgx
IP: 127.0.0.1
IP: 10.42.8.35
RemoteAddr: 10.42.9.32:35968
GET / HTTP/1.1
Host: whoami-clusterip
User-Agent: curl/7.81.0
Accept: */*

Cluster IP形式Q?/p>

# curl 10.43.247.74
Hostname: whoami-767d459f67-qffqw
IP: 127.0.0.1
IP: 10.42.3.73
RemoteAddr: 10.42.9.32:42398
GET / HTTP/1.1
Host: 10.43.247.74
User-Agent: curl/7.81.0
Accept: */*

域名解析Q只解析到Cluster IP上:

# nslookup whoami-clusterip
Server:		10.43.0.10
Address:	10.43.0.10:53

Name:	whoami-clusterip.default.svc.cluster.local
Address: 10.43.247.74

External IP模式

原理同Cluster IP模式Qؓ指定服务l定一个额外的一个IP地址。当l端讉K该IP地址Q将量一栯{发到Service?/p>

当访?code>external IPQ其端口转换q程Q?code>port -> targetPort -> containerPort?/p>

与默认Service相比Q端口{换流E没有增加,但好处对外暴露了一个可讉K的IP地址Q不q可能需要在交换?路由器层面提供动静态\由支持?/p>

cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami-externalip
  name: whoami-externalip
spec:
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: whoami
  externalIPs:
  - 10.10.10.10
EOF

服务昄如下Q绑定了指定的扩展IP地址10.10.10.10?/p>

# NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/whoami-externalip     ClusterIP      10.43.192.118   10.10.10.10   80/TCP         57s

kube-proxy 在每一个Node节点?code>10.10.10.10上徏立一个{发规则,该IP地址?code>80端口直接{发到对应的后端三?code>whoami Pod 上?/p>

-A KUBE-SERVICES -d 10.10.10.10/32 -p tcp -m comment --comment "default/whoami-externalip external IP" -m tcp --dport 80 -j KUBE-EXT-QN5HIEVYUPDP6UNK

......
-A KUBE-EXT-QN5HIEVYUPDP6UNK -j KUBE-SVC-QN5HIEVYUPDP6UNK
......

-A KUBE-SVC-QN5HIEVYUPDP6UNK ! -s 10.42.0.0/16 -d 10.43.192.118/32 -p tcp -m comment --comment "default/whoami-externalip cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-QN5HIEVYUPDP6UNK -m comment --comment "default/whoami-externalip -> 10.42.2.79:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-JSAT6D2KFCSF4YLF
-A KUBE-SVC-QN5HIEVYUPDP6UNK -m comment --comment "default/whoami-externalip -> 10.42.3.77:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-2R66UI3G2AY2IMNM
-A KUBE-SVC-QN5HIEVYUPDP6UNK -m comment --comment "default/whoami-externalip -> 10.42.8.42:80" -j KUBE-SEP-ZHHIL2SAN2G37GCM

讉K域名Q?/p>

# curl whoami-externalip
Hostname: whoami-767d459f67-gwpgx
IP: 127.0.0.1
IP: 10.42.8.35
RemoteAddr: 10.42.9.32:46746
GET / HTTP/1.1
Host: whoami-externalip
User-Agent: curl/7.81.0
Accept: */*

讉KClusterIP形式Q?/p>

# curl 10.43.192.118
Hostname: whoami-767d459f67-qffqw
IP: 127.0.0.1
IP: 10.42.3.73
RemoteAddr: 10.42.9.32:47516
GET / HTTP/1.1
Host: 10.43.192.118
User-Agent: curl/7.81.0
Accept: */*

讉K暴露的External IPQ?/p>

# curl 10.10.10.10
Hostname: whoami-767d459f67-gwpgx
IP: 127.0.0.1
IP: 10.42.8.35
RemoteAddr: 10.42.9.0:38477
GET / HTTP/1.1
Host: 10.10.10.10
User-Agent: curl/7.81.0
Accept: */*

域名解析l果只解析到其对应的Cluster IPQ?/p>

# nslookup whoami-externalip
Server:		10.43.0.10
Address:	10.43.0.10:53

Name:	whoami-externalip.default.svc.cluster.local
Address: 10.43.192.118

NodePort 模式

?code>Cluster IP相比Q多了一?code>nodePortQ这个NodePort会在K8S所有Node节点上都会开放?/p>

q里有一个端口{换过E:nodePort -> port -> targetPort -> containerPortQ多了一层数据{换过E?/p>

服务定义如下Q?/p>

cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami-nodeport
  name: whoami-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
    protocol: TCP
  selector:
    app: whoami
EOF

查看一下服务分配地址Q?/p>

NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/whoami-nodeport       NodePort       10.43.215.233   <none>        80:30080/TCP   57s

讉K域名Q?/p>

# curl whoami-nodeport
Hostname: whoami-767d459f67-xdv9p
IP: 127.0.0.1
IP: 10.42.2.75
RemoteAddr: 10.42.9.32:36878
GET / HTTP/1.1
Host: whoami-nodeport
User-Agent: curl/7.81.0
Accept: */*

试 CLUSTER IP Q?/p>

# curl 10.43.215.233
Hostname: whoami-767d459f67-qffqw
IP: 127.0.0.1
IP: 10.42.3.73
RemoteAddr: 10.42.9.32:40552
GET / HTTP/1.1
Host: 10.43.215.233
User-Agent: curl/7.81.0
Accept: */*

因ؓ是在每一个K8S Node节点上都会开放一?code>30080端口Q因此可以这栯?{Node IP}:{nodePort}Q如下Node IP地址?code>10.0.1.11

# curl 10.0.1.11:30080
Hostname: whoami-767d459f67-qffqw
IP: 127.0.0.1
IP: 10.42.3.73
RemoteAddr: 10.42.1.0:1880
GET / HTTP/1.1
Host: 10.0.1.11:30080
User-Agent: curl/7.81.0
Accept: */*

域名q是只解析到对应Cluster IPQ?/p>

# nslookup whoami-nodeport
Server:		10.43.0.10
Address:	10.43.0.10:53

Name:	whoami-nodeport.default.svc.cluster.local
Address: 10.43.215.233

LoadBalancer 模式

LoadBalancer模式Q会强制K8S Service自动开?code>nodePort?/p>

q里有一张图Q详l解析数据流向?/p>

服务数据端口转换q程Q?code>port -> nodePort -> port -> targetPort -> containerPortQ?/p>

  • 与默?code>Cluster IP相比Q多了两层数据{换过E?/li>
  • ?code>nodePort相比Q对了一层数据{换过E?/li>
  • ?code>externalIP相比Q在流量场景下没有什么优势了

具体服务定义Q?/p>

cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami-clusterip-none
  name: whoami-clusterip-none
spec:
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: whoami
EOF

查看一下部|结果:

NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/whoami-loadbalancer   LoadBalancer   10.43.63.92     <pending>     80:30906/TCP   57s

服务域名形式Q?/p>

# curl whoami-loadbalancer
Hostname: whoami-767d459f67-qffqw
IP: 127.0.0.1
IP: 10.42.3.73
RemoteAddr: 10.42.9.32:57844
GET / HTTP/1.1
Host: whoami-loadbalancer
User-Agent: curl/7.81.0
Accept: */*

试 CLUSTER-IP

# curl 10.43.63.92
Hostname: whoami-767d459f67-xdv9p
IP: 127.0.0.1
IP: 10.42.2.75
RemoteAddr: 10.42.9.32:42400
GET / HTTP/1.1
Host: 10.43.63.92
User-Agent: curl/7.81.0
Accept: */*

域名解析到Cluster IPQ?/p>

#  nslookup whoami-loadbalancer
Server:		10.43.0.10
Address:	10.43.0.10:53

Name:	whoami-loadbalancer.default.svc.cluster.local
Address: 10.43.63.92

安装LoadBalancer

此时whoami-loadbalancer服务对应?code>EXTERNAL-IP ?<pending>Q我们需要安装一个负载均衡器Q可以选择MetalLB作ؓ负蝲均衡器?/p>

# kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.11/config/manifests/metallb-native.yaml

E后分配可用的LoadBalaner可分配的地址池:

cat << 'EOF' | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default-pool
  namespace: metallb-system
spec:
  addresses:
  - 10.0.1.100-10.0.1.200
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default-pool
EOF

{安装完成之后,可以看到服务whoami-loadbalancer分配的IP地址?10.0.1.101 Q?/p>

NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
......
service/whoami-loadbalancer LoadBalancer   10.43.63.92     10.0.1.101         80:30906/TCP   27h
......

试负蝲均衡IP地址

试一下:

# curl 10.0.1.101
Hostname: whoami-767d459f67-xdv9p
IP: 127.0.0.1
IP: 10.42.2.78
RemoteAddr: 10.42.8.0:33658
GET / HTTP/1.1
Host: 10.0.1.101
User-Agent: curl/7.79.1
Accept: */*

我们看到该服务分配的端口?code>80:30906/TCPQ?code>30906为K8S服务自动生成的NodePortcd端口?/p>

可以找Q一K8S Node节点IP地址试一下:

# curl 10.0.1.12:30906
Hostname: whoami-767d459f67-qffqw
IP: 127.0.0.1
IP: 10.42.3.77
RemoteAddr: 10.42.2.0:9717
GET / HTTP/1.1
Host: 10.0.1.12:30906
User-Agent: curl/7.81.0
Accept: */*

分析一下\pQ可以分析到该负载均衡的External_IP:80的打量?code>NodePort:30906上,然后走Service对应{Pod:80}量分发逻辑?/p>

-A KUBE-NODEPORTS -p tcp -m comment --comment "default/whoami-loadbalancer" -m tcp --dport 30906 -j KUBE-EXT-NBTYBEEXACZI7DPC

......

-A KUBE-SERVICES -d 10.0.1.101/32 -p tcp -m comment --comment "default/whoami-loadbalancer loadbalancer IP" -m tcp --dport 80 -j KUBE-EXT-NBTYBEEXACZI7DPC

......

-A KUBE-EXT-NBTYBEEXACZI7DPC -m comment --comment "masquerade traffic for default/whoami-loadbalancer external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-NBTYBEEXACZI7DPC -j KUBE-SVC-NBTYBEEXACZI7DPC

......

-A KUBE-SVC-NBTYBEEXACZI7DPC ! -s 10.42.0.0/16 -d 10.43.63.92/32 -p tcp -m comment --comment "default/whoami-loadbalancer cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-NBTYBEEXACZI7DPC -m comment --comment "default/whoami-loadbalancer -> 10.42.2.79:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-E3K3SUYNFWT2VICE
-A KUBE-SVC-NBTYBEEXACZI7DPC -m comment --comment "default/whoami-loadbalancer -> 10.42.3.77:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-HG5MYVVID7GJOZA7
-A KUBE-SVC-NBTYBEEXACZI7DPC -m comment --comment "default/whoami-loadbalancer -> 10.42.8.42:80" -j KUBE-SEP-GFJH72YCBKBFB6OG

Headless 无头模式

一般应用在有状态的服务Q或需要终端调用者自己实现负载均衡,{一些特定场景?/p>

通过调用者从端口角度分析Q数据{换流E:targetPort -> containerPort?/p>

在意服务性能的场景,不妨试试无头模式?/p>


服务定义Q?/p>
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami-clusterip-none
  name: whoami-clusterip-none
spec:
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: whoami
EOF

查看服务部v情况Q?/p>

NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/whoami-clusterip-none   ClusterIP      None            <none>                     80/TCP         9h

通过service域名讉KQK8S会自动根据服务域?code>whoami-clusterip-noneq行pick后端对应Pod IP地址?/p>

# curl whoami-clusterip-none
Hostname: whoami-767d459f67-xdv9p
IP: 127.0.0.1
IP: 10.42.2.75
RemoteAddr: 10.42.9.32:34998
GET / HTTP/1.1
Host: whoami-clusterip-none
User-Agent: curl/7.81.0
Accept: */*

查询DNS会把所有节炚w列出来?/p>

# nslookup whoami-clusterip-none
Server:		10.43.0.10
Address:	10.43.0.10:53

Name:	whoami-clusterip-none.default.svc.cluster.local
Address: 10.42.3.73
Name:	whoami-clusterip-none.default.svc.cluster.local
Address: 10.42.2.75
Name:	whoami-clusterip-none.default.svc.cluster.local
Address: 10.42.8.35

External Name模式

用于引进带域名的外部服务Q这里引入内部服务作为测试?/p>

多了一层域名解析过E,端口转换程依赖于所引入服务的服务设定?/p>

服务定义Q?/p>

cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami-externalname
  name: whoami-externalname
spec:
  type: ExternalName
  externalName: whoami-clusterip.default.svc.cluster.local
EOF

q里外联的是whoami-clusterip服务的完整访问域名?/p>

查看服务部v情况Q?/p>

NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/whoami-externalname     ExternalName   <none>          whoami-clusterip.default   <none>         9h

Ҏ域名讉K试Q?/p>

# curl whoami-externalname
Hostname: whoami-767d459f67-qffqw
IP: 127.0.0.1
IP: 10.42.3.77
RemoteAddr: 10.42.9.35:36756
GET / HTTP/1.1
Host: whoami-externalname
User-Agent: curl/7.81.0
Accept: */*

DNS解析l果Q?/p>

# nslookup whoami-externalname
Server:		10.43.0.10
Address:	10.43.0.10:53

whoami-externalname.default.svc.cluster.local	canonical name = whoami-clusterip.default.svc.cluster.local
Name:	whoami-clusterip.default.svc.cluster.local
Address: 10.43.247.74

要分析了各种cdService定义、服务引用场景以及测试流E等Q整理清楚了Q也方便在具体业务场景中q行抉择选择具体服务cd?/p>

nieyong 2023-09-13 10:13 发表评论
]]>
K8S 修改默认 StorageClasshttp://www.tkk7.com/yongboy/archive/2023/09/08/451193.htmlnieyongnieyongFri, 08 Sep 2023 07:30:00 GMThttp://www.tkk7.com/yongboy/archive/2023/09/08/451193.htmlhttp://www.tkk7.com/yongboy/comments/451193.htmlhttp://www.tkk7.com/yongboy/archive/2023/09/08/451193.html#Feedback0http://www.tkk7.com/yongboy/comments/commentRss/451193.htmlhttp://www.tkk7.com/yongboy/services/trackbacks/451193.html业务需要将默认的K8S存储服务修改NFSQ这里记录一下操作记录?/p>

列出当前StorageClassQ?/p>

kubectl get sc
NAME                   PROVISIONER                                         RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path                               Delete          WaitForFirstConsumer   false                  17d
nfs                    cluster.local/nfs-nfs-subdir-external-provisioner   Delete          Immediate              true                   6d14h

首先Q将默认的名UCؓlocal-path修改?code>falseQ?/p>

kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'

然后Q将nfs讄为默认:

kubectl patch storageclass nfs -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

操作完成之后Q校验一下,可以看到已经成功?code>nfs讄为默认的StorageClass选项?/p>

kubectl get sc
NAME            PROVISIONER                                         RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path      rancher.io/local-path                               Delete          WaitForFirstConsumer   false                  17d
nfs (default)   cluster.local/nfs-nfs-subdir-external-provisioner   Delete          Immediate              true                   6d14h

RefQ?a >https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/



nieyong 2023-09-08 15:30 发表评论
]]>
参与Apisix开源的一ơ完整提交过E分?/title><link>http://www.tkk7.com/yongboy/archive/2021/03/10/435820.html</link><dc:creator>nieyong</dc:creator><author>nieyong</author><pubDate>Wed, 10 Mar 2021 03:20:00 GMT</pubDate><guid>http://www.tkk7.com/yongboy/archive/2021/03/10/435820.html</guid><wfw:comment>http://www.tkk7.com/yongboy/comments/435820.html</wfw:comment><comments>http://www.tkk7.com/yongboy/archive/2021/03/10/435820.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/yongboy/comments/commentRss/435820.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/yongboy/services/trackbacks/435820.html</trackback:ping><description><![CDATA[<h2 id="toc_0">前言</h2> <p>参与开源不是ؓ了证明什么,而是Z更好的配合工作。开源和工作在绝大部分时_都是可以和谐共处Q互怿q,Win-WIn双赢?/p> <p>本文内容记录了ؓ <a >apisix</a> 目提交的一ơpull request提交 Q访问地址Q?a >https://github.com/apache/apisix/pull/3615</a> Q完整过E,提交内容Z个独立的服务发现模块Q本文目的是为团队的其他同学参与C֌目分n的行为提供一个简单可遵@、可操作模型?</p> <p>概括来讲Q简要操作流E如下:</p> <ul> <li>首先Q确定需要开源的部分</li> <li>其次Q在目C֌中分享我们的看法和后l行为等</li> <li>然后Q准备提交内?/li> <li>接着Q提交pull requestQ接受社区审核,反复调整修改</li> <li>后箋Q关注社区的走向Q持l改q?/li> </ul> <p>下面为每一步具体操作的水账?/p> <blockquote> <p>提前预警Q图多费量Q慎?:))</p> </blockquote> <h2 id="toc_1">首先Q我们有一个Consul KV服务发现lg</h2> <p>作ؓNginx用户Q我们实际场景?<a >Nginx Upsync</a> 模块Q结合Consul KV作ؓ服务注册和发现Ş式?/p> <p>我们ZApisix构徏HTTP API服务|关Q没有发现现成的Consul KV形式服务发现模块Q既然实际业务需要,我们需要把它按照接口规范开发出来,以适应我们自己的实际场景?/p> <p>当服务发现模块功能开发出来后Q也是仅仅能满基本需求,q不够完善,但这时改q的思\q不是非常清楚,<br/> 既然开源社Z有类似的需求,那我们可以考虑分n开源出去,接收整个C֌的考验Q大家一hq?/p> <p>限于日常思维角度的局限,若是仅仅满工作需要,那么开源出M让你的代码接受到C֌Ҏ面面的审核,其是针对代码风根{功能、执行等有严D求的apisix目。摆正心态,接受代码评审q调_最l结果无疑是让代码更加健壮,好事一桩嘛?/p> <p>当然开源出M后,该模块的变更以及优化{行为就完全归属整个C֌了,策力Q是一U比较期待的演进方式?/p> <h2 id="toc_2">W一步,咨询C֌意见</h2> <p>一个优U的开源项目,ZE_健康发展Q一般会提供邮gl方便社区参与者咨询、沟通协调等?/p> <p>一般来_Github会提?code>issues</code>列表方便目使用者提交BUGQ若我们惛_C֌中表达意图、观点等Q就不如发在C֌邮gl中Q这栯够得到更多的x。比如,我们想给C֌׃n一个完整的服务发现模块Q就可以直接在邮件组中描q大致功能,以及大致处理程{,让社区知道我们的真实意图?/p> <p>Apisix开发邮件组地址为:<code>dev@apisix.apache.org</code>Q但一般的邮gl都需要注意如下事:</p> <ol> <li>沟通需要用英?<ul> <li>q也是Apisix目国际化需?/li> <li>虽然你也知道阅读邮g的有几个中国的糙老爷们,但也会有来自其他国家的用?/li> <li>当然在Github上所有的目沟通都需要用英文,q是一个良好的开源社区沟通习?/li> <li>推荐一个微软英语在U协作辅助工P<a >https://aimwriting.mtutor.engkoo.com/</a> Q可以帮助校验语法错误等</li> </ul></li> <li>无法传递富文本 <ul> <li>使用U文本即?/li> <li>cM我有格式化强q症患者,直接_脓 markdown 格式文本</li> </ul></li> <li><p>无法传递图?/p> <ul> <li>直接传递图片URL地址</li> <li>若需要传递图片,提供一个小技巧:新徏一个issues表单Q直接拖拽图片到表单处,然后获得囄地址卛_Q无L?code>issues</code>表单</li> </ul> <p><figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/16142220526647.jpg" alt="上传囄"/><figcaption>上传囄</figcaption></figure></p></li> </ol> <p>下面是我发送的邮g截图Q?/p> <p><figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/16142222879314.jpg" alt=""/></figure><br/> 因ؓapache邮gl不支持富文本和囄Q实际看到的效果没有那么好看了Q下面的q接包含了该讨论完整的回复内容:</p> <p><a >https://lists.apache.org/thread.html/rf9e392dd76e4701935940d22b4b9d9f8ed130cca34a2e951357a4c2a%40%3Cdev.apisix.apache.org%3E</a></p> <p>不方便打开的话Q下面提供完整邮件讨论截图,很长的截图,呵呵Q?br/> <figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/maillist.png" alt="maillist"/><figcaption>maillist</figcaption></figure></p> <p>MQ断断箋l经q三周时间的讨论Q这个过E需要有些耐心。发完邮件等有了U极反馈Q下面就可以着手准备提交代码了?/p> <h2 id="toc_3">W二步,准备提交</h2> <h3 id="toc_4">Fork到自׃?/h3> <p>?<a >https://github.com/apache/apisix</a> Fork到自׃库中Q然后克隆到自己工作机来?/p> <p>注意Q需要时M持和d保持一_</p> <pre class="line-numbers"><code class="language-bash">git remote add upstream https://github.com/apache/apisix.git </code></pre> <p>下面是动手开q了?/p> <h3 id="toc_5">按需调整代码</h3> <p>Consul KV服务发现模块文g?<code>consul_kv.lua</code>Q相对位|ؓQ?code>apisix/discovery/consul_kv.lua</code>。我们想提交到项目主qԌ那么代码必遵循已有规范?/p> <p>针对<code>apisix</code>的服务发C码,需要有配置,必ȝZ套完整的服务配置 <code>schema</code> 定义Q如下?/p> <pre class="line-numbers"><code class="language-lua">local schema = { type = "object", properties = { servers = { type = "array", minItems = 1, items = { type = "string", } }, fetch_interval = {type = "integer", minimum = 1, default = 3}, keepalive = { type = "boolean", default = true }, prefix = {type = "string", default = "upstreams"}, weight = {type = "integer", minimum = 1, default = 1}, timeout = { type = "object", properties = { connect = {type = "integer", minimum = 1, default = 2000}, read = {type = "integer", minimum = 1, default = 2000}, wait = {type = "integer", minimum = 1, default = 60} }, default = { connect = 2000, read = 2000, wait = 60, } }, skip_keys = { type = "array", minItems = 1, items = { type = "string", } }, default_service = { type = "object", properties = { host = {type = "string"}, port = {type = "integer"}, metadata = { type = "object", properties = { fail_timeout = {type = "integer", default = 1}, weigth = {type = "integer", default = 1}, max_fails = {type = "integer", default = 1} }, default = { fail_timeout = 1, weigth = 1, max_fails = 1 } } } } }, required = {"servers"} } </code></pre> <p>当然Q你需要区分每一个配|项是不是必填项Q非必传w要具有默认|以及上限或下限约束等?/p> <p>下面需要在该模块启动时q行用户配|是否错误,无法兼容、恢复错误的话,需要直接用Lua内置错误日志接口输出Q?/p> <pre class="line-numbers"><code class="language-lua">error("Errr MSG") </code></pre> <p>另外Q若要引?<code>resty.worker.events</code> lgQ不要提?code>require</code>Q比如在文g头部提前声明Ӟ</p> <pre class="line-numbers"><code class="language-lua">loca events = require("resty.worker.events") </code></pre> <p>启动后,有可能在日志文件中出现如下异常Q?/p> <pre class="line-numbers"><code class="language-shell">2021/02/23 02:32:20 [error] 7#7: init_worker_by_lua error: /usr/local/share/lua/5.1/resty/worker/events.lua:175: attempt to index local 'handler_list' (a nil value) stack traceback: /usr/local/share/lua/5.1/resty/worker/events.lua:175: in function 'do_handlerlist' /usr/local/share/lua/5.1/resty/worker/events.lua:215: in function 'do_event_json' /usr/local/share/lua/5.1/resty/worker/events.lua:361: in function 'post' /usr/local/share/lua/5.1/resty/worker/events.lua:614: in function 'configure' /usr/local/apisix/apisix/init.lua:94: in function 'http_init_worker' init_worker_by_lua:5: in main chunk </code></pre> <p>推荐做法是gq加载,在该模块被加载时q行引用?/p> <pre class="line-numbers"><code class="language-lua">local events local events_list ...... function _M.init_worker() ...... events = require("resty.worker.events") events_list = events.event_list( "discovery_consul_update_application", "updating" ) if 0 ~= ngx.worker.id() then events.register(discovery_consul_callback, events_list._source, events_list.updating) return end ...... end </code></pre> <h3 id="toc_6">单元试依赖</h3> <p>单元试代码的执行,会在你提交PR代码后自动执行持l集成行为内执行?/p> <p>首先Q需要本机执行单元测试前Q需要提前准备好所需Docker试实例Q?/p> <pre class="line-numbers"><code class="language-bash">docker run --rm --name consul_1 -d -p 8500:8500 consul:1.7 consul agent -server -bootstrap-expect=1 -client 0.0.0.0 -log-level info -data-dir=/consul/data docker run --rm --name consul_2 -d -p 8600:8500 consul:1.7 consul agent -server -bootstrap-expect=1 -client 0.0.0.0 -log-level info -data-dir=/consul/data docker run --rm -d \ -e ETCD_ENABLE_V2=true \ -e ALLOW_NONE_AUTHENTICATION=yes \ -e ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379 \ -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \ -p 2379:2379 \ registry.api.weibo.com/wesync/wbgw/etcd:3.4.9 </code></pre> <p>然后Q安装项目依赖:</p> <pre class="line-numbers"><code class="language-bash">make deps </code></pre> <p>其次Q别忘记在apisix目持箋集成脚本相应位置d相应依赖?/p> <p>比如Q因为单元测试依赖于端口分别?500?600的两个Consul Server实例Q需要在执行单元试之前提前q行Q因此你需要在对应的持l集成文件上d所需q行实例。比如其中一个位|:</p> <p><figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/16142399820887.jpg" alt=""/></figure></p> <h3 id="toc_7">无测试不~码</h3> <p>仅仅提供服务发现<code>consul_kv.lua</code>q一个文Ӟ是无法被仓库理员采U的Q因为除了你自己以外Q别人无法确定你提交的代码所提供功能是否_让h信服Q除非你能提供较为完整的 <code>Test::Nginx</code> 单元试支持Q自我证明?/p> <p><code>Test::Nginx</code> 单元试可能针对很多人来Ԍ是一个拦路虎Q但其实有些耐心Q你会发现它的美妙之处?/p> <p>单入门可参?<a >https://time.geekbang.org/column/article/109506</a> Q若只需要学习单元测试,其实不需要购买整个专辑的Q。在使用q程中需要参考在U文档:<a >https://metacpan.org/pod/Test::Nginx::Socket</a> Q需要一些耐心p一Ҏ间慢慢消化?/p> <p>如何q行Nginx单元试案例Q具体参看:<br/> <a >https://github.com/apache/apisix/blob/master/doc/zh-cn/how-to-build.md</a></p> <p>至于Apisix定制部分单元试部分Q可以直接参考已有的单元试文g卛_?/p> <p>Consul KV服务发现的单元测试模块相对\?<code>t/discovery/consul_kv.lua</code>Q在U地址为: <a >https://github.com/apache/apisix/blob/master/t/discovery/consul_kv.t</a> 。该文g大约500多行Q比真正的模?code>consul_kv.lua</code>代码行数q多。但比较完整覆盖了所能想到的所有场景,虽然写v来虽然有些麻烦,但针对应用到U上大量业务的核心代码,无论多认真和谨慎都是不ؓq的?/p> <p>以往针对关键核心模块的每一ơP代,心里面大概有些忐忑七上八下吧Q也不太敢直接应用到U上。现在有了单元测试各U场景的覆盖辅助验证q代变更效果Q自信心是有了,也可以给别h拍着胸脯保证修改没问题。当然若后箋发现隐藏的问题,直接d上对应的单元试覆盖上即可?/p> <p>我们q次只提供一个服务发现模块,因此只需要单独测?code>consul_kv.t</code>文g卛_Q?/p> <pre class="line-numbers"><code class="language-bash"># prove -Itest-nginx/lib -I./ t/discovery/consul_kv.t ...... t/discovery/consul_kv.t .. ok All tests successful. Files=1, Tests=102, 36 wallclock secs ( 0.05 usr 0.01 sys + 0.78 cusr 0.41 csys = 1.25 CPU) Result: PASS </code></pre> <p>出现试案例p|问题Q可以去 <code>apisix/t/servroot/logs</code> 路径下查?<code>error.log</code> 文g暴露出的异常{问题?/p> <p>有些一些测试用例需要组合一l较为复杂的使用场景Q比如我们准备一l后端节点:</p> <ul> <li>127.0.0.1:30511Q输?<code>server 1</code></li> <li>127.0.0.1:30512Q输?<code>server 2</code></li> <li>127.0.0.1:30513Q输?<code>server 3</code></li> <li>127.0.0.1:30514Q输?<code>server 4</code></li> </ul> <p>q些节点被频繁执行注册Consul节点然后再解除注册若q@环过E:<code>清理注册 -> 注册 -> 解除注册 -> 注册 -> 解除注册 -> 注册 -> 解除注册 -> 注册</code> Q目的检验已解除注册的失效节Ҏ否还会存在内存中{?/p> <p>有些操作Q比如注册或解除注册节点q些操作Q网关的<code>consul_kv.lua</code>服务模块在物理层面需要wait一Ҏ间等待网x化这些变化,因此我们需要额外提供一?<code>/sleep</code> 接口Q请求时需要故意休眠几U钟旉{待下一ơ请求生效?/p> <pre class="line-numbers"><code class="language-perl">=== TEST 7: test register & unregister nodes --- yaml_config eval: $::yaml_config --- apisix_yaml routes: - uri: /* upstream: service_name: http://127.0.0.1:8500/v1/kv/upstreams/webpages/ discovery_type: consul_kv type: roundrobin #END --- config location /v1/kv { proxy_pass http://127.0.0.1:8500; } location /sleep { content_by_lua_block { local args = ngx.req.get_uri_args() local sec = args.sec or "2" ngx.sleep(tonumber(sec)) ngx.say("ok") } } --- timeout: 6 --- request eval [ "DELETE /v1/kv/upstreams/webpages/?recurse=true", "PUT /v1/kv/upstreams/webpages/127.0.0.1:30511\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", "GET /sleep?sec=5", "GET /hello", "PUT /v1/kv/upstreams/webpages/127.0.0.1:30512\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", "GET /sleep", "GET /hello", "GET /hello", "DELETE /v1/kv/upstreams/webpages/127.0.0.1:30511", "DELETE /v1/kv/upstreams/webpages/127.0.0.1:30512", "PUT /v1/kv/upstreams/webpages/127.0.0.1:30513\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", "PUT /v1/kv/upstreams/webpages/127.0.0.1:30514\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", "GET /sleep", "GET /hello?random1", "GET /hello?random2", "GET /hello?random3", "GET /hello?random4", "DELETE /v1/kv/upstreams/webpages/127.0.0.1:30513", "DELETE /v1/kv/upstreams/webpages/127.0.0.1:30514", "PUT /v1/kv/upstreams/webpages/127.0.0.1:30511\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", "PUT /v1/kv/upstreams/webpages/127.0.0.1:30512\n" . "{\"weight\": 1, \"max_fails\": 2, \"fail_timeout\": 1}", "GET /sleep?sec=5", "GET /hello?random1", "GET /hello?random2", "GET /hello?random3", "GET /hello?random4", ] --- response_body_like eval [ qr/true/, qr/true/, qr/ok\n/, qr/server 1\n/, qr/true/, qr/ok\n/, qr/server [1-2]\n/, qr/server [1-2]\n/, qr/true/, qr/true/, qr/true/, qr/true/, qr/ok\n/, qr/server [3-4]\n/, qr/server [3-4]\n/, qr/server [3-4]\n/, qr/server [3-4]\n/, qr/true/, qr/true/, qr/true/, qr/true/, qr/ok\n/, qr/server [1-2]\n/, qr/server [1-2]\n/, qr/server [1-2]\n/, qr/server [1-2]\n/ ] </code></pre> <h3 id="toc_8">准备文档</h3> <p>除了代码能够正常q{Q我们还需要准备相应的Markdown文档辅助说明如何使用我们的模块,帮助C֌用户更好使用它?/p> <p>C֌一般以英文文档为先Q?只有在精力满的情况下,可以补充中文文档?/p> <p>下面是要准备Markdown文档了,其文档\径ؓQ?code>doc/discovery/consul_kv.md</code>Q单独的文档需要在其它已有文档挂接上对应链接,方便索引?/p> <p>文档路径为:<code>doc/discovery/consul_kv.md</code>Q在U地址Q?a >https://github.com/apache/apisix/blob/master/docs/en/latest/discovery/consul_kv.md</a></p> <p>一般徏议需要在文档中能够清楚说明模块的使用方式Q以及注意事,其是配|参C用方式等。比如下面的配置说明:</p> <pre class="line-numbers"><code class="language-markdown">```yaml discovery: consul_kv: servers: - "http://127.0.0.1:8500" - "http://127.0.0.1:8600" prefix: "upstreams" skip_keys: # if you need to skip special keys - "upstreams/unused_api/" timeout: connect: 1000 # default 2000 ms read: 1000 # default 2000 ms wait: 60 # default 60 sec weight: 1 # default 1 fetch_interval: 5 # default 3 sec, only take effect for keepalive: false way keepalive: true # default true, use the long pull way to query consul servers default_server: # you can define default server when missing hit host: "127.0.0.1" port: 20999 metadata: fail_timeout: 1 # default 1 ms weight: 1 # default 1 max_fails: 1 # default 1 ``` ...... The `keepalive` has two optional values: - `true`, default and recommend value, use the long pull way to query consul servers - `false`, not recommend, it would use the short pull way to query consul servers, then you can set the `fetch_interval` for fetch interval </code></pre> <p>每一个文档都不应该成Z息孤岛,它需要在其它文档上挂载上一个连接地址Q因此我们需要在合适的地方Q比如需要在 <code>doc/discovery.md</code>最下面d链接地址描述Q?/p> <pre class="line-numbers"><code class="language-markdown">## Discovery modules - eureka - [Consul KV](discovery/consul_kv.md) </code></pre> <p>模块代码Q测试文Ӟ以及文档{准备好了之后,下面是准备提交代码到自׃库?/p> <h3 id="toc_9">验证提交语法规范</h3> <p>所有内容准备好之后Q徏议执?<code>make lint</code> ?<code>make license-check</code> 两个命o代码、markdown文档{是否满项目规范要求?/p> <pre class="line-numbers"><code class="language-bash"># make lint ./utils/check-lua-code-style.sh + luacheck -q apisix t/lib Total: 0 warnings / 0 errors in 133 files + find apisix -name '*.lua' '!' -wholename apisix/cli/ngx_tpl.lua -exec ./utils/lj-releng '{}' + + grep -E 'ERROR.*.lua:' /tmp/check.log + true + '[' -s /tmp/error.log ']' ./utils/check-test-code-style.sh + find t -name '*.t' -exec grep -E '\-\-\-\s+(SKIP|ONLY|LAST)$' '{}' + + true + '[' -s /tmp/error.log ']' + find t -name '*.t' -exec ./utils/reindex '{}' + + grep done. /tmp/check.log + true + '[' -s /tmp/error.log ']' # make license-check .travis/openwhisk-utilities/scancode/scanCode.py --config .travis/ASF-Release.cfg ./ Reading configuration file [.travis/ASF-Release.cfg]... Scanning files starting at [./]... All checks passed. </code></pre> <p>若检查出语法斚w问题Q认真调_直到找不到问题所在?/p> <blockquote> <p>q次PR提交之前Q忘记这回事了,会导致多了若q次ơsubmit提交?/p> </blockquote> <h2 id="toc_10">W三步,提交Pull Request</h2> <p>d|:<a >https://github.com/apache/apisix/pulls</a> 新徏一?code>New pull request</code>Q后面将使用PR指代<code>pull request</code>?/p> <h3 id="toc_11">PR标题格式</h3> <p>PR提交标题是规范要求的Q模板如下:</p> <pre class="line-numbers"><code class="language-text">{type}: {desc} </code></pre> <p>其中<code>{type}</code>指代本次PRcdQ具体值如下,量不要搞错Q?/p> <ul> <li><code>feat</code>Q新功能QfeatureQ?/li> <li><code>fix</code>Q修补bug</li> <li><code>docs</code>Q文档(documentationQ?/li> <li><code>style</code>Q?格式Q不影响代码q行的变动)</li> <li><code>refactor</code>Q重构(即不是新增功能,也不是修改bug的代码变动)</li> <li><code>test</code>Q增加测?/li> <li><code>chore</code>Q构E或辅助工具的变?/li> <li>……</li> </ul> <p>其中<code>{desc}</code>需要概括本ơ提交内宏V?/p> <p>比如q次标题为:<code>feat: add consul kv discovery module</code>?/p> <h3 id="toc_12">填充PR内容</h3> <p>PR内容模板化,为标准的Github Markdown格式Q主要目的说明本ơ提交内容,C如下Q?/p> <pre class="line-numbers"><code class="language-markdown">### What this PR does / why we need it: <!--- Why is this change required? What problem does it solve? --> <!--- If it fixes an open issue, please link to the issue here. --> ### Pre-submission checklist: * [ ] Did you explain what problem does this PR solve? Or what new features have been added? * [ ] Have you added corresponding test cases? * [ ] Have you modified the corresponding document? * [ ] Is this PR backward compatible? **If it is not backward compatible, please discuss on the [mailing list](https://github.com/apache/apisix/tree/master#community) first** </code></pre> <p>按照模板格式填写Q省心省力,如下Q?/p> <pre class="line-numbers"><code class="language-markdown">### What this PR does / why we need it: As I mentioned previously in the mail-list, my team submit our `consul_kv` discovery module now. More introductions here: https://github.com/yongboy/apisix/blob/consul_kv/doc/discovery/consul_kv.md ### Pre-submission checklist: * [x] Did you explain what problem does this PR solve? Or what new features have been added? * [x] Have you added corresponding test cases? * [x] Have you modified the corresponding document? * [x] Is this PR backward compatible? **If it is not backward compatible, please discuss on the [mailing list](https://github.com/apache/apisix/tree/master#community) first** </code></pre> <h3 id="toc_13">认真接受评审和徏?/h3> <p>提交PR之后Q才是一个开始,L?/p> <p>Apisix目会自动针Ҏ们所提交内容执行持箋集成Q?code>apisix</code>目的检查项很多Q比如针对Markdown格式很严格Q?/p> <p><figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/16143310492921.jpg" alt=""/></figure></p> <p>持箋集成不通过Q按照要求微调吧Q也是标准化的要求?/p> <p>我们在PUSH代码之前Q? <code>make lint</code> ?<code>make license-check</code> 两个命o提前还是十分有必要的,提前语法等?/p> <p>首先Q一定要保持箋集成不能出错。持l集成通不q,说明我们的准备还不充分,l箋调整修改Ql提交,一直到持箋集成完全执行成功为止?/p> <p>保证持箋集成执行成功Q这是最基本的要求,否则C֌无法认我们的代码是否基本合根{?/p> <p>放松心态,准备开始改qBUGQ以及接受社区的各种代码评审和改q意见吧?/p> <p>其次Q就是要虚心接受C֌代码评审和改q意见了Q这是最关键的一步?/p> <p>下面是一些徏议:</p> <ol> <li>真正代码BUGQ认真修?<figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/16143102606148.jpg" alt=""/></figure></li> <li>逻辑处理不合理的地方Q思考ƈl出一些处理思\Q确定好之后开始调整即?<figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/16143103301329.jpg" alt=""/></figure></li> <li>有些提议可能会超出本ơ提交范_说明原因Q给出拒l理由,可以婉拒嘛,比如可以攑֜下一ơ的提交中?<figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/16143104332310.jpg" alt=""/></figure></li> <li>若有遇到自己处理不了的问题,U极向社区寻求帮助吧?/li> <li>针对一Ҏ修改再次提交后,会再ơ执行持l集成,一L保持l集成不能够p|Q然后l等待下一轮的审核</li> </ol> <p>认真对待每一个徏议,有则改之无则加勉Q不知不觉之间就q步了很多,代码质量也得C提升?/p> <p>l过多次的微调,我们的服务发现核心模块基本上已趋于完善了一版,q已l和q没准备分n出来之前的原始文件相比已l天差地别了 :))</p> <p>下面是本ơPR包含的多ơ提交、代码评审以及答复等完整程截图Q?br/> <figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/consul_kv_pr.png" alt="consul_kv模块一ơPR完整程"/><figcaption>consul_kv模块一ơPR完整程</figcaption></figure></p> <p>被合q到d支之后,有没有感觉到整个C֌都在帮助我们一hq,快不快哉 Q?/p> <h3 id="toc_14">关于依赖的处理</h3> <p>本次提交的服务发现模块依赖一个组Ӟ<code>lua-resty-consul</code>Q其仓库地址Q?a >https://github.com/hamishforbes/lua-resty-consulQ最新版本ؓQ`0.3.2`。因为我们在实际部v定制Ӟ直接下蝲了该文gQ简单直接粗暴?/a></p> <p>?code>apisix</code>目针对目依赖Q采用的 LuaRocks 理Q在 2021-2-20 之前该组件托在 <a >https://luarocks.org/modules/hamish/lua-resty-consul</a> 上面最新版本ؓ <code>0.2-0</code>Q这很隑֊了?/p> <p>我的处理步骤如下Q?/p> <ol> <li>首先我在github上面向作者提交一个求助:<a >https://github.com/hamishforbes/lua-resty-consul/issues/20Q?/a> 然而ƈ没有在一两周旉内没有等C者回?/li> <li>无奈Q只好自己在 LuaRocks 单独提交一个暂时性的解决ҎQ?a >https://luarocks.org/modules/yongboy/lua-resty-consul-0.3.2Q在本次PR中直接包含了该组件时地址</a></li> <li>三周左右Q终于等到该lg作者提交最新版?LuaRocks 站点Q既然官ҎCQ那把服务发现模块里面的依赖修改ؓ官方最新地址吧,再次提h一个PRQ?a >https://github.com/apache/apisix/pull/3654</a></li> </ol> <p>有些一波三?:))</p> <p><figure><img src="http://www.tkk7.com/images/blogjava_net/yongboy/consul_deps_issues.png" alt=""/></figure></p> <h2 id="toc_15">W四步,关于后箋</h2> <p>一旦合q到d支后Q后l的演进整个C֌都可以参与进来,可能有h?<code>issue</code>Q可能有人提 PR 修改{,后箋我们想ؓ该模块l提交,那将是另外一个PR的事情?/p> <p>我们可以l箋做以下事情:</p> <ol> <li>Ҏ实际需要重?/li> <li>若有人提Issue是,自然是FixbugQ实践中遇到的BugQ修复它</li> <li>需要添加新的单元测试覆盖到新的Ҏ?/li> <li>若有需要,需要添加新的文档进行描q?/li> </ol> <p>毫无疑问Q这是一个良性@环?/p> <h2 id="toc_16">结</h2> <p>参与C֌开发的其它cd提交Q可能会比上面所q简单很多,但大都可以看做是以上行ؓ的一个子集?/p> <p>参与开源,也会为我们打开一扇窗P去除自n的狭隘。积极向C֌靠拢Q这需要磨M些思维或认知的pQ虚心认识到自我的不Iq不断调整不断进步?/p> <p>加aQ?/p> <img src ="http://www.tkk7.com/yongboy/aggbug/435820.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/yongboy/" target="_blank">nieyong</a> 2021-03-10 11:20 <a href="http://www.tkk7.com/yongboy/archive/2021/03/10/435820.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Apisix 1.5 升?2.2 t坑备忘http://www.tkk7.com/yongboy/archive/2021/02/23/435806.htmlnieyongnieyongTue, 23 Feb 2021 06:57:00 GMThttp://www.tkk7.com/yongboy/archive/2021/02/23/435806.htmlhttp://www.tkk7.com/yongboy/comments/435806.htmlhttp://www.tkk7.com/yongboy/archive/2021/02/23/435806.html#Feedback0http://www.tkk7.com/yongboy/comments/commentRss/435806.htmlhttp://www.tkk7.com/yongboy/services/trackbacks/435806.html零、前a

U上q行?APISIX ?1.5 版本Q而社区已l发布了 Apisix 2.2Q是时候需要升U到最新版了,能够享受最版本带来的大量的BugFixQ性能增强Q以及新增特性的支持{~

从Apisix 1.5升到Apisix 2.2q程中,不是一帆风的Q中间踩了不坑Q所谓前车之鉴后事之师,q里l大家简单梳理一下我们团队所在具体业务环境下Q升U过E中t的若干坑,以及一些需要避免的若干注意事项{?/p>

下文所说原先版本,皆指Apisix 1.5Q新版则是Apisix 2.2版本?/p>

一、已有服务发现机制无法正常工?/h2>

针对上游Upstream没有使用服务发现的\由来Ԍ本次升没有遇到什么问题?/p>

公司内部U上业务大都ZConsul KV方式实现服务注册和服务发玎ͼ因此我们自行实现了一?consul_kv.lua 模块实现服务发现程?/p>

q在Apisix 1.5下面一切工作正常?/p>

但在Apisix 2.2下面Q就无法直接工作了,原因如下Q?/p>

  • 服务发现配置指o变了
  • 上游对象包含服务发现旉增加字段 discovery_type q行索引

2.1 服务发现配置指o变了

原先q行中仅支持一U服务发现机Ӟ需要配|在 apisix层下面Q?/p>

apisix:
    ......
    discover: consul_kv
    ......    

新版需要直接在config*.yaml文g中顶层层U下q行配置Q可支持多种不同的\由发现机Ӟ如下Q?/p>

discovery:                      # service discovery center
  eureka:
    host:                       # it's possible to define multiple eureka hosts addresses of the same eureka cluster.
      - "http://127.0.0.1:8761"
    prefix: "/eureka/"
    fetch_interval: 30          # default 30s
    weight: 100                 # default weight for node
    timeout:
      connect: 2000             # default 2000ms
      send: 2000                # default 2000ms
      read: 5000

我们有所变通,直接在配|文仉层配|consul_kv多个集群相关参数Q避?discovery 层q深?/p>

 discovery:
    consul_kv: 1
consul_kv:
  servers:
    -
      host: "172.19.5.30"
      port: 8500
    -
      host: "172.19.5.31"
      port: 8500
  prefix: "upstreams"
  timeout:
    connect: 6000
    read: 6000
    wait: 60
  weight: 1
  delay: 5
  connect_type: "long" # long connect
  ......

当然Q这仅仅保证了服务发现模块能够在启动时被正常加蝲?/p>

推荐阅读Q?/p>

2.2 upstream对象新增字段discovery_type

Apisix当前同时支持多种服务发现机制Q这个很赞。对应的代hQ就是需要额外引?discovery_type 字段Q用于烦引可能同时存在的多个服务发现机制?/p>

?Cousul KV方式服务发现ZQ那么需要在已有?upstream 对象中需要添加该字段Q?/p>

"discovery_type" : "consul_kv"

原先的一?code>upstream对象Q仅仅需?service_name 字段属性指定服务发现相兛_址卛_Q?/p>

{
    "id": "d6c1d325-9003-4217-808d-249aaf52168e",
    "name": "grpc_upstream_hello",
    ......
    "service_name": "http://172.19.5.30:8500/v1/kv/upstreams/grpc/grpc_hello",
    "create_time": 1610437522,
    "desc": "demo grpc service",
    "type": "roundrobin"
}

而新版的则需要添?code>discovery_type字段Q表明该service_name 字段对应的具体模块名Uͼ效果如下Q?/p>

{
    "id": "d6c1d325-9003-4217-808d-249aaf52168e",
    "name": "grpc_upstream_hello",
    ......
    "service_name": "http://172.19.5.30:8500/v1/kv/upstreams/grpc/grpc_hello",
    "create_time": 1610437522,
    "desc": "demo grpc service",
    "type": "roundrobin",
    "discovery_type":"consul_kv"
}

后面我们若支持Consul Service或ETCD KV方式服务发现机制Q则会非常弹性和清晰?/p>

调整了配|指令,d上述字段之后Q后端服务发现其实就已经起作用了?/p>

但gRPC代理路由q不会生?#8230;…

二、gRPC当前不支持upstream_id

在我们的pȝ中,上游和\由是需要单独分开理的,因此创徏的HTTP或GRPC路由需要处理支?code>upstream_id的烦引?/p>

q在1.5版本中,grpc路由是没问题的,但到了apisix 2.2版本中,l护?@spacewander 暂时没做支持Q原因是规划grpc路由和dubbo路由处理逻辑于一_更ؓ紧凑。从l护角度我是认可的,但作Z用者来Ԍq就有些不合理了Q直接丢弃了针对以往数据的支持?/p>

作ؓ当前Geek一些方式,?apisix/init.lua 中,最成?Q优雅和成本成反比)修改如下Q找到如下代码:

    -- todo: support upstream id
    api_ctx.matched_upstream = (route.dns_value and
                                route.dns_value.upstream)
                               or route.value.upstream 

直接替换Z面代码即可解决燃眉之急:

    local up_id = route.value.upstream_id
    if up_id then
        local upstreams = core.config.fetch_created_obj("/upstreams")
        if upstreams then
            local upstream = upstreams:get(tostring(up_id))
            if not upstream then
                core.log.error("failed to find upstream by id: " .. up_id)
                return core.response.exit(502)
            end
            if upstream.has_domain then
                local err
                upstream, err = lru_resolved_domain(upstream,
                                                    upstream.modifiedIndex,
                                                    parse_domain_in_up,
                                                    upstream)
                if err then
                    core.log.error("failed to get resolved upstream: ", err)
                    return core.response.exit(500)
                end
            end
            if upstream.value.pass_host then
                api_ctx.pass_host = upstream.value.pass_host
                api_ctx.upstream_host = upstream.value.upstream_host
            end
            core.log.info("parsed upstream: ", core.json.delay_encode(upstream))
            api_ctx.matched_upstream = upstream.dns_value or upstream.value
        end
    else
        api_ctx.matched_upstream = (route.dns_value and
                                route.dns_value.upstream)
                               or route.value.upstream  
    end

三、自定义auth插g需要微?/h2>

新版的apisix auth授权插g支持多个授权插g串行执行Q这个功能也很赞Q但此DD了先前ؓ具体业务定制的授权插件无法正常工作,q时需要微调一下?/p>

原先调用方式Q?/p>

    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
            consumer_conf.conf_version,
            create_consume_cache, consumer_conf)

因ؓ新版?code>lrucache不再提供 plugin 函数Q需要微调一下:

local lrucache = core.lrucache.new({
  type = "plugin",
})
......
    local consumers = lrucache("consumers_key", consumer_conf.conf_version,
        create_consume_cache, consumer_conf)

另一处是Q顺利授权之后,需要赋?code>consumer相关信息Q?/p>

    ctx.consumer = consumer
    ctx.consumer_id = consumer.consumer_id

此时需要替换成如下方式QؓQ可能存在的Q后l的授权插gl箋作用?/p>

consumer_mod.attach_consumer(ctx, consumer, consumer_conf)

更多请参考:apisix/plugins/key-auth.lua 源码?/p>

四、ETCD V2数据q移到V3

q移分ؓ三步Q?/p>

  1. 升U上已有ETCD 3.3.*版本?.4.*Q满x版Apisix的要求,q时ETCD实例同时支持了V2和V3格式数据
  2. q移V2数据到V3
    • 因ؓ数据量不是非常多Q我采取了一个非常简单和原始的方?/li>
    • 使用 etcdctl 完成V2数据到导?/li>
    • 然后使用文本~辑器vim{完成数据的替换Q生成etcdctl v3格式的数据导入命令脚?/li>
    • q行之后V3数据导入脚本Q完成V2到V3的数据导?/li>
  3. 修改V3 /apisix/upstreams 中包含服务注册的数据Q一一d "discovery_type" : "consul_kv"属?/li>

Z以上操作之后Q从而完成了ETCD V2到V3的数据迁UR?/p>

五、启动apisix后发现ETCD V3已有数据无法加蝲

我们在运l层面,使用 /usr/local/openresty/bin/openresty -p /usr/local/apisix -g daemon off; 方式q行|关E序?/p>

q也导_自动忽略了官Ҏ倡的Q?code>apisix start 命o自动提前为ETCD V3初始化的一些键值对内容?/p>

因此Q需要提前ؓETCD V3建立以下键值对内容Q?/p>

Key                         Value
/apisix/routes          :   init_dir
/apisix/upstreams       :   init_dir
/apisix/services        :   init_dir
/apisix/plugins         :   init_dir
/apisix/consumers       :   init_dir
/apisix/node_status     :   init_dir
/apisix/ssl             :   init_dir
/apisix/global_rules    :   init_dir
/apisix/stream_routes   :   init_dir
/apisix/proto           :   init_dir
/apisix/plugin_metadata :   init_dir

不提前徏立的话,׃Dapisix重启后,无法正常加蝲ETCD中已有数据?/p>

其实有一个补救措施,需要修?apisix/init.lua 内容Q找到如下代码:

            if not dir_res.nodes then
                dir_res.nodes = {}
            end

比较geek的行为,使用下面代码替换一下即可完成兼容:

                if dir_res.key then
                    dir_res.nodes = { clone_tab(dir_res) }
                else
                    dir_res.nodes = {}
                end

六、apisix-dashboard的支?/h2>

我们Zapisix-dashboard定制开发了大量的针对公司实际业务非常实用的企业U特性,但也D了无法直接升U到最新版的apisix-dashboard?/p>

因ؓ非常基础的上游和路由没有发生多大改变Q因此这部分升的需求可以忽略?/p>

实际上,只是在提交上游表单时Q包含服务注册信息JSON字符串中需要增?discovery_type 字段和对应值即可完成支持?/p>

七、小l?/h2>

p了一些时间完成了从Apisix 1.5升到Apisix 2.2的行为,虽然有些坑,但整体来Ԍq算利。目前已l上Uƈ全量部vq行Q目前运行良好?/p>

针对q停留在Apisix 1.5的用P新版增加了Control API以及多种服务发现{新Ҏ支持,q是非常值得升的?/p>

升之前Q不妨仔l阅L一个版本的升日志Q地址Q?a >https://github.com/apache/apisix/blob/2.2/CHANGELOG.md Q,然后需要根据具体业务做好兼Ҏ试准备和准备升步骤Q这些都是非常有必要的?/p>

针对我们团队来讲Q升U到最新版Q一斚w降低了版本升U的压力Q另一斚w也能够辅助我们能参与到开源社Z去,挺好~



nieyong 2021-02-23 14:57 发表评论
]]>HTTP API设计W记http://www.tkk7.com/yongboy/archive/2018/01/02/433000.htmlnieyongnieyongTue, 02 Jan 2018 12:53:00 GMThttp://www.tkk7.com/yongboy/archive/2018/01/02/433000.htmlhttp://www.tkk7.com/yongboy/comments/433000.htmlhttp://www.tkk7.com/yongboy/archive/2018/01/02/433000.html#Feedback0http://www.tkk7.com/yongboy/comments/commentRss/433000.htmlhttp://www.tkk7.com/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.tkk7.com/yongboy/archive/2016/08/16/431601.htmlnieyongnieyongTue, 16 Aug 2016 13:17:00 GMThttp://www.tkk7.com/yongboy/archive/2016/08/16/431601.htmlhttp://www.tkk7.com/yongboy/comments/431601.htmlhttp://www.tkk7.com/yongboy/archive/2016/08/16/431601.html#Feedback2http://www.tkk7.com/yongboy/comments/commentRss/431601.htmlhttp://www.tkk7.com/yongboy/services/trackbacks/431601.html前言

?a href="http://www.tkk7.com/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.tkk7.com/yongboy/archive/2016/08/08/431498.htmlnieyongnieyongMon, 08 Aug 2016 13:31:00 GMThttp://www.tkk7.com/yongboy/archive/2016/08/08/431498.htmlhttp://www.tkk7.com/yongboy/comments/431498.htmlhttp://www.tkk7.com/yongboy/archive/2016/08/08/431498.html#Feedback1http://www.tkk7.com/yongboy/comments/commentRss/431498.htmlhttp://www.tkk7.com/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 >http://tsung.erlang-projects.org/
  • 在线手册Q?a >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.tkk7.com/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.tkk7.com/yongboy/archive/2016/07/30/431396.htmlnieyongnieyongSat, 30 Jul 2016 11:37:00 GMThttp://www.tkk7.com/yongboy/archive/2016/07/30/431396.htmlhttp://www.tkk7.com/yongboy/comments/431396.htmlhttp://www.tkk7.com/yongboy/archive/2016/07/30/431396.html#Feedback0http://www.tkk7.com/yongboy/comments/commentRss/431396.htmlhttp://www.tkk7.com/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 >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.tkk7.com/yongboy/archive/2016/07/29/431367.htmlnieyongnieyongFri, 29 Jul 2016 00:49:00 GMThttp://www.tkk7.com/yongboy/archive/2016/07/29/431367.htmlhttp://www.tkk7.com/yongboy/comments/431367.htmlhttp://www.tkk7.com/yongboy/archive/2016/07/29/431367.html#Feedback0http://www.tkk7.com/yongboy/comments/commentRss/431367.htmlhttp://www.tkk7.com/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.tkk7.com/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.tkk7.com/yongboy/archive/2016/07/28/431354.html</guid><wfw:comment>http://www.tkk7.com/yongboy/comments/431354.html</wfw:comment><comments>http://www.tkk7.com/yongboy/archive/2016/07/28/431354.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/yongboy/comments/commentRss/431354.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/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 >赛门铁克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 >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 >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 >https://github.com/weibomobile/tsung</a> ?/p> <p>q且已经递交<code>pull request</code>Q?<a >https://github.com/processone/tsung/pull/189</a> ?/p> <p>比较有意思的是,有这样一条评论:</p> <p><img src="http://www.tkk7.com/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 >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.tkk7.com/yongboy/aggbug/431354.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/yongboy/" target="_blank">nieyong</a> 2016-07-28 08:37 <a href="http://www.tkk7.com/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.tkk7.com/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.tkk7.com/yongboy/archive/2016/07/27/431340.html</guid><wfw:comment>http://www.tkk7.com/yongboy/comments/431340.html</wfw:comment><comments>http://www.tkk7.com/yongboy/archive/2016/07/27/431340.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/yongboy/comments/commentRss/431340.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/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 >https://en.wikipedia.org/wiki/Remote_Shell</a>。作Z个终端工PLinux界鸟哥曾l写q?<a >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 >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 >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 >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.tkk7.com/yongboy/aggbug/431340.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/yongboy/" target="_blank">nieyong</a> 2016-07-27 09:28 <a href="http://www.tkk7.com/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.tkk7.com/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.tkk7.com/yongboy/archive/2016/07/26/431322.html</guid><wfw:comment>http://www.tkk7.com/yongboy/comments/431322.html</wfw:comment><comments>http://www.tkk7.com/yongboy/archive/2016/07/26/431322.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/yongboy/comments/commentRss/431322.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/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.tkk7.com/yongboy/aggbug/431322.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/yongboy/" target="_blank">nieyong</a> 2016-07-26 08:47 <a href="http://www.tkk7.com/yongboy/archive/2016/07/26/431322.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TsungW记之主从资源协调篇http://www.tkk7.com/yongboy/archive/2016/07/25/431310.htmlnieyongnieyongMon, 25 Jul 2016 06:02:00 GMThttp://www.tkk7.com/yongboy/archive/2016/07/25/431310.htmlhttp://www.tkk7.com/yongboy/comments/431310.htmlhttp://www.tkk7.com/yongboy/archive/2016/07/25/431310.html#Feedback1http://www.tkk7.com/yongboy/comments/commentRss/431310.htmlhttp://www.tkk7.com/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.tkk7.com/yongboy/archive/2016/07/23/431294.htmlnieyongnieyongSat, 23 Jul 2016 03:56:00 GMThttp://www.tkk7.com/yongboy/archive/2016/07/23/431294.htmlhttp://www.tkk7.com/yongboy/comments/431294.htmlhttp://www.tkk7.com/yongboy/archive/2016/07/23/431294.html#Feedback0http://www.tkk7.com/yongboy/comments/commentRss/431294.htmlhttp://www.tkk7.com/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.tkk7.com/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.tkk7.com/yongboy/archive/2016/07/22/431291.html</guid><wfw:comment>http://www.tkk7.com/yongboy/comments/431291.html</wfw:comment><comments>http://www.tkk7.com/yongboy/archive/2016/07/22/431291.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.tkk7.com/yongboy/comments/commentRss/431291.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/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 >蝴蝶效应</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.tkk7.com/yongboy/archive/2016/07/23/431294.html">TsungW记之主从模型篇</a></li> <li><a href="http://www.tkk7.com/yongboy/archive/2016/07/25/431310.html">TsungW记之主从资源协调篇</a></li> <li><a href="http://www.tkk7.com/yongboy/archive/2016/07/26/431322.html">TsungW记之压端资源限制?/a></li> <li><a href="http://www.tkk7.com/yongboy/archive/2016/07/27/431340.html">TsungW记之分布式增强跛_SSH绊?/a></li> <li><a href="http://www.tkk7.com/yongboy/archive/2016/07/28/431354.html">TsungW记之IP直连支持?/a></li> <li><a href="http://www.tkk7.com/yongboy/archive/2016/07/29/431367.html">TsungW记之监控数据收集篇</a></li> <li><a href="http://www.tkk7.com/yongboy/archive/2016/07/30/431396.html">TsungW记之插件编写篇</a></li> <li><a href="http://www.tkk7.com/yongboy/archive/2016/08/08/431498.html">TsungW记?00万用户压执行步骤篇</a></li> <li><a href="http://www.tkk7.com/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.tkk7.com/yongboy/aggbug/431291.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/yongboy/" target="_blank">nieyong</a> 2016-07-22 15:36 <a href="http://www.tkk7.com/yongboy/archive/2016/07/22/431291.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss> <footer> <div class="friendship-link"> <p>лǵվܻԴȤ</p> <a href="http://www.tkk7.com/" title="亚洲av成人片在线观看">亚洲av成人片在线观看</a> <div class="friend-links"> </div> </div> </footer> վ֩ģ壺 <a href="http://www-959kj.com" target="_blank">һӰȷɫԴ</a>| <a href="http://qu41.com" target="_blank">ѹۿAVƬ߲</a>| <a href="http://8mav1000.com" target="_blank">޾Ʒ2021</a>| <a href="http://rdccc.com" target="_blank">޾aa߿</a>| <a href="http://clzqb2b.com" target="_blank">޻ɫƬ߹ۿ</a>| <a href="http://rj150.com" target="_blank">þ޾Ʒ</a>| <a href="http://blblkj.com" target="_blank">ձһѹۿ </a>| <a href="http://864007.com" target="_blank">þþƷվ</a>| <a href="http://wulegu.com" target="_blank">Ʒާѡ2021</a>| <a href="http://6wss.com" target="_blank">޾ƷƷ</a>| <a href="http://t66ycom.com" target="_blank">޾Ʒٸ30P</a>| <a href="http://ivy-fund.com" target="_blank">йŮר</a>| <a href="http://ax445.com" target="_blank">ŷŷɫ</a>| <a href="http://szq18888.com" target="_blank">jiz zz</a>| <a href="http://778002.com" target="_blank">ձɱ˹ۿ</a>| <a href="http://cnpc1002.com" target="_blank">ŷݵһղsuv</a>| <a href="http://517fanfan.com" target="_blank">޸Ƶ</a>| <a href="http://71caoxee.com" target="_blank">޸һƷ</a>| <a href="http://51xinshiji.com" target="_blank">޸һѿ</a>| <a href="http://www876444.com" target="_blank">þþžȫ</a>| <a href="http://chiyizi.com" target="_blank">һƬɫ</a>| <a href="http://www-188588.com" target="_blank">ĻƬa</a>| <a href="http://kekead.com" target="_blank">2019Ļ</a>| <a href="http://cmanpower.com" target="_blank">aëƬ߹ۿ</a>| <a href="http://www64427.com" target="_blank">պ߹ۿƵ</a>| <a href="http://wwwby1385.com" target="_blank">ѵĻɫҳѹۿ</a>| <a href="http://ding001.com" target="_blank">޹AVվ</a>| <a href="http://qiwangxuan.com" target="_blank">ŷۺһ </a>| <a href="http://whxhjc.com" target="_blank">þþþһƷ޹ۺAV </a>| <a href="http://zhnetbar.com" target="_blank">޳AVַ</a>| <a href="http://5s6b.com" target="_blank">ŷ޾Ʒ˾þ </a>| <a href="http://wwwabxx.com" target="_blank">Ʒһ</a>| <a href="http://ztsf6688.com" target="_blank">þҹ</a>| <a href="http://hzkjjy.com" target="_blank">ͤͤѸ</a>| <a href="http://91tapp.com" target="_blank">Ļѿ</a>| <a href="http://juguanghr.com" target="_blank">ҰһƵ </a>| <a href="http://hkschooltv.com" target="_blank">ۺϼ</a>| <a href="http://kppp4.com" target="_blank">ĻСۺ</a>| <a href="http://hndsfwl.com" target="_blank">պƷĻ</a>| <a href="http://ahzlgj.com" target="_blank">һۺ</a>| <a href="http://microston.com" target="_blank">ëƬѹۿȫ</a>| <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> </body>