月度归档: 2021 年 5 月

Python中 and 和 or 运算短路逻辑

Python中 and 和 or 运算短路逻辑

短路逻辑规则如下:

表达式从左至右运算,若 or 的左侧逻辑值为 True ,则短路 or 后所有的表达式(不管是 and 还是 or),直接输出 or 左侧表达式 。若 or 的左侧逻辑值为 False ,则输出or右侧的表达式,不论其后表达式是真是假,整个表达式结果即为其后表达式的结果。

 

表达式从左至右运算,若 and 的左侧逻辑值为 False ,则短路其后所有 and 表达式,直到有 or 出现,输出 and 左侧表达式到 or 的左侧,参与接下来的逻辑运算。若 and 的左侧逻辑值为 True,则输出其后的表达式,不论其后表达式是真是假,整个表达式结果即为其后表达式的结果

若 or 的左侧为 False ,或者 and 的左侧为 True 则不能使用短路逻辑。

注意:

1、在Python中and的优先级是大于or的,而且and和or都是会返回值的并且不转换为True和False。当not和and及or在一起运算时,优先级为是not>and>or

2、在Python中,None、任何数值类型中的0、空字符串“”、空元组()、空列表[]、空字典{}都被当作False,还有自定义类型,如果实现了  __ nonzero __ () 或 __ len __ () 方法且方法返回 0 或False,则其实例也被当作False,其他对象均为True。

下面是*简单的逻辑运算:

True  and True    ==> True                                     True  or True    ==> True
True  and False   ==> False                                   True  or False   ==> True
False and True    ==> False                                   False or True    ==> True
False and False   ==> False                                   False or False   ==> False
接下来我们再举一个具体的例子:

先来定义一组函数:

1>  def a():

2>     print (‘A’)

3>     return 1

4>  def b():

5>     print (‘B’)

6>     return 1

7>  def c():

8>     print (‘C’)

9>     return []

10> def d():

11>    print (‘D’)

12>    return []

13> def e():

14>    print (‘E’)

15>    return 1
例1:

17> if a() and b() and c() and d() and e():

18>    print (‘ok’)

#显示结果如下

A
a() 为假 ,其后均为 and 语句,全部短路,*终只返回 a() 的表达式。记住,所有被短路的表达式均不会被输出。所以,此处仅仅打印 A

 

例2:

17> if a() and b() and c() and d() and e():

18>    print (‘ok’)

#显示结果如下

A

B

C
python 从左至右先执行 a() ,a() 返回的逻辑值为 True,后面是 and 语句,所以不能短路其后,继续与 b() 进行逻辑运算,a() and b() 输出 b() 的逻辑值 True,接着与 c() 进行逻辑运算,b() and c() 输出 c() 的逻辑值 False,而其后均为 and 语句, 则全部短路,*终只打印了 A B C 。

 

例3:

17> if a() or b() or c() or d() or e():

18>    print (‘ok’)

#显示结果如下

A

Ok

#显示结果如下

A

ok
a() 的逻辑值为 True ,其后均为 or 语句,全部短路,*终只打印了 A,而 if 语句为 True ,所以还要打印一个 ok。

 

例4:

17> if a() or b() or c() or d() or e():

18>    print (‘ok’)

#显示结果如下

A

B

C

Ok
python 从左至右先执行 a() ,a() 返回的逻辑值为 False,后面是 or 语句,所以不能短路其后,继续与 b() 进行逻辑运算,a() or b() 输出 b() 的逻辑值 False,接着与 c() 进行逻辑运算,b() or c() 输出 c() 的逻辑值 True,而其后为 or 语句, 则全部短路,*终只打印了 A B C ok。

 

例5:

26> if a() and b() and  c() and d() or e() and f() or g() and h():

27>    print (‘ok’)

#输出结果如下:

A

E

F

Ok
别以为语句很长就很难,我们好好分析一下,从左至右,首先a() 的逻辑值为 False,其后到 or 语句为止有三个 and 语句: a() and b() and c() and d(),均被短路。只输出 a(), 得到 a() or e() 为True,输出 e() ,得 e() and F() 为 True ,输出 f(), 其后接 or 语句,则短路其后所有。*终只打印了A E F ok 。

 

Nginx 的请求处理流程你了解多少

Nginx 的请求处理流程你了解多少

之前我们已经讲解了 Nginx 的基础内容,接下来我们开始介绍 Nginx 的架构基础。

为什么我们要讨论 Nginx 的架构基础?

因为 Nginx 运行在企业内网的*外层也就是边缘节点,那么他处理的的流量是其他应用服务器处理流量的数倍,甚至几个数量级,我们知道任何一种问题在不同的数量级下,他的解决方案是完全不同的,所以在 Nginx 它所处理的应用场景中,所有的问题都会被放大,所以我们必须要去理解,为什么 Nginx 采用 master-worker 这样的一种架构模型,为什么 worker 进程的数量要和 CPU 的核数相匹配?当我们需要在多个 worker 进程之间共享数据的时候,为什么在 TLS 或者说限流、限速这样的场景,他们的共享方式是有所不同的,那么这些都需要我们对 Nginx 的架构有一个清晰的了解。

下面我们先来看一下 Nginx 的请求处理流程。

为什么要去看 Nginx 中的请求处理流程呢?因为其实在之前中我们了解到 Nginx 会记录 access 日志和 error 日志,也可以处理静态的资源,那么也可以做反向代理,那么这些东西我们从 Nginx 内部去看他究竟是怎样处理这些请求,它包含一些什么样的组成部分呢?

Nginx 的请求处理流程

 

我们从这张图的*左边来看,*左边在 WEB、EMAIL 和 TCP,也就是说大致有三种流量从这里进入 Nginx 以后,我们 Nginx 中有三个大的状态机,一个是处理 TCP/UDP 的 4 层的传输层状态机和处理应用层的 HTTP 状态以及处理邮件的 MAIL 状态机。

那么为什么我们叫它状态机呢?是因为 Nginx 核心的这个大绿色的框他是用非阻塞的事件驱动处理引擎就是用我们所熟知的 epoll,那么一旦我们使用这种异步处理引擎以后,通常都是需要用状态机来把这个请求正确的识别和处理。

基于这样的一种事件状态处理机,我们在解析出请求需要访问静态资源的时候,我们看到走左下方的这个箭头,那么它就找到了静态资源,如果我们去做反向代理的时候呢,那么对反向代理的内容,我可以做磁盘缓存,缓存到磁盘上,也在下面左下方这条线,但是我们在处理静态资源的时候,会有一个问题就是当整个内存已经不足以完全的缓存所有的文件和信息的时候,那么像 send File 这样的调用或者 AIO 会退化成阻塞的磁盘调用,所以在这里我们需要有一个线程池来处理,对于每一个处理完成的请求呢,我们会进入 access 日志或 error 日志。

那么这里也是进入了磁盘中的,当然我们可以通过 syslog 协议把它进入到远程的机器上,那么更多的时候我们的 Nginx 是作为负载均衡或者反向代理来使用的,就是我们可以把请求通过协议级(HTTP,Mail 及 stream(TCP))传输到后面的服务器,也可以通过例如应用层的一些协议(FastCGI、uWSGI、SCGI、memcached)代理到相应的应用服务器。以上就是 Nginx 的请求处理流程。

Python教程:Numpy的安装

Python教程:Numpy的安装

 

一、numpy简介
NumPy是一个功能强大的Python库,主要用于对多维数组执行计算。NumPy这个词来源于两个单词– Numerical和Python。NumPy提供了大量的库函数和操作,可以帮助程序员轻松地进行数值计算。这类数值计算广泛用于以下任务:

机器学习模型:在编写机器学习算法时,需要对矩阵进行各种数值计算。例如矩阵乘法、换位、加法等。NumPy提供了一个非常好的库,用于简单(在编写代码方面)和快速(在速度方面)计算。NumPy数组用于存储训练数据和机器学习模型的参数。
图像处理和计算机图形学:计算机中的图像表示为多维数字数组。NumPy成为同样情况下*自然的选择。实际上,NumPy提供了一些优秀的库函数来快速处理图像。例如,镜像图像、按特定角度旋转图像等。
数学任务:NumPy对于执行各种数学任务非常有用,如数值积分、微分、内插、外推等。因此,当涉及到数学任务时,它形成了一种基于Python的MATLAB的快速替代。
二、numpy安装
numpy要求python版本在3.5以上

1.windows下的安装

pip安装
python -m pip install numpy scipy matplotlib ipython jupyter pandas sympy nose -i https://pypi.douban.com/simple/
#建议使用用户安装,将–user标志发送给pip。 pip为本地用户安装软件包,并且不写入系统目录。
手动安装
到https://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy下载和系统python版本匹配的whl包,注意区分32位和64位。

#numpy‑1.18.4 + mkl‑cp37‑cp37m‑win_amd64.whl
pip install numpy‑1.18.4 + mkl‑cp37‑cp37m‑win_amd64.whl
2.ubuntu下安装

sudo apt-get install python-numpy python-scipy python-matplotlib ipython ipython-notebook python-pandas python-sympy python-nose
3.centos下安装

sudo dnf install numpy scipy python-matplotlib ipython python-pandas sympy python-nose atlas-devel
手动安装
# 安装依赖
[root@localhostmyhaspl]#yum install gcc
[root@localhostmyhaspl]#yum install python-devel

[root@localhostmyhaspl]# wget http://jaist.dl.sourceforge.net/project/numpy/NumPy/1.11.2/numpy-1.11.2.tar.gz
[root@localhost myhaspl]# tar -xzvf numpy-1.11.2.tar.gz
[root@localhost myhaspl]# cd numpy-1.11.2
[root@localhost numpy-1.11.2]# python setup.py install
4.mac下安装

python -m pip install numpy scipy matplotlib
手动安装
首先到https://sourceforge.net/projects/numpy/files/下载安装包;然后解压。到安装目录下执行:

sudo python setup.py install

国内中高级的Android技术人才仍然稀缺?

前言

看到一篇文章中提到“*近几年国内的初级Android程序员已经很多了,但是中高级的Android技术人才仍然稀缺“,这的确不假,从我在百度所进行的一些面试来看,找一个适合的高级Android工程师的确不容易,一般需要进行大量的面试才能挑选出一个比较满意的。为什么中高级Android程序员不多呢?这是一个问题,我不好回答,但是我想写一篇文章来描述下Android的学习路线,期望可以帮助更多的Android程序员提升自己。由于我也是从一个菜鸟过来的,所以我会结合我的个人经历以及我对Android学习过程的认识来写这篇文章,这会让这篇文章更加真实,而并非纸上谈兵。

我的工作经历

我也是从一个Android菜鸟过来的。其实这句话放在任何人的身上都是适用的,即大家都是一步步过来的,因此作为初学者也不必因为技术差而郁闷,同理,高手也不要看不起一些所谓的菜鸟,因为这不公平,技术只有在同等的工作年限上才具有一定的可比性,也许你眼中的菜鸟只是个工作半年的新手,而你已经工作5年,可想而知,这根本不具有可比性,搞不好别人5年后可以达到比你更高的技术高度。
我是硕士研究生毕业,我已经工作3年零3个月了,职位上从*开始的腾讯初级工程师变成了现在的百度Android资深工程师。*开始我并不是做Android的,先是做了半年的C++,接着做了3个月的Web前端,然后公司内部转岗做Android到至今,纯Android工作年限的话其实是2.5年。但是我认为我的Android工作经验(注:工作年限不等同于工作经验)不止2.5年,我投入大量的业余时间在Android开发上,并且我习惯去思考问题、总结问题并将其融会贯通,因此我的Android技术在短时间内有了很大的提升。
在Android学习过程中,初学者踩过的坑我也都踩过,我也深深地知道大家在学习过程中*棘手的问题是什么。举个例子,在3年前,我想在SlidingMenu中放入一个ListView,发现二者总是不能很好地一起工作,这肯定是由于滑动冲突的问题,我也知道,但是不知道怎么解决。我就去学校图书馆翻遍了所有的Android书籍,无果。大家肯定都知道原因,为什么我无法从书中查找到问题的答案,因为入门书不讲滑动冲突,所谓的高级编程书也不讲。还有一个问题,我曾经以为view.post(runnable)可以让runnable的run方法在线程中执行,所以我就在run方法里面去做耗时的操作,现在想想我当时是多菜啊,因此我曾经也是菜鸟。
直到若干年后的某一天,我终于琢磨透了滑动冲突的事,然后我就把解决问题的思想写在了CSDN博客上,但是好像看得人并不多,很多人并不能体会我的用心,后来我博客的访问量越来越大,这才慢慢地得到了一些人的关注。后来有一天我有了写书的契机,我想到了我*开始学习Android时所踩过的坑,想到滑动冲突对我的困扰,为了更好地传播我的技术经验,为了让更多的人少踩一些坑,为了让更多地人成为Android高级工程师,我毅然决定将Android开发中*重要的、*疑难的、*容易困扰大家的、成为高级工程师所必备的知识点和盘托出,这就是《Android开发艺术探索》存在的原因以及意义。书的反响怎么样呢?从目前读者的评价来看,内容基本无差评,我收到了很多读者的肯定以及感谢,这说明很多人能够理解我的用心。
说了那么多,言归正传,下面说下Android学习路线的话题,本文打算从4个阶段来对Android的学习过程做一个全面的分析,分别为Android初级、中级、高级以及资深工程师,具体请看下面的分析。同理,本篇学习路线仍然只针对Android应用开发,不针对Rom开发和逆向工程等。这里虚拟一位“小明”的角色,在这里小明就是Android初学者的代表。
初级工程师

小明之前完全没接触过Android开发,是个应届生,他的待遇是13k,然后小明以校招的身份进入了百度,然后小明需要怎么学习才能成为初级工程师呢?这个时候,小明对编程基础、数据结构、C语言都有一定基础,Java语法什么的也都掌握的比较好,Android才有java语言,无奈的是小明并不会搞Android。
小明首先需要购买一本Android入门的书籍,为了更快地学习Android,小明业余时间也都用来一边看书一边照着书中的例子敲代码,结果2周时间小明就把这本书学了一遍。看完这本书后,小明对Android的历史、结构、代码规范等都有了一个大概的了解,并且,小明已经可以写出一些简单的Activity了。这个时候在小明眼里,Android开发很简单很好玩,通过在xml中摆放一些按钮文本框什么的就可以做一些界面了。
小明开始跟着他的技术导师做需求,一些简单的小需求小明自然是不在话下了。突然有一天来了一个需求,该需求要求小明在Activity中为一个button加一个动画效果,小明慌了:“完全没接触过,书上也没有讲,怎么办呢?”小明冷静了下,打开了百度搜索,输入“Android 动画”,打开前几个链接,小明恍然大悟,照着网上的例子把需求给实现了。后来导师告诉他:“学好Android,官方文档是必须看的,既全面又权威”。然后小明如获至宝,花了一年时间把上面的guide和training都看了一遍,并且他还动手抄了几个小例子。
有一天,小明又需要做一个动画相关的需求,这可难不倒小明,它熟练地打开了www.baidu.com,输入“Android 动画”,突然他楞了一下:”总不能每次写动画都要百度一下吧!“,于是他在CSDN开了一个博客,把动画相关的知识点都写上去,为的是后面再写动画相关的代码就不用百度去搜了,事实如何呢?后面再写动画相关的代码,小明的确不用再去百度搜了,因为通过写一篇动画博客,他把动画相关的细节都已经记住了,这样他就可以不用再去参考任何文档了,后来小明还学会了把一些琐碎的不方便放在博客上的东西写到了印象笔记上面,什么时候忘了10秒钟以内都可以快速找回来,而不是花10分钟去再次搜索一遍。
这里总结一下,Android入门的时候,需要有一本入门书,好好学习书中的内容,同时花一年时间把Android官方文档中的training和guide看一遍,同时通过写博客和记笔记的方式来做总结,建议让自己的每篇博客都有价值些。通过一年时间的学习,相信每个人都可以达到中级工程师的水平。
技术要求:

  • 基本知识点
    比如四大组件如何使用、如何创建Service、如何进行布局、简单的自定义View、动画等常见技术
  • 书籍推荐
    《*行代码 Android》、《疯狂Android》
    中级工程师
    小明经过一年的努力学习终于成为Android中级工程师了,月薪变成了17k。到了中级工程师,已经可以在公司里干很多体力活了,但是一些很重要的任务小明还不能一个人承担起来,这个时候小明需要学习的内容就很多了,如下所示:
  • AIDL:熟悉AIDL,理解其工作原理,懂transact和onTransact的区别;
  • Binder:从Java层大概理解Binder的工作原理,懂Parcel对象的使用;
  • 多进程:熟练掌握多进程的运行机制,懂Messenger、Socket等;
  • 事件分发:弹性滑动、滑动冲突等;
  • 玩转View:View的绘制原理、各种自定义View;
  • 动画系列:熟悉View动画和属性动画的不同点,懂属性动画的工作原理;
  • 懂性能优化、熟悉mat等工具
  • 懂点常见的设计模式
    学习方法
    阅读进阶书籍,阅读Android源码,阅读官方文档并尝试自己写相关的技术文章,需要有一定技术深度和自我思考。在这个阶段的学习过程中,有2个点是比较困扰大家的,一个是阅读源码,另一个是自定义View以及滑动冲突。
    如何阅读源码呢?这是个头疼的问题,但是源码必须要读。阅读源码的时候不要深入代码细节不可自拔,要关注代码的流程并尽量挖掘出对应用层开发有用的结论。另外仔细阅读源码中对一个类或者方法的注释,在看不懂源码时,源码中的注释可以帮你更好地了解源码中的工作原理,这个过程虽然艰苦,但是别无他法。
    如何玩转自定义View呢?我的建议是不要通过学习自定义view而学习自定义view。为什么这么说呢?因为自定义view的种类太多了,各式各样的绚丽的自定义效果,如何学的玩呢!我们要透过现象看本质,更多地去关注自定义view所需的知识点,这里做如下总结:
  • 搞懂view的滑动原理
  • 搞懂如何实现弹性滑动
  • 搞懂view的滑动冲突
  • 搞懂view的measure、layout和draw
  • 然后再学习几个已有的自定义view的例子
  • *后就可以搞定自定义view了,所谓万变不离其宗
    大概再需要1-2年时间,即可达到高级工程师的技术水平。我个人认为通过《Android开发艺术探索》和《Android群英传》可以缩短这个过程为0.5-1年。注意,达到高级工程师的技术水平不代表就可以立刻成为高级工程师(受机遇、是否跳槽的影响),但是技术达到了,成为高级工程师只是很简单的事。
    技术要求:
  • 稍微深入的知识点
    AIDL、Messenger、Binder、多进程、动画、滑动冲突、自定义View、消息队列等
  • 书籍推荐
    《Android开发艺术探索》、《Android群英传》
    高级工程师
    小明成为了梦寐以求的高级工程师,月薪达到了20k,还拿到了一丢丢股票。这个时候小明的Android水平已经不错了,但是小明的目标是资深工程师,小明听说资深工程师月薪可以达到30k+。
    为了成为Android资深工程师,需要学习的东西就更多了,并且有些并不是那么具体了,如下所示:
  • 继续加深理解”稍微深入的知识点“中所定义的内容
  • 了解系统核心机制:
  1. 了解SystemServer的启动过程
  2. 了解主线程的消息循环模型
  3. 了解AMS和PMS的工作原理
  4. 能够回答问题”一个应用存在多少个Window?“
  5. 了解四大组件的大概工作流程
  • 基本知识点的细节
  1. Activity的启动模式以及异常情况下不同Activity的表现
  2. Service的onBind和onReBind的关联
  3. onServiceDisconnected(ComponentName className)和binderDied()的区别
  4. AsyncTask在不同版本上的表现细节
  5. 线程池的细节和参数配置
  6. 熟悉设计模式,有架构意识

web基础教程:随笔

web基础教程:随笔

一、用自己的语言描述get、post、Accept、Referer、User-Agent、host、cookie、X_Forwarded_for、Location各请求头的含义

1. GET

http请求方法,从浏览器获取一个资源
2. POST

提交数据、账号密码等,加密传输
3. Accept

支持的语言程序、接收的文件类型等等…
4. Referer

起过渡作用,从一个页面转到另一个页面
5. User-Agent

显示浏览器的指纹信息
6. host

主机
7. cookie

记录并保存你去过哪些地方,可以用于分析用户的喜好推荐广告
8. X_Forwarded_for

识别http代理、负载均衡方式连接到web服务器的客户端ip地址(可修改ip地址),
9. Location

用于重定向响应中的重定向目标
二、常用 http 支持的方法有那些,同时对Head、options、put、get、post用自己的语言进行描述

1. Head

检查服务器上的资源,判断页面服务是否存在
2. options

判断并显示浏览器所支持的方法

3. put

向服务器上传资源,开启这个服务容易被攻击
4. get

向浏览器获取数据
5. post

向浏览器提交数据,加密传输
三、cookie头里面的secure与HttpOnly项分别代表什么含义

1. secure

仅在https请求中提交cookie。
2. HttpOnly

不会被钓鱼网站盗走cookie,安全级别高,
四、写出安全渗透里面常用编码有那些

Unicode编码、HTML编码、Base64编码、十六进制编码
五、burp大概有那些功能模块

Proxy(代理):默认端口8080,开启代理可以截获并修改web应用的数据包
Spider(抓取):抓取web提交的数据资源
Scanner(扫描器):扫描web程序的漏洞
Intruder(入侵):漏洞利用,web程序模糊测试,暴力破解等
Repeater(中继器):重放模拟数据包的请求与响应的过程
Sequenecer:检查web程序会话令牌的随机性并执行各种测试
Decoder(解码);解码和编码
六、静态 动态语言区别

1. http

静态语言,不存在漏洞,访问速度快,服务端和客户端代码一致(如html)
2. php

动态语言,可连接数据库实时更新,服务端和客户端代码不一致(如: asp,php,aspx,jsp)
七、常见的脚本语言有那些

如PHP, VBScript和Perl ;
八、常见的数据库有那些

mysql 、SQL Server、Oracle、Sybase、DB2
九、常见的数据库与脚本语言搭配

asp+access
asp+mssql
php+mysql
aspx+mssql
aspx+oracle
jsp+oracle
jsp+mssql
十、系统、脚本语言、中间件如何组合

Windows2003/2008/2012+asp、aspx、php+iis6.0/7.0+7.5
Apache+Windows/Linux+PHP Windows/Linux+Tomcat+JSP
十一、渗透测试过程中如何查看对方操作系统是什么系统或版本

1、工具(RASS、天镜、NMAP、X-SCAN)

2、第三方平台(seo.chinaz.com)

3、通过ping观看TTL值

C:\Users\陈婷>ping 127.0.0.1

正在 Ping 127.0.0.1 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
4、网页文件大小写

windows不区分大小写
Linux区分大小写

Python中格式化字符串更酷的方式

Python中格式化字符串更酷的方式
在 Python 中,大家都习惯使用 %s 或 format 来格式化字符串,在 Python 3.6 中,有了一个新的选择 f-string。

使用对比
我们先来看下 Python 中已经存在的这几种格式化字符串的使用比较。

# %s
username = ‘tom’
action = ‘payment’
message = ‘User %s has logged in and did an action %s.’ % (username, action)
print(message)

# format
username = ‘tom’
action = ‘payment’
message = ‘User {} has logged in and did an action {}.’.format(username, action)
print(message)

# f-string
username = ‘tom’
action = ‘payment’
message = f’User {user} has logged in and did an action {action}.’
print(message)

f”{2 * 3}”
# 6

comedian = {‘name’: ‘Tom’, ‘age’: 20}
f”The comedian is {comedian[‘name’]}, aged {comedian[‘age’]}.”
# ‘The comedian is Tom, aged 20.’
相比于常见的字符串格式符 %s 或 format 方法,f-strings 直接在占位符中插入变量显得更加方便,也更好理解。

方便的转换器
f-string 是当前*佳的拼接字符串的形式,拥有更强大的功能,我们再来看一下 f-string 的结构。

f ‘ <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> … ‘
其中 ‘!s’ 调用表达式上的 str(),’!r’ 调用表达式上的 repr(),’!a’ 调用表达式上的 ascii()。大家可以看看下面的例子。

class Person:

def __init__(self, name, nickname):
self.name = name
self.nickname = nickame

def __str__(self):
return self.name

def __repr__(self):
return self.nickname

person = Person(‘王大锤’, ‘Wang Gangdan’)
print(f'{person!s}’)
print(f'{person!r}’)
print(f'{person.name!a}’)
print(f'{person.nickname!a}’)
性能
f-string 除了提供强大的格式化功能之外,还是这三种格式化方式中性能*高的实现。

>>> import timeit
>>> timeit.timeit(“””name = “Eric”
… age = 74
… ‘%s is %s.’ % (name, age)”””, number = 10000)
0.003324444866599663
>>> timeit.timeit(“””name = “Eric”
… age = 74
… ‘{} is {}.’.format(name, age)”””, number = 10000)
0.004242089427570761
>>> timeit.timeit(“””name = “Eric”
… age = 74
… f'{name} is {age}.'”””, number = 10000)
0.0024820892040722242
坦白的说,f-string 就是字符串 format 方法一个语法糖,但它进一步简化了格式化字符串的操作并带来了性能上的提升。使用 Python 3.6+ 的同学,使用 f-string 来代替你的 format 函数,以获得更强大的功能和更高的性能。

一个小例子助你彻底理解协程

一个小例子助你彻底理解协程

一个小例子助你彻底理解协程
协程,可能是Python中*让初学者困惑的知识点之一,它也是Python中实现并发编程的一种重要方式。Python中可以使用多线程和多进程来实现并发,这两种方式相对来说是大家比较熟悉的。事实上,还有一种实现并发的方式叫做异步编程,而协程就是实现异步编程的必要方式。

所谓协程,可以简单的理解为多个相互协作的子程序。在同一个线程中,当一个子程序阻塞时,我们可以让程序马上从一个子程序切换到另一个子程序,从而避免CPU因程序阻塞而闲置,这样就可以提升CPU的利用率,相当于用一种协作的方式加速了程序的执行。所以,我们可以言简意赅的说:协程实现了协作式并发。

接下来用一个小例子帮助大家理解什么是协作式并发,先看看下面的代码。

import time

def display(num):
time.sleep(1)
print(num)

for num in range(10):
display(num)
上面这段代码相信大家很容看懂,程序会输出0到9的数字,每隔1秒中输出一个数字,因此整个程序的执行需要大约10秒时间。值得注意的是,因为没有使用多线程或多进程,程序中只有一个执行单元,而time.sleep(1)的休眠操作会让整个线程停滞1秒钟,对于上面的代码来说,在这段时间里面CPU是完全闲置的没有做什么事情。

我们再来看看使用协程会发生什么事情。从Python 3.5开始,使用协程实现协作式编发有了更为便捷的语法,我们可以使用async来定义异步函数,可以使用await让一个阻塞的子程序将CPU让给与它协作的子程序。在Python 3.7中,asyanc和await成为了正式的关键字,让开发者有一种喜大普奔的感觉。我们先看看如何定义一个异步函数。

import asyncio

async def display(num):
await asyncio.sleep(1)
print(num)
接下来敲黑板说重点。异步函数不同于普通函数,调用普通函数会得到返回值,而调用异步函数会得到一个协程对象。我们需要将协程对象放到一个事件循环中才能达到与其他协程对象协作的效果,因为事件循环会负责处理子程序切换的操作,简单的说就是让阻塞的子程序让出CPU给可以执行的子程序。

我们先通过下面的列表生成式来代码10个协程对象,跟刚才在循环中调用display函数的道理一致。

coroutines = [display(num) for num in range(10)]
通过下面的代码可以获取事件循环并将协程对象放入事件循环中。

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coroutines))
loop.close()
执行上面的代码会发现,10个分别会阻塞1秒钟的协程总共只阻塞了约1秒种的时间,这就说明协程对象一旦阻塞会将CPU让出而不是让CPU处于闲置状态,这样就大大的提升了CPU的利用率。而且我们还会注意到,0到9的数字并不是按照我们创建协程对象的顺序打印出来的,这正是我们想要的结果啊;另外,多次执行该程序会发现每次输出的结果都不太一样,这正是并发程序本身执行顺序不确定性造成的结果。

上面的例子来自于著名的“花书”(《Python高级并发编程》),为了让大家对协程的体会更加深刻,对原书的代码做了小的改动,这个例子虽然简单,但是它已经让你体会到了协作式并发的魅力。在商业项目中,如果需要使用协作式并发,还可以将系统默认的事件循环替换为uvloop提供的事件循环,这样会获得更好的性能,因为uvloop是基于著名的跨平台异步I/O库libuv实现的。另外,如果要做基于HTTP的网络编程,三方库aiohttp是不错的选择,它基于asyncio实现了异步的HTTP服务器和客户端。

使用协程快速获得一个代理池

使用协程快速获得一个代理池

使用协程快速获得一个代理池
前言
在执行 I/O 密集型任务的时候,程序会因为等待 I/O 而阻塞。比如我们使用 requests 库来进行网络爬虫请求的话,如果网站响应速度过慢,程序会一直等待网站响应,*终导致其爬取效率十分低下。本文以爬取 IP 代理池为例,演示 Python 中如何利用异步协程来加速网络爬虫。

协程
协程(Coroutine),又称微线程,纤程,协程是一种用户态的轻量级线程。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存,在调度回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合。

协程本质上是个单进程,协程相对于多进程来说,无需进程间上下文切换的开销,无需原子操作锁定及同步的开销,编程模型也非常简单。

我们可以使用协程来实现异步操作,比如在网络爬虫场景下,在发出一个请求之后,需要等待一定的时间才能得到响应。其实在这个等待过程中,程序可以干许多其他的事情,等到响应返回之后再切换回来继续处理,这样可以充分利用 CPU 和其他资源,这就是异步协程的优势。

Python 中的协程
从 Python 3.4 开始,Python 中加入了协程的概念,这个版本的协程是通过生成器对象来实现的,在 Python 3.5 中增加了 asyncio 库和 async、await 关键字,使得协程的实现更加方便。

asyncio 库
首先我们先来看一个不使用协程的程序,代码如下:

import time


def job(t):
print(‘Start job {}’.format(t))
time.sleep(t) # 等待 t 秒
print(‘Job {0} takes {0}s’.format(t))


def main():
[job(t) for t in range(1, 3)]


start = time.time()
main()
print(“total time: {}”.format(time.time() – start))
运行结果:

Start job 1
Job 1 takes 1s
Start job 2
Job 2 takes 2s
total time: 3.001577138900757
从运行结果可以看出,我们的 job 是按顺序执行的。必须执行完 job 1 才能开始执行 job 2, job 1 需要 1 秒的执行时间,job 2 需要 2 秒的执行时间,所以总时间是 3 秒多。

如果我们使用协程的方式,job 1 在等待 time.sleep(t) 执行结束的时候(可以看做是等待一个网页的下载成功),是可以切换到 job 2 执行的。

我们再来看一下使用协程改造后的代码:

import time
import asyncio


async def job(t): # 使用 async 关键字将一个函数定义为协程
print(‘Start job {}’.format(t))
await asyncio.sleep(t) # 等待 t 秒, 期间切换执行其他任务
print(‘Job {0} takes {0}s’.format(t))


async def main(loop): # 使用 async 关键字将一个函数定义为协程
tasks = [loop.create_task(job(t)) for t in range(1, 3)] # 创建任务, 不立即执行
await asyncio.wait(tasks) # 执行并等待所有任务完成


start = time.time()
loop = asyncio.get_event_loop() # 建立 loop
loop.run_until_complete(main(loop)) # 执行 loop
loop.close() # 关闭 loop
print(“total time: {}”.format(time.time() – start))
运行结果:

Start job 1
Start job 2
Job 1 takes 1s
Job 2 takes 2s
total time: 2.0033459663391113
从运行结果可以看出,我们没有等待 job 1 执行结束再开始执行 job 2,而是 job 1 触发 await 的时候切换到了 job 2 。 这时 job 1 和 job 2 同时在执行 await asyncio.sleep(t),所以*终程序的执行时间取决于执行时间*长的那个 job,也就是 job 2 的执行时间:2 秒。

aiohttp 库
在对 asyncio 库做了简单了解之后,我们来看一下如何通过协程来改造我们的爬虫程序。

安装 aiohttp 库:

pip install aiohttp
我们先来看一下使用 reqeusts 库实现一个网页的爬取:

import time

import requests

def fetch(url):
r = requests.get(url)
return r.url


def main():
results = [fetch(‘http://www.baidu.com’) for _ in range(2)]
print(results)


start = time.time()
main()
print(“total time: {}”.format(time.time() – start))
运行结果:

[‘http://www.baidu.com/’, ‘http://www.baidu.com/’]
total time: 1.5445010662078857
使用 requests 库,访问两次 http://www.baidu.com,共耗时 1.5 秒

我们用 aiohttp 库来改造上面的代码:

import time
import asyncio

import aiohttp


async def fetch(session, url):
response = await session.get(url) # await 等待网络 IO 并切换协程
return str(response.url)


async def main(loop):
async with aiohttp.ClientSession() as session:
tasks = [
loop.create_task(fetch(session, ‘http://www.baidu.com’))
for _ in range(2)
]
done, pending = await asyncio.wait(tasks) # 执行并等待所有任务完成
results = [r.result() for r in done] # 获取所有返回结果
print(results)


start = time.time()
loop = asyncio.get_event_loop() # 建立 事件循环
loop.run_until_complete(main(loop)) # 在 事件循环 中执行协程
loop.close() # 关闭 事件循环
print(“total time: {}”.format(time.time() – start))
运行结果:

[‘http://www.baidu.com’, ‘http://www.baidu.com’]
total time: 0.10848307609558105
使用 aiohttp 的代码执行时间较使用 reqeusts 的代码有大幅的提升。

上例中,我们使用官方推荐的方式创建 session,并通过 session 执行 get 操作。aiohttp 官方建议一个 application 中共享使用一个 session,不要为每个请求都创建 session。

使用 asyncio 和 aiohttp 快速获得一个代理池
通过爬虫解析免费的代理发布网站页面,来生成代理池。

import os
import re
import time
import asyncio

import aiohttp

HEADERS = {
‘User-Agent’:
‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15’
}

OUTPUT_FILE = ‘proxies.txt’ # 代理池输出文件
SITES = [‘http://www.live-socks.net’, ‘http://www.proxyserverlist24.top’] # 代理发布网站
CHECK_URL = ‘http://www.baidu.com’
LOCAL_PROXY = None # 在本地发起请求时的代理


# http get 协程
async def fetch(session, url, proxy=None):
proxy_headers = HEADERS if proxy else None
try:
async with session.get(
url, headers=HEADERS, proxy=proxy,
proxy_headers=proxy_headers,
timeout=aiohttp.ClientTimeout(total=5)) as response:
if response.status == 200:
return await response.text()
else:
return ”
except:
return ”


# 从代理发布网站获取代理发布页面链接
async def get_page_links(loop, session):
tasks = [loop.create_task(fetch(session, url, proxy=LOCAL_PROXY))
for url in SITES] # 创建协程任务
done, _ = await asyncio.wait(tasks) # 执行并等待所有任务完成
htmls = [f.result() for f in done] # 获取所有返回结果

# 解析出 html 页面中的代理发布链接
def parse(html):
return re.findall(r'<h3[\s\S]*?<a.*?(http.*?\.html).*?</a>’, html)

results = map(parse, htmls) # 逐个解析 html 页面

return [y for x in results for y in x]


# 从代理发布页面获取代理 IP
async def get_proxies(loop, session, page_links):
tasks = [loop.create_task(fetch(session, url, proxy=LOCAL_PROXY))
for url in page_links] # 创建协程任务
done, _ = await asyncio.wait(tasks) # 执行并等待所有任务完成
htmls = [f.result() for f in done] # 获取所有返回结果

# 解析出 html 页面中的代理 IP
def parse(html):
return re.findall(r’\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}’, html)

results = map(parse, htmls) # 逐个解析 html 页面

return list(set([y for x in results for y in x]))


# 验证代理 IP
async def check_proxy(session, proxy):
html = await fetch(session, CHECK_URL, proxy=proxy)

# 如果返回通过代理 IP 访问的页面,则说明代理 IP 有效
return proxy if html else ”


# 通过协程批量验证代理 IP,每次同时发起 200 个验证请求
async def check_proxies(loop, session, proxies):
checked_proxies = []
for i in range(0, len(proxies), 200):
_proxies = [proxy.strip() if proxy.strip().startswith(‘http://’)
else ‘http://’ + proxy.strip() for proxy in proxies[i:i + 200]]
tasks = [loop.create_task(check_proxy(session, proxy))
for proxy in _proxies]
done, _ = await asyncio.wait(tasks) # 执行并等待所有任务完成
checked = [f.result() for f in done] # 获取所有返回结果
checked_proxies += [p for p in checked if p] # 获取不为空的返回值,即验证成功的代理 IP

return checked_proxies


# 将代理 IP 逐行保存到文件
def save_proxies(proxies):
# 创建新文件,如果文件已存在,则清空文件内容
with open(OUTPUT_FILE, ‘w’) as f:
f.write(”)

# 通过追加写模式,逐行写入文件
with open(OUTPUT_FILE, ‘a’) as f:
for proxy in proxies:
f.write(proxy + ‘\n’)


async def main(loop):
async with aiohttp.ClientSession() as session:
page_links = await get_page_links(loop, session) # 获得代理发布页面链接
# 从代理发布页面获得代理 IP
proxies = await get_proxies(loop, session, page_links)
print(‘total proxy: {}’.format(len(proxies))) # 解析出的代理 IP 总量
proxies = await check_proxies(loop, session, proxies) # 验证代理 IP

print(‘total checked proxy: {}’.format(len(proxies))) # 验证后的代理 IP 总量
save_proxies(proxies) # 保存代理 IP 到文件


start = time.time()
loop = asyncio.get_event_loop() # 建立 事件循环
loop.run_until_complete(main(loop)) # 在 事件循环 中执行协程
loop.close() # 关闭 事件循环
total_time = time.time() – start
print(f’total time: {total_time}’)
运行结果:

total proxy: 15675
total checked proxy: 4503
total time: 487.2807550430298
更加高效的爬虫
在爬虫程序中,通常有网络请求任务、页面解析任务、数据清洗任务和数据入库任务。

网络请求任务、数据入库任务属于 IO 密集型任务,在 Python 中通常使用多线程模型来提高这类任务的性能,现在还可以通过 aiohttp,Motor(MongoDB 的异步 Python 驱动)等异步框架将性能进一步提升。

页面解析任务、数据清洗任务这类 CPU 密集型的任务我们该如何来提高性能?在 Python 中针对 CPU 密集型任务可以通过 multiprocessing 模块来提升性能,通过 multiprocessing 模块可以使程序运行在多核 CPU 中,增加 CPU 的利用率以提升计算性能。

给代理池爬虫示例增加多核计算支持:

import os
import re
import time
import asyncio
from multiprocessing import Pool

import aiohttp

HEADERS = {
‘User-Agent’:
‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15’
}

OUTPUT_FILE = ‘proxies.txt’ # 代理池输出文件
SITES = [‘http://www.live-socks.net’, ‘http://www.proxyserverlist24.top’] # 代理发布网站
CHECK_URL = ‘http://www.baidu.com’
LOCAL_PROXY = ‘http://127.0.0.1:1087′ # •在本地发起请求时的代理


# http get 协程
async def fetch(session, url, proxy=None):
proxy_headers = HEADERS if proxy else None
try:
async with session.get(
url, headers=HEADERS, proxy=proxy,
proxy_headers=proxy_headers,
timeout=aiohttp.ClientTimeout(total=5)) as response:
if response.status == 200:
return await response.text()
else:
return ”
except:
return ”

# 解析出 html 页面中的代理发布链接


def parse_page_link(html):
return re.findall(r'<h3[\s\S]*?<a.*?(http.*?\.html).*?</a>’, html)

# 从代理发布网站获取代理发布页面链接


async def get_page_links(loop, session):
tasks = [loop.create_task(fetch(session, url, proxy=LOCAL_PROXY))
for url in SITES] # 创建协程任务
done, _ = await asyncio.wait(tasks) # 执行并等待所有任务完成
htmls = [f.result() for f in done] # 获取所有返回结果

# 利用多核 CPU 的计算能力提升页面解析性能
with Pool(processes=os.cpu_count() * 2) as pool:
results = pool.map(parse_page_link, htmls)

return [y for x in results for y in x]

# 解析出 html 页面中的代理 IP


def parse_proxy(html):
return re.findall(r’\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}’, html)

# 从代理发布页面获取代理 IP


async def get_proxies(loop, session, page_links):
tasks = [loop.create_task(fetch(session, url, proxy=LOCAL_PROXY))
for url in page_links] # 创建协程任务
done, _ = await asyncio.wait(tasks) # 执行并等待所有任务完成
htmls = [f.result() for f in done] # 获取所有返回结果

# 利用多核 CPU 的计算能力提升页面解析性能
with Pool(processes=os.cpu_count() * 2) as pool:
results = pool.map(parse_proxy, htmls)

return list(set([y for x in results for y in x]))


# 验证代理 IP
async def check_proxy(session, proxy):
html = await fetch(session, CHECK_URL, proxy=proxy)

# 如果返回通过代理 IP 访问的页面,则说明代理 IP 有效
return proxy if html else ”


# 通过协程批量验证代理 IP,每次同时发起 200 个验证请求
async def check_proxies(loop, session, proxies):
checked_proxies = []
for i in range(0, len(proxies), 200):
_proxies = [proxy.strip() if proxy.strip().startswith(‘http://’)
else ‘http://’ + proxy.strip() for proxy in proxies[i:i + 200]]
tasks = [loop.create_task(check_proxy(session, proxy))
for proxy in _proxies]
done, _ = await asyncio.wait(tasks) # 执行并等待所有任务完成
checked = [f.result() for f in done] # 获取所有返回结果
checked_proxies += [p for p in checked if p] # 获取不为空的返回值,即验证成功的代理 IP

return checked_proxies


# 将代理 IP 逐行保存到文件
def save_proxies(proxies):
# 创建新文件,如果文件已存在,则清空文件内容
with open(OUTPUT_FILE, ‘w’) as f:
f.write(”)

# 通过追加写模式,逐行写入文件
with open(OUTPUT_FILE, ‘a’) as f:
for proxy in proxies:
f.write(proxy + ‘\n’)


async def main(loop):
async with aiohttp.ClientSession() as session:
page_links = await get_page_links(loop, session) # 获得代理发布页面链接
# 从代理发布页面获得代理 IP
proxies = await get_proxies(loop, session, page_links)
print(‘total proxy: {}’.format(len(proxies))) # 解析出的代理 IP 总量
proxies = await check_proxies(loop, session, proxies) # 验证代理 IP

print(‘total checked proxy: {}’.format(len(proxies))) # 验证后的代理 IP 总量
save_proxies(proxies) # 保存代理 IP 到文件


start = time.time()
loop = asyncio.get_event_loop() # 建立 事件循环
loop.run_until_complete(main(loop)) # 在 事件循环 中执行协程
loop.close() # 关闭 事件循环
total_time = time.time() – start
print(f’total time: {total_time}’)
进程间的调度及上下文切换是非常消耗资源的。上面例子中解析任务比较简单,解析量也非常少,增加多核计算支持后,性能几乎没有提升还有可能降低。在实际爬虫项目中需要根据实际情况来衡量和选择。

用 pprint 代替 print 更友好的打印调试信息

用 pprint 代替 print 更友好的打印调试信息

用 pprint 代替 print 更友好的打印调试信息
pprint 是 “pretty printer” 的简写,“pretty” 的含义是 “漂亮的、美观的”,因此 pprint 的含义便是:漂亮的打印。

这是个相当简单却有用的模块,主要用于打印复杂的数据结构对象,例如多层嵌套的列表、元组和字典等。

先看看 print() 打印的一个例子:

mylist = [“Beautiful is better than ugly.”, “Explicit is better than implicit.”, “Simple is better than complex.”, “Complex is better than complicated.”]

print(mylist)

[‘Beautiful is better than ugly.’, ‘Explicit is better than implicit.’, ‘Simple is better than complex.’, ‘Complex is better than complicated.’]
这是一个简单的例子,全部打印在一行里。如果对象中的元素是多层嵌套的内容(例如复杂的 字典 数据),再打印出那肯定是一团糟的,不好阅读。

使用 pprint 模块的 pprint() 替代 print(),可以解决如下痛点:

设置合适的行宽度,作适当的换行
设置打印的缩进、层级,进行格式化打印
判断对象中是否有无限循环,并优化打印内容
基本使用

pprint(object, stream=None, indent=1, width=80, depth=None, *,compact=False)
默认的行宽度参数为 80,当打印的字符小于 80 时,pprint() 基本上等同于内置函数 print(),当字符超出时,它会作美化,进行格式化输出。

import pprint

mylist = [“Beautiful is better than ugly.”, “Explicit is better than implicit.”, “Simple is better than complex.”, “Complex is better than complicated.”]

pprint.pprint(mylist)

# 超出80字符,打印的元素是换行的
[‘Beautiful is better than ugly.’,
‘Explicit is better than implicit.’,
‘Simple is better than complex.’,
‘Complex is better than complicated.’]
设置缩进

pprint.pprint(mylist, indent=4)

[ ‘Beautiful is better than ugly.’,
‘Explicit is better than implicit.’,
‘Simple is better than complex.’,
‘Complex is better than complicated.’]
设置打印行宽

mydict = {‘students’: [{‘name’:’Tom’, ‘age’: 18},{‘name’:’Jerry’, ‘age’: 19}]}

pprint.pprint(mydict)

# 正常打印
{‘students’: [{‘age’: 18, ‘name’: ‘Tom’}, {‘age’: 19, ‘name’: ‘Jerry’}]}

pprint.pprint(mydict, width=20)

# 行宽为 20
{‘students’: [{‘age’: 18,
‘name’: ‘Tom’},
{‘age’: 19,
‘name’: ‘Jerry’}]}

pprint.pprint(mydict, width=70)

# 行宽为 70
{‘students’: [{‘age’: 18, ‘name’: ‘Tom’},
{‘age’: 19, ‘name’: ‘Jerry’}]}
设置打印层级

newlist = [1, [2, [3, [4, [5]]]]]

pprint.pprint(newlist, depth=3)

# 超出的层级会用 … 表示
[1, [2, [3, […]]]]
用 pprint 替换 print
import pprint
print = pprint.pprint

mylist = [“Beautiful is better than ugly.”, “Explicit is better than implicit.”, “Simple is better than complex.”, “Complex is better than complicated.”]

print(mylist)

[‘Beautiful is better than ugly.’,
‘Explicit is better than implicit.’,
‘Simple is better than complex.’,
‘Complex is better than complicated.’]

zookeeper的Leader选举机制详解

分布式开发必须知道的Zookeeper知识及其的Leader选举机制(ZAB原子广播协议)

ZooKeeper是Hadoop下的一个子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护、名字服务、分布式同步、组服务等; 它的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

ZooKeeper系统架构

下图就是Zookeeper的架构图:

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

从上面的架构图中,我们需要了解的主要的信息有:

①ZooKeeper分为服务器端(Server)和客户端(Client),客户端可以连接到整个ZooKeeper服务的任意服务器上(Leader除外)。

②ZooKeeper 启动时,将从实例中选举一个Leader,Leader 负责处理数据更新等操作,一个更新操作成功的标志是当且仅当大多数Server在内存中成功修改数据(Quorom机制)。每个Server 在内存中存储了一份数据。

③Zookeeper是可以集群复制的,集群间通过Zab协议(Zookeeper Atomic Broadcast)来保持数据的一致性;

④Zab协议包含两个阶段:Leader Election阶段和Atomic Brodcast阶段。群中将选举出一个Leader,其他的机器则称为Follower,所有的写操作都被传送给Leader,并通过Brodcast将所有的更新告诉给Follower。 当Leader被选举出来,且大多数服务器完成了和leader的状态同步后,Leadder Election 的过程就结束了,就将会进入到Atomic Brodcast的过程。Atomic Brodcast同步Leader和Follower之间的信息,保证Leader和Follower具有形同的系统状态。


Quorom机制简介

在分布式系统中,冗余数据是保证可靠性的手段,因此冗余数据的一致性维护就非常重要。一般而言,一个写操作必须要对所有的冗余数据都更新完成了,才能称为成功结束。比如一份数据在5台设备上有冗余,因为不知道读数据会落在哪一台设备上,那么一次写操作,必须5台设备都更新完成,写操作才能返回。

对于写操作比较频繁的系统,这个操作的瓶颈非常大。Quorum算法可以让写操作只要写完3台就返回。剩下的由系统内部缓慢同步完成。而读操作,则需要也至少读3台,才能保证至少可以读到一个*新的数据。


Zookeeper中的四种角色

①Leader:领导者,负责进行投票的发起和决议,更新系统状态。

②Learner:学习者

③Follower(Learner的子类):跟随者,用于接受客户端请求并向客户端返回结结果,在选主过程中参与投票,Follower可以接收Client请求,如果是写请求将转发给Leader来更新系统状态。

④Observer:观察者,可以接收客户端连接,将写请求转发给Leader节点,但是不参与投票过程,只是同步Leader状态,因为Follower增多会导致投票阶段延迟增大,影响性能。Observer的目的是为了扩展系统,提高读取数据。


为什么Zookeeper中的Server数目一般为基数?

我们知道在Zookeeper中 Leader 选举算法采用了Quorom算法。该算法的核心思想是当多数Server写成功,则任务数据写成功。假设有3个Server,则*多允许一个Server挂掉;如果有4个Server,则同样*多允许一个Server挂掉。既然3个或者4个Server,同样*多允许1个Server挂掉,那么它们的可靠性是一样的,所以选择奇数个ZooKeeper Server即可,这里选择3个Server。


Zookeeper用于Leader选举的算法

①基于UDP的LeaderElection

②基于UDP的FastLeaderElection

③基于UDP和认证的FastLeaderElection

④基于TCP的FastLeaderElection(默认值)


FastLeaderElection机制

接下来要说的就是Zookeeper的Leader选举机制核心算法FastLeaderElection类。FastLeaderElection实现了Election接口,其需要实现接口中定义的lookForLeader(核心的选举算法入口)方法和shutdown方法FastLeaderElection选举算法是标准的Fast Paxos算法实现,可解决LeaderElection选举算法收敛速度慢的问题。

术语介绍

sid(myid)

每个Zookeeper服务器,都需要在数据文件夹下创建一个名为myid的文件,该文件包含整个Zookeeper集群唯一的ID(整数)。例如某Zookeeper集群包含三台服务器,hostname分别为zoo1、zoo2和zoo3,其myid分别为1、2和3,则在配置文件中其ID与hostname必须一一对应,如下所示。在该配置文件中,server.后面的数据即为myid(Leader选举时用的sid或者leader)。

server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888

zxid

类似于RDBMS中的事务ID,用于标识一次更新操作的Proposal ID。为了保证顺序性,该zkid必须单调递增。因此Zookeeper使用一个64位的数来表示,高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一。低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置。这样保证了zxid的全局递增性。


Zookeeper节点的四种状态

截图为Zookeeper定义的四种服务器节点状态:

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

  • LOOKING: 不确定Leader状态。该状态下的服务器认为当前集群中没有Leader,会发起Leader选举。
  • FOLLOWING: 跟随者状态。表明当前服务器角色是Follower,并且它知道Leader是谁。
  • LEADING: 领导者状态。表明当前服务器角色是Leader,它会维护与Follower间的心跳。
  • OBSERVING: 观察者状态。表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票。

FastLeaderElection内部类

FastLeaderElection的内部类的情况如下图:

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

  • Notification:表示收到的选举投票信息(其他服务器发来的选举投票信息),其包含了被选举者的id、zxid、选举周期等信息。
  • ToSend:表示发送给其他服务器的选举投票信息(其他服务器发来的选举投票信息),其包含了被选举者的id、zxid、选举周期等信息。
  • Messenger:包含了WorkerReceiverWorkerSender两个内部类。WorkerReceiver实现了Runnable接口,是选票接收器。WorkerSender也实现了Runnable接口,为选票发送器

Notification(收到的投票信息)

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

  • leader:被推选的leader的id。
  • zxid:被推选的leader的事务id。
  • electionEpoch:推选者的选举周期。
  • state:推选者的状态。
  • sid:推选者的id。
  • peerEpoch:被推选者的选举周期。

ToSend(发送的投票信息)

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

  • leader:被推选的leader的id。
  • zxid:被推选的leader的事务id。
  • electionEpoch:推选者的选举周期。
  • state:推选者的状态。
  • sid:推选者的id。
  • peerEpoch:被推选者的选举周期。

WorkerSender(选票发送器)

WorkerSender也实现了Runnable接口,为选票发送器,其会不断地从sendqueue中获取待发送的选票,并将其传递到底层QuorumCnxManager中。

  • 获取选票

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

  • 发送选票

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 


WorkerReceiver(选票接收器)

WorkerReceiver实现了Runnable接口,是选票接收器。其会不断地从QuorumCnxManager中获取其他服务器发来的选举消息中。先会从QuorumCnxManager中的pollRecvQueue队列中取出其他服务器发来的选举消息,消息封装在Message数据结构中。然后判断消息中的服务器id是否包含在可以投票的服务器集合中,若不是,则会将本服务器的内部投票发送给该服务器,其流程如下:

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

若包含该服务器,则根据消息(Message)解析出投票服务器的投票信息并将其封装为Notification,然后判断当前服务器是否为LOOKING,若为LOOKING,则直接将Notification放入FastLeaderElection的recvqueue。然后判断投票服务器是否为LOOKING状态,并且其选举周期小于当前服务器的逻辑时钟,则将本(当前)服务器的内部投票发送给该服务器,否则,直接忽略掉该投票。其流程如下:

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

若本服务器的状态不为LOOKING,则会根据投票服务器中解析的version信息来构造ToSend消息,放入sendqueue,等待发送,起流程如下:

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 


核心函数分析

sendNotifications函数

其会遍历所有的参与者投票集合,然后将自己的选票信息发送至上述所有的投票者集合,其并非同步发送,而是将ToSend消息放置于sendqueue中,之后由WorkerSender进行发送。

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

totalOrderPredicate函数

该函数将接收的投票与自身投票进行PK,查看是否消息中包含的服务器id是否更优,其按照epoch、zxid、id的优先级进行PK。

  • 判断消息里的epoch是不是比当前的大,如果大则消息中id对应的服务器就是leader。
  • 如果epoch相等则判断zxid,如果消息里的zxid大,则消息中id对应的服务器就是leader。
  • 如果前面两个都相等那就比较服务器id,如果大,则其就是leader。

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

termPredicate函数

该函数用于判断Leader选举是否结束,即是否有一半以上的服务器选出了相同的Leader,其过程是将收到的选票与当前选票进行对比,选票相同的放入同一个集合,之后判断选票相同的集合是否超过了半数。

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

checkLeader函数

该函数检查是否已经完成了Leader的选举,此时Leader的状态应该是LEADING状态。

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

lookForLeader函数

该函数就是leader选举的核心方法,代码行数有点多,这里仅分析其中的一部分。

  • 更新逻辑时钟、更新选票、发送选票

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

  • 获取投票数据、连接服务器

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

  • 选举Leader

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

  • LEADING状态处理

这可能是全网把zookeeper的Leader选举机制讲的*透彻的一篇文章

 

以上就是关于zookeeper的所有基本知识与Leader选举机制的讲解。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速