Code war of Angel


  • Home

  • Archives

数据预处理

Posted on 2019-05-24 | In 机器学习
  1. 数据挖掘的流程

    • 获取数据
    • 数据预处理:从数据中检测,纠正或删除损坏,不准确或不适用于模型的记录过程。
    • 特征工程:特征工程是将原始数据转换为更能代表预测模型的潜在问题的特征的过程,可以通过挑选最相关的特征,提取特征以及创造特征来实现。
    • 建模
    • 上线、验证
  2. 数据无量纲化
    在机器学习算法实践中,我们往往有着将不同规格的数据转换到同一规格,或不同分布的数据转换到某个特定分布 的需求,这种需求统称为将数据“无量纲化”。

    • 线性的无量纲化
      • 中心化
      • 缩放
    • 非线性的无量纲化
    1. 数据归一化(异常值敏感)
      当数据(x)按照最小值中心化后,再按极差(最大值 - 最小值)缩放,数据移动了最小值个单位,并且会被收敛到 [0,1]之间。
      image.png

      1
      2
      3
      4
      5
      6
      7
      8
      from sklearn.preprocessing import MinMaxScaler
      scaler = MinMaxScaler(feature_range=[5,10]) # scaler后5~10之间,默认 0~1
      result = scaler.fit(data)
      result = scaler.transform(data)
      # 一步达成
      result_ = scaler.fit_transform(data)
      # 逆转
      scaler.inverse_transform(result)
    2. 数据标准化(优先选用)
      当数据(x)按均值(μ)中心化后,再按标准差(σ)缩放,数据就会服从为均值为0,方差为1的正态分布(即标准正态分布)
      image.png

      1
      2
      3
      4
      5
      6
      7
      8
      from sklearn.preprocessing import StandardScaler
      scaler = StandardScaler()
      scaler.fit(data)
      print(scaler.mean_)
      print(scaler.var_)
      res = scaler.transform(data)
      res.mean()
      res.std()
![image.png](2.png)
  1. 处理缺失值

    1. impute.SimpleImputer(missing_values=nan, strategy=’mean’, fill_value=None, verbose=0, copy=True)

      • missing_values :缺失数据的值
      • strategy:填补策略
        • mean:均值填补(仅对数值型特征可用)
        • median:中值填补(仅对数值型特征可用)
        • most_frequent:众数填补(对数值型和字符型特征都可用)
        • constant:参考参数“fill_value”中的值(对数值型和字符型特征都可用)
      • fill_value:填补值
      • copy: 创建副本

        1
        2
        3
        4
        from sklearn.impute import SimpleImputer
        im = SimpleImputer(strategy='median')
        im.fit(age)
        im.transform(age)
    2. 使用pandas处理

      1
      2
      data.loc[:,"Age"] = data.loc[:,"Age"].fillna(data.loc[:,"Age"].median())
      data.dropna(axis=0,inplace=True) #删除行
  2. 处理分类型特征
    编码: 文字型数据转化为数字型数据。
    哑变量:它是人为虚设的变量,来反映某个变量的相互独立的不同属性。(比如身份:学生、工人、教师)

    1. preprocessing.LabelEncoder: 标签y专用,将分类转化为分类数值

      1
      2
      3
      4
      from sklearn.preprocessing import LabelEncoder
      y = data.Survived
      le = LabelEncoder()
      data.Survived=le.fit_transform(y)
    2. preprocessing.LabelBinarizer: 标签y专用,将分类转化为哑变量

    3. preprocessing.OrdinalEncoder: 特征X专用,将分类特征转化为分类数值 (如果特征的值有关联性,处理有序变量)

      1
      2
      3
      4
      from sklearn.preprocessing import OrdinalEncoder
      x = data.Embarked
      le = OrdinalEncoder()
      data.Embarked=le.fit_transform(x.values.reshape(-1,1))
    4. preprocessing.OneHotEncoder: 独热编码,创建哑变量 (特征的值无关关联性,处理名义变量)
      image.png

      1
      2
      3
      4
      5
      6
      7
      from sklearn.preprocessing import OneHotEncoder
      xs = data.loc[:,['Sex','Embarked']]
      one= OneHotEncoder()
      res= one.fit(xs)
      columns = res.get_feature_names() #查看类型名称
      res= one.transform(xs).toarray()
      newdata = pd.concat([data,pd.DataFrame(res,columns=columns)],axis=1)

      image.png

  3. 处理连续性变量
    二值化:根据阈值将数据二值化,分为0/1。
    分段:连续型变量划分为分类变量的类,能够将连续型变量排序后按顺序分箱后编码。

    1. preprocessing.Binarizer: 根据阈值将数据二值化

      1
      2
      3
      4
      from sklearn.preprocessing import Binarizer
      x = data2.Age.values.reshape(-1,1)
      bins = Binarizer(threshold=30) # 阈值=30
      res = bins.fit_transform(x)
    2. preprocessing.KBinsDiscretizer: 将连续性变量排序后分箱
      参数:

      - n_bins: 每个特征中分箱的个数。默认5。
      - encode: 编码方式
          - onehot: 哑变量,返回一个稀疏数组
          - ordinal: 每个特征中每个箱都被编码为一个整数
          - onehot-dense: 哑变量,返回密集数组
      - strategy: 定义箱框的方式
          - uniform: 等宽分箱
          - quantile: 等位分箱,每个箱内样本数量相同(默认)
          - kmeans: 按聚类分箱
      
      1
      2
      3
      4
      5
      6
      7
      from sklearn.preprocessing import KBinsDiscretizer
      X = data.Age.values.reshape(-1,1)
      est = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
      est.fit_transform(X)
      #查看转换后分的箱:变成了一列中的三箱 set(est.fit_transform(X).ravel())
      est = KBinsDiscretizer(n_bins=3, encode='onehot', strategy='uniform') #查看转换后分的箱:变成了哑变量
      est.fit_transform(X).toarray()

随机森林

Posted on 2019-05-24 | In 机器学习
  1. 随机森林是一种集成算法,集成算法的目标:考虑多个评估器的建模结果,汇总之后得到一个综合的结果,以此来获取比单个模型更好的回归或
    分类表现。
  2. 多个模型集成成为的模型叫做集成评估器(ensemble estimator),组成集成评估器的每个模型都叫做基评估器 (base estimator)。通常来说,有三类集成算法:装袋法(Bagging),提升法(Boosting)和stacking。
  3. 随机森林是一种装袋法模型,装袋法的核心是构建多个独立的评估器,然后对其预测进行平均或多数表决原则决定集成评估器的效果。而提升法中,基评估器是相关的,是按顺序一一构建的。其核心思想是结合弱评估器的力量一次次对难以评估的样本 进行预测,从而构成一个强评估器。提升法的代表模型有Adaboost和梯度提升树。

  4. 随机森林的特点:

    • 在与其它现有的算法相比,其预测准确率很好
    • 在较大的数据集上计算速度依然很快
    • 不需要降维,算法本身是采取随机降维的
    • 他能处理有缺失值的数据集。算法内部有补缺失值的函数
    • 能给出变量的重要性
    • 能处理imbalanced data set
    • 能给出观测实例间的相似度矩阵,其实就是proximity啦,继而能做clustering 和 location outlier
    • 能对unlabeled data 进行无监督的学习,进行clustering
    • 生成的森林可以保留,应用在新的数据集上
  5. 控制评估器的参数

    • criterion: 不纯度衡量指标
    • max_depth: 树最大深度
    • min_samples_leaf:一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本
    • min_samples_split: 一个分支至少含有min_samples_split个训练样本
    • max_features: 限制分支时考虑特征个数
    • min_impurity_decrease: 限制信息增益大小

重要参数

- n_estimators: 这是森林中树木的数量,即基基评估器的数量。这个参数对随机森林模型的精确性影响是单调的,n_estimators 越大,模型的效往往越好。但是相应的,任何模型都有决策边界,n_estimators达到一定的程度之后,随机森林 的精确性往往不在上升或开始波动。

重要属性

- .estimators_是用来查看随机森林中所有树的列表的。
- oob_score_指的是袋外得分。随机森林为了确保林中的每棵树都不尽相同,所以采用了对训练集进行有放回抽样的 方式来不断组成信的训练集,在这个程中,会有一些数据从来没有被随机挑选到,他们就被叫做“袋外数据”。
- .feature_importances_和决策树中的.feature_importances_用法和含义都一致,是返回特征的重要性。
- predict_proba(): 返回每个测试样本对应的被分到每一类标签的概率
  1. 在sklearn中的使用

    1
    2
    from sklearn.ensemble import RandomForestClassifier
    ...
  2. 泛化误差
    image.png
    当模型太复杂,模型就会过拟合,泛化能力就不够,所以泛化误差大。当模型太简单,模型就会欠拟合,拟合能力就不够,所以误差也会大。只有当模型的复杂度刚刚好的才能够达到泛化误差最小的目标。
    树模型是天生位于图的右上角的模型,随机森林是以树模型为基础,所以随机森林也是天生复杂度高的模型。随机森林的参数,都是向着一个目标去:减少模型的复杂度,把模型往图像的左边移动,防止过拟合。

  3. 参数对误差的影响

参数 影响
n_estimators 提升至平稳,n_estimators↑,不影响单个模型的复杂度
max_depth 有增有减,默认最大深度,即最高复杂度
min_samples_leaf 有增有减,默认最小限制1,即最高复杂度
min_samples_split 有增有减,默认最小限制2,即最高复杂度
max_features 有增有减,默认auto,是特征总数的开平方,位于中间复杂度,max_features↓,模型更简单
criterion 有增有减,一般使用gini
  1. 调参

    • 学习曲线

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      from sklearn.model_selection import cross_val_score
      import matplotlib.pyplot as plt
      scorel = []
      for i in range(0,200,10):
      rfc = RandomForestClassifier(n_estimators=i+1,
      n_jobs=-1,
      random_state=90)
      score = cross_val_score(rfc,data.data,data.target,cv=10).mean()
      scorel.append(score)
      print(max(scorel),(scorel.index(max(scorel))*10)+1)
      plt.figure(figsize=[20,5])
      plt.plot(range(1,201,10),scorel)
      plt.show()
    • 网格搜索

      1
      2
      3
      4
      5
      6
      7
      8
      9
      from sklearn.model_selection import GridSearchCV
      param_grid = {'max_depth':np.arange(1,20,1)}
      rfc = RandomForestClassifier(n_estimators=39
      ,random_state=90
      )
      GS = GridSearchCV(rfc,param_grid,cv=10)
      GS.fit(data.data,data.target)
      GS.best_params_
      GS.best_score_

参考:
菜菜的机器学习sklearn课堂
随机森林算法-Deep Dive

决策树

Posted on 2019-05-23 | In 机器学习
  1. 决策树算法的核心是要解决两个问题:
    • 如何从数据表中找出最佳节点和最佳分枝?
    • 如何让决策树停止生长,防止过拟合?
  2. 流程:
    • 计算全部特征的不纯度
    • 选取不纯度指标最优的特征来分化
    • 在第一个特征分支下,计算全部特征不纯度
    • 选取不纯度指标最优的特征继续分化
  3. sklearn中使用

    1
    2
    3
    4
    5
    6
    from sklearn import tree
    tree.DecisionTreeClassifier:分类树
    tree.DecisionTreeRegressor:回归树
    tree.export_graphviz:将生成的决策树导出为DOT格式,画图专用
    tree.ExtraTreeClassifier:高随机版本的分类树
    tree.ExtraTreeRegressor:高随机版本的回归树
  4. 重要参数

    • criterion: 不纯度的计算方法(不纯度越低、拟合效果越好)
      • entropy 信息熵
      • gini 基尼系数(默认)
    • random_state: 随机模式,高纬度下随机性更明显
      • None (默认)
      • 任意整数
    • splitter: 随机选项
      • best: 优先选择更重要的特征进行分支
      • random: 分支随机(防止过拟合)
  5. 剪枝参数
    在不加限制的情况下,一棵决策树会生长到衡量不纯度的指标最优,或者没有更多的特征可用为止,这样的决策树往往会过拟合。
    • max_depth: 限制最大深度
    • min_samples_leaf:一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本
    • min_samples_split: 一个分支至少含有min_samples_split个训练样本
    • max_features: 限制分枝时考虑的特征个数
    • min_impurity_decrease: 限制信息增益的大小
  6. 目标权重参数
    • class_weight:均衡标签样本的权重
    • min_ weight_fraction_leaf:基于权重的剪枝参数
  7. 确定最优参数:
    • 学习曲线

参考
菜菜的机器学习sklearn课堂

机器学习链接

Posted on 2019-05-23 | In 机器学习

持续更新中….

  1. courea上吴恩达的机器学习课程,笔记
  2. 《利用python进行数据分析》,包含pandas、numpy、matplotlib的使用
  3. sklearn 的使用:
    • 通用机器学习 Scikit-learn
    • 菜菜的sklearn课堂

tensorflow Web服务部署的坑

Posted on 2019-04-30

问题场景:

由于需要部署一个基于tensorflow的算法Web服务,使用了uwsgi+flask去部署。
uwsgi 使用了多进程,参数为:

1
2
3
4
5
6
7
8
[uwsgi]
socket = 0.0.0.0:5051
chdir = /vagrant/images_check
wsgi-file = /vagrant/images_check/uwsgi_entry.py
callable = app
processes = 4
;harakiri = 30
log-format = %(addr) - %(user) [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size) "%(referer)" "%(uagent)" %(msecs)ms

而算法服务初始化,在入口出使用了queue去初始化,本来的目的是使多个进程都去同一个queue里获取算法服务的实例

1
2
3
4
q = Queue() 
for i in range(multiprocessing.cpu_count()):
bqc = CheckImage()
q.put(bqc)

然而发现,运行之后,只有一个进程是能正确运行的,其他进程会阻塞在tf.session.run无法返回

问题跟踪

  1. 因为在uwsgi中,工作进程是fork()主进程,获得了一个主进程“拷贝”的内存,所以每个进程都会有一个queue。
  2. tensorflow不是进程安全的,所以上面“拷贝”内存的操作,可能倒是tensorflow hang了。
    在tensorflow issue有类似的提问,Session got stuck after fork

    1
    The in-process session (i.e. tf.Session() with no arguments) is not designed to be fork()-safe. If you want to share a set of devices between multiple processes, create a tf.train.Server in one process, and create sessions that connect to that server (with tf.Session("grpc://...")) in the other processes.
  3. 这篇文章中提到机器学习web服务化实战:一次吐血的服务化之路,的方法,在gunicorn的配置中实例化算法服务,gunicorn可以保证配置文件中的代码只运行一次。同时利用gc.freeze()把截止到当前的所有对象放入持久化区域,不进行回收,从而model占用的内存不会被copy-on-write。但是按照这个方法发现这个实例依然在fork的时候被copy了。

问题解决

  1. 方案一:改成了在每个进程初始化之后,初始化一个算法服务实例,根据cpu核数配置uwsgi工作进程数量

    1
    2
    3
    4
    5
    6
    @app.before_first_request
    def first_quest():
    q = Queue()
    for i in range(1):
    bqc = CheckImage()
    q.put(bqc)
  2. 方案二;
    使用redis/mc等中间件。但是有可能有序列化失败的问题。

  3. 使用tensorflow server

最后

在gunicron中,配置preload_app = True是可以预加载资源的,但是fork工作进程还是可能出现坑。

参考:
Session got stuck after fork
How to serve tensorflow model using flask+uwsgi?
机器学习web服务化实战:一次吐血的服务化之路
fork-safe和thread-safe简介
【已解决】Flask的gunicorn中多进程多worker如何共享数据或单实例

使用多进程服务器gunicorn中多线程问题

Posted on 2019-04-28 | In 后端

问题描述:

场景:
gunicorn + flask
gunicorn.conf:

1
2
worker = 1  # 1个工作进程
worker_class = "geventwebsocket.gunicorn.workers.GeventWebSocketWorker" # 因为使用了websocket

在flask 入口处新增了一个子线程做redis的监听工作

1
2
3
4
5
6
7
8
import threading
def listen_redis():
while True:
print(threading.currentThread())
.....
print('get redis')
t = threading.Thread(target=listen_redis)
t.start()

问题1: 在使用gunicron运行的时候,发现子进程也运行了listen_redis的循环。
问题2: 同时由两个线程进行while true的操作,发现有时候一个while true断了再也不工作了。

问题跟踪:

在查看gunicron的源码可以发现:
/venv/lib/python3.x/site-packages/gunicorn/arbiter.py

1
2
3
4
5
6
7
8
9
10
11
12
# line: 575
def spawn_worker(self):
self.worker_age += 1
worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS,
self.app, self.timeout / 2.0,
self.cfg, self.log)
self.cfg.pre_fork(self, worker)
pid = os.fork() #是在fork之后出现的“多余的子线程”
if pid != 0:
worker.pid = pid
self.WORKERS[pid] = worker
return pid

通过print(threading.currentThread())可以看到log:
主进程的循环打印出来:

1
<Thread(Thread-2, started 140048726495560)>

而子进程的循环打印出来的是:

1
<_DummyThread(DummyThread-4, started daemon 140048726495560)>

可以确定的是,多了一个不是由threading创建的“虚拟线程”

然后查看gunicron对线程做了什么的时候发现这个问题
虽然不是同一个问题,但是这个说到了 monkey_patch对threading做了补丁
然后发现在 __init__.py 使用了猴子补丁,是为了websocket,使用了multiple workers时需要共享client connect,详情可以看flask-socketio文档Using Multiple Workers这一章。

1
If eventlet or gevent are used, then monkey patching the Python standard library is normally required to force the message queue package to use coroutine friendly functions and classes.

然后把猴子补丁改成:

1
2
from gevent import monkey
monkey.patch_all(thread=False)

之后,发现“虚拟线程”没有被创建了。
但是偶尔会有下面报错,不影响正常功能,暂时不知道原因:

1
2
Exception ignored in: <module 'gevent.threading' from '/demo/venv/lib/python3.5/site-packages/gevent/threading.py'>
AttributeError: module 'gevent.threading' has no attribute '_after_fork'

解析

  1. 首先明确的是,os.fork()创建出来对子进程并不会继承父进程的子线程。在fork(2)-Linux Man Page,中的描述:
    1
    The child process is created with a single thread--the one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.

也就是说,在Linux中,fork的时候只复制当前线程到子进程。

  1. 那么,monkey_patch究竟做了什么?
    monkeypatch修改了threading标准库中的_start_new_thread方法, Condition类等,创建了一个greenlet而不是真正的线程,然后就会在fork的时候被复制了。因此,也可以在gunicron的配置文件中,在on_starting的hook中创建真正的线程。
    gevent是第三方库,通过greenlet实现协程,其基本思想是:当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
    事实上,gunicron在使用gevent的时候,已经monkey patch了一次,如果patch多次,将会求多次中参数为True的并集。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # venv/lib/python3.5/site-packages/gunicorn/workers/ggevent.py:65
    def patch(self):
    from gevent import monkey
    monkey.noisy = False

    # if the new version is used make sure to patch subprocess
    if gevent.version_info[0] == 0:
    monkey.patch_all() # 默认值socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True, httplib=False, subprocess=True, sys=False, aggressive=True, Event=False, builtins=True, signal=True
    else:
    monkey.patch_all(subprocess=True)
  2. 问题2: 从gevent的文档,可以知道,同一时间,只能有一个greenlet在运行,所以如果一个greenlet阻塞了,另一个greentlet就不可能运行,可以通过在while true末尾添加gevent.sleep(0.1),把控制权(有可能)交给另外一个greenlet。

最终方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方案一:阻塞事件使用线程
from gevent import monkey
monkey.patch_all(thread=False)

from threading
ta = threading.Thread(target=task)
ta.start

while true:
....
# gevent.sleep(0.1)

# 方案二:阻塞事件使用进程
from gevent import monkey
monkey.patch_all()

from multiprocess import Process
p = Process(target=task)
p.start()

while true:
...

参考:
谨慎使用多线程中的fork
eventlet 之 monkeypatch 带来的若干兼容性问题实例分析
monkey.patch_all and gunicorn with more than 1 worker
源码分析之gevent monkey.patch_all实现原理
python异步 I/O模块gevent
gevent-廖雪峰
gunicorn源码解析
深入理解uwsgi和gunicorn网络模型[上]

Redis的性能

Posted on 2019-04-25 | In 缓存

Redis的基本数据结构

首先,众所周知,Redis是单线程的。

有多快?

Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。
image.png

为什么快?

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
  2. 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  4. 使用多路I/O复用模型,非阻塞IO;
  5. 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

Redis的内存模型

Redis的内存主要包括:对象内存+缓冲内存+自身内存+内存碎片。

  1. 对象内存
    对象内存是Redis内存中占用最大一块,存储着所有的用户的数据。Redis所有的数据都采用的是key-value型数据类型
  2. 缓冲内存
    主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区

    • 客户端缓冲:普通的客户端的连接(大量连接),从客户端(主要是复制的时候,异地跨机房,或者主节点下有多个从节点),订阅客户端(发布订阅功能,生产大于消费就会造成积压)
    • 复制积压缓冲:2.8版本之后提供的可重用的固定大小缓冲区用于实现部分复制功能,默认1MB,主要是在主从同步时用到。
    • AOF缓冲区:持久化用的,会先写入到缓冲区,然后根据响应的策略向磁盘进行同步,消耗的内存取决于写入的命令量和重写时间,通常很小。
  3. 内存碎片
    目前可选的分配器有jemalloc、glibc、tcmalloc默认jemalloc
    出现高内存碎片问题的情况:大量的更新操作,比如append、setrange;大量的过期键删除,释放的空间无法得到有效利用
    解决办法:数据对齐,安全重启(高可用/主从切换)。

  4. 自身内存
    主要指AOF/RDB重写时Redis创建的子进程内存的消耗,Linux具有写时复制技术(copy-on-write),父子进程会共享相同的物理内存页,当父进程写请求时会对需要修改的页复制出一份副本来完成写操作。

其他

  • 单进程多线程模型:MySQL、Memcached、Oracle
  • 多进程模型:Oracle

参考:
Redis是单线程的,但Redis为什么这么快?
理解Redis的内存

Nginx 工作原理

Posted on 2019-04-24 | In 服务器

Nginx 模块结构

image.png

Nginx 工作流程

Nginx的IO通常使用epoll,epoll函数使用了I/O复用模型。

  1. master进程一开始根据配置,来建立需要listen的网络socket fd,然后fork出多个worker进程。根据进程的特性,新建立的worker进程,也会和master进程一样,具有相同的设置。因此,其也会去监听相同ip端口的套接字socket fd。
  2. 当一个请求进来的时候,所有的worker都会感知到。这样就会产生所谓的“惊群现象”。为了保证只会有一个进程成功注册到listenfd的读事件,nginx中实现了一个“accept_mutex”类似互斥锁,只有获取到这个锁的进程,才可以去注册读事件。其他进程全部accept 失败。
  3. 监听成功的worker进程,读取请求,解析处理,响应数据返回给客户端,断开连接。即nginx用一个独立的worker进程来处理一个请求,一个worker进程可以处理多个请求。
    因此,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。采用这种方式的好处:
    • 节省锁带来的开销。对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查上时,也会方便很多。
    • 独立进程,减少风险。
    • 采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快重新启动新的worker进程。
    • 在一次请求里无需进程切换。

Nginx的配置

  • worker_processes:worker角色的进程个数
  • worker_connections:每一个worker进程能并发处理(发起)的最大连接数(包含所有连接数)
  • Nginx作为http服务器的时候:max_clients(最大连接数) = worker_processes * worker_connections
  • Nginx作为反向代理服务器的时候:max_clients(最大连接数) = worker_processes * worker_connections/4 (/4原因:因为浏览器默认会开启2个连接到nginx server,而且nginx还会为每个连接使用fds(file descriptor)从连接池建立connection到upstream后端。)

参考:
nginx快速入门之基本原理篇
理解Nginx工作原理

I/O操作模式

Posted on 2019-04-24 | In 底层

概念

  • 流:一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象
  • I/O操作:流的操作(读/写)。缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
  • 文件描述符fd:是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。

IO模式

一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:

  1. 等待数据准备 (Waiting for the data to be ready)
  2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。

  • 阻塞 I/O(blocking IO)
  • 非阻塞 I/O(nonblocking IO)
  • I/O 多路复用( IO multiplexing)
  • 信号驱动 I/O( signal driven IO)
  • 异步 I/O(asynchronous IO)

image.png

I/O多路复用

select,poll,epoll都是IO多路复用的机制。一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O。

与阻塞I/O相比,多路复用需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。

epoll 操作

1
2
3
int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//对指定描述符fd执行op操作
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);//等待epfd上的io事件,最多返回maxevents个事件。

在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。(此处去掉了遍历文件描述符,而是通过监听回调的的机制。)

优点:

  1. 监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目。
  2. IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。

参考:
我读过的最好的epoll讲解–转自”知乎“
Linux IO模式及 select、poll、epoll详解
文件描述符和流的关系?

服务性能分析

Posted on 2019-04-22

概念

  1. 并发数: 也叫并发连接数,指网络设备所能处理的最大会话数量。这里的会话数是指请求->响应一次会话。
  2. 吞吐量:用户请求是由一个个数据包组成,网络设备(防火墙/路由器/交换机)对每个数据包的处理要耗费资源。吞吐量是指在不丢包的情况下单位时间内通过网络设备的数据包数量。
  3. 网络层面并发数和吞吐量的关系:
    并发数x包长度=吞吐量
  4. TPS:Transactions Per Second(每秒事务处理数),指服务器每秒处理的事务次数。一般用于评估数据库、交易系统的基准性能。
  5. QPS:Queries Per Second(查询量/秒),是服务器每秒能够处理的查询次数,例如域名服务器、Mysql查询性能。
  6. QPS(TPS)= 并发数/平均响应时间

并发数高,吞吐量不一定高。
如果谈的是网络设备,参照:并发数x包长度=吞吐量,吞吐量依赖于并发数和包长度。
如果谈的是服务器及完整整体性能,需要明确吞吐量的度量指标,假定以吞吐量以QPS作为度量指标,如果并发数高,但平均响应也很高的话,则QPS可能降低。

优化

请求响应时间=请求发送耗时+请求解析耗时+请求处理耗时+处理结果返回耗时
从性能优化角度出发,就需要我们尽力保持和降低系统的99%RT(即一段时间内请求响应时间从低到高排序,低于99%响应时间的上限边界值(比如容忍值是3s))的同时,提高单位时间内的处理能力。

step 1:
测试指标

指标名称 指标数值 指标说明
TPS 100 每秒事务数,很重要的一个指标,衡量系统的处理能力
RT 95%、99%、99.99% 百分比请求的响应时间,即n%以内的RT请求响应时间是多少,百分比越高,RT越低,系统越稳定
error 0.1%、0.01% 错误率,即可接受的请求失败的占比
Cache 90%、95% 缓存命中率:命中率越高,使用缓存的收益越高,系统的性能越好
CPU 75%、90% CPU使用率,一般来说75%是一个阈值,超过85%就需要重点关注

step 2:
《jmeter测试指南》

step 3:
分析
常见的性能测试缺陷

参考
衡量网站性能时,并发数与吞吐量为何要分别考量?
性能测试基础-常见性能指标详解
*性能测试
JMeter压测上对于并发的认识误区
什么是QPS,TPS,吞吐量

1…456…10
Angel Teng

Angel Teng

97 posts
14 categories
37 tags
© 2021 Angel Teng
Powered by Hexo
|
Theme — NexT.Muse v5.1.4