Code war of Angel


  • Home

  • Archives

《Python高性能编程》笔记

Posted on 2019-07-23 | In Python

性能分析

  1. 基本技术如 IPython 的 timeit 魔法函数、time.time()、以及一个计时修饰器,使用这些技术来了解语句和函数的行为。
  2. 内置工具如 cProfile,了解代码中哪些函数耗时最长,并用 runsnake 进行可视化。
  3. line_profiler 工具,对选定的函数进行逐行分析,其结果包含每行被调用的次数以及每行花费的时间百分比。
  4. memory_profiler 工具,以图的形式展示RAM的使用情况随时间的变化,解释为什么某个函数占用了比预期更多的 RAM。
  5. Guppy 项目的 heapy 工具,查看 Python 堆中对象的数量以及每个对象的大小,这对于消灭奇怪的内存泄漏特别有用。
  6. dowser 工具,通过Web浏览器界面审查一个持续运行的进程中的实时对象。
  7. dis 模块,查看 CPython 的字节码,了解基于栈的 Python 虚拟机如何运行。
  8. 单元测试,在性能分析时要避免由优化手段带来的破坏性后果。

数据结构影响

  1. 列表和元组就类似于其它编程语言的数组,主要用于存储具有内在次序的数据;

    1. 高效搜索必需的两大要素是排序算法和搜索算法。Python 列表有一个内建的排序算法使用了Tim排序。
    2. 动态数组支持 resize 操作,可以增加数组的容量。当一个大小为N的列表第一次需要添加数据时,Python会创建一个新的列表,足够存放原来的N个元素以及额外需要添加的元素。
    3. 元组固定且不可变。这意味着一旦元组被创建,和列表不同,它的内容无法被修改或它的大小也无法被改变。
  2. 字典和集合就类似其它编程语言的哈希表/散列集,主要用于存储无序的数据。

    1. 新插入数据的位置取决于数据的两个属性:键的散列值以及该值如何跟其他对象比较。这是因为当我们插入数据时,首先需要计算键的散列值并掩码来得到一个有效的数组索引。
    2. 如果被占用,那么要找到新的索引,我们用一个简单的线性函数计算出一个新的索引,这一方法称为嗅探。Python的嗅探机制使用了原始散列值的高位比特。使用这些高位比特使得每一个散列值生成的下一可用散列序列都是不同的,这样就能帮助防止未来的碰撞。
    3. 当一个值从散列表中被删除时,我们不能简单地写一个NULL到内存的那个桶里。这是因为我们已经用NULL来作为嗅探散列碰撞的终止值。所以,我们必须写一个特殊的值来表示该桶虽空,但其后可能还有别的因散列碰撞而插入的值。
    4. 不超过三分之二满的表在具有最佳空间节约的同时依然具有不错的散列碰撞避免率。改大散列表的代价非常昂贵,但因为我们只在表太小时而不是在每一次插入时进行这一操作。
    5. Python 对象通常以散列表实现,因为它们已经有内建的hash和cmp函数。
    6. 每当 Python 访问一个变量、函数或模块时,都有一个体系来决定它去哪里查找这些对象。
      1. locals()数组
      2. globals()字典
      3. __builtin__对象(builtin中的一个 属性时,我们其实是在搜索它的 locals()字典)

迭代器、生成器

  1. 节约内存
  2. 延迟估值

矩阵与矢量计算

原生 Python 并不支持矢量操作,因为 Python 列表存储的不是实际的数据,而是对实际数据的引用。在矢量和矩阵操作时,这种存储结构会造成极大的性能下降。
Numpy 能够将数据连续存储在内存中并支持数据的矢量操作,在数据处理方面,它是高性能编程的最佳解决方案之一。
Numpy 带来性能提升的关键在于,它使用了高度优化且特殊构建的对象,取代了通用的列表结构来处理数组,由此减少了内存碎片;此外,自动矢量化的数学操作使得矩阵计算非常高效。
Numpy 在矢量操作上的缺陷是一次只能处理一个操作。例如,当我们做 A B + C 这样的矢量操作时,先要等待 A B 操作完成,并保存数据在一个临时矢量中,然后再将这个新的矢量和 C 相加。

编译器

让你的代码运行更快的最简单的办法就是让它做更少的工作。编译器把代码编译成机器码,是提高性能的关键组成部分。

  • Cython ——这是编译成C最通用的工具,覆盖了Numpy和普通的Python代码(需要一些C语言的知识)。
  • Shed Skin —— 一个用于非Numpy代码的,自动把Python转换成C的转换器。
  • Numba —— 一个专用于Numpy代码的新编译器。
  • Pythran —— 一个用于Numpy和非numpy代码的新编译器。
  • PyPy —— 一个用于非Numpy代码的,取代常规Python可执行程序的稳定的即时编译器。

密集型任务

  1. I/O 密集型:异步编程
    • Gevent
    • Tornado
    • Asyncio
  2. CPU 密集型:多核 CPU 进行多进程
    • Multiprocessing
      • multiprocessing.Pool
      • 内存共享
        • multiprocessing.Manager()
        • redis等中间件
        • mmap

集群与现场教训

  1. 集群带来的问题:
    1. 机器间信息同步的延迟
    2. 机器间配置与性能的差异
    3. 机器的损耗与维护
    4. 其它难以预料的问题
  2. 集群化解决方案:
    1. Parallel Python
    2. IPython Parallel
    3. NSQ

Hadoop安装使用

Posted on 2019-07-22

安装

  1. 安装配置JAVA及其环境变量
    1
    2
    export JAVA_HOME=/usr/local/java/jdk-12.0.1
    export PATH=$PATH:${JAVA_HOME}/bin

如果是ubuntu,需要将JAVA_HOME变量配置到~/.bashrc,这篇文章所说,因为hadoop运行的时候是无登录的。

  1. 配置ssh登录

    1
    2
    ssh-keygen -t rsa
    cp ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys
  2. 解压hadoop,~/.bashrc 添加环境变量

    1
    2
    3
    export HADOOP_HOME=/etc/hadoop-3.1.2/hadoop-3.1.2
    export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
    export HADOOP_OPTS="-Djava.library.path=$HADOOP_HOME/lib"
  3. etc/hadoop/hadoop-env.sh添加

    1
    2
    3
    4
    5
    6
    export HDFS_NAMENODE_USER="root"
    export HDFS_DATANODE_USER="root"
    export HDFS_SECONDARYNAMENODE_USER="root"
    export YARN_RESOURCEMANAGER_USER="root"
    export YARN_NODEMANAGER_USER="root"
    export HADOOP_OPTS="$HADOOP_OPTS -Djava.library.path=$HADOOP_HOME/lib/native"
  4. etc/hadoop/core-site.xml添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <configuration>
    <property>
    <name>fs.defaultFS</name>
    <value>hdfs://localhost:8020</value>
    </property>
    <property>
    <name>hadoop.tmp.dir</name>
    <value>/tmp/hadoop/</value>
    </property>
    </configuration>
  5. etc/hadoop/hdfs-site.xml添加

    1
    2
    3
    4
    5
    6
    <configuration>
    <property>
    <name>dfs.replication</name>
    <value>1</value>
    </property>
    </configuration>
  6. 格式化hdfs

    1
    bin/hdfs namenode -format
  7. 启动

    1
    2
    3
    sbin/start-dfs.sh 
    sbin/stop-dfs.sh #关闭
    jps #检查是否启动正常
  8. 打开 http://localhost:9870 可以看见Hadoop的管理页面

Hadoop的组成

Hadoop 生态是一个庞大的、功能齐全的生态,但是围绕的还是名为 Hadoop 的分布式系统基础架构,其核心组件由四个部分组成,分别是:Common、HDFS、MapReduce 以及 YARN。

  • Common 是 Hadoop 架构的通用组件;
  • HDFS 是 Hadoop 的分布式文件存储系统;
  • MapReduce 是Hadoop 提供的一种编程模型,可用于大规模数据集的并行运算;
  • YARN 是 Hadoop 架构升级后,目前广泛使用的资源管理器。

NameNode

  • NameNode 是管理文件系统命名空间的主服务器,用于管理客户端对文件的访问,执行文件系统命名空间操作,如打开,关闭和重命名文件和目录。它还确定了Block 到 DataNode 的映射。
  • NameNode 做着有关块复制的所有决定,它定期从群集中的每个 DataNode 接收 Heartbeat 和 Blockreport。收到 Heartbeat 意味着 DataNode正常运行,Blockreport 包含 DataNode 上所有块的列表。
  • 文件系统的元数据(MetaData)也存储在 NameNode 中,NameNode 使用名为 EditLog 的事务日志来持久记录文件系统元数据发生的每个更改。
  • 整个文件系统命名空间(包括块到文件和文件系统属性的映射)存储在名为 FsImage 的文件中。 FsImage 也作为文件存储在 NameNode 的本地文件系统中。

    DataNode

    DataNode 通常是群集中每个节点一个,用于存储数据,负责提供来自文件系统客户端的读写请求。并且还会根据 NameNode 的指令执行块创建,删除和复制。

Hadoop生态圈

  • HBase:来源于Google的BigTable;是一个高可靠性、高性能、面向列、可伸缩的分布式数据库。
  • Hive:是一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。
  • Pig:是一个基于Hadoop的大规模数据分析工具,它提供的SQL-LIKE语言叫Pig Latin,该语言的编译器会把类SQL的数据分析请求转换为一系列经过优化处理的MapReduce运算。
  • ZooKeeper:来源于Google的Chubby;它主要是用来解决分布式应用中经常遇到的一些数据管理问题,简化分布式应用协调及其管理的难度。
  • Ambari:Hadoop管理工具,可以快捷地监控、部署、管理集群。
  • Sqoop:用于在Hadoop与传统的数据库间进行数据的传递。
  • Mahout:一个可扩展的机器学习和数据挖掘库。

参考:
Hadoop: Setting up a Single Node Cluster.
Hadoop – 1. 从零搭建HDFS
Hadoop入门(一)之Hadoop伪分布式环境搭建
hadoop和大数据的关系?和spark的关系?

《流畅的Python》笔记

Posted on 2019-07-11 | In Python

序列构成的数组

  1. 序列类型

    • 容器序列(可以存放不同数据类型):list、tuple、collection.deque
    • 扁平序列: str、bytes、bytearray、memeryview、array.array
  2. 按是否能被修改

    • 可变序列: list、bytearray、array.array、collection.deque 、memoryview
    • 不可变序列: tuple、str、bytes
  3. 具名元组 collections.nametuple

    1
    2
    City = nametuple('City','name size population')
    tokyo = City('Tokyo','199999','36.9')
  4. 切片

    • 切片总是忽略最后一个元素
    • s[a:b:c] c是间隔
      - 切片时调用getitem(slice(start,end,step))
  5. + *
    • 都不修改原有对象
    • *是个浅复制,因此如果序列里含有对象的话,复制出来的是同一个对象的引用,比如[[]]*3
  6. += 使用iadd(不改变原有对象) 如果不存在,则会调用add(创建了新对象)。*= 对应 imul同理。
  7. 如果元组含有可变对象(eg. (1,2,[1,2]) ),改变了可变对象会抛出错误但是仍会执行。
  8. 排序
    • list.sort 就地排序
    • sorted 创建一个新列表
  9. bisect 管理已排序的列表

    1
    2
    3
    4
    5
    # 搜索
    bisect(hystack,needle)
    # 插入
    bisect.insert
    bisect.insort
  10. 数组 array.array

  11. 内存视图 memoryview:不复制内容的情况下操作同一个数组的不同切片
  12. 双向队列 collection.deque

字典和集合

  1. 标准库所有的映射类型都是利用dict,dict要求键必须是可散列的数据类型。
  2. update 批量更新
  3. setdefault处理找不到的值

    1
    my_dict.setdefault(key,[]).append(new_value)
  4. collection.defaultdict 为找不到的键创建默认值

  5. 所有映射类型在处理找不到的键时,会涉及missing方法,该方法只会被getitem调用
  6. 创建自定义映射类型的时候,继承collections.UserDict比继承dict更好。
  7. 不可变映射类型 types.MappingProxyType,创建了一个只读的映射视图.
  8. 集合实现了很多中缀运算
  9. 散列表是个稀疏数组,里面的单元叫说表元,每个表元包含键的引用、值的引用,表元大小一致,可以通过偏移量读取某个表元。
  10. 计算键的散列值-> 利用散列值的一部分定位散列表中的一个表元,如果散列冲突,再使用另一个部分定位表元。
    因此:
    • 键必须是可散列的
    • 字典在内存上开销巨大
    • 键查询很快
    • 键的次序是乱的
    • 新增键有可能导致扩容,从而改变键的顺序
  11. set、frozenset也依赖散列表
  12. PHP的数组其实是个有序的映射
  13. Python3.6之后字典的底层数据结构发生了变化,参考

文本和字节序列

  1. Python3中的str对象获取的元素是Unicode字符
  2. Unicode标准中,字符的标志(码位),字符的具体表述取决于所用的编码。把码位转换成字节序列的过程是编码,反之则为解码。
  3. 二进制序列的切片始终是同一类型的二进制序列。
  4. 二进制的方法与str的方法类似,特别的有 fromhex()
  5. struct模块,把打包的字节序列转换程不同类型字段组成的元组。
  6. memoryview用于共享内存,memory对象的切片是一个新的memoryview对象,不会复制字节序列。
  7. 错误
    • UnicodeEncodeError:目标编码没有定义某个字符
    • UnicodeDecodeError:无法转换字节序列
    • SyntaxError:加载的.py模块中含有UTF-8之外的数据,而且没有声明编码。
  8. BOM:字节序标记,指明编码时使用Intel CPU的小字节序。
  9. unicodedata.normalize:规范化Unicode. NFC是最好的规范。
  10. 大小写折叠:str.casefold() str.lower()

一等函数

  1. 高阶函数:接受函数作为参数,或者把函数作为结果返回。
  2. 匿名函数Lambda
  3. 实现call实例方法,可以使任何对象表现得像函数。
  4. 函数使用dict属性存储赋予它的用户属性。__defaults__保存定位参数和关键字参数的默认值,__kwdefaults__保存关键字参数默认值。__code__保存参数的名称。__annotation__保存注释。
  5. 定义函数时,如果想指定仅限关键字参数,要把它们放在前面有*的参数后面.

    1
    def f(a,*,b)
  6. 提取函数的信息:inspect.signature

  7. 冻结参数: functools.partial
  8. 设计模式:
    • 对接口编程,而不是对实现编程
    • 优先使用对象组合,而不是类继承

装饰器与闭包

  1. 装饰器的特点:
    • 能把被装饰的函数替换成别的函数
    • 装饰器在加载模块时立即执行
  2. 变量作用域

    1
    2
    3
    4
    5
    6
    b=6
    def f2(a):
    global b
    print(a)
    print(b)
    b = 9 # Python在编译函数时,发现b被赋值了,如果没有定义global,会判断b时局部变量而报错
  3. 自由变量:未在本地作用域中绑定的变量。闭包会保留定义函数时存在的自由变量的绑定。只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量。

  4. nonlocal:把变量标记为自由变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def make_averager():
    count = 0
    total = 0
    def averager(new_value):
    nonlocal count,total
    count = count +1
    total = total + new_value
    return total / count
    return averager
  5. functools.wraps,把相关属性从真实被装饰函数复制到装饰后返回的函数中,从而保持name和doc等不变。

  6. functools.lru_cache: 实现LRU缓存,把耗时的函数结果保存起来。
  7. functools.singledispatch 实现单分派泛函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @singledispatch
    def htmlize(obj):
    pass
    @htmlize.register(str):
    def _(text):
    pass
    @htmlize.register(tuple):
    def _(tup):
    pass

对象引用、可变性、垃圾回收

  1. Python中的变量类似JAVA引用变量。是一个标识。
  2. is比较对象的标识,==比较对象的值。is更快因为不能重载。
  3. 默认做浅复制,比如构造方法或[:],深复制用deepcopy
  4. Python中唯一支持的参数传递模式是共享传参(call by share),各个形式参数获得实参中各个引用的副本。因此,函数可能会改变参数传入的可变对象。
  5. 不要使用可变类型作为参数的默认值,因为默认值在定义时计算,因此默认值成为了函数的属性,会被后续的函数调用中共享。
  6. del删除名称,而不是对象。
  7. CPython中垃圾回收使用引用计算算法。
  8. 弱引用(weakref WeakValueDictionary)不会增加对象的引用数量
  9. 对于不可变类型(比如元组),使用t[:],不创建副本,而是返回同一个对象的引用。
  10. 对于字符串字面量,会有“驻留”去共享字符串字面量。即字符串值相同的两个变量,共享了一个对象引用。
  11. 对于*= +=,如果左边的变量绑定了不可变对象,会创建新对象,如果是可变对象,就就地修改。

符合Python风格的对象

  1. classmethod常用于定义备选构造方法。
  2. staticmethod可有可无。
  3. 私有属性 mood,实际做了名称改写,变成了 _Dogmood
  4. slots类属性让解释器以元组存储实例而不用字典,以节省内存。
    • 每个子类都要定义slots属性
    • 实例只能拥有slots定义了的属性,除非把dict加入slots
    • 如果不把weakref加入slots,无法被弱引用
  5. 类属性可以为实例属性提供默认值
  6. 类属性是公开,会被继承,经常用于创建一个子类,定制类的数据属性。

序列的修改、散列、切片

  1. 使类表现得像序列:实现getitem和len方法。
  2. 动态存取属性:getattr和setattr
  3. zip拆包元组并重新组合

接口:从协议到抽象基类

  1. 从鸭子类型代表动态协议。--让动态类型语言实现多态
  2. 虽然没有interface关键字,每个类都有接口,类的实现或继承公开的属性(方法/数据属性),包括特殊方法。
  3. 使用猴子补丁在运行时实现协议:

    • 猴子补丁:在运行时修改类/模块,而不改动源码。
    • 内置类型不能打猴子补丁。
      1
      2
      3
      def set_cart(deck,position,card):
      deck._cards[position]=card
      FrenchDeck.__setitem = set_card #Monkey Patch
  4. 协议是动态的,只要对象部分实现了协议,就可以当成是该类型那样使用。

  5. 白鹅类型:只要cls是抽象基类,即cls的元类是abc.ABCMeta,就可以使用isinstance(obj,cls)
    • 即便不继承,也有办法把一个类注册为抽象基类的虚拟子类。只要实现了抽象基类定义的接口。
    • 注册虚拟子类的方式实在抽象基类上调用register方法。但是注册的类不会从抽象基类中继承任何方法或属性。
  6. 大部分标准库中的抽象基类在collection.abc模块中国年定义。
  7. 抽象基类可以有实现代码,即使实现了,子类也必须覆盖抽象方法,但可以通过super()调用抽象方法。
  8. 声明抽象基类最简单的方式是继承abc.ABC或其他抽象基类。

    1
    2
    3
    4
    class Tombola(metaclass=abc.ABCMeta):
    @abstractmentd
    def methoss():
    pass
  9. 动态识别子类subclasshook

继承的优缺点

  1. 子类化内置类型时,内置类型不会调用用户定义的类覆盖的特殊方法。这种行为违背了面向对象编程的其中一个原则:始终应该从实例所属的类开始搜索方法。
  2. 内置类型的方法调用的其他类的方法,如果被覆盖,也不会被调用。
  3. 多重继承中,确定使用哪个类的方法,必须使用类名限定方法调用来避免歧义。

    1
    2
    3
    class D(B,C):
    def function(self):
    B.method1(self)
  4. 解析顺序mro,该顺序即考虑继承图也考虑子类声明时列出的超类的顺序。

  5. 处理继承建议:
    • 区分接口继承、实现继承。
      • 继承接口,创建子类型,实现“是什么”关系
      • 继承实现,通过重用避免代码重复。
    • 使用抽象基类显式表示接口
    • 通过混入重用代码,混入类mixin class
    • 在名称中明确指明混入 XViewMixin
    • 抽象基类可以作为混入,反过来不成立
    • 不要子类化多个具体类
    • 为用户提供聚合类
    • 优先使用对象组合,而不是继承

正确重载运算符

  1. 规定:
    1. 不能重载内置类型运算符
    2. 不能新建运算符
    3. 某些运算符不能重载,包括is and or not
  2. 一元运算符
    1. - __neg__
    2. + __pos__
    3. ~ __invert__
    4. * __mul__和rmul
    5. += __iadd__(就地计算)或add
  3. 反向方法:__add__和radd
  4. 如果由于类型不兼容倒是运算符特殊方法无法返回有效结果,应该返回NotImplemented,这样Python会尝试使用反向方法。
  5. eq正向反向都一样,只是参数顺序对调

可迭代的对象、迭代器、生成器

  1. 对比:迭代器用于从集合取出元素。生成器用于凭空生成元素,所有的生成器都是迭代器。
  2. 序列可以迭代的原因:实现iter函数。如果没有iter,则会调用getitem方法。
  3. 检查对象x能否迭代:iter(x,stop),stop是哨符,当可调用对象返回这个值时,触发迭代器抛出StopInteration异常。
  4. Python从可迭代对象获取迭代器。标准迭代器有iter和next方法。
  5. 迭代器:实现了无参数的next方法,返回序列中下一个元素,如果没有元素了,抛出StopIteration异常。
  6. 可迭代对象的iter方法,每次都实例化一个新的迭代器,迭代器本身实现next方法返回单个元素,iter方法返回自身。
  7. 生成器,只要函数定义体中有yield关键字
  8. re.finditer时re.findall惰性版本,返回了一个生成器。
  9. yield from创建了通道,把内层生成器直接与外层生成器的客户端联系起来。

上下文管理器和else

  1. with:设置一个临时上下文,交给上下文管理器对象控制,并负责清理上下文。上下文管理器对象协议包括enter和exit
  2. for/else, while/else, try/else,这里的else类似于then的用法。
  3. 如果exit返回None,True以外的值,with中任何异常会向上冒泡。
  4. 模块contextlib中,@contextmanager把简单的生成器函数变成上下文管理器,yield前所有的代码在with开始时调用,yield后代码在with结束时调用。此时yield与迭代无关。

协程

  1. yield作为控制流程的方法。

    1
    2
    3
    4
    5
    6
    7
    def x():
    print(1)
    x = yield
    print(x)
    y = X()
    next(y) # 预激协程
    y.send(42)
  2. 异常处理:generator.throw 终止协程generator.close

  3. 协程的返回值捕捉需要被except中获取err.value
  4. yield from 主要功能是打开双向通道
    1. 子生成器产出的值都直接传给委派生成器的调用方
    2. 使用send()方法发给委派生成器的值都直接传给子生成器。如果值时None,会调用子生成器的next方法,如果不是None,会调用子生成器的send()
    3. 生成器退出时,生成器/子生成器中的return expr表达式触发StopInteration(expr)异常抛出
    4. yield from表达式的值时子生成器终止时传给StopIteration异常的第一个参数

使用future处理并发

  1. concurrent.future模块主要有ThreadPoolExcutor、ProcessPoolExcutor类。
  2. future类有concurrent.future.Future和asyncio.Future。两者都有
    1. .done()
    2. .add_done_callback()
    3. .result(),但 concurrent.future.Future会阻塞直到有返回值,asyncio.Future则会抛出异常。
  3. concurrent.future。as_completed返回迭代器,在future运行结束后产出future
  4. CPython本身不是线程安全,因此有全局解释锁GIL,一次只允许使用一个线程执行Python字节码
  5. Excutor.map可以并发运行多个可调用对象

使用asyncio处理并发

  1. asyncio API协程在定义体必须使用yield from
  2. @asyncio.coroutine装饰器定义协程函数
  3. Task对象可以取消,取消后在协程当前yield处抛出asyncio.CancelledError,协程可以捕获异常,延迟取消,拒绝取消。
  4. Task对象用于驱动协程

    1
    task = asyncio.async(methos('think'))
  5. 线程与协程区别:调度程序在任何时候都可能中断线程,但协程默认做好全方位保护。

  6. asycio只支持TCP、UDP,HTTP使用aiohttp
  7. 避免阻塞的方法:
    1. 在单独线程中运行各个阻塞操作 -- 内存问题
    2. 把每个阻塞型操作转换成非阻塞的异步调用
      1. callback
      2. 协程:必须使用事件循环显式排定协程的执行时间,或者在其他排定了执行时间的协程中使用yield from把它激活。
  8. 访问本地文件会阻塞,调用asyncio.run_in_excutor,底层时用线程处理了。
  9. 异步框架:tornado

元编程

  1. 特性:在不改变类接口的前提下,使用存取方法修改数据属性。管理实例属性的类属性。
  2. 是否有效标识符:s.isidentifier()
  3. 构造实例的特殊方法new,该方法返回一个实例,作为第一个参数传给init。
  4. 使用装饰器 @property, @[prop_name].setter, @[prop_name].getter
  5. property构造方法,可以使用函数调用而不是装饰器。

    1
    property(fget=None, fset=None, fdel=None, doc=None)
  6. 特例都是类属性,但是特例管理的是实例属性的存取,

    1. 实例和类有同名属性,实例会覆盖类属性。
    2. 实例属性不会覆盖类特性
    3. 新添的类特性会覆盖实例属性
  7. 特性工厂

    1
    2
    3
    4
    5
    6
    def factory(prop_name):
    def prop_getter(instance):
    return instance.__dict__[prop_name]
    def prop_setter(instance):
    pass
    return property(prop_getter,prop_setter)
  8. 属性的内置函数

    1. dir
    2. getattr
    3. setattr
    4. hasattr
    5. vars:返回dict属性
  9. 直接通过dict属性读写的属性不会触发这些特殊方法。

属性描述符

  1. 描述符是实现类特定协议的类,包括get,__set__,__delete__
  2. 描述符的作用:创建一个实例,作为另一个类的类属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Factory:
    def __init__(self,prop_name):
    self.prop_name = prop_name
    # self是描述符实例,instance是托管实例
    def __set__(self,instance,values):
    pass
    # owner是托管类的引用
    def __get__(self,instance,owner):
    pass
  3. 描述符与特性工厂

    1. 描述符可以用子类扩展
    2. 相比于闭包,类属性、实例属性保持状态更容易理解
  4. 实现set方法的描述符是覆盖性描述符,会覆盖实例属性的复制操作。
  5. 没有get方法的覆盖性描述符,只有读操作时,实例属性会覆盖描述符。
  6. 没有实现set方法的描述符是非覆盖性描述符。
  7. 读类属性可以由依附在托管类上定义的get方法的描述符处理,但是写操作不会。
  8. 描述符使用
    1. 使用特性以保持简单
    2. 只读描述符必须有set
    3. 用于验证的描述符可以只有set
    4. 仅用get方法的描述符实现高效缓存
    5. 非特殊方法可以被实例属性遮盖

类元编程

  1. 类元编程是指在运行时创建或定制类的操作。
  2. type是一个类,传入三个参数可以创建一个类,type的实例也是类。

    1
    MyClass= type('MyClass',(MySuperClass,MyMin),{'x':42,'y':50})
  3. 类装饰器与函数装饰器类似,但子类不会继承类装饰器

  4. 类的定义体属于“顶层代码”在导入时运行。包括嵌套类。
  5. object是type的实例,type是object的子类。
  6. 元类的特殊方法prepare,在new前被调用,使用类定义体中的属性创建映射。

基于OAuth2 + jwt授权系统设计

Posted on 2019-07-09

Oauth2是什么

OAuth是一个标准

开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth是OpenID的一个补充,但是完全不同的服务。

OAuth功能

OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。

OAuth四种授权方式

  1. Authorization Code

    • (A)Client使用浏览器(用户代理)访问Authorization server。也就是用浏览器访问一个URL,这个URL是Authorization server提供的,访问的收Client需要提供(客户端标识,请求范围,本地状态和重定向URL)这些参数。
    • (B)Authorization server验证Client在(A)中传递的参数信息,如果无误则提供一个页面供Resource owner登陆,登陆成功后选择Client可以访问Resource server的哪些资源以及读写权限。
    • (C)在(B)无误后返回一个授权码(Authorization Code)给Client。
    • (D)Client拿着(C)中获得的授权码(Authorization Code)和(客户端标识、重定向URL等信息)作为参数,请求Authorization server提供的获取访问令牌的URL。
    • (E)Authorization server返回访问令牌和可选的刷新令牌以及令牌有效时间等信息给Client。

      当采用jwt/传统cookie session登录模式下,可以有两种不同的流程,上面为传统登录模式,下面为jwt登录模式。

  2. Implicit

    和Authorzation Code类型下重要的区分就是省略了Authorization Response和Access Token Request。

  3. Resource Owner Password Credentials Grant Client直接使用Resource owner提供的username和password来直接请求access_token(直接发起Access Token Request然后返回Access Token Response信息)。这种模式一般适用于Resource server高度信任第三方Client的情况下。
  4. Client Credentials Grant Client直接已自己的名义而不是Resource owner的名义去要求访问Resource server的一些受保护资源。

安全性问题

  1. 要求Authorization server进行有效的Client验证;
    包括client是否注册、client所属权限。
  2. client_serect,access_token,refresh_token,code等敏感信息的安全存储(不得泄露给第三方)、传输通道的安全性(TSL的要求);
    必须使用HTTPS。
  3. 维持refresh_token和第三方应用的绑定,刷新失效机制;
  4. 维持Authorization Code和第三方应用的绑定,这也是state参数为什么是推荐的一点,以防止CSRF;
    同3,要求验证请求来源域名、重定向域名是否合法。
    必须使用state:客户端发出去, 授权服务器原样返回(对于授权服务器来说state是黑盒的东西,说也是可有可无的存在),所以这个参数如何运用,全靠客户端。比如如果“李四”在客户端已经登录,那么客户端在发送state的时候就可以把“李四”的标识信息和随机数等其他信息加密后作为state。同时存入自己的cookie中。在接收到回调后先对比验证cookie,然后从里面取出“李四”。

jwt是什么

传统登录模式

一般流程如下:

  1. 用户向服务器发送用户名和密码。
  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  3. 服务器向用户返回一个 session_id,写入用户的 Cookie。
  4. 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
  5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
    问题:
  6. 集群部署下数据共享问题。
  7. 如果用户直接点击了攻击者发的链接,因为用户浏览器带了登录cookie,使得攻击能成功。

jwt

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户。用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了。

jwt特点

  1. JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  2. JWT 不加密的情况下,不能将秘密数据写入 JWT。
  3. JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  4. JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  5. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  6. 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

改进

针对4.提到的缺点。增加:

  1. 生成的token存入redis或持久层,实现集群下数据共享,及中途废止token,更改权限等功能,服务器进行验证时先验证redis/持久层的token,再进行解密验证。
  2. 客户端、服务端不存储token进cookie,服务端返回给客户端时存储进localstorage,验证时在header 增加Authorize 参数传输。

设计流程

代码

主要参考:
[认证 & 授权] 1. OAuth2授权

《Head first Servlet & JSP》笔记

Posted on 2019-07-02 | In JAVA

Web应用体系结构

  1. Servlet 生命周期:

    1. servlet类加载
    2. servlet实例化
    3. 调用init方法
    4. 调用service方法
    5. 调用destory方法
  2. Servlet没有main方法,他们受控于另一个JAVA容器应用,如Tomcat。
    Web服务器应用(ng、Apache)-> Web容器应用(Tomcat) -> Servlet

  3. 容器的作用:

    1. 通信支持:管理与web服务器的通信。
    2. 生命周期管理:控制Servlet的生命周期、垃圾回收。
    3. 多线程支持:管理一个线程池,为每个Servlet请求创建一个新/分配的Java线程。
    4. 声明方式实现安全:XML描述文件。
    5. JSP支持:将JSP翻译成JAVA。
  4. MVC
  5. J2EE 包含了Servlet规范、JSP规范、EJB规范,Web容器用于Web组件(Servlet、JSP),EJB容器用于业务组件。
    • 独立的Web容器:Tomcat、Resin
    • J2EE服务器:WebLogic、WebSphere

Servlet

  1. 容器调用流程
    • 容器 –> servlet.init()
    • 容器的线程 –> service() –> doGet()/doPost()…
  2. service方法总是在自己的栈中调用

Request and Response

  1. ServletRequest
    • getAttribute(String)
    • genContentLength()
    • getInputStream() 输入流
    • getLocalPort() 请求最后发送到到端口(每个线程有不同到本地端口)
    • getRemotePort() 客户端的端口
    • getServerPort() 请求原来发送到的端口
    • getParameter(String)
    • getParameterValues(String)
    • getParameterName
  2. HttpServletRequest extends ServletRequest

    • getContextPath()
    • getCookies()
    • getHeader()
    • getMethod()
    • getQueryString()
    • getSession()
  3. ServletResponse

    • getBufferSize()
    • setContentType()
    • getOutputStream(): 输出字节 ServletOutputStream s = response.getOutputStream(); out.write(Byte);
    • getWriter(): 输出字符 PrintWriter w = response.getWriter(); w.println(String);
    • setContentLength()
  4. HttpServletResponse extends ServletResponse

    • addCookie()
    • addHeader()
    • encodeURL()
    • sendError()
    • setStatus()
    • setRedirect()
  5. HttpServletResponse常用setContentType()和getWrite()/getOutputStream()

初始化

  1. getServletContext().getInitParameter(“email”) ,对应用中所有的servlet跟jsp都可用 ,非线程安全。

    1
    2
    3
    4
    5
    6
    <web-app>
    <context-param>
    <param-name>email</param-name>
    <param-value>sdfsd</param-value>
    </context-param>
    </web_app>
  2. getServletConfig().getInitParamter(“email”),只对该servlet可用

    1
    2
    3
    4
    5
    6
    7
    8
    <web-app>
    <servlet>
    <init-param>
    <param-name>email</param-name>
    <param-value>sdfsd</param-value>
    </init-param>
    </servlet>
    </web_app>
  3. 监听Webapp,ServletContextListener

Python Requests库中Max retries问题

Posted on 2019-06-25 | In Python

查日志的时候发现如下报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Traceback (most recent call last):
File "./app/business_api/business_api.py", line 87, in callback_function
response = requests.post('xxx'+json.dumps(results), timeout=self.timeout)
File "/usr/lib/python3.6/site-packages/requests/api.py", line 116, in post
return request('post', url, data=data, json=json, **kwargs)
File "/usr/lib/python3.6/site-packages/requests/api.py", line 60, in request
return session.request(method=method, url=url, **kwargs)
File "/usr/lib/python3.6/site-packages/requests/sessions.py", line 524, in request
resp = self.send(prep, **send_kwargs)
File "/usr/lib/python3.6/site-packages/requests/sessions.py", line 637, in send
r = adapter.send(request, **kwargs)
File "/usr/lib/python3.6/site-packages/requests/adapters.py", line 516, in send
raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='xxx', port=8094): Max retries exceeded with url: xxx (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7f882011f390>: Failed to establish a new connection: [Errno -3] Try again',))

网上查了一下“Max retries” ,一般出现在爬虫的场景,http连接数过多,因为requests默认使用了keep-alive,所以要在header增加{“Connect”:”close”},但是出现问题的场景是后台服务,从阿里云监控上看该时段的tcp连接数也没有很大的波峰。
参考了requests的高级功能-重试机制这篇文章所说,requests是有重试机制的,使用重试会导致返回的错误为MaxRetriesError,而不是确切的异常。
网上暂时没查到[Errno -3] Try again 这个错误具体原因。

其他参考:
关于python爬虫的深坑:requests抛出异常Max retries exceeded with url
requests库的Failed to establish a new connection

《Head First Java》笔记

Posted on 2019-06-24 | In JAVA

基本概念

  1. 实例变量有默认值,局部变量没有默认值。
  2. JAVA中都是值的(拷贝)传递。
  3. 引用到对象的变量并不是对象的容器,而是类似指向对象的指针/地址。所有引用的大小都一样。

类与对象

  1. ArrayList&一般数组区别:
    • 一般数组创建时确定大小、以索引存放、使用特殊语法[]
    • ArrayList是参数化的 ArrayList
  2. final:
    • final的变量代表你不能改变它的值。
    • final的Method代表你不能覆盖这个方法
    • final的类代表你不能继承它
  3. 方法的重载与多态无关,两个方法名称相同但参数不同。
  4. 抽象类代表类必须要被extent,抽象方法代表方法必须要被覆盖。抽象方法没有实体。必须实现所有的抽象方法。
  5. 编译器是根据引用类型(定义变量时指定的类型)判断哪个方法可以调用,而不是根据对象的实际类型。
  6. extent只能有1个,implement(接口)可以有多个。
  7. 一个类可以有多个构造函数,但参数必须不一样。
  8. 函数构造链:创建新对象时,所有继承下来的构造函数都会执行。
  9. 调用父类构造函数super().使用this()调用对象本身的其他构造函数。两者不能同时使用。
  10. 静态变量:所有实例都共享的,且只会在类第一次载入的时候初始化。
    • 静态变量 == Python类变量, 非静态变量即实例变量。
    • 静态变量会在该类的任何对象创建之前就完成初始化
    • 静态变量会在该类的任何静态方法执行之前就初始化
    • 常量:静态的final变量 (常量名应全大写)

变量

  1. 所有对象存活于可垃圾回收的堆(heap)中。
  2. 变量存活空间:
    • 如果在方法体内定义的,这时候就是在栈上分配的
    • 如果是类的成员变量,这时候就是在堆上分配的
    • 如果是类的静态成员变量,在方法区(堆,各个线程共享的内存区域)上分配的

异常处理

  1. JAVA虚拟机只会从头开始往下找到第一个符合范围的异常处理块
  2. 可以抛出多重异常
    1
    public void function name () throw PantException, LionException{}

序列化与反序列化

  1. 序列化保存对象的状态

    1
    2
    3
    4
    FileOutputStream fileStream = new FileOutputStream (“TEST.ser”);
    ObjectOutputStream os = new ObjectOutputStream(fileStream);
    os.writeObject(characterOne);
    os.close();
  2. 反序列化

    1
    2
    3
    4
    FileInputStream fileStream = new FileInputStream(“TEST.ser”);
    ObjectInputStream os = new ObjectInputStream(fileStream);
    Object one = os.readObject();
    GameChara one = (GameChara) one;
  3. 类如果可序列化,必须implements Serializable。如果某实例变量不应该被序列化,定义为transient。

网络编程

  1. 网络 java.net
  2. 建立socket

    1
    2
    3
    4
    Socket s = new Socket(’127.0.0.0’,8888);
    InputStream steam = new InputStream(s.getInputStream());
    BufferReader reader = new BufferReader(stream);
    String msg = reader.readLine();
  3. 建立socket Server

    1
    2
    ServerSocket ser = new ServerSocket(8888)
    Socket s = ser.accept()
  4. 线程Thread 需要一个实现Runnable接口的类,类需要实现run()方法

  5. 线程命名 setName()
  6. synchronized修饰方法使其每次只能被一个线程存取。锁是在对象上。没有处理死锁机制。

集合

  1. 集合
    • TreeSet:有序防重复
    • HashMap
    • LinkedList:针对经常插入删除
    • HashSet
    • LinkedHaskMap:记住元素插入顺序

泛型

  1. 泛型的类代表类的声明用到类型函数,泛型的方法代表方法的声明特征用到类型函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class GenericTest {
    // 这个类是个泛型类
    public class Generic<T>{
    private T key;

    public Generic(T key) {
    this.key = key;
    }
    }
    // 这是一个泛型方法。
    // 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
    // 这个T可以出现在这个泛型方法的任意位置.
    // 泛型的数量也可以为任意多个
    public <T,K> K showKeyName(Generic<T> container){
    ...
    }
    }

    Java 泛型

  2. 相等
    • 引用的相等性 ==
    • 对象的相等性 equals() ,必须要覆盖hashCode()方法保证两个对象有相同的hashcode
  3. TreeSet集合中的元素必须实现comparable类型,或者使用重载,取用comparator参数构造函数创建TreeSet
  4. 万用字符 <?>

    1
    2
    3
    public void takeThing(ArrayList<? extent Animal> list)
    // 相当于
    public <T extent Animal> void takeThing (ArrayList<T> list)
  5. <> 使编译器验证类型安全,不用到运行时才去验证

参考:
《Head First JAVA》
重学Java-一个Java对象到底占多少内存

值传递?引用传递?傻傻分不清楚

Posted on 2019-06-22

# 不可变对象
基本类型的不可变对象,对它进行运算操作其实就是创建新的对象,然后将原先的变量名绑定到新的对象上

  1. 值传递
  2. 引用传递

# 可变对象

  1. 值传递:将实参的值完全复制一份给形参,改变形参的值并不会影响外部实参的值。
    这种方式对于可变对象比较少见,因为对复杂类型的参数进行完全复制,会付出额外的空间与时间代价。
    php中数组,会将数组完全复制一份进行传递。

  2. 引用传递:将实参的引用给形参,两个变量指向同一个对象,即引用传递
    特征:对于变量的成员进行修改时,会直接影响原变量;而如果对传递过来的变量进行重新赋值,则不会影响原变量,并且此后再修改变量的成员,也不会影响原变量。
    大多数编程语言都采用这种方式传递复杂类型的参数。

  3. 指针传递(实际上也是值传递):此时指针的值是另一个变量的地址
    特征:无论是对于变量成员的修改,还是对变量重新赋值,都会影响到原对象。
    php/JAVA 中的 & ,使两个变量实际上使用了同一个变量容器(又名: zval )

不同语言

  1. Python中只有引用传递
  2. PHP有如上面所说的特点
  3. javascript
  4. JAVA中只有值传递

Python描述符

Posted on 2019-06-21 | In Python

描述符

定义

  1. 一个描述符是一个有“绑定行为”的对象属性(object attribute),它的访问控制会被描述器协议方法重写。
  2. 任何定义了 __get__, __set__ 或者 __delete__ 任一方法的类称为描述符类,其实例对象便是一个描述符,这些方法称为描述符协议。
    • 同时定义了 __get__ 和 __set__ 的描述符称为 数据描述符(data descriptor);仅定义了 __get__ 的称为 非数据描述符(non-data descriptor) 。
    • 两者区别在于:如果 obj.__dict__ 中有与描述符同名的属性,若描述符是数据描述符,则优先调用描述符,若是非数据描述符,则优先使用 obj.__dict__ 中属性。
  3. 当对一个实例属性进行访问时,Python 1、类属性,2、数据描述符,3、实例属性,4、非数据描述符,5、__getattr__()顺序进行查找,如果查找到目标属性并发现是一个描述符,Python 会调用描述符协议来改变默认的控制行为。
  4. 描述符是 @property,@classmethod,@staticmethod 和 super 的底层实现机制。

描述符方法

1
2
3
__get__(self, instance, owner)
__set__(self, instance, value)
__delete__(self, instance)

1. 使用类方法创建描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Descriptor(object):
def __init__(self):
self._name = ''

def __get__(self, instance, owner):
print("Getting: %s" % self._name)
return self._name

def __set__(self, instance, name):
print("Setting: %s" % name)
self._name = name.title()

def __delete__(self, instance):
print("Deleting: %s" %self._name)
del self._name

class Person(object):
name = Descriptor()

2. 使用属性类型创建描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person(object):
def __init__(self):
self._name = ''

def fget(self):
print("Getting: %s" % self._name)
return self._name

def fset(self, value):
print("Setting: %s" % value)
self._name = value.title()

def fdel(self):
print("Deleting: %s" %self._name)
del self._name

# property() 的语法是 property(fget=None, fset=None, fdel=None, doc=None)
name = property(fget, fset, fdel, "I'm the property.")

3. 使用属性修饰符(proprty)创建描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person(object):
def __init__(self):
self._name = ''

@property
def name(self):
print "Getting: %s" % self._name
return self._name

@name.setter
def name(self, value):
print "Setting: %s" % value
self._name = value.title()

@name.deleter
def name(self):
print ">Deleting: %s" % self._name
del self._name

staticMethod 原理

@staticmethod : when this method is called, we don’t pass an instance of the class to it (as we normally do with methods). This means you can put a function inside a class but you can’t access the instance of that class (this is useful when your method does not use the instance).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class StaticMethod(object):
def __init__(self, f):
self.f = f

def __get__(self, obj, objtype=None):
return self.f


class E(object):
@StaticMethod
def f( x):
return x

print(E.f('staticMethod Test'))
ee = E()
print(ee.f('ee static'))

classmethod 原理

A class method receives the class as implicit first argument, just like an instance method receives the instance. To declare a class method, use this idiom.
A class method can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class. If a class method is called for a derived class, the derived class object is passed as the implied first argument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ClassMethod(object):
def __init__(self, f):
self.f = f

def __get__(self, obj, owner=None):
if owner is None:
owner = type(obj)

def newfunc(*args):
return self.f(owner, *args)

return newfunc

class E(object):
name = 'name'
@ClassMethod
def f(cls,x):
return x + cls.name

print(E.f('classMethod Test'))

参考:
Python 描述符简介
Python 描述符(Descriptor) 附实例
【案例讲解】Python为什么要使用描述符?
python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解
python装饰器、描述符模拟源码实现

分布式锁

Posted on 2019-06-19 | In 系统

分布式锁含义

分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

分布式锁特点

  1. 互斥性:在任意一个时刻,只有一个客户端持有锁,但是分布式锁需要保证在不同节点的不同线程的互斥。
  2. 可重入性: 同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
  3. 锁超时:即便持有锁的客户端崩溃或者其他意外事件,锁仍然可以被获取。
  4. 高效,高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
  5. 支持阻塞和非阻塞(可选):和ReentrantLock一样支持lock和trylock以及tryLock(long timeOut)。
  6. 支持公平锁和非公平锁(可选):公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。

分布式锁的实现

MySql

  1. 基于表主键唯一做分布式锁(乐观锁):利用主键唯一的特性,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。
  2. 基于表字段版本号做分布式锁(悲观锁)
  3. 基于数据库排他锁做分布式锁:在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁 (注意: InnoDB 引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。这里我们希望使用行级锁,就要给要执行的方法字段名添加索引,值得注意的是,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。)。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。我们可以认为获得排他锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,通过connection.commit()操作来释放锁。但是如果MySQL 认为全表扫效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁,会有问题;排他锁也会占用数据库连接数。

Memcache(add)

ZooKeeper(临时节点)

Redis(setnx),衍生开源库Redisson

  1. 原生redis(setnx):

    1. value=当前线程id,同时设置过期时间。如果设置失败,判断值是否当前线程id

      1
      set lockName value ex 5 nx
    2. 释放锁的时候需要先比对锁值是否等于当前线程id,是才del锁。(保证原子操作,lua)

    3. 保证锁过期时间大于业务处理时间。(开一个线程刷新过期时间)
  2. Redisson库:

    自研分布式锁:如谷歌的Chubby。

分布式锁的安全问题

  1. 长时间的GC pause 利用自增序列的方案。
  2. 时钟发生跳跃
    对于Redis服务器如果其时间发生了向跳跃,那么肯定会影响我们锁的过期时间,那么我们的锁过期时间就不是我们预期的了,也会出现client1和client2获取到同一把锁,那么也会出现不安全。
    通过人为调整、NTP自动调整。
  3. 长时间的网络I/O
    和GC的STW很像,也就是我们这个获取了锁之后我们进行网络调用,其调用时间由可能比我们锁的过期时间都还长,那么也会出现不安全的问题。
    可以通过控制网络调用的超时时间解决这个问题。

参考:
redis系列:分布式锁
再有人问你分布式锁,这篇文章扔给他
拜托,面试请不要再问我Redis分布式锁的实现原理!【石杉的架构笔记】

1234…10
Angel Teng

Angel Teng

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