本教程的知识点为:简介 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/嘿马头...
感兴趣的小伙伴可以自取哦~
全套教程部分目录:
部分文件图片:
APScheduler定时任务
定时修正统计数据
在toutiao-backend/toutiao/__init__.py
中添加APScheduler调度器对象
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.executors.pool import ThreadPoolExecutor
def create_app(config, enable_config_file=False):
...
# 添加定时任务APScheduler
executors = {
'default': ThreadPoolExecutor(10)
}
app.scheduler = BackgroundScheduler(executors=executors)
from .schedule.statistic import fix_statistics
# 每天3点执行
app.scheduler.add_job(fix_statistics, 'cron', hour=3, args=[app])
# 立即执行,用于测试
# app.scheduler.add_job(fix_statistics, 'date', args=[app])
app.scheduler.start()
...
在toutiao-backend/toutiao
中新建schedule目录用于存放定时任务
toutiao-backend/toutiao/schedule/statistics.py
from cache import statistic as cache_statistic
def fix_process(count_storage_cls):
"""
修复处理方法
"""
# 进行数据库查询
ret = count_storage_cls.db_query()
# 设置redis数据
count_storage_cls.reset(ret)
def fix_statistics(flask_app):
"""
修正统计数据
"""
with flask_app.app_context():
fix_process(cache_statistic.UserArticlesCountStorage)
fix_process(cache_statistic.UserFollowingsCountStorage)
common/cache/statistic.py
class CountStorageBase(object):
"""
统计数量存储的父类
"""
...
@classmethod
def reset(cls, db_query_ret):
"""
由定时任务调用的重置数据方法
"""
# 设置redis的存储记录
pl = current_app.redis_master.pipeline()
pl.delete(cls.key)
# zadd(key, score1, val1, score2, val2, ...)
# 方式一
# for data_id, count in db_query_ret:
# pl.zadd(cls.key, count, data_id)
# 方式二
redis_data = []
for data_id, count in db_query_ret:
redis_data.append(count)
redis_data.append(data_id)
# redis_data = [count1, data_id1, count2, data_id2, ..]
pl.zadd(cls.key, *redis_data)
# pl.zadd(cls.key, count1, data_id1, count2, data_id2, ..]
pl.execute()
class UserArticlesCountStorage(CountStorageBase):
"""
用户文章数量
"""
key = 'count:user:arts'
@staticmethod
def db_query():
ret = db.session.query(Article.user_id, func.count(Article.id)) \
.filter(Article.status == Article.STATUS.APPROVED).group_by(Article.user_id).all()
return ret
class UserFollowingsCountStorage(CountStorageBase):
"""
用户关注数量
"""
key = 'count:user:followings'
@staticmethod
def db_query():
ret = db.session.query(Relation.user_id, func.count(Relation.target_user_id)) \
.filter(Relation.relation == Relation.RELATION.FOLLOW)\
.group_by(Relation.user_id).all()
return ret
RPC
RPC简介
1. 什么是RPC
远程过程调用(英语:Remote Procedure Call,缩写为 RPC,也叫远程程序调用)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用。
2. 背景与用途
在单台计算机中,我们可以通过程序调用来传递控制和数据;或者说通过程序调用,我们可以将多个程序组成一个整体来实现某个功能。
如果将这种调用机制推广到多台彼此间可以进行网络通讯的计算机,由多台计算机中的多个程序组成一个整体来实现某个功能,这也是可以的。调用的一方(发起远程过程调用,然后调用这方的环境挂起,参数通过网络传递给被调用方,被调用的一方执行程序,当程序执行完成后,产生的结果再通过网络回传给调用的一方,调用的一方恢复继续执行。这样一种原型思想,就是我们所说的RPC远程过程调用。
RPC这种思想最早可以追溯到1976年,RPC的发展到今天已经40年有余了。
如今的计算机应用中,单机性能上很难承受住产品的压力,需要不断扩充多台机器来提升整体的性能。同时为了充分利用这些集群里的计算机,需要对其从架构上进行划分,以提供不同的服务,服务间相互调用完成整个产品的功能。RPC就能帮助我们解决这些服务间的信息传递和调用。
3. 概念说明
关于RPC的概念,我们可以从广义和狭义来分别进行理解。
广义
我们可以将所有通过网络来进行通讯调用的实现统称为RPC。
按照这样来理解的话,那我们发现HTTP其实也算是一种RPC实现。
狭义
区别于HTTP的实现方式,在传输的数据格式上和传输的控制上独立实现。比如在机器间通讯传输的数据不采用HTTP协议的方式(分为起始行、header、body三部份),而是使用自定义格式的二进制方式。
我们更多时候谈到的RPC都是指代这种狭义上的理解。
4. 优缺点
相比于传统HTTP的实现而言:
优点
- 效率高
- 发起RPC调用的一方,在编写代码时可忽略RPC的具体实现,如同编写本地函数调用一样
缺点
- 通用性不如HTTP好 因为传输的数据不是HTTP协议格式,所以调用双方需要专门实现的通信库,对于不同的编程开发语言,都要有相关实现。而HTTP作为一个标准协议,大部分的语言都已有相关的实现,通用性更好。
HTTP更多的面向用户与产品服务器的通讯。
RPC更多的面向产品内部服务器间的通讯。 thrift
RPC结构
RPC的设计思想是力图使远程调用中的通讯细节对于使用者透明,调用双方无需关心网络通讯的具体实现。因而实现RPC要进行一定的封装。
RPC原理上是按如下结构流程进行实现的。
流程:
- 调用者(Caller, 也叫客户端、Client)以本地调用的方式发起调用;
- Client stub(客户端存根,可理解为辅助助手)收到调用后,负责将被调用的方法名、参数等打包编码成特定格式的能进行网络传输的消息体;
- Client stub将消息体通过网络发送给对端(服务端)
- Server stub(服务端存根,同样可理解为辅助助手)收到通过网络接收到消息后按照相应格式进行拆包解码,获取方法名和参数;
- Server stub根据方法名和参数进行本地调用;
- 被调用者(Callee,也叫Server)本地调用执行后将结果返回给server stub;
- Server stub将返回值打包编码成消息,并通过网络发送给对端(客户端);
- Client stub收到消息后,进行拆包解码,返回给Client;
- Client得到本次RPC调用的最终结果。
gRPC
简介
- gRPC是由Google公司开源的高性能RPC框架。
- gRPC支持多语言
gRPC原生使用C、Java、Go进行了三种实现,而C语言实现的版本进行封装后又支持C++、C#、Node、ObjC、 Python、Ruby、PHP等开发语言
- gRPC支持多平台
支持的平台包括:Linux、Android、iOS、MacOS、Windows
- gRPC的消息协议使用Google自家开源的Protocol Buffers协议机制(proto3) 序列化
- gRPC的传输使用HTTP/2标准,支持双向流和连接多路复用
架构
C语言实现的gRPC支持多语言,其架构如下
使用方法
- 使用Protocol Buffers(proto3)的IDL接口定义语言定义接口服务,编写在文本文件(以
.proto
为后缀名)中。 - 使用protobuf编译器生成服务器和客户端使用的stub代码
- 编写补充服务器和客户端逻辑代码
RPC
Protocol Buffers
Protocol Buffers 是一种与语言无关,平台无关的可扩展机制,用于序列化结构化数据。使用Protocol Buffers 可以一次定义结构化的数据,然后可以使用特殊生成的源代码轻松地在各种数据流中使用各种语言编写和读取结构化数据。
现在有许多框架等在使用Protocol Buffers。gRPC也是基于Protocol Buffers。 Protocol Buffers 目前有2和3两个版本号。
在gRPC中推荐使用proto3版本。
1 文档结构
1) Protocol Buffers版本
Protocol Buffers文档的第一行非注释行,为版本申明,不填写的话默认为版本2。
syntax = "proto3";
或者
syntax = "proto2";
2)Package包
Protocol Buffers 可以声明package,来防止命名冲突。 Packages是可选的。
package foo.bar;
message Open { ... }
使用的时候,也要加上命名空间,
message Foo {
...
foo.bar.Open open = 1;
...
}
注意:对于Python而言,package
会被忽略处理,因为Python中的包是以文件目录来定义的。
3)导入
Protocol Buffers 中可以导入其它文件消息等,与Python的import类似。
import “myproject/other_protos.proto”;
4)定义各种消息和服务
消息messge是用来定义数据的,服务service是用来gRPC的方法的。
2 注释
Protocol Buffers 提供以下两种注释方式。
// 单行注释
//
//
//
/*
多行注释
*/
3 数据类型
3.1 基本数据类型
.proto | 说明 | Python |
---|---|---|
double | float | |
float | float | |
int32 | 使用变长编码,对负数编码效率低, 如果你的变量可能是负数,可以使用sint32 | int |
int64 | 使用变长编码,对负数编码效率低,如果你的变量可能是负数,可以使用sint64 | int/long |
uint32 | 使用变长编码 | int/long |
uint64 | 使用变长编码 | int/long |
sint32 | 使用变长编码,带符号的int类型,对负数编码比int32高效 | int |
sint64 | 使用变长编码,带符号的int类型,对负数编码比int64高效 | int/long |
fixed32 | 4字节编码, 如果变量经常大于2^{28} 的话,会比uint32高效 | int |
fixed64 | 8字节编码, 如果变量经常大于2^{56} 的话,会比uint64高效 | int/long |
sfixed32 | 4字节编码 | int |
sfixed64 | 8字节编码 | int/long |
bool | bool | |
string | 必须包含utf-8编码或者7-bit ASCII text | str |
bytes | 任意的字节序列 | str |
3.2 枚举
在 Proto Buffers 中,我们可以定义枚举和枚举类型,
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
枚举定义在一个消息内部或消息外部都是可
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。