【Python使用】嘿馬頭條項目從到完整開發(fā)教程第9篇:緩存,1 緩存穿透【附代碼文檔】

本教程的知識點為:簡介 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ù)庫連接異常烫堤。

解決方案:

  1. 約定:對于返回為NULL的依然緩存,對于拋出異常的返回不進行緩存,注意不要把拋異常的也給緩存了。采用這種手段的會增加我們緩存的維護成本塔逃,需要在插入緩存的時候刪除這個空緩存讯壶,當然我們可以通過設置較短的超時時間來解決這個問題。
cache_null
  1. 制定一些規(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).
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末鼓鲁,一起剝皮案震驚了整個濱河市蕴轨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骇吭,老刑警劉巖橙弱,帶你破解...
    沈念sama閱讀 212,080評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異燥狰,居然都是意外死亡棘脐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評論 3 385
  • 文/潘曉璐 我一進店門龙致,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛀缝,“玉大人,你說我怎么就攤上這事目代∏海” “怎么了?”我有些...
    開封第一講書人閱讀 157,630評論 0 348
  • 文/不壞的土叔 我叫張陵榛了,是天一觀的道長俘闯。 經常有香客問我,道長忽冻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,554評論 1 284
  • 正文 為了忘掉前任此疹,我火速辦了婚禮僧诚,結果婚禮上,老公的妹妹穿的比我還像新娘蝗碎。我一直安慰自己湖笨,他們只是感情好,可當我...
    茶點故事閱讀 65,662評論 6 386
  • 文/花漫 我一把揭開白布蹦骑。 她就那樣靜靜地躺著慈省,像睡著了一般。 火紅的嫁衣襯著肌膚如雪眠菇。 梳的紋絲不亂的頭發(fā)上边败,一...
    開封第一講書人閱讀 49,856評論 1 290
  • 那天,我揣著相機與錄音捎废,去河邊找鬼笑窜。 笑死,一個胖子當著我的面吹牛登疗,可吹牛的內容都是我干的排截。 我是一名探鬼主播嫌蚤,決...
    沈念sama閱讀 39,014評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼断傲!你這毒婦竟也來了脱吱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,752評論 0 268
  • 序言:老撾萬榮一對情侶失蹤认罩,失蹤者是張志新(化名)和其女友劉穎箱蝠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猜年,經...
    沈念sama閱讀 44,212評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡抡锈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,541評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乔外。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片床三。...
    茶點故事閱讀 38,687評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖杨幼,靈堂內的尸體忽然破棺而出撇簿,到底是詐尸還是另有隱情,我是刑警寧澤差购,帶...
    沈念sama閱讀 34,347評論 4 331
  • 正文 年R本政府宣布四瘫,位于F島的核電站,受9級特大地震影響欲逃,放射性物質發(fā)生泄漏找蜜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,973評論 3 315
  • 文/蒙蒙 一稳析、第九天 我趴在偏房一處隱蔽的房頂上張望洗做。 院中可真熱鬧,春花似錦彰居、人聲如沸诚纸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畦徘。三九已至,卻和暖如春抬闯,著一層夾襖步出監(jiān)牢的瞬間井辆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評論 1 266
  • 我被黑心中介騙來泰國打工溶握, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掘剪,地道東北人。 一個月前我還...
    沈念sama閱讀 46,406評論 2 360
  • 正文 我出身青樓奈虾,卻偏偏與公主長得像夺谁,于是被迫代替她去往敵國和親廉赔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,576評論 2 349

推薦閱讀更多精彩內容