本教程的知識點為:簡介 1. 內容 2. 目標 產品效果 ToutiaoWeb虛擬機使用說明 數(shù)據(jù)庫 理解ORM 作用 思考: 使用ORM的方式選擇 數(shù)據(jù)庫 SQLAlchemy操作 1 新增 2 查詢 all() 數(shù)據(jù)庫 分布式ID 1 方案選擇 2 頭條 使用雪花算法 (代碼 toutiao-backend/common/utils/snowflake) 數(shù)據(jù)庫 Redis 1 Redis事務 基本事務指令 Python客戶端操作 Git工用流 調試方法 JWT認證方案 JWT & JWS & JWE Json Web Token(JWT) OSS對象存儲 存儲 需求 方案 使用 緩存 緩存架構 多級緩存 頭條項目的方案 緩存數(shù)據(jù) 緩存 緩存問題 1 緩存 2 緩存 頭條項目緩存與存儲設計 APScheduler定時任務 定時修正統(tǒng)計數(shù)據(jù) RPC RPC簡介 1. 什么是RPC RPC 編寫客戶端 頭條首頁新聞推薦接口編寫 即時通訊 即時通訊簡介 即時通訊 Socket.IO 1 簡介 優(yōu)點: 缺點: Elasticsearch 簡介與原理 1 簡介 屬于面向文檔的數(shù)據(jù)庫 2 搜索的原理——倒排索引(反向索引)廉油、分析糟港、相關性排序 Elasticsearch 文檔 索引文檔(保存文檔數(shù)據(jù)) 獲取指定文檔 判斷文檔是否存在 單元測試 為什么要測試 測試的分類 什么是單元測試 斷言方法的使用:
完整筆記資料代碼:https://gitee.com/yinuo112/Backend/tree/master/Python/嘿馬頭條項目從到完整開發(fā)教程/note.md
感興趣的小伙伴可以自取哦~
全套教程部分目錄:
部分文件圖片:
緩存
緩存問題
1 緩存穿透
緩存只是為了緩解數(shù)據(jù)庫壓力而添加的一層保護層而昨,當從緩存中查詢不到我們需要的數(shù)據(jù)就要去數(shù)據(jù)庫中查詢了盆赤。如果被黑客利用扮匠,頻繁去訪問緩存中沒有的數(shù)據(jù)饲宿,那么緩存就失去了存在的意義仰坦,瞬間所有請求的壓力都落在了數(shù)據(jù)庫上,這樣會導致數(shù)據(jù)庫連接異常烫堤。
解決方案:
- 約定:對于返回為NULL的依然緩存,對于拋出異常的返回不進行緩存,注意不要把拋異常的也給緩存了。采用這種手段的會增加我們緩存的維護成本塔逃,需要在插入緩存的時候刪除這個空緩存讯壶,當然我們可以通過設置較短的超時時間來解決這個問題。
cache_null
- 制定一些規(guī)則過濾一些不可能存在的數(shù)據(jù)湾盗,小數(shù)據(jù)用BitMap伏蚊,大數(shù)據(jù)可以用布隆過濾器,比如你的訂單ID 明顯是在一個范圍1-1000格粪,如果不是1-1000之內的數(shù)據(jù)那其實可以直接給過濾掉躏吊。
cache_filter
2 緩存雪崩
緩存雪崩是指緩存不可用或者大量緩存由于超時時間相同在同一時間段失效,大量請求直接訪問數(shù)據(jù)庫帐萎,數(shù)據(jù)庫壓力過大導致系統(tǒng)雪崩比伏。
cache_down
解決方案:
1、給緩存加上一定區(qū)間內的隨機生效時間疆导,不同的key設置不同的失效時間赁项,避免同一時間集體失效。比如以前是設置10分鐘的超時時間澈段,那每個Key都可以隨機8-13分鐘過期悠菜,盡量讓不同Key的過期時間不同。
2败富、采用多級緩存悔醋,不同級別緩存設置的超時時間不同,及時某個級別緩存都過期兽叮,也有其他級別緩存兜底芬骄。
3、利用加鎖或者隊列方式避免過多請求同時對服務器進行讀寫操作鹦聪。
頭條項目緩存與存儲設計
緩存設計
1 User Cache
用戶資料
key | 類型 | 說明 | 舉例 |
---|---|---|---|
user:{user_id}:profile | string | user_id用戶的數(shù)據(jù)緩存账阻,包括手機號、用戶名椎麦、頭像 |
用戶擴展資料
key | 類型 | 說明 | 舉例 |
---|---|---|---|
user:{user_id}:profilex | string | user_id用戶的性別 生日 |
用戶狀態(tài)
key | 類型 | 說明 | 舉例 |
---|---|---|---|
user:{user_id}:status | string | user_id用戶是否可用 |
key | 類型 | 說明 | 舉例 |
---|---|---|---|
user:{user_id}:following | zset | user_id的關注用戶 | [{user_id, update_time}] |
key | 類型 | 說明 | 舉例 |
---|---|---|---|
user:{user_id}:fans | zset | user_id的粉絲用戶 | [{user_id, update_time}] |
key | 類型 | 說明 | 舉例 |
---|---|---|---|
user:{user_id}:art | zset | user_id的文章 | [{article_id, create_time}] |
2 Comment Cache
key | 類型 | 說明 | 舉例 |
---|---|---|---|
art:{article_id}:comm | zset | article_id文章的評論數(shù)據(jù)緩存宰僧,值為comment_id | [{comment_id, create_time}] |
comm:{comment_id}:reply | zset | comment_id評論的評論數(shù)據(jù)緩存,值為comment_id | [{'comment_id', create_time}] |
comm:{comment_id} | string | 緩存的評論數(shù)據(jù) |
3 Article Cache
key | 類型 | 說明 | 舉例 |
---|---|---|---|
ch:all | string | 所有頻道 | |
user:{user_id}:ch | string | 用戶頻道 | |
ch:{channel_id}:art:top | zset | 置頂文章 | [{article_id, sequence}] |
art:{article_id}:info | string | 文章的基本信息 | |
art:{article_id}:detail | string | 文章的內容 |
4 Announcement Cache
key | 類型 | 說明 | 舉例 |
---|---|---|---|
announce | zset | [{'json data', announcement_id}] | |
announce:{announcement_id} | string | 'json data' |
持久存儲設計
1 閱讀歷史
key | 類型 | 說明 | 舉例 |
---|---|---|---|
user:{user_id}:his:reading | zset | [{article_id, read_time}] |
2 搜索歷史
key | 類型 | 說明 | 舉例 |
---|---|---|---|
user:{user_id}:his:searching | zset | [{keyword, search_time}] |
3 統(tǒng)計數(shù)據(jù)
key | 類型 | 說明 | 舉例 |
---|---|---|---|
count:art:reading | zset | 文章閱讀數(shù)量 | [{article_id, count}] |
count:user:arts | zset | 用戶發(fā)表文章數(shù)量 | [{user_id, count}] |
count:art:collecting | zset | 文章收藏數(shù)量 | [{article_id, count}] |
count:art:liking | zset | 文章點贊數(shù)量 | [{article_id, count}] |
count:art:comm | zset | 文章評論數(shù)量 | [{article_id, count}] |
頭條項目緩存實現(xiàn)
以用戶信息數(shù)據(jù)緩存為例
common/cache/user.py
from flask import current_app
from redis.exceptions import RedisError
import json
from sqlalchemy.orm import load_only
from models.user import User
from . import constants
class UserProfileCache(object):
"""
用戶資料信息緩存
"""
def __init__(self, user_id):
self.key = 'user:{}:info'.format(user_id)
self.user_id = user_id
def save(self):
"""
查詢數(shù)據(jù)庫保存緩存記錄
:return:
"""
r = current_app.redis_cluster
# 查詢數(shù)據(jù)庫
user = User.query.options(load_only(User.name,
User.profile_photo,
User.introduction,
User.certificate)).filter_by(id=self.user_id).first()
# 判斷結果是否存在
# 保存到redis中
if user is None:
try:
r.setex(self.key, constants.USER_NOT_EXISTS_CACHE_TTL, -1)
except RedisError as e:
current_app.logger.error(e)
return None
else:
cache_data = {
'name': user.name,
'photo': user.profile_photo,
'intro': user.introduction,
'certi': user.certificate
}
try:
r.setex(self.key, constants.UserProfileCacheTTL.get_val(), json.dumps(cache_data))
except RedisError as e:
current_app.logger.error(e)
return cache_data
def get(self):
"""
獲取用戶的緩存數(shù)據(jù)
:return:
"""
r = current_app.redis_cluster
# 先查詢redis
try:
ret = r.get(self.key)
except RedisError as e:
current_app.logger.error(e)
ret = None
if ret is not None:
# 如果存在記錄观挎,讀取
if ret == b'-1':
# 判斷記錄值琴儿,如果為-1,表示用戶不存在
return None
# 如果不為-1嘁捷,需要json轉換造成,返回
else:
return json.loads(ret)
else:
# 如果記錄不存在,
cache_data = self.save()
return cache_data
def clear(self):
"""
清除用戶緩存
"""
try:
current_app.redis_cluster.delete(self.key)
except RedisError as e:
current_app.logger.error(e)
def exists(self):
"""
判斷用戶是否存在
"""
# 查詢redis
r = current_app.redis_cluster
try:
ret = r.get(self.key)
except RedisError as e:
current_app.logger.error(e)
ret = None
# 如果緩存記錄存在
if ret is not None:
if ret == b'-1':
# 如果緩存記錄為-1 雄嚣,表示用戶不存在
return False
else:
# 如果緩存記錄不為-1晒屎, 表示用戶存在
return True
# 如果緩存記錄不存在喘蟆,查詢數(shù)據(jù)庫
else:
cache_data = self.save()
if cache_data is not None:
return True
else:
return False
common/cache/constants.py
class BaseCacheTTL(object):
"""
緩存有效期
為防止緩存雪崩,在設置緩存有效期時采用設置不同有效期的方案
通過增加隨機值實現(xiàn)
"""
TTL = 0 # 由子類設置
MAX_DELTA = 10 * 60 # 隨機的增量上限
@classmethod
def get_val(cls):
return cls.TTL + random.randrange(0, cls.MAX_DELTA)
class UserProfileCacheTTL(BaseCacheTTL):
"""
用戶資料數(shù)據(jù)緩存時間, 秒
"""
TTL = 30 * 60
接口示例
定義獲取當前用戶信息的接口
GET /v1_0/user
返回JSON
在toutiao/resources/user/__init__.py
中定義路由
user_api.add_resource(profile.CurrentUserResource, '/v1_0/user', endpoint='CurrentUser')
在toutiao/resources/ user/profile.py 中
class CurrentUserResource(Resource):
"""
用戶自己的數(shù)據(jù)
"""
method_decorators = [login_required]
def get(self):
"""
獲取當前用戶自己的數(shù)據(jù)
"""
user_data = cache_user.UserProfileCache(g.user_id).