【Python使用】嘿马头条项目从到完整开发教程第4篇:数据库,分布式ID【附代码文档】

60 阅读1分钟

教程全知识点简介:APScheduler使用 1 安装 2 使用方式 3 调度器 Scheduler 4 执行器 executors 5 触发器 Trigger RPC简介 1. 什么是RPC 2. 背景与用途 3. 概念说明 4. 优缺点 Protocol Buffers 1 文档结构 2 注释 3 数据类型 3.2 枚举 4 消息类型 4.1 字段编号 4.2 指定字段规则 4.3 添加更多消息类型 4.4 保留字段 4.5 默认值 4.6 嵌套类型 编写客户端 头条首页新闻推荐接口编写 即时通讯简介 需求场景 传统的推送实现 Socket.IO 1 简介 2 Python服务器端开发 安装 创建服务器 事件处理 Elasticsearch 简介与原理 2 搜索的原理——倒排索引(反向索引)、分析、相关性排序 概念与集群 概念 IK中文分析器 索引与类型 索引 文档 索引文档(保存文档数据) 获取指定文档 判断文档是否存在 更新文档 删除文档 Logstash导入数据 查询 1 基本查询 2 高级查询 头条全文检索实现 elasticsearch python客户端使用 头条项目搜索接口视图实现 添加ES新文章索引数据 联想提示 1 拼写纠错 2 自动补全 头条suggest查询实现 思路 实现 单元测试 为什么要测试 测试的分类 单元测试的基本写法 Gunicorn Supervisor 课程简介 1. 课程内容 2. 目标 ToutiaoWeb虚拟机使用说明 Pycharm远程开发 产品与开发 1 产品介绍 2 原型图与UI图 3 技术架构 4 开发 数据库 理解ORM 作用 思考: 使用ORM的方式选择 SQLAlchemy映射构建 3 数据库连接设置 4 模型类字段与选项 5 构建模型类映射 分布式ID 1 方案选择 2 黑马头条 Twitter's Snowflake algorithm implementation which is used to generate distributed IDs. 64位ID的划分 最大取值计算 移位偏移计算 序号循环掩码 Twitter元年时间戳 Redis 2 Redis持久化 Git工用流 Gitflow工作流 1 工作方式 2 历史分支 3 功能分支 4 发布分支 5 维护分支 6 示例 调试方法 JWT & JWS & JWE Json Web Token(JWT) JSON Web Signature(JWS) JWT的Python库 用例 头条项目封装 头条项目实施方案 需求 OSS对象存储 七牛云存储 缓存 缓存架构 缓存数据 缓存数据的保存方式 缓存有效期与淘汰策略 有效期 TTL (Time to live) 缓存淘汰 eviction 缓存问题 1 缓存穿透 2 缓存雪崩 头条项目缓存与存储设计 缓存设计 1 User Cache 3 Article Cache 4 Announcement Cache 持久存储设计 1 阅读历史 2 搜索历史 3 统计数据

👉👉👉   gitee.com/yinuo112/Ba…

数据库

  • 数据库设计

  • SQLAlchemy

  • 数据库理论

  • 分布式ID

  • Redis

分布式ID

1 方案选择

  • UUID

UUID是通用唯一识别码(Universally Unique Identifier)的缩写,开放软件基金会(OSF)规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成UUID。

UUID是由128位二进制组成,一般转换成十六进制,然后用String表示。

550e8400-e29b-41d4-a716-446655440000

UUID的优点:

  • 通过本地生成,没有经过网络I/O,性能较快
  • 无序,无法预测他的生成顺序。(当然这个也是他的缺点之一)

UUID的缺点:

  • 128位二进制一般转换成36位的16进制,太长了只能用String存储,空间占用较多。

  • 不能生成递增有序的数字

  • 数据库主键自增

大家对于唯一标识最容易想到的就是主键自增,这个也是我们最常用的方法。例如我们有个订单服务,那么把订单id设置为主键自增即可。

  • 单独数据库 记录主键值

  • 业务数据库分别设置不同的自增起始值和固定步长,如

第一台 start 1  step 9 
第二台 start 2  step 9 
第三台 start 3  step 9

优点:

  • 简单方便,有序递增,方便排序和分页

缺点:

  • 分库分表会带来问题,需要进行改造。

  • 并发性能不高,受限于数据库的性能。

  • 简单递增容易被其他人猜测利用,比如你有一个用户服务用的递增,那么其他人可以根据分析注册的用户ID来得到当天你的服务有多少人注册,从而就能猜测出你这个服务当前的一个大概状况。

  • 数据库宕机服务不可用。

  • Redis

熟悉Redis的同学,应该知道在Redis中有两个命令Incr,IncrBy,因为Redis是单线程的所以能保证原子性。

优点:

  • 性能比数据库好,能满足有序递增。

缺点:

  • 由于redis是内存的KV数据库,即使有AOF和RDB,但是依然会存在数据丢失,有可能会造成ID重复。

  • 依赖于redis,redis要是不稳定,会影响ID生成。

  • 雪花算法-Snowflake

Snowflake是Twitter提出来的一个算法,其目的是生成一个64bit的整数:

  • 1bit:一般是符号位,不做处理
  • 41bit:用来记录时间戳,这里可以记录69年,如果设置好起始时间比如今年是2018年,那么可以用到2089年,到时候怎么办?要是这个系统能用69年,我相信这个系统早都重构了好多次了。
  • 10bit:10bit用来记录机器ID,总共可以记录1024台机器,一般用前5位代表数据中心,后面5位是某个数据中心的机器ID
  • 12bit:循环位,用来对同一个毫秒之内产生不同的ID,12位可以最多记录4095个,也就是在同一个机器同一毫秒最多记录4095个,多余的需要进行等待下毫秒。

上面只是一个将64bit划分的标准,当然也不一定这么做,可以根据不同业务的具体场景来划分,比如下面给出一个业务场景:

  • 服务目前QPS10万,预计几年之内会发展到百万。
  • 当前机器三地部署,上海,北京,深圳都有。
  • 当前机器10台左右,预计未来会增加至百台。

这个时候我们根据上面的场景可以再次合理的划分62bit,QPS几年之内会发展到百万,那么每毫秒就是千级的请求,目前10台机器那么每台机器承担百级的请求,为了保证扩展,后面的循环位可以限制到1024,也就是2^10,那么循环位10位就足够了。

机器三地部署我们可以用3bit总共8来表示机房位置,当前的机器10台,为了保证扩展到百台那么可以用7bit 128来表示,时间位依然是41bit,那么还剩下64-10-3-7-41-1 = 2bit,还剩下2bit可以用来进行扩展。

时钟回拨

因为机器的原因会发生时间回拨,我们的雪花算法是强依赖我们的时间的,如果时间发生回拨,有可能会生成重复的ID,在我们上面的nextId中我们用当前时间和上一次的时间进行判断,如果当前时间小于上一次的时间那么肯定是发生了回拨,算法会直接抛出异常.

2 黑马头条

使用雪花算法 (代码 toutiao-backend/common/utils/snowflake)
  
  
# Twitter's Snowflake algorithm implementation which is used to generate distributed IDs.
  
  
  
  
# https://github.com/twitter-archive/snowflake/blob/snowflake-2010/src/main/scala/com/twitter/service/snowflake/IdWorker.scala
  
  

import time
import logging

class InvalidSystemClock(Exception):
    """
    时钟回拨异常
    """
    pass

  
  
# 64位ID的划分
  
  
WORKER_ID_BITS = 5
DATACENTER_ID_BITS = 5
SEQUENCE_BITS = 12

  
  
# 最大取值计算
  
  
MAX_WORKER_ID = -1 ^ (-1 << WORKER_ID_BITS)  # 2**5-1 0b11111
MAX_DATACENTER_ID = -1 ^ (-1 << DATACENTER_ID_BITS)

  
  
# 移位偏移计算
  
  
WOKER_ID_SHIFT = SEQUENCE_BITS
DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS
TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS

  
  
# 序号循环掩码
  
  
SEQUENCE_MASK = -1 ^ (-1 << SEQUENCE_BITS)

  
  
# Twitter元年时间戳
  
  
TWEPOCH = 1288834974657


logger = logging.getLogger('flask.app')


class IdWorker(object):
    """
    用于生成IDs
    """

    def __init__(self, datacenter_id, worker_id, sequence=0):
        """
        初始化
        :param datacenter_id: 数据中心(机器区域)ID
        :param worker_id: 机器ID
        :param sequence: 其实序号
        """
        # sanity check
        if worker_id > MAX_WORKER_ID or worker_id < 0:
            raise ValueError('worker_id值越界')

        if datacenter_id > MAX_DATACE