python-git-jwt-jwt-jws-jwe-json-web-tokenjwt-oss-1-2-apscheduler-rpc-rpc-1-rpc-rpc-socketio-1-elasticsearch-1-2-elasticsearch">本教程的知识点为:简介 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 文档 索引文档(保存文档数据) 获取指定文档 判断文档是否存在 单元测试 为什么要测试 测试的分类 什么是单元测试 断言方法的使用:
pythonnotemd">完整笔记资料代码:https://gitee.com/yinuo112/Backend/tree/master/Python/嘿马头条项目从到完整开发教程/note.md
感兴趣的小伙伴可以自取哦~
全套教程部分目录:
部分文件图片:
RPC
编写客户端
在toutiao-backend/common/rpc目录下新建client.py
python linenums">import grpc
import reco_pb2
import reco_pb2_grpc
import timedef feed_articles(stub):# 构建rpc调用的调用参数user_request = reco_pb2.UserRequest()user_request.user_id = '1'user_request.channel_id = 1user_request.article_num = 10user_request.time_stamp = round(time.time()*1000)# 通过stub进行方法调用,并接收调用返回值ret = stub.user_recommend(user_request)print('ret={}'.format(ret))def run():"""rpc客户端调用的方法"""# 使用with语句连接rpc服务器with grpc.insecure_channel('127.0.0.1:8888') as channel:# 创建调用rpc远端服务的辅助对象stubstub = reco_pb2_grpc.UserRecommendStub(channel)# 通过stub进行rpc调用feed_articles(stub)if __name__ == '__main__':run()
头条首页新闻推荐接口编写
在toutiao-backend/toutiao/resources/news/article.py中编写
python linenums">from rpc import reco_pb2, reco_pb2_grpcclass ArticleListResource(Resource):"""获取推荐文章列表数据"""def _feed_articles(self, channel_id, timestamp, feed_count):"""获取推荐文章:param channel_id: 频道id:param feed_count: 推荐数量:param timestamp: 时间戳:return: [{article_id, trace_params}, ...], timestamp"""user_request = reco_pb2.UserRequest()user_request.user_id = g.user_id or 'annoy'user_request.channel_id = channel_iduser_request.article_num = feed_countuser_request.time_stamp = round(time.time() * 1000)stub = reco_pb2_grpc.UserRecommendStub(current_app.rpc_reco)ret = stub.user_recommend(user_request)return ret.recommends, ret.time_stampdef get(self):"""获取文章列表"""qs_parser = RequestParser()qs_parser.add_argument('channel_id', type=parser.channel_id, required=True, location='args')qs_parser.add_argument('timestamp', type=inputs.positive, required=True, location='args')args = qs_parser.parse_args()channel_id = args.channel_idtimestamp = args.timestampper_page = constants.DEFAULT_ARTICLE_PER_PAGE_MINtry:feed_time = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(time.time()))except Exception:return {'message': 'timestamp param error'}, 400results = []# 获取推荐文章列表feeds, pre_timestamp = self._feed_articles(channel_id, timestamp, per_page)# 查询文章for feed in feeds:article = cache_article.ArticleInfoCache(feed.article_id).get()if article:article['pubdate'] = feed_timearticle['trace'] = {'click': feed.track.click,'collect': feed.track.collect,'share': feed.track.share,'read': feed.track.read}results.append(article)return {'pre_timestamp': pre_timestamp, 'results': results}
即时通讯
即时通讯简介
即时通讯(Instant Messaging)是一种基于互联网的即时交流消息的业务。
类型:
-
在线push
-
适用:web页面 和 App
-
自己构建IM服务器
- 使用WebSocket
- 采用成熟的框架方案Socket.IO
- 对于App还可自己封装socket
-
使用第三方IM服务商提供的服务
-
离线push
-
适用:App
- 对于iOS,使用APNs
- 对于andorid,使用FCM(国外)或第三方IM服务商提供的服务
提供第三方IM服务的服务商有:
- 网易云信
- 融云
- 环信
- LeanCloud
下面只针对 「在线推送」 的自建方案来展开讲解。
需求场景
服务端需要主动推送消息给客户端,例如
- 用户下了订单,需要在运营管理后台向运营人员推送新订单通知
- 用户A关注了用户B,系统需要向用户B推送提示消息
- 即时聊天
传统的推送实现
HTTP/1.x 不支持服务器主动推送,只能在客户端发起请求后做出回应。 (HTTP/2支持服务器主动推送,但HTTP/2 还未全面实施)
- 轮询
轮询是在特定的的时间间隔(如每1秒),由客户端对服务器发出HTTP请求,了解服务器有没有新的信息,然后由服务器告知有无新数据或返回最新的数据给客户端。
缺点:
- 效率低下,浪费资源
必须不停连接,或者连接始终打开,但传输HTTP请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
-
Comet (基于长连接)
-
长轮询 长轮询是在打开一条连接以后保持,等待服务器推送来数据再关闭的方式。
- iframe流 iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。
缺点:
依然需要反复发出请求,而且长连接也会消耗服务器资源。
WebSocket
HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
在2008年诞生,2011年成为国际标准。
现在基本所有浏览器都已经支持了。
WebSocket是一种在单个TCP连接上进行全双工通信的协议。在WebSocket API中,浏览器和服务器只需要完成一次握手(不是指建立TCP连接的那个三次握手,是指在建立TCP连接后传输一次握手数据),两者之间就直接可以创建持久性的连接,并进行双向数据传输。
Websocket使用ws或wss的统一资源标志符,类似于HTTPS,其中wss表示在TLS之上的Websocket。如:
python linenums">ws://example.com/wsapi
wss://secure.example.com/
Websocket使用和 HTTP 相同的 TCP 端口,可以绕过大多数防火墙的限制。默认情况下,Websocket协议使用80端口;运行在TLS之上时,默认使用443端口。
握手协议
WebSocket 是独立的、创建在 TCP 上的协议。 报文
Websocket 通过 HTTP/1.1 协议的101状态码进行握手。
为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。
一个典型的Websocket握手请求如下:
客户端请求
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin:
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
服务器回应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
- Connection必须设置Upgrade,表示客户端希望连接升级。
- Upgrade字段必须设置Websocket,表示希望升级到Websocket协议。
- Sec-WebSocket-Key是随机的字符串,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一个特殊字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算SHA-1摘要,之后进行BASE-64编码,将结果做为“Sec-WebSocket-Accept”头的值,返回给客户端。如此操作,可以尽量避免普通HTTP请求被误认为Websocket协议。
- Sec-WebSocket-Version 表示支持的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均应当弃用。
- Origin字段是可选的,通常用来表示在浏览器中发起此Websocket连接所在的页面,类似于Referer。但是,与Referer不同的是,Origin只包含了协议和主机名称。
- 其他一些定义在HTTP协议中的字段,如Cookie等,也可以在Websocket中使用。
优点
- 较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。
- 更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。
- 保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。 更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。
- 可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
- 更好的压缩效果。相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。
- 没有同源限制,客户端可以与任意服务器通信。
- 可以发送文本,也可以发送二进制数据。