本教程的知识点为:简介 1. 内容 2. 目标 产品效果 ToutiaoWeb虚拟机使用说明 数据库 理解ORM 作用 思考: 使用ORM的方式选择 数据库 SQLAlchemy操作 1 新增 2 查询 all() 数据库 分布式ID 1 方案选择 2 头条 使用雪花算法 (代码 toutiao-backend/common/utils/snowflake) 数据库 Redis 1 Redis事务 基本事务指令 Python客户端操作 Git工用流 调试方法 JWT认证方案 JWT & JWS & JWE Json Web Token(JWT) OSS对象存储 存储 需求 方案 使用 缓存 缓存架构 多级缓存 头条项目的方案 缓存数据 缓存 缓存问题 1 缓存 2 缓存 头条项目缓存与存储设计 APScheduler定时任务 定时修正统计数据 RPC RPC简介 1. 什么是RPC RPC 编写客户端 头条首页新闻推荐接口编写 即时通讯 即时通讯简介 即时通讯 Socket.IO 1 简介 优点: 缺点: Elasticsearch 简介与原理 1 简介 属于面向文档的数据库 2 搜索的原理——倒排索引(反向索引)、分析、相关性排序 Elasticsearch 文档 索引文档(保存文档数据) 获取指定文档 判断文档是否存在 单元测试 为什么要测试 测试的分类 什么是单元测试 断言方法的使用:

完整笔记资料代码->: https://gitee.com/yinuo112/Backend/tree/master/Python/嘿马头条项目从到完整开发教程/note.md

感兴趣的小伙伴可以自取哦~

全套教程部分目录:


部分文件图片:

数据库

  • 数据库设计

  • SQLAlchemy

  • 数据库理论

  • 分布式ID

  • Redis

Redis

1 Redis事务

基本事务指令

Redis提供了一定的事务支持,可以保证一组操作原子执行不被打断,但是如果执行中出现错误,事务不能回滚,Redis未提供回滚支持。

  • multi 开启事务
  • exec 执行事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 100
QUEUED
127.0.0.1:6379> set b 200
QUEUED
127.0.0.1:6379> get a
QUEUED
127.0.0.1:6379> get b
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "100"
4) "200"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

使用multi开启事务后,操作的指令并未立即执行,而是被redis记录在队列中,等待一起执行。当执行exec命令后,开始执行事务指令,最终得到每条指令的结果。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set c 300
QUEUED
127.0.0.1:6379> hgetall a
QUEUED
127.0.0.1:6379> set d 400
QUEUED
127.0.0.1:6379> get d
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
4) "400"
127.0.0.1:6379>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

如果事务中出现了错误,事务并不会终止执行,而是只会记录下这条错误的信息,并继续执行后面的指令。所以事务中出错不会影响后续指令的执行。

Python客户端操作

在Redis的Python 客户端库redis-py中,提供了pipeline (称为流水线 或 管道),该工具的作用是:

  • 在客户端统一收集操作指令
  • 补充上multi和exec指令,当作一个事务发送到redis服务器执行
from redis import StrictRedis
r = StrictRedis.from_url('redis://127.0.0.1:6381/0')
pl = r.pipeline()
pl.set('a', 100)
pl.set('b', 200)
pl.get('a')
pl.get('b')
ret = pl.execute()
print(ret) #  [True, True, b'100', b'200']
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
watch监视

若在构建的redis事务在执行时依赖某些值,可以使用watch对数据值进行监视。

127.0.0.1:6379> set stock 100
OK
127.0.0.1:6379> watch stock
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby stock -1
QUEUED
127.0.0.1:6379> incr sales
QUEUED
127.0.0.1:6379> exec
1) (integer) 99
2) (integer) 1
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

事务exec执行前被监视的stock值未变化,事务正确执行。

127.0.0.1:6379> set stock 100
OK
127.0.0.1:6379> watch stock
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby stock -1
QUEUED
127.0.0.1:6379> incr sales
QUEUED
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

此时在另一个客户端修改stock的值,执行

127.0.0.1:6379> incrby stock -2
(integer) 98
  • 1.
  • 2.

当第一个客户端再执行exec时

127.0.0.1:6379> exec
(nil)
  • 1.
  • 2.

表明事务需要监视的stock值发生了变化,事务不能执行了。

注意:Redis Cluster 集群不支持事务

2 Redis持久化

redis可以将数据写入到磁盘中,在停机或宕机后,再次启动redis时,将磁盘中的备份数据加载到内存中恢复使用。这是redis的持久化。持久化有如下两种机制。

RDB 快照持久化

redis可以将内存中的数据写入磁盘进行持久化。在进行持久化时,redis会创建子进程来执行。

redis默认开启了快照持久化机制。

进行快照持久化的时机如下

  • 定期触发

redis的配置文件

  
  
#   save  
  
  
  #
  #   Will save the DB if both the given number of seconds and the given
  #   number of write operations against the DB occurred.
  #
  #   In the example below the behaviour will be to save:
  #   after 900 sec (15 min) if at least 1 key changed
  #   after 300 sec (5 min) if at least 10 keys changed
  #   after 60 sec if at least 10000 keys changed
  #
  #   Note: you can disable saving completely by commenting out all "save" lines.
  #
  #   It is also possible to remove all the previously configured save
  #   points by adding a save directive with a single empty string argument
  #   like in the following example:
  #
  #   save ""

  save 900 1
  save 300 10
  save 60 10000
  • 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.
  • BGSAVE

执行BGSAVE命令,手动触发RDB持久化

  • SHUTDOWN

关闭redis时触发

AOF 追加文件持久化

redis可以将执行的所有指令追加记录到文件中持久化存储,这是redis的另一种持久化机制。

redis默认未开启AOF机制。

redis可以通过配置如下项开启AOF机制

appendonly yes  # 是否开启AOF
appendfilename "appendonly.aof"  # AOF文件
  • 1.
  • 2.

AOF机制记录操作的时机

  
  
# appendfsync always  # 每个操作都写到磁盘中
  
  
appendfsync everysec  # 每秒写一次磁盘,默认
  
  
# appendfsync no  # 由操作系统决定写入磁盘的时机
  
  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

使用AOF机制的缺点是随着时间的流逝,AOF文件会变得很大。但redis可以压缩AOF文件。

结合使用

redis允许我们同时使用两种机制,通常情况下我们会设置AOF机制为everysec 每秒写入,则最坏仅会丢失一秒内的数据。

3 Redis高可用

为了保证redis最大程度上能够使用,redis提供了主从同步+Sentinel哨兵机制。

Sentinel 哨兵

[

redis提供的哨兵是用来看护redis实例进程的,可以自动进行故障转移,其功能如下:

  • Monitoring. Sentinel constantly checks if your master and slave instances are working as expected.
  • Notification. Sentinel can notify the system administrator, another computer programs, via an API, that something is wrong with one of the monitored Redis instances.
  • Automatic failover. If a master is not working as expected, Sentinel can start a failover process where a slave is promoted to master, the other additional slaves are reconfigured to use the new master, and the applications using the Redis server informed about the new address to use when connecting.
  • Configuration provider. Sentinel acts as a source of authority for clients service discovery: clients connect to Sentinels in order to ask for the address of the current Redis master responsible for a given service. If a failover occurs, Sentinels will report the new address

在redis安装后,会自带sentinel哨兵程序,修改sentinel.conf配置文件

bind 127.0.0.1
port 26380
daemonize yes
logfile /var/log/redis-sentinel.log
sentinel monitor mymaster 127.0.0.1 6380 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • sentinel monitor mymaster 127.0.0.1 6380 2 说明

    • mymaster 为sentinel监护的redis主从集群起名
    • 127.0.0.1 6300 为主从中任一台机器地址
    • 2 表示有两台以的sentinel认为某一台redis宕机后,才会进行自动故障转移。

启动方式:

redis-sentinel sentinel.conf
  • 1.
高可用方案注意事项
  • 至少三个sentinel以上
  • sentinel要分散运行在不同的机器上
Python客户端使用
  
  
# redis 哨兵
  
  
REDIS_SENTINELS = [
    ('127.0.0.1', '26380'),
    ('127.0.0.1', '26381'),
    ('127.0.0.1', '26382'),
]
REDIS_SENTINEL_SERVICE_NAME = 'mymaster'

from redis.sentinel import Sentinel
_sentinel = Sentinel(REDIS_SENTINELS)
redis_master = _sentinel.master_for(REDIS_SENTINEL_SERVICE_NAME)
redis_slave = _sentinel.slave_for(REDIS_SENTINEL_SERVICE_NAME)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

使用示例

  
  
# 读数据,master读不到去slave读
  
  
try:
    real_code = redis_master.get(key)
except ConnectionError as e:
    real_code = redis_slave.get(key)

  
  
# 写数据,只能在master里写
  
  
try:
    current_app.redis_master.delete(key)
except ConnectionError as e:
    logger.error(e)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

4 Redis集群

[

Reids Cluster集群方案,内部已经集成了sentinel机制来做到高可用。

Python客户端
  
  
# redis 集群
  
  
REDIS_CLUSTER = [
    {'host': '127.0.0.1', 'port': '7000'},
    {'host': '127.0.0.1', 'port': '7001'},
    {'host': '127.0.0.1', 'port': '7002'},
]

from rediscluster import StrictRedisCluster
redis_cluster = StrictRedisCluster(startup_nodes=REDIS_CLUSTER)

  
  
# 可以将redis_cluster就当作普通的redis客户端使用
  
  
redis_master.delete(key)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
注意:
  • redis cluster 不支持事务
  • redis cluster 不支持多键操作,如mset

5 用途

  • 缓存

  • 持久存储

    • 数据库的统计冗余字段 放到 redis中保存

6 相关补充阅读

  • [
  • 《Redis实践》 (Redis in action)

Git工用流

Git工用流

Gitflow工作流

Gitflow工作流通过为功能开发、发布准备和维护分配独立的分支,让发布迭代过程更流畅。严格的分支模型也为大型项目提供了一些非常必要的结构。

这节介绍的Gitflow工作流借鉴自在nvie的Vincent Driessen

Gitflow工作流定义了一个围绕项目发布的严格分支模型。虽然比功能分支工作流复杂几分,但提供了用于一个健壮的用于管理大型项目的框架。

Gitflow工作流没有用超出功能分支工作流的概念和命令,而是为不同的分支分配一个明确的角色,并定义分支之间如何和什么时候进行交互。 除了使用功能分支,在做准备、维护和记录发布时,也定义了各自的分支。 当然你可以用上功能分支工作流所有的好处:Pull Requests、隔离实验性开发和更高效的协作。

1 工作方式

Gitflow工作流仍然用中央仓库作为所有开发者的交互中心。和其它的工作流一样,开发者在本地工作并push分支到要中央仓库中。

2 历史分支

相对于使用仅有的一个master分支,Gitflow工作流使用两个分支来记录项目的历史。master分支存储了正式发布的历史,而develop分支作为功能的集成分支。 这样也方便master分支上的所有提交分配一个版本号。

剩下要说明的问题围绕着这2个分支的区别展开。

3 功能分支

每个新功能位于一个自己的分支,这样可以push到中央仓库以备份和协作。 但功能分支不是从master分支上拉出新分支,而是使用develop分支作为父分支。当新功能完成时,合并回develop分支。 新功能提交应该从不直接与master分支交互。

注意,从各种含义和目的上来看,功能分支加上develop分支就是功能分支工作流的用法。但Gitflow工作流没有在这里止步。

4 发布分支

一旦develop分支上有了做一次发布(或者说快到了既定的发布日)的足够功能,就从develop分支上checkout一个发布分支。 新建的分支用于开始发布循环,所以从这个时间点开始之后新的功能不能再加到这个分支上—— 这个分支只应该做Bug修复、文档生成和其它面向发布任务。 一旦对外发布的工作都完成了,发布分支合并到master分支并分配一个版本号打好Tag。 另外,这些从新建发布分支以来的做的修改要合并回develop分支。

使用一个用于发布准备的专门分支,使得一个团队可以在完善当前的发布版本的同时,另一个团队可以继续开发下个版本的功能。 这也打造定义良好的开发阶段(比如,可以很轻松地说,『这周我们要做准备发布版本4.0』,并且在仓库的目录结构中可以实际看到)。

常用的分支约定:

用于新建发布分支的分支: develop
用于合并的分支: master
分支命名: release-* 或 release/*
  • 1.
  • 2.
  • 3.
5 维护分支

维护分支或说是热修复(hotfix)分支用于给产品发布版本(production releases)快速生成补丁,这是唯一可以直接从master分支fork出来的分支。 修复完成,修改应该马上合并回master分支和develop分支(当前的发布分支),master分支应该用新的版本号打好Tag

Bug修复使用专门分支,让团队可以处理掉问题而不用打断其它工作或是等待下一个发布循环。 你可以把维护分支想成是一个直接在master分支上处理的临时发布。

6 示例

下面的示例演示本工作流如何用于管理单个发布循环。假设你已经创建了一个中央仓库。

创建开发分支

第一步为master分支配套一个develop分支。简单来做可以本地创建一个空的develop分支,push到服务器上:

git branch develop
git push -u origin develop
  • 1.
  • 2.

以后这个分支将会包含了项目的全部历史,而master分支将只包含了部分历史。其它开发者这时应该克隆中央仓库,建好develop分支的跟踪分支:

git clone ssh://user@host/path/to/repo.git
git checkout -b develop origin/develop
  • 1.
  • 2.

现在每个开发都有了这些历史分支的本地拷贝。

小红和小明开始开发新功能

这个示例中,小红和小明开始各自的功能开发。他们需要为各自的功能创建相应的分支。新分支不是基于master分支,而是应该基于develop分支:

git checkout -b some-feature develop
  • 1.

他们用老套路添加提交到各自功能分支上:编辑、暂存、提交:

git status
git add <some-file>
git commit
  • 1.
  • 2.
  • 3.
小红完成功能开发

添加了提交后,小红觉得她的功能OK了。如果团队使用Pull Requests,这时候可以发起一个用于合并到develop分支。 否则她可以直接合并到她本地的develop分支后push到中央仓库:

git pull origin develop
git checkout develop
git merge some-feature
git push
git branch -d some-feature
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

第一条命令在合并功能前确保develop分支是最新的。注意,功能决不应该直接合并到master分支。 冲突解决方法和集中式工作流一样。

小红开始准备发布

这个时候小明正在实现他的功能,小红开始准备她的第一个项目正式发布。 像功能开发一样,她用一个新的分支来做发布准备。这一步也确定了发布的版本号:

git checkout -b release-0.1 develop
  • 1.

这个分支是清理发布、执行所有测试、更新文档和其它为下个发布做准备操作的地方,像是一个专门用于改善发布的功能分支。

只要小红创建这个分支并push到中央仓库,这个发布就是功能冻结的。任何不在develop分支中的新功能都推到下个发布循环中。

小红完成发布

一旦准备好了对外发布,小红合并修改到master分支和develop分支上,删除发布分支。合并回develop分支很重要,因为在发布分支中已经提交的更新需要在后面的新功能中也要是可用的。 另外,如果小红的团队要求Code Review,这是一个发起Pull Request的理想时机。

git checkout master
git merge release-0.1
git push
git checkout develop
git merge release-0.1
git push
git branch -d release-0.1
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

发布分支是作为功能开发(develop分支)和对外发布(master分支)间的缓冲。只要有合并到master分支,就应该打好Tag以方便跟踪。

git tag -a 0.1 -m "Initial public release" master
git push --tags
  • 1.
  • 2.

Git有提供各种勾子(hook),即仓库有事件发生时触发执行的脚本。 可以配置一个勾子,在你push中央仓库的master分支时,自动构建好版本,并对外发布。

最终用户发现Bug

对外版本发布后,小红小明一起开发下一版本的新功能,直到有最终用户开了一个Ticket抱怨当前版本的一个Bug。 为了处理Bug,小红(或小明)从master分支上拉出了一个维护分支,提交修改以解决问题,然后直接合并回master分支:

git checkout -b issue-#001 master
  
  
# Fix the bug
  
  
git checkout master
git merge issue-#001
git push
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

就像发布分支,维护分支中新加这些重要修改需要包含到develop分支中,所以小红要执行一个合并操作。然后就可以安全地删除这个分支了:

git checkout develop
git merge issue-#001
git push
git branch -d issue-#001
  • 1.
  • 2.
  • 3.
  • 4.

Git总结

1 Gitflow工作流分支

分支作用
master迭代历史分支
dev集成最新开发特性的活跃分支
f_xxxfeature 功能特性开发分支
b_xxxbug修复分支
r_xxxrelease 版本发包分支

2 Confict冲突解决

  • 方式一

    1. 获取最新代码

    git fetch

    
    
    
    2. 对比代码
    
    ```shell
    git diff origin/dev
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    1. 修改冲突地方后提交并推送代码

    2. 发起合并请求

    • 方式二

      1. 拉取并合并最新代码

      git pull origin dev

      
      
      
      2. 查看冲突代码
      
      ```shell
      git status
      
      • 1.
      • 2.
      • 3.
      • 4.
      • 5.
      • 6.
      • 7.
      1. 修改冲突代码后提交并推送代码

      2. 发起合并请求

      头条项目目录

      toutiao-backend
      ├── common                                  # 存放用户端、自媒体端、MIS端等应用的公共代码
      │   ├── cache                                # 缓存层的实现代码
      │   │   ├── __init__.py
      │   ├── celery_tasks                        # celery的异步任务代码
      │   │   ├── __init__.py
      │   │   ├── main.py                            # celery的启动代码
      │   │   └── sms                                # 发送短信的异步任务    
      │   ├── models                                # 数据库ORM模型类相关
      │   │   ├── init.sql                        # 数据库建标SQL语句
      │   │   └── ....py                            # 模型类文件
      │   ├── rpc                                    # gRPC接口代码文件
      │   │   ├── __init__.py
      │   │   ├── chatbot                            # 聊天机器人接口
      │   │   └── recommend                        # 推荐系统接口
      │   ├── settings                            # 工程默认配置代码
      │   │   ├── __init__.py
      │   │   ├── default.py
      │   └── utils                                # 工具代码
      │       ├── __init__.py
      │       ├── constants.py                    # 工程常量
      │       ├── converters.py                    # flask转换器
      │       ├── decorators.py                    # 自定义装饰器
      │       ├── dysms                            # 阿里大于短信库
      │       ├── gt3                                # 极验验证码库
      │       ├── jwt_util.py                        # JWT封装库
      │       ├── limiter.py                        # flask限流扩展
      │       ├── logging.py                        # 日志配置
      │       ├── output.py                        # flask-restful输出格式定制
      │       ├── parser.py                        # 自定义flask-restful RequstParser验证方法
      │       ├── snowflake                        # 分布式ID雪花算法实现
      │       └── storage.py                        # 七牛对象存储上传方法封装
      ├── docs                                    # 开发文档记录
      ├── im                                        # 即时通讯代码目录        
      ├── mis                                        # MIS后台接口代码目录
      ├── mp                                        # 自媒体平台接口代码目录
      ├── requirements.txt                        # 项目依赖包
      ├── schedule                                # 定时任务
      ├── scripts                                    # 脚本目录
      └── toutiao                                    # 用户端接口代码目录
          ├── __init__.py                            # flask app工厂函数文件
          ├── main.py                                # 用户端后端启动文件
          └── resources                            # 视图目录
              ├── __init__.py            
              ├── news                            # 文章蓝图
              ├── notice                            # 系统公告蓝图
              ├── search                            # 搜索蓝图
              └── user                            # 用户蓝图
                  ├── __init__.py                    # 蓝图初始化文件
                  ├── constants.py                # 常量文件
                  └── passport.py                    # 蓝图视图文件
      
      • 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.