0%

单机架构

单机架构是最原始的传统架构,如下图所示,Model V1中Web服务器和App的服务器直接放在一起,随着业务持续增长,Model V2中把Web和App服务器分开管理,随着进一步业务增长需求,DB也很出现性能瓶颈,Web和App服务器频繁读取文件也会造成IO瓶颈,为了不影响现在业务,于是集群方案就产生

image-20211018151234673

集群结构

  • 如图 所示 Model V3 结构 Web&App 服务都可以用多台机器来进行负载分担,DB的瓶颈也可以采用分区、分库、分表的方式来缓解;分库、分区、分表宗旨是减小遍历范围,提高响应速度。
  • 还可以采用读写分离的方式来减轻单台服务器的 IO 负担,相当于增加了机器的处理能力。读写分离比较适合以读操作为主的应用,可以减轻写服务器压力,但是读服务器会有一定的延迟。当一些热点数据过多时,我们还可以对这些热点数据进行缓存(Model V4)。

image-20211018152315051

  • 对于负载均衡层,目前主要是在 TCP\IP 协议的四层与七层进行负载分发,四层负载流行的有 LVS(LVS 集群采用 IP 负载均衡技术和基于内容请求分发技术,目前互联网公司大量使用,如阿里、京东等)、F5(强大的商业交换机,好处是快、但就是贵),七层流行的有 Tengine、 Nginx、Haproxy、Vanish、ATS、Squid 等。目前互联网企业多采用 LVS+Tengine/Nginx的组合来进行负载均衡。
  • Model V3、Model V4 的集群架构基本能够解决多数企业的性能问题,但缺点也比较明显。多个 Web 服务器之间的用户请求状态(Session)需要同步(为保证高可用,如果其中一台宕机,另一台服务器能够正常处理用户请求,专业术语叫 Session 黏滞),这会消耗不少 CPU 资源。另外数据库实现读写分离后,数据同步(数据一致性保证)成为一个性能问题,大量数据的同步 IO 会面临瓶颈。另外业务量大以后,数据的安全保障机制也受到挑战,备份问题凸显,也催生了分布式的发展。

分布式结构

  • 系统分层、系统服务化(SOA 架构、微服务化等)、服务分布式、DB 分布式、缓存分布式及良好的水平扩展能力是当前分布式架构的典型特征:哪一个服务性能不佳直接增加机器即可,性能与机器数量呈线性增长关系,从而解决前面架构遇到的问题。

    image-20211018154844654

DNS&CDN静态加速

  • DNS:智能 DNS,用户请求进入后,域名解析服务器智能判断用户请求的线路,如果是电信用户就解析到电信 IP,联通用户就解析到联通 IP。

  • CDN:用户访问 Web 页面时往往会有很多静态资源(图片、样式、JS 等),而这些资源都是比较耗时的,CDN 服务其实就是把静态页面缓存到不同地区很多台专门的缓存服务器上,然后根据用户线路所在的地区通过 CND 服务商的智能 DNS 自动选择一个最近的缓存服务器让用户访问,以此提高速度,这种方案对静态页面效果非常好,同时它也需要智能 DNS 的帮助才能实现把用户引导到离自己最近的缓存服务器上。

负载均衡器

  • 负载均衡器的作用是把用户请求按一定规则分发到不同的服务器进行处理,在使用负载均衡集群时,分发负载是一件性能要求极高的事情,流行的产品有 LVS、nginx、apache、F5 等。

  • LVS:LVS 集群采用 IP 负载均衡技术和基于内容请求分发技术,也就是能够在 TCP/IP层的第四层进行请求分发。LVS 调度器具有很好的吞吐率,将请求均衡地转移到不同的服务器上执行,且调度器自动屏蔽掉服务器的故障,从而将一组服务器构成一个高性能的、高可用的虚拟服务器。整个服务器集群的结构对客户透明,无需修改客户端和服务器端的程序。为此,在设计时需要考虑系统的透明性、可伸缩性、高可用性和易维护性。关键一点是 LVS开源而且效率高,相比商业负载工具 F5 赢在免费,而且效率达到 F5 的 60%。

  • Tengine:Tengine 是一个强大的高性能反向代理服务器,Tengine 是由淘宝网发起的 Web服务器项目,它在 Nginx 的基础上针对大访问量网站的需求,添加了很多高级功能和特性。目前很多公司采用 LVS+Tengine/Nginx 的负载架构来构建自己的负载均衡部分

Web 服务分布式集群

  • Web:Web 服务层,按照 MVC 的设计理念 Web 服务层主要是进行页面渲染,Session 保持等工作。这些应用部署在诛如 Tomcat、Jetty、Jboss 这些容器上。图 10-13 所示为一个典型的分布式 Web 结构(已经简化),Client 请求通过前端负载均衡器(比如 LVS+Tengine)分发到 Web 层,Web 层通过 ZK(Zookeeper)注册中心找到提供业务处理(App 层中的某一个节点)的节点。Web 层请求传送到 App 层的路由由负载算法(用程序实现的负载路由)来实现通常叫软路由,它能够把请求按一定规则分发到 App 层的各节点上,Dubbo 框架中就内置了这样的软路由。
  • 对于 Web 层来说,请求会话状态(用 Session 来代替)的保持是一个问题,Session 同步是一个容易引起性能的地方,在分布式框架中一般会把 Session 信息独立出来放到缓存设备中,比如用 redis 来存储 Session 信息,当然大量的以亿来计的 Session 信息如果保存在一台或者少量几台 redis 中也会造成风险,首先是需要一个大的内存来存储数据,另外要考虑到数据安全,当服务器挂掉后数据如何恢复?想想一个200G 的 redis 数据集想恢复得花多长时间,本着风险分散原则,还是拆分成多个 redis 节点保险,所以 redis 分布式集群也变得很有必要。不少互联网公司会在 redis 之上加上一个中间层,来构建分布式缓存服务

App 服务分布式集群

  • App:应用服务层,实现主要的业务逻辑。应用服务不仅在单机上要具备更优的性能,在结构上要易于水平扩展,功能服务化且服务无状态。比如我们网购,选择商品准备结算时,如果没有登录会跳出登录框,提交登录请求会调用会员系统进行身份验证,这是一个服务;会员系统调用账务系统查询余额是另一个服务。这些服务部署多个,任意一台处理请求返回结果都一样,这样就具备良好的水平扩展能力。当遇到某一类服务性能吃紧时直接增加机器就可以了。Dubbo 就是经过实践验证的使用广泛的分布式服务框架,具备良好的水平扩展能力

分布式缓存

  • Cache:缓存数据到内存,解决热点数据问题。比如 redis,memcache 等缓存产品。在内存中存储数据时不可忽视的问题是数据的安全性与存储量,当前解决数据安全性的方法主要是数据持久化与数据冗余(主从缓存服务器结构,为了性能会进行读写分离);解决存储量的问题主要是分而治之,进行分布式存储,每一个存储节点我们叫做分片,比如100G的数据我们分5个片区来存储,每个分片就是20G。
  • 图 10-14 所示常见的分布式缓存架构,Cache1 与 Cache n 构成分布式缓存集群,以 redis 为例(比如 Cache1 由 redis 担当),Cache1 是一个分片(物理节点),Cachen是第n个分片(物理节点),redis 以(Key,Value)结构存储数据(有关redis 的知识请自行查阅相关资料)。
  • Web/App 服务先从 Zookeeper 中心取得缓存服务器访问地址(比如 Cache 1 地址),然后向缓存服务器发起请求(读、写、修改)。缓存服务器由 Zookeeper 来提供一致性服务,这样很方便对缓存服务器数据进行冗余(读写分离),保证数据安全,提高访问效率。当缓存数据过多时,可以水平扩展来提高服务能力。
  • 分布式缓存不仅解决热点数据问题,有些企业直接用其作为数据持久化介质,比如秒杀分布式缓存在整个分布式架构中是重要的组成部分。

image-20211018163041453

分布式数据库

  • 传统集中式的数据库结构随着数据的激增,为提供良好的用户体验的成本越来越高。对于海量数据基本是分区、分表、读写分离这些手段,海量数据的访问使得对 CPU、内存、硬盘的要求更高,最后依然是无法突破瓶颈,我们并不能生产出更强的服务器,暂时办不到,也没必要,就像我们搬不动一堆东西时,我们可以分开搬,也可以几个人一起搬。所以我们可以分而治之,用普通的 PC 来做高端服务器的工作。
  • 分布式数据库是一种趋势,用廉价的普通 PC 设备堆叠出具备高可用性、高扩展性的服务集群,正如本章开篇中说到的去IOE 化,摆脱对大型设备的依赖,减少运营成本,提高服务能力。

以上知识抄录于<<全栈性能测试修炼宝典JMeter实战>>

什么是反向代理服务器

  • ‘反向代理服务器’ 有两个概念,一是‘代理服务器’,二是‘反向’

  • ‘代理服务器’比较好理解,例如需要访问某网站,不想或不能直接访问,例如网络的原因、隐私的原因等,我们就连接一个代理服务器,这样对于这个网站来说,访问他的用户是这个代理服务器,而不是我们自己

  • ‘反向’的意思我们没有通过代理服务器访问网站,但网站却在自己服务器前面加了一个代理,我们输入网址发起请求后,请求先到了这个代理服务器,代理服务器再把请求转给网站服务器

    image-20211018163759022

  • 参考这里

中间件总结

Dubbo

  • Dubbo是一个分布式服务框架,是管理中间层的工具,至于你架子上放什么是完全取决于你的,就像一个汽车骨架,你需要配你的轮子引擎。这个框架中要完成调度必须要有一个分布式的注册中心,储存所有服务的元数据,你可以用zk(Zookeeper),也可以用别的,只是大家都用zk

  • Dubbo的将注册中心进行抽象,是得它可以外接不同的存储媒介给注册中心提供服务,有ZooKeeper,Memcached,Redis等。

  • 为什么要用zookeeper作为dubbo的注册中心?能选择其他的吗?

    • Zookeeper的数据模型是由一系列的Znode数据节点组成,和文件系统类似。zookeeper的数据全部存储在内存中,性能高;
    • zookeeper也支持集群,实现了高可用;同时基于zookeeper的特性,也支持事件监听(服务的暴露方发生变化,可以进行推送),所以zookeeper适合作为dubbo的注册中心区使用。redis、Simple也可以作为dubbo的注册中心来使用。
  • 开发步骤

1
2
3
4
5
6
7
1:增加dubbo的配置文件(提供者)
zk的通讯注册地址
dubbo的对外暴露接口的地址
对外暴露并注册的接口(zk)
2:增加获取服务接口的配置文件dubbo(消费者)
配置dubbo调用的接口暴露地址
获取要调用消费的接口bean组件
  • 使用步骤
1
2
1:开启zk的服务,将dubbo的接口注册到服务中
2:启动dubbo的监控中心,监控提供者和消费者的接口调用和注册信息

Dubbo与Kafka对比

  • 都可以以分布式方式处理任务和负载均衡
  • Dubbo处理任务是同步的,可以收到返回结果值。Kafka是异步的,不能返回结果值。
  • Kafka可以存储消息,可以从某个消息重新开始执行。如果任务执行速度较慢,消息可以缓存,不会阻塞

分布式开发应用场景

  • 服务器中间件:Tomcat 6、7、Jboss 7、WebLogic 10、WebSphere 8

  • 分布式服务:Dubbo+Zookeeper+Proxy+Restful

  • 分布式消息中间件:KafKa+Flume+Zookeeper

  • 分布式缓存:Redis 分布式文件:FastDFS

  • 负载均衡:Keepalived+Nginx+Proxy(三重负载)

其他总结

Elasticsearch

  • ElasticSearch是一个基于Lucene的搜索服务器。通过HTTP使用JSON进行数据索引,用于分布式全文检索,解决人们对于搜索的众多要求。

Nginx

  • nginx本是一个web服务器和反向代理服务器,但由于丰富的负载均衡策略,常常被用于客户端可真实的服务器之间,作为负载均衡的实现。用于HTTP、HTTPS、SMTP、POP3和IMAP协议

ActiveMQ

  • activeMQ是一种开源的,面向消息的中间件,用来系统之间进行通信的

说明

  • 按照官网给出的配置后,发现底部和每个文章都不展示统计数据,查询到资料是live2dbusuanzi冲突引起,最终解决方案如下

解决

  • 解决网站底部不展示统计数据,打开\themes\next\layout\_third-party\statistics\busuanzi-counter.swig,将

    1
    2
    3
    4
    5
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
    修改
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;"></span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;"></span>

image-20211014155848022

  • 解决文章标题下不展示统计数据,我看很多人都是修改源busuanzi.pure.mini.js文件,最终我新增了个 style属性就行,打开\themes\next\layout\_macro\post.swig ,新增style属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {%- if not is_index and theme.busuanzi_count.enable and theme.busuanzi_count.post_views %}
    <style>
    #busuanzi_container_page_pv {display:inline !important;margin-left:10px;}
    </style>
    <span class="post-meta-item" title="{{ __('post.views') }}" id="busuanzi_container_page_pv">

    <span class="post-meta-item-icon">
    <i class="{{ theme.busuanzi_count.post_views_icon }}"></i>
    </span>
    <span class="post-meta-item-text">{{ __('post.views') + __('symbol.colon') }}</span>
    <span id="busuanzi_value_page_pv"></span>
    </span>
    {%- endif %}

    image-20211014155808658

注意

  • 此次实践的jmeter版本不能过高,建议用3.1
  • 大量性能压测时,不介意使用此教程,此次实践只为简单练手

下载插件

准备

  • 首先把解压出来的JMeterPlugins-Standard.jarJMeterPlugins-Extras.jar两个jar包放在apache-jmeter\lib\ext目录下

    image-20211014103834047

  • 重新启动jmeter之后可以看到监听器里多了许多jp@gc开头的就成功了

    image-20211014103940321

    image-20211014104308298

  • Agent方面,只需要将ServerAgent在目标机器上启动即可。比如win上直接打开bat文件即可

    image-20211014104201505

    查看监控效果

  • 运行脚本,查看效果

    image-20211014104558450

环境

  • jmeter version 5.4.1
  • Badboy录制了三个步骤
    • 登录
    • 打开用户列表
    • 新增用户

问题点

  • Badboy录制后的脚本给jmeter打开,直接是无法运行的,需要做如下配置

设置jmeter代理

image-20211009150800398

  • 设置代理后,登录接口出错,发现传参应该为json但是传的是

image-20211009153859635

  • 改为放到body data中放json

    image-20211009154035718

  • 设置代理后,登录接口抱错,抓包登录接口发现是header的Accept设置错误,设置加了个applicaiton/json

    image-20211009151254512

    登录后的设置

  • 再次回放,登录成功了,但是登录后的其他接口一直返回403,因为所有接口的头部加了Authorization 验证,在登录接口设置后置处理器json提取器

  • 查看登录接口返回的数据

    image-20211009151904817

  • 新建后置处理器json提取器

    image-20211009152109721

  • 设置提取Authorization的值

    image-20211009152223210

  • HTTP Header设置请求参数

    image-20211009152326586

结论

  • 最终回放成功

    image-20211009152629801

修正

  • 2021年10月28日,无法回放和jmeter设置的代理无关,设置代理后可以直接用来录制,后续会有笔记对这个进行说明

  • 打开chrome浏览器的调试工具,如图所示:

image.png

主要看下这里的Finish,DOMLoadedLoad的区别

DOMLoadedLoad

  • DOMContentLoaded Load 分别对应 页面 DOMContentLoadedLoad 事件触发的时间点
  • DOMContentLoadedDOM树构建完成。即HTML页面由上向下解析HTML结构到末尾封闭标签</html>
  • Load:页面加载完毕。 DOM树构建完成后,继续加载html/css 中的图片资源等外部资源,加载完成后视为页面加载完毕。
  • DOMContentLoaded 会比 Load 时间小,两者时间差大致等于外部资源加载的时间。
    看看下面这个例子:
1
2
3
4
5
6
<html>
<script src=1.js></script>
<script src=2.js></script>
<img src=1.jpg />
<script src=3.js></script>
</html>
  • 3.js 执行(不包括异步部分)后,后面的 html 才能允许渲染, DOMContentLoaded 应该是指 最后一个字节都被渲染出来后的时间 (onDocumentChange 状态变成 ready )。而 onLoad 的触发除了dom还包括所有依赖元素,上例中就是要等 1.jpg 加载完成(或出错)后才能触发

看下Finish

  • Chrome devtools中的Finish时间似乎包括页面上的异步加载(非阻塞)对象/元素,这些对象/元素可能会在页面的onload事件触发后继续下载。
  • 一般来说,网站的响应时间意味着Load时间,因为用户可以更容易地感知到这一点,此时用户可以看到浏览器已完成工作并且页面已准备就绪。
  • 在某些情况下,似乎Finish永远不会停止并继续增加,因此它可能不是对网页响应时间的最佳评估。
  • 经过测试会出现会出现Finish 的时间比 Load 大也有可能小,引用于这篇文章

    Finish 时间与DOMContentLoaded 和 Load 并无直接关系。
    Finish 时间是页面上所有 http 请求发送到响应完成的时间,HTTP1.0/1.1 协议限定,单个域名的请求并发量是 6 个,即Finish是所有请求(不只是XHR请求,还包括DOC,img,js,css等资源的请求)在并发量为6的限制下完成的时间。
    Finish 的时间比 Load 大,意味着页面有相当部分的请求量,
    Finish 的时间比 Load 小,意味着页面请求量很少,如果页面是只有一个 html文档请求的静态页面,Finish时间基本就等于HTML文档请求的时间
    页面发送请求和页面解析文档结构,分属两个不同的线程,

实践列子

  • 看看官网的例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import PyChromeDevTools
    import time
    import os
    os.chdir(r"C:\Users\Administrator\AppData\Local\Google\Chrome\Application")
    cmd = "chrome.exe --remote-debugging-port=9222"
    os.popen(cmd)
    chrome = PyChromeDevTools.ChromeInterface()
    chrome.Network.enable()
    chrome.Page.enable()
    chrome.Page.reload(ignoreCache=True) # 不带缓存
    start_time=time.time()
    chrome.Page.navigate(url="http://www.baidu.com/")
    chrome.wait_event("Page.loadEventFired", timeout=60)
    end_time = time.time()
    print("Page Loading Time:", end_time-start_time)
    chrome.close()

得到结果为:

1
2
3
Page Loading Time: 1.702894687652588
Page Loading Time: 1.658094882965088
Page Loading Time: 1.5752882957458496

在chrome浏览器的console下调试,基本上和load时间一致:
image.png

在chrome 浏览器里调试

  • Console输入 window.performance.getEntries(),可以看到页面上所有的资源请求,不统计404的请求
    image.png

  • 有65个请求,里面有请求的哪个节点耗时,和url,查看第一个请求duration其实就是页面的load时间
    image.png
    image.png

  • 想过把这所有资源的duration相加应该就能得到Finish时间?,经过测试,当然是不行的,第一个请求duration虽然是页面的load时间,但是它可能包含了页面上的非异步的请求,同时也包含了css,img,dom的加载时间,因此相加统计肯定会被Finish要大

关于自动化

  • 可以结合selenium来使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from selenium import webdriver
    import os
    PATH = lambda p: os.path.abspath(
    os.path.join(os.path.dirname(__file__), p)
    )
    chrome_driver = PATH("exe/chromedriver.exe")
    os.environ["webdriver.chrome.driver"] = chrome_driver
    driver = webdriver.Chrome(chrome_driver)
    driver.get("http://www.baidu.com")
    data = driver.execute_script("return window.performance.getEntries();")
    print(data)

移动端h5性能测试

  • 打开手机usb调试
  • 如果是想调试混合app的webview,请打开:
    1
    2
    3
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    MtcWebView.setWebContentsDebuggingEnabled(true);
    }
  • 手机连接电脑后,打开chrome,输入chrome://inspect/#devices
  • 然后就可以进行调试了
    image.png

image.png
image.png

扩展阅读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import pandas as pd
from ast import literal_eval


class OperateExcel():
def __init__(self, file_path):
self.file_path = file_path
self.df = pd.read_excel(file_path, sheet_name="Sheet1") # sheet_name不指定时默认返回全表数据
# self.df = pd.read_excel(file_path, sheet_name="Sheet1", usecols=['name', 'method'])

def get_excel(self):
"""
读取表格数据存为json
:return:
"""

data = []
for i in self.df.values:
params = ""
if i[3] == "post":
params = literal_eval(i[4])
app = {"id": i[0], "name": i[2], "method": i[3], "params": params}
data.append(app)
print(data)

def write_excel(self):
# 根据条件累加数据
self.df['id'][self.df['name'] == '测试2'] += 100
print(self.df.head())
self.df.to_excel('data3.xlsx', sheet_name='Sheet1', index=False, header=True)

# 新增一行
self.df.loc[10] = [5, 'Eric', 'male', 20, '']

# 新增一列
self.df['favorite'] = None

self.df.to_excel('data3.xlsx', sheet_name='Sheet1', index=False, header=True)
print(self.df.head())

def get_filter_excel(self):
# 查看所有的值
print(self.df.values)

# 查看第一行的值
print(self.df.values[0])

# 查看某一列所有的值
print(self.df['name'].values)
print("===打印头部数据,仅查看数据示例时常用====")
print(self.df.head())
print("====打印列标题===")
print(self.df.columns)
print("====打印行========")
print(self.df.index)
print("========打印指定列============")
print(self.df["name"])


if __name__ == "__main__":
file_path = 'data3.xlsx'
o_excel = OperateExcel(file_path)
o_excel.get_excel()
# o_excel.write_excel()
# o_excel.get_filter_excel()

  • excel文件内容

image.png

  • 其他几款操作excel对比,图片来源这里
    image.png

python3.x已经把md5 module移除了。要想用md5得用hashlib module,

1
2
3
4
5
import hashlib  
m = hashlib.md5()
m.update(b"Nobody inspects the spammish repetition") #参数必须是byte类型,否则报Unicode-objects must be encoded before hashing错误
md5value=m.hexdigest()
print(md5value) #bb649c83dd1ea5c9d9dec9a18df0ffe9

其实可简写,如下面的md5的例子,hashlib.md5(data),就可以了

但是对中文字符串md5怎么办?
中文字符在Python中是以unicode存在的,同一个字符串在不同的编码体系下有不同的值,所以在hash前要进行编码,个人建议转为gb2312,因为对比发现,我下载的一个工具算出的md5值是与gb2312编码后算出的md5值一样。

1
2
3
4
import hashlib  
data='我是'
m = hashlib.md5(data.encode(encoding='gb2312'))
print(m.hexdigest())

  • 单线程,多线程下载某云音乐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import re
import urllib.request
import requests
from bs4 import BeautifulSoup
import os
import time
from Threads import BaseThread

PATH = lambda p: os.path.abspath(
os.path.join(os.path.dirname(__file__), p)
)

'''
https://music.163.com/playlist?id= 得到播放列表
http://music.163.com/song/media/outer/url?id= 得到下载链接
urllib.request.urlretrieve 把远程下载的mp3文件下载到本地
'''


class Music163:
def __init__(self):
pass
def get_music_163(self, id):
user_agent = 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 ' \
'Safari/537.36 '
headers = {'User-Agent': user_agent}
data = requests.get("https://music.163.com/playlist?id=" + id, headers).text
soup = BeautifulSoup(data, 'lxml')
temp = []
for i in soup.find("ul", {"class", "f-hide"}).find_all("a"):
pattern = re.compile('<a .*?id=(.*?)">(.*?)</a>', re.S)
items = re.findall(pattern, str(i))
temp.append([items[0][0], items[0][1]])
return temp

# 批量下载
def download(self, value):
for i in value:
if os.path.isfile(PATH("mp3/" + i[1] + ".mp3")):
print("%s已经被下载了" % i[1])
else:
url = 'http://music.163.com/song/media/outer/url?id=' + i[0] + '.mp3'
urllib.request.urlretrieve(url, '%s' % PATH("mp3/" + i[1] + ".mp3"))
print("%s下载成功" % i[1])

# 单个下载
def get(self, value):
if os.path.isfile(PATH("mp3/" + value[1] + ".mp3")):
print("%s已经被下载了" % value[1])
else:
url = 'http://music.163.com/song/media/outer/url?id=' + value[0] + '.mp3'
urllib.request.urlretrieve(url, '%s' % PATH("mp3/" + value[1] + ".mp3"))
print("%s下载成功" % value[1])


# 多线程
def multi_thread():
id = "2786226719" # 播放的列表id
start_time = time.time()
threads = []
mc = Music163()
data = mc.get_music_163(id)
count = len(data)
for i in range(0, count):
threads.append(BaseThread(mc.get(data[i])))
for j in range(0, count):
threads[j].start()
for k in range(0, count):
threads[k].join()
end_time = time.time()
print("共耗时%.2f" % (end_time - start_time) + "秒")
# 多线程47秒


# 运行单线程
def run():
id = "2786226719" # 播放的列表id
start_time = time.time()
mc = Music163()
data = mc.get_music_163(id)
mc.download(data)
end_time = time.time()
print("共耗时%.2f" % (end_time - start_time) + "秒")
# 单线程43秒


if __name__ == "__main__":
# run()
multi_thread()
  • 单线程共下载100首歌,耗时9.09秒

dan.png

  • 多线程共下载100首歌,耗时9.60秒

image.png

  • 协程下载的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
mport time
from multiprocessing import Process
from gevent import monkey
import urllib.request
import BaseMusic163
monkey.patch_all()
import gevent
import os

PATH = lambda p: os.path.abspath(
os.path.join(os.path.dirname(__file__), p)
)
'''
协程发请求,
'''
class Producer(object):
def __init__(self):
self._rungevent()

def _rungevent(self):
jobs = []
id = "2786226719" # 播放的列表id
start_time = time.time()
mc = BaseMusic163.Music163()
data = mc.get_music_163(id)
count = len(data)
for i in range(count): # windows下有1024端口限制
jobs.append(gevent.spawn(self.produce(data[i])))
gevent.joinall(jobs)
end_time = time.time()
print("共耗时%.2f" % (end_time - start_time) + "秒")

def produce(self, value):
if os.path.isfile(PATH("mp3/" + value[1] + ".mp3")):
print("%s已经被下载了" % value[1])
else:
url = 'http://music.163.com/song/media/outer/url?id=' + value[0] + '.mp3'
urllib.request.urlretrieve(url, '%s' % PATH("mp3/" + value[1] + ".mp3"))
print("%s下载成功" % value[1])


def main():
p1 = Process(target=Producer, args=())
p1.start()


if __name__ == '__main__':
main()
  • 下载时间
    image.png

结论

  • 昨天测试,发现是协程>多线程>单线程
  • 今天测试却是:多线程>协程>单线程
  • 当然也会出现单线程耗时反而比多线程耗时短的情况
  • 一直流传多进程+协程,可以解决python的GIL问题,因为本次测试的数据不多,使用的也是单进程+协程的方式,后续对协程的测试,有机会进行大量数据的测试,采用多进程+协程的方式进行测试
  • 源码获取

  • python socket server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import socket

try:
sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
print("create socket succ!")

sock.bind(('192.168.1.38',50006))
print('bind socket succ!')

sock.listen(5)
print('listen succ!')

except:
print("init socket error!")

while True:
print("listen for client...")
conn,addr=sock.accept()
print("get client")
print(addr)

conn.settimeout(30)
szBuf=conn.recv(1024)
print("recv:"+str(szBuf,'gbk'))

if "0"==szBuf:
conn.send(b"exit")
else:
conn.send(b"welcome client")

conn.close()
print("end of servive")


  • python socket client
1
2
3
4
5
6
7
8
9
10
11
12
13
import socket
HOST = '192.168.1.38'
PORT = 50006

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
sock.send("232132132131".encode())

szBuf = sock.recv(1024)
byt = 'recv:' + szBuf.decode('utf-8')
print(byt)

sock.close()
  • java socket client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;


public class SocketClient {

public static void main(String args[])throws Exception {

try {
Socket socket = new Socket("192.168.1.38",50006);

//获取输出流,向服务器端发送信息
OutputStream os=socket.getOutputStream();//字节输出流
PrintWriter pw=new PrintWriter(os);//将输出流包装为打印流
pw.write("我是Java服务器");
pw.flush();
socket.shutdownOutput();//关闭输出流

InputStream is=socket.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(is));
String info=null;
while((info=in.readLine())!=null){
System.out.println("我是客户端,Python服务器说:"+info);
}
is.close();
in.close();
socket.close();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 值得注意的是,这里的服务器域名应该写本电脑上的ip,而不是127.0.0.1

来自: http://blog.csdn.net/ChenTianSaber/article/details/52274257?locationNum=4

开放端口

1
2
3
4
5
6
7
8
firewall-cmd --zone=public --add-port=8100/tcp --permanent
firewall-cmd --reload # 配置立即生效
firewall-cmd --zone=public --list-port # 查看防火墙所有开放的端口
firewall-cmd --state # 查看防火墙状态
netstat -lnpt # 查看监听的端口
netstat -lnpt |grep 5672 # 查看监听的具体端口


  • 编写和启动脚本start.sh,启动sh start.sh
    1
    2
    3
    4
    5
    # 后台启动django服务,输出日志到日志文件中,内容为标准输出和标准错误
    MYDATE=`date +%Y%m%d`
    ALL_LOGFILE=/usr/local/log/log_$MYDATE

    nohup python3 manage.py runserver 0.0.0.0:8100 > ${ALL_LOGFILE} 2>&1 &

按照nginx

  • 按照依赖文件

    1
    2
    3
    4
    5
    yum install gcc-c++
    yum install -y pcre pcre-devel
    yum install -y zlib zlib-devel
    yum install -y openssl openssl-devel

  • 配置nginx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    wget -c https://nginx.org/download/nginx-1.10.1.tar.gz
    tar -zxvf nginx-1.10.1.tar.gz
    cd nginx-1.10.1
    ./configure # 使用默认配置
    # 编辑安装
    make
    make install

    whereis nginx # 查询安装路径
    # 启动、停止nginx
    cd /usr/local/nginx/sbin/
    ./nginx # 启动
    ./nginx -t # 检查
    ./nginx -s stop
    ./nginx -s quit
    ./nginx -s reload
  • 修改配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    vim nginx.conf



    #user nobody;
    worker_processes 1;

    #error_log logs/error.log;
    #error_log logs/error.log notice;
    #error_log logs/error.log info;

    #pid logs/nginx.pid;

    events {
    worker_connections 1024;
    }

    http {
    include mime.types;
    default_type application/octet-stream;

    #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    # '$status $body_bytes_sent "$http_referer" '
    # '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log logs/access.log main;

    sendfile on;
    #tcp_nopush on;

    #keepalive_timeout 0;
    keepalive_timeout 65;

    #gzip on;
    #upstream表示负载服务器池,定义名字为goserver的服务器池
    #upstream goserver {
    # server 47.123.15.125:8887 weight=7;
    # server 192.111.50.133:8888 weight=3;
    #}
    #基于域名的虚拟主机
    server {
    #8880为监听端口号
    listen 8880;
    server_name 192.168.111.128;
    index index.html index.htm;
    #root 是你的访问目录
    root /home/dist;
    #charset koi8-r;

    #access_log logs/host.access.log main;

    #error_page 404 /404.html;

    # redirect server error pages to the static page /50x.html
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root html;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    # root html;
    # fastcgi_pass 127.0.0.1:9000;
    # fastcgi_index index.php;
    # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
    # include fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    # deny all;
    #}
    }
    }


  • 关掉防火墙的端口

    1
    2
    3
    4
    5
    //8088,自己指定端口
    firewall-cmd --permanent --add-port=8880/tcp --zone=public
    //重新加载
    firewall-cmd --reload

  • 重启nginx

    1
    2
    3
    4
    5
    //检查配置是否正确
    ./nginx -t
    //启动nginx
    ./nginx

  • 前台正常访问
    image.png