当前位置:   article > 正文

深度学习多线程部署—学习笔记

深度学习多线程

分布式、多线程、高并发

  • 分布式是从物理资源的角度去将不同的机器组成一个整体对外服务,技术范围非常广且难度非常大,有了这个基础,高并发、高吞吐等系统很容易构建;
  • 高并发是从业务角度去描述系统的能力,实现高并发的手段可以采用分布式,也可以采用诸如缓存、CDN等,当然也包括多线程;
  • 多线程则聚焦于如何使用编程语言将CPU调度能力最大化
    在这里插入图片描述

分布式

分布式更多的一个概念,是为了解决单个物理服务器容量和性能瓶颈问题而采用的优化手段。

该领域需要解决的问题在不同的技术层面上,又包括:分布式文件系统、分布式缓存、分布式数据库、分布式计算等,一些名词Hadoop、zookeeper、MQ等都跟分布式有关。

从理念上讲,分布式的实现有两种形式:

  • 水平扩展:当一台机器扛不住流量时,就通过添加机器的方式,将流量平分到所有服务器上,所有机器都可以提供相当的服务;
  • 垂直拆分:前端有多种查询需求时,一台机器扛不住,可以将不同的需求分发到不同的机器上,比如A机器处理余票查询的请求,B机器处理支付的请求。

高并发

相对于分布式来讲,高并发在解决问题上会集中一些,其反应的是同时有多少量:比如在线直播服务,同时与上万人观看。

高并发可以通过分布式技术去解决,将并发流量分到不同的物理服务器上。

但除此之外,还可以有很多其他优化手段:比如使用缓存系统,将所有的吗,静态内容放到CDNA等;还可以使用多线程技术将一台服务器的服务器能量最大化。

在这个‘云’的时代,提高分布式系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。

  • 垂直扩展单机处理能力。垂直扩展的方式又有两种:
    • 增强单机硬件性能,例如增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G。
    • 提升单机架构性能,例如使用Cache来减少I/O次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间。
  • 水平扩展,只要增加服务器数量,就能线性扩充系统性能。虚拟化技术的出现,让水平扩展变得轻松且简单。现在的云主机几乎是虚拟机,而不是物理主机。这样的话,线性扩充也是分分钟的事,前提是有足够的物理主机支撑。

高并发的三个经典问题:

  • 单台服务器最大并发,单台服务器最大并发问题,一般是指台服务器能够支持多少TCP并发连接。一种理论说法是受到端口号范围限制。操作系统上端口号1024以下是系统保留的,从1024-65535是用户使用的。由于每个TCP连接都要占一个端口号,所以我们最多可以有60000多个并发连接。但实际上单击并发连接数肯定要受到硬件资源(内存、网卡)、网络资源(宽带)的限制。特别是网卡处理数据的能力,它是最大并发的瓶颈。
  • C10K并发连接问题是指单机1万个并发连接问题。如何突破单机性能局限,是高性能网络编程所必须要直面的问题。这些局限和问题最早被Dan Kegel进行归纳总结,首次成系统的分析和提出解决方案,后来这种普遍的网络现象和技术局限都被大家称为C10K问题。C10K问题本质上是操作系统问题。对于Web1.0/2.0时代的操作系统而言,传统的同步阻塞I/O模型都是一样的,处理的方式都是requests per second,并发10K和100K的区别关键在于CPU。创建的进程线程多了,数据拷贝频繁(缓存I/O、内核将数据拷贝到用户进程空间、阻塞),进程/线程上下文切换消耗大,导致操作系统崩溃,这就是C10K问题的本质。

多线程

多线程是指从软件或者硬件上实现多个线程并发执行的技术,它更多的是解决CPU调度多个进程的问题,从而让这些进程看上去是同时执行(实际是交替运行的)。

在这几个概念中,多线程解决的问题是最明确的,手段也是比较单一的,基本上遇到的最大问题就是线程安全。

Flask

Flask默认是单进程、单线程阻塞的任务模式,在项目上线的时候可以通过nginx+gunicorn的方式部署flask任务,但是在开发的过程中如果想通过延迟的方式测试高并发,需要在app.run()中通过threaded和processes开启线程支持和进程支持。
但是多进程或多线程只能选择一个,不能同时开启。

  • threaded 多线程支持,默认为False,即不开启多线程。
  • processes 进程数量,默认为1.
app.run(host='0.0.0.0', port=4000, threaded=True, processes=1)
  • 1

gunicorn 部署Flask项目

使用flask自带的服务器,可以完成web服务的启动。在生产环境中flask自带的服务器,无法满足性能需求,需要采用Gunicorn做wsgi容器,来部署flask程序。Gunicorn(绿色独角兽)是一个Python WSGI的HTTP服务器。从Ruby的独角兽(Unicorn)项目移植。该Gunicorn服务器与各种Web框架兼容,实现非常简单,轻量级的资源消耗。

WSGI(Web Server Gateway Interface)web服务器网关接口,是一种规范,它是web服务器和web应用程序之间的接口。它的作用就像是桥梁,连接在web服务器和web应用框架之间。

Gunicorn是基于pre-fork模型的,有一个中心管理进程(master process)用来管理worker进程集合。Master从不知道任何关于客户端的信息。所有的请求和响应处理都是由worker进程来处理的。

主程序是一个简单的循环,监听各种信号以及相应的响应进程。master管理着正在运行的worker集合,通过监听各种信号比如TTIN,TTOU和CHLD。TTIN和TTOU响应的增加和减少worker的数目。CHLD信号表明一个子进程已经结束了,在这种情况下master会自动的重启失败的worker。

gunicorn架构

gunicorn实现了一个Unix的预分发web的服务端。

  • gunicorn 启动会被分发到一个主线程,然后产生的子线程就是对应的worker。
  • 主线程的作用是确保worker数量与设置中定义的数量相同。如果任何一个worker挂掉,主线程都可以通过分发它自身而另行启动。
  • worker的角色是处理HTTP请求。
  • 预分发就意味着主线程在处理HTTP请求之前就创建好了worker。
  • 操作系统的内核就负责处理worker进程之间的负载均衡。

为了提高使用gunicorn时的性能,需理解3种并发方式。

  • worker模式(又成Unix进程模式)
    每个worker都是一个加载Python应用程序的unix进程。worker之间没有共享内存。这时候建议的workers数量是(2*CPU)+1。
    在这里插入图片描述
  • 多线程
    Gunicorn 还允许每个 worker 拥有多个线程。在这种场景下,Python 应用程序每个 worker 都会加载一次,同一个 worker 生成的每个线程共享相同的内存空间。
    在这里插入图片描述
  • 伪线程
    有一些 Python 库比如(gevent 和 Asyncio)可以在 Python 中启用多并发。那是基于协程实现的“伪线程”。Gunicrn 允许通过设置对应的 worker 类来使用这些异步 Python 库。
    在这里插入图片描述

并发 vs. 并行

  • 并发是指同时执行 2 个或更多任务,这可能意味着其中只有一个正在处理,而其他的处于暂停状态。
  • 并行是指两个或多个任务正在同时执行。
    在 Python 中,线程和伪线程都是并发的一种方式,但并不是并行的。但是 workers 是一系列基于并发或者并行的方式。

安装gunicorn

pip3  install gunicorn --user
  • 1

启动gunicorn

  • 最简单方式,默认监听127.0.0.1:8000
gunicorn 入口文件名:app
  • 1
  • 处理高并发需要开多个进程和修改监听端口号
gunicorn -w 4 -b 0.0.0.0:8000 入口文件名:app
  • 1
  • 后台执行方式启动服务

    • 方法一
      nohup 启动服务的命令 &
      
      • 1
      nohup gunicorn -w 4 -b 0.0.0.0:8000 入口文件名:app &
      
      • 1
    • 方法二
      在gunicorn的配置文件中零daemon参数为True
      #gunicorn_config.py
      daemon = True
      reload =True #修改程序代码,不用每次都通过gunicorn进行重启
      
      • 1
      • 2
      • 3
  • 查看程序是否运行起来,如果运行起来会有两个进程

    fuser -v -n tcp 端口号
    
    • 1
  • 结束进程

    kill -9 进程号
    
    • 1

gunicorn配置文件

在这里插入图片描述

# coding=utf-8
# /usr/bin/env python
"""
Author: buty
Date: 2019/9/17 下午1:36
Description: gunicorn config
"""


import gevent.monkey
gevent.monkey.patch_all()

import multiprocessing

#server socket
bind = '0.0.0.0:4000'

#worker
# workers = multiprocessing.cpu_count() * 2 + 1
workers = 4
threads = 1
# worker_class = 'gunicorn.workers.gevent.GeventWorker'
# worker_class = 'gthread'#gevent
worker_class = 'sync'


debug = True
# daemon = True
worker_connections = 100

#log
pidfile = 'log/gunicorn.pid'
accesslog = 'log/gunicorn_access.log'
# errorlog = 'log/gunicorn_error.log'
loglevel = 'debug'
access_log_format = '%(h)s %(l)s %(u)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
  • 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

与logging自带log记录合并,减少文件数量。

# !/usr/bin/env python
# -*- coding: utf-8 -*-
#
# @Author: Buty
# @Date  : 11/17/21 9:09 AM
# @File  : config.py

# config.py
import os
import gevent.monkey
gevent.monkey.patch_all()

import multiprocessing

debug = True

bind = '0.0.0.0:8000'
pidfile = 'log/gunicorn.pid'
# accesslog = 'log/access.log'
# errorlog = 'log/error.log'
logconfig_dict = {
    'version':1,
    'disable_existing_loggers': False,
    'loggers':{
        "gunicorn.error": {
            "level": "WARNING",# 打日志的等级可以换的,下面的同理
            "handlers": ["error_file"], # 对应下面的键
            "propagate": 1,
            "qualname": "gunicorn.error"
        },

        "gunicorn.access": {
            "level": "DEBUG",
            "handlers": ["access_file"],
            "propagate": 0,
            "qualname": "gunicorn.access"
        }
    },
    'handlers':{
        "error_file": {
            "class": "logging.handlers.RotatingFileHandler",
            "maxBytes": 1024*1024*1024,# 打日志的大小,我这种写法是1个G
            "backupCount": 1,# 备份多少份,经过测试,最少也要写1,不然控制不住大小
            "formatter": "generic",# 对应下面的键
            # 'mode': 'w+',
            "filename": "./log/gunicorn.error.log"# 打日志的路径
        },
        "access_file": {
            "class": "logging.handlers.RotatingFileHandler",
            "maxBytes": 1024*1024*1024,
            "backupCount": 1,
            "formatter": "generic",
            "filename": "./log/gunicorn.access.log",
        }
    },
    'formatters':{
        "generic": {
            "format": "'[%(process)d] [%(asctime)s] %(levelname)s [%(filename)s:%(lineno)s] %(message)s'", # 打日志的格式
            "datefmt": "[%Y-%m-%d %H:%M:%S %z]",# 时间显示方法
            "class": "logging.Formatter"
        },
        "access": {
            "format": "'[%(process)d] [%(asctime)s] %(levelname)s [%(filename)s:%(lineno)s] %(message)s'",
            "class": "logging.Formatter"
        }
    }
}

capture_output = True
loglevel = 'warning'

daemon = True #后台启动
reload = True

#workers = multiprocessing.cpu_count()
workers = 1
worker_class = 'gevent'
x_forwarded_for_header = 'X-FORWARDED-FOR'

  • 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
# 添加配置文件后的启动方式
gunicorn -c gun.py 入口文件名:app
  • 1
  • 2

列出所有进程

pstree -ap|grep gunicorn
  • 1

最小的一级是worker进程,它们的上一级是gunicorn进程。使用kill -HUP [gunicorn 进程ID]可以杀掉进程。如果该进程还存在上一级进程,使用kill -9 [进程ID]将其彻底关闭。
在这里插入图片描述

导出测试结果报告

  • 保存View Results Tree(查看结果树)结果
    在这里插入图片描述
  • 生成HTML报告
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 点击index.html即可查看结果
    在这里插入图片描述

报告结构

报告总体分为Dashboard和Charts两部分

Dashboard
  • Test and Report informations(测试和报告信息):测试结果保存文件/测试开始时间/测试结束时间/展示过滤器。
    在这里插入图片描述
  • APDEX(Application Performance Index):应用程序性能满意度的标准,范围在0-1之间,1表示达到所有用户均满意,可以在配置文件设置。
    在这里插入图片描述
  • Request Summary:请求的通过率(OK)与失败率(KO),百分比显示。
    在这里插入图片描述
  • Statistics:数据分析,基本将Summary Report和Aggregate Report的结果合并。
    在这里插入图片描述
  • Errors: 错误情况,依据不同的错误类型,将所有错误结果展示。
    在这里插入图片描述
  • Top 5 Errors by sampler:Top5错误信息采样。
    在这里插入图片描述
Chart

Chart分为三大模块:时间维度信息(Over Time)、吞吐量(Throughput)、响应时间(Response Times)。
在这里插入图片描述

Over Time
  • Response Times Over Time脚本运行时间内响应时间分布曲线
    在这里插入图片描述
  • Response Time Percentiles Over Time (successful responses) 脚本运行时间内成功响应的请求,响应时间百分位
    在这里插入图片描述
  • Active Threads Over Time 脚本运行时间内的活动线程分布
    在这里插入图片描述
  • Bytes Throughput Over Time脚本运行时间内的吞吐量,单位是byte
    在这里插入图片描述
  • Latencies Over Time脚本运行时间内毫秒级的响应延时
    在这里插入图片描述
  • Connect Time Over Time脚本运行时间内平均连接时间
    在这里插入图片描述
Throughput
  • Hits Per Second (excluding embedded resources) 每秒点击数曲线
    在这里插入图片描述
  • Codes Per Second (excluding embedded resources)每秒状态码分布曲线
    在这里插入图片描述
  • Transactions Per Second 每秒事物数曲线
    在这里插入图片描述
  • Total Transactions Per Second
    在这里插入图片描述
  • Response Time Vs Request 响应时间中值与每秒请求数关系曲线
    在这里插入图片描述
  • Latency Vs Request 延迟时间中值与每秒请求数关系曲线

在这里插入图片描述

Response Times
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/702796
推荐阅读
相关标签
  

闽ICP备14008679号