緩存的一些問題和一些加密算法【緩存問題】

緩存

1 需求背景

  • 緩存不是必須的,是爲了提高性能而增長的
  • 目標: 減小磁盤數據庫的查詢,好比mysql的查詢 ,更多的從內存中讀取數據
    • mysql查詢 一般在1s左右 (幾百毫秒, 0.xxs),1s以上一般認爲是慢查詢
    • redis 支持操做的性能 1s能夠支持1w+ 操做(更高 可達10W+)
  • 場景
    • 前提: 讀取頻繁
      • 數據不常常變化,基本必定會作緩存處理
      • 數據可能變化頻繁,若是數據是產品的核心數據(好比評論數據),能夠考慮構建緩存, 緩存時間短,即時緩存5分鐘,也能減小很大程度的數據庫查詢操做,能夠提高性能

2 緩存架構

數據存在哪?html

多級緩存html5

  • 本地緩存
    • 全局變量保存
    • orm框架 queryset 查詢集(查詢結果集) 起到本地緩存的做用
      • django orm
      • sqlalchemy
  • 外部緩存
    • 能夠構建多級
    • 外部存儲
      • redis
      • memcached

3 緩存數據

保存哪些數據 ? 數據以什麼形式(類型)保存?python

3.1 緩存的數據內容

  • 一個數值mysql

    • 手機短信驗證碼
    • 好比用戶的狀態數據 user:status -> 0 / 1
  • 數據庫記錄redis

    • 不以單一視圖單獨考慮,而是考慮不少視圖可能都會用到一些公共數據,就把這些公共的數據緩存,哪一個視圖用到,哪一個視圖本身讀取緩存取數據 ,(好比用戶的我的信息,文章的信息)
    • 比較通用,緩存一個數據能夠被多個視圖利用,節省空間算法

    • 方式:sql

      • Caching at the object level 緩存數據對象級別數據庫

        • 通用
        mysql 中有用戶的我的信息表
        每條記錄 是一個用戶的數據    一個數據實體
        
        user:1 ->  user_id ,name  mobile profile_photo intro  certi
        user:20 ->  user_id ,name  mobile profile_photo intro  certi
      • Caching at the database query level 緩存數據庫查詢級別django

        • 相比緩存數據對象級別 不太通用,只適用於比較複雜的查詢,才考慮使用
        sql = 'select  * from ..inner join  where ... group by  order by  limit'  -> query_results
        
        
        hash(sql) -> 'wicwiugfiwuegfwiugiw238'  md5(sql)
        
        緩存
        數據名稱                    數據內容
        'wicwiugfiwuegfwiugiw238' ->  query_results
        
        使用的時候
        sql ->  md5(sql) -> 'wicwiugfiwuegfwiugiw238'
  • 一個視圖的響應結果json

    • 考慮單一的視圖 ,只只對特定的視圖結果進行緩存
    @route('/articles')
      @cache(exipry=30*60)
      def get_articles():
          ch = request.args.get('ch')
          articles = Article.query.all()
          for article in articles:
              user = User.query.filter_by(id=article.user_id).first()
              comment = Comment.query.filter_by(article_id=article.id).all()
            results = {...} # 格式化輸出
         return results
    
    # /articles?ch=123   視圖的結果resuls 緩存
    # 下一次再訪問  ‘/articles?ch=123’
  • 一個頁面
    • 只針對 h5頁面 (html5) 網頁

    • 方式

      • 若是是服務器端渲染 (先後端不分離)

        @route('/articles')
          @cache(exipry=30*60)
          def get_articles():
              ch = request.args.get('ch')
              articles = Article.query.all()
              for article in articles:
                  user = User.query.filter_by(id=article.user_id).first()
                  comment = Comment.query.all()
             results = {...}
             return render_template('article_temp', results)
        
          #  redis
          # '/artciels?ch=1':  html
      • 頁面靜態化 算是一種頁面緩存方式

3.2 緩存數據保存形式

針對的是外部緩存 redis

  • 字符串形式

    user:1 ->  user_id ,name  mobile profile_photo intro  certi
    user1 -> User()對象 -> user1_dict
    
    key          value
    user:1   ->  json.dumps(user1_dict)
                 pickle.dumps()
    
    json:
       1. 只能接受 列表 字典 bytes類型
       2. json轉換成字符串 效率速度慢
    pickle :
        1. 基本支持python中的全部類型,(包括自定義的類的對象)
        2. json轉換成字符串 效率速度 快
    • 優勢: 保存一組數據的時候,存儲佔用的空間 相比其餘類型可能節省空間
    • 缺點:整存整取 ,若是想獲取其中的單一字段 不是很方便,須要總體取出 再序列化或反序列化, 更新某個字段 相似 , 不靈活
  • 非字符串形式

    • list set hash zset
    • 須要針對特定的數據來選型
    user:1 ->  user_id ,name  mobile profile_photo intro  certi
    user1 -> User()對象 -> user1_dict
    
    key    value 
    user:1  ->  hash {
            name: xxx,
            moible: xxx
            photo: xxx
    }
    • 優勢: 能夠針對特定的字段進行讀寫,相對靈活
    • 缺點: 保存一組數據的時候,佔用的空間相比字符串會稍大

4 緩存數據的有效期 TTL (time to live)

緩存數據必定要設置有效期,緣由/做用:

  • 即時清理能夠節省空間
  • 保證數據的一致性,(弱一致性) ,保證mysql中的數據與redis中的數據,在更新數據以後還能一直, 雖然在必定的時間內(緩存數據的有效期) redis與mysql中的數據不一樣 ,可是過了有效期後 redis會清理數據, 再次查詢數據時 會造成新的緩存數據,redis與mysql又相同了

4.1 redis的有效期策略

通用的有效期策略:

  • 定時過時

    set a 100  有效期 10min
    set b 100  有效期 20min

    開啓一個計時器計時,當有效期到達以後 清理數據, 能夠理解爲每一個數據都要單獨維護一個計時器

    缺點: 耗費性能

  • 惰性過時

    保存數據 設置有效期後 不主動維護這個數據的有效期,不計時,只有在再次訪問這個數據(讀寫)的時候,判斷數據是否到期,若是到期清理並返回空,若是沒到期,返回數據

  • 按期過時

    • 週期性的檢查哪些數據過時哪些數據沒過時,好比每100ms判斷哪些數據過時,若是有過時的數據,進行清理

Redis的有效期策略 : 惰性過時 + 按期過時

  • redis實現按期過時的時候,還不是查詢全部數據,而是每100ms 隨機選出一些數據判斷是否過時,再過100ms 再隨機選出一些判斷

思考:

若是在redis中保存了一條數據,設置有效期爲10min,可是數據設置以後 再無操做, 請問 10min以後 這條數據是否還在redis的內存中? 答案: 還可能存在

5 緩存淘汰 (內存淘汰)

背景: redis的數據有效期策略不能保證數據真正的即時被清理,可能形成空間浪費,再有新的數據的時候,沒地方能夠存存儲, 爲了存儲新數據,須要清理redis中的一批數據,騰出空間保存新數據

淘汰策略 指 刪除哪些數據

通用的內存淘汰算法: LRU & LFU

  • LRU(Least recently used,最近最少使用)

    思想: 認爲 越是最近用過的數據,接下來使用的機會越大,應該清理那些好久之前使用過的數據

    cache_data = [
        cache1      時間最近
        cache2
        cache5
        cache4
        cache3     時間最遠
    ]
    
    操做過cache3
    cache_data = [
        cache3
        cache1      時間最近
        cache2
        cache5
        cache4
    ]
    
    增長cache6
    cache_data = [
        cache6
        cache3
        cache1      時間最近
        cache2
        cache5
    ]
  • LFU (Least Frequently Used, 最少使用) 以頻率 次數來考慮

    思想: 認爲使用次數越多的數據,接下來使用的機會越大,應該清理那些使用次數少的數據

    cache_data = {
        cache1 : 100     
        cache2: 2
        cache5: 23
        cache4: 89
        cache3  :  10000   
    }
    
    操做了cache2
    cache_data = {
        cache1 : 100     
        cache2: 3
        cache5: 23
        cache4: 89
        cache3  :  10000   
    }
    
    新增 cache6
    cache_data = {
        cache1 : 100     
        cache5: 23
        cache4: 89
        cache3  :  10000   
        cache6: 1
    }
    
    cache_data = {
        cache1 : 100     -> 50 
        cache5: 23  -> 11
        cache4: 89 -> 45
        cache3  :  10000    -> 5000
        cache6: 1 -> 1
    }
    • 效果更好
    • 缺點: 性能不高,須要額外記錄次數 頻率, 還須要按期衰減

Redis的內存淘汰策略 (3.x版本之後)

  • noeviction:當內存不足以容納新寫入數據時,新寫入操做會報錯。 默認
  • allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key。
  • allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。
  • volatile-lru:當內存不足以容納新寫入數據時,在設置了過時時間的鍵空間中,移除最近最少使用的key。
  • volatile-random:當內存不足以容納新寫入數據時,在設置了過時時間的鍵空間中,隨機移除某個key。
  • volatile-ttl:當內存不足以容納新寫入數據時,在設置了過時時間的鍵空間中,有更早過時時間的key優先移除。

redis 4.x 版本以後 增長了兩種

  • allkeys-lfu
  • volatile-lfu

redis中的配置

maxmemory <bytes>   指明redis使用的最大內存上限
maxmemory-policy volatile-lru  指明內存淘汰策略

總結:

  1. 若是將redis做爲持久存儲 ,內存淘汰策略 採用默認配置 noeviction
  2. 若是將redis做爲緩存,須要配置內存淘汰策略 選擇合適的淘汰策略

6. 緩存模式

應用程序如何使用緩存

  • 讀緩存
    • 場景: 須要頻繁讀取查詢數據 的場景
    • 方式 在應用程序與mysql數據庫中間架設redis 做爲緩存 ,讀取數據的時候先從緩存中讀取, 可是寫入新數據的時候,直接保存到mysql中
  • 寫緩存
    • 場景: 須要頻繁的保存數據 的場景
    • 方式 在應用程序與mysql數據庫中間架設redis 做爲緩存 ,保存數據的時候先保存到緩存redis中,並不直接保存的mysql中, 後續再從redis中同步數據到mysql中

讀緩存的數據同步問題:

修改了mysq中的數據,如何處理redis緩存中的數據

  • 先更新數據庫,再更新緩存

  • 先刪除緩存,再更新數據庫
  • 先更新數據庫,再刪除緩存 發生問題的概率最小 ,負面影響最小

7 緩存使用過程當中可能存在的問題

  • 緩存穿透
    • 問題: 訪問不存在的數據, 數據庫沒有 緩存也沒有存儲,每次訪問都落到數據查詢
    • 解決:
      • 緩存中保存不存在的數據,好比將數據以-1保存,表示數據不存在,能夠攔截 這種攻擊,減小數據庫查詢
      • 須要引入其餘工具 ,過濾器 ,按照規則來判斷 是否可能存在, 好比 布隆過濾器
  • 緩存雪崩
    • 問題: 同一批產生 的緩存數據 可能在同一時間失效,若是在同一時間大量的緩存失效,查詢時又會落到數據庫中查詢,對數據庫併發的有大量的查詢,數據庫吃不消,數據庫又可能崩潰
    • 解決:
      • 將數據的有效期增長誤差值,讓同一批產生的緩存數據不在同一時間 失效,將失效時間錯開
      • 架設多級緩存, 每級緩存有效期不一樣
      • 以保護數據庫出發, 爲數據庫的操做 添加鎖 或者 放到隊列中,強行的將並行的數據庫操做改成串行操做,一個一個執行,防止數據庫崩潰

8 頭條項目的緩存的設計

  • 服務器硬件層面的架設
    • 本地緩存
      • orm 查詢結果集緩存
    • 一級外部緩存
      • redis cluster 配置了緩存淘汰策略 (無需配置 持久化策略) volatile-lru 4.0.13
  • 程序編寫開發上
    • 緩存 的數據 Caching at the object level 數據庫對象級別,能夠被多個視圖利用
    • 緩存數據必定要設置有效期 , 爲了防止緩存雪崩,有效期要設置誤差值
    • 爲了防止緩存穿透,緩存數據時 不存在的數據也要緩存下來
    • 讀緩存
    • 大多數狀況選擇 先更新數據庫 再刪除緩存

9 頭條項目緩存的數據保存形式

redis數據類型的設計 (redis數據類型選型)

redis 的list set hash zset 數據是不容許嵌套的, 數據元素都是字符串

  • 用戶我的信息數據 (相似參考的 文章緩存 評論緩存)

    user1 -> User1 -> name mobile photo
    user2 -> User2 ->
    • 設計方式1 全部用戶在redis中以一條記錄保存
    key              value
    users:info  -> str X
                      json.dumps({'user1': cache_data, 'user2': cache_data})
    
                   list   set  X
                   [json.dumps(user1_dict), json.dumps(user2_dict)]
    
                   hash
                   {
                       'user1': json.dumps(user1_dict),
                       'user2': json.dumps(user2_dict)
                   }
    
                   zset X
                      member 成員                 score  分數/權重
                    json.dumps(user1_dict)      user_id

    考慮有效期:

    redis中的有效期不能對一條記錄中的不一樣字段單獨設置,最小隻能給一條記錄設置有效期

    全部人只能有一個有效期,很差 緩存雪崩

    不採用

    • 方式2 每一個用戶在redis中單獨一條記錄

      user1 -> User1 -> name mobile photo
      user2 -> User2 -> 
      
      key                   value
      user:{user_id}:info 
      user:1:info
      user:2:info   ->    str     json.dumps(user2_dict)
                          hash  
                          {
                              "name": xxx,
                              "mobile": xx
                              'photo':xxx
                          }
      
      
      str: 佔用空間小 頭條項目 爲了保存更多的緩存數據 選擇字符串
      hash: 存取靈活
  • 用戶關注列表信息數據 ( 相似的還有 用戶的文章列表 文章的評論列表 用戶的粉絲列表等)

    須要緩存的是關注裏中 關注的用戶的user_id

    1號用戶關注過 2 3 4 5 6 7

    每一個人單獨一條redis記錄

    key                      value
    user:{user_id}:follows
    user:1:follows
    user:2:follows ->      str
                               json.dumps([2,3,4,5..user_id])
    
                           list   set  X
                               ['2','3','4', 'use_id',..]
    
                           hash  X
                                field      value
                                user_id_2  follow_time  
                                user_id_3   follow_time
    
                           zset  有序集合  既能去重 還有序
                                member       score
                                user_id_2   follow_time
                                user_id_3    follow_time 時間戳
    
    str  用戶若是關注的人過多,整取數據不方便,並且列表通常是要分頁取
    zset  能夠批量分頁取數據  還能排序  頭條項目選擇zset  
             更新數據庫後 添加數據

10 頭條項目redis持久保存的數據保存形式

  • 服務器硬件層面的架設
    • redis 單機存儲容量足夠 ,再構建複製集 作高可用,防止主機redis掛掉
    • 配置持久化存儲策略 RDB + AOF
    • 內存淘汰策略 配置 noeviction
  • 保存的數據

    • 閱讀歷史 搜索歷史
    • 統計數據 (以前使用數據庫反範式設計的 冗餘字段)好比用戶的關注數量 粉絲數量等
  • 閱讀歷史 (文章id列表)

    方式一: 全部人一條記錄 X

    key                     value
    users:read:history      str json.dumps({'user_1': [], user_2:[]})
    
                            list  set  X
                            hash
                              {
                                "user_1": '2,3,4,5',
                                "user_2": '100, 20, 30'
                              }
                             zset
                                member    score
                                article_id   user_id
                                ‘2,3,4,5'   user_id1
                                '100, 20, 30'  user_id2

    方式二: 每人一條記錄

    key                             value
    user:{user_id}:read:history
    user:1:read:history
    user:2:read:history     ->      list  
                                 [artilce_id, 2, 3, 4, ...]
                                    set   沒有順序  X
                                (artilce_id, 2, 3, 4, ...)
                                    hash  X
                                    article_id   read_time
                                    2             16724383275342
                                    3             163232763827822
    
                                    zset  選擇
                                    member      score
                                    article_id   read_time
                                    2             16724383275342
                                    3             163232763827822
  • 統計數據

    方式一

    key                             value
    user:{user_id}:statistic
    user:1:statistic 
    user:2:statistic     ->     hash
                            {
                                'article_count': 120,
                                "follow_count": xx,
                                "fans_count": xxx,
                                ..
                            }

    方式二: 採用

    考慮運營平臺可能須要對產品進行全平臺大排名,好比 篩選發佈文章數量最多的前20名用戶 top問題

    每一個統計指標 一條redis記錄(保存全部用戶這個統計指標的數據)

    key                         value
    statistic:user:follows
    statistic:user:fans
    statistic:user:articles  -> zset
                                mebmer      score
                                user_id    article_count
                                    1       100
                                    2       3
                                    3      11
    • list set zset hash 一條記錄能保存的元素數量上限 42億

加密算法

  • 散列 hash (好比密碼的處理)
    • 特色:
      • 不一樣的數據 計算以後獲得的結果必定不一樣
      • 相同的數據計算以後獲得的結果相同
      • 不可逆
    • md5
    • sha1
    • sha256
  • 簽名 (好比jwt token)
    • HS256 簽名與驗籤時 使用相同的祕鑰字符串 進行sha256計算 -> 簽名值
    • RS256 簽名與驗籤時 使用不一樣的祕鑰字符串 進行sha256計算 -> 簽名值
  • 加密 (能夠解密的)
    • 對稱加密
      • 加密與解密使用相同的祕鑰
      • AES
      • DES
    • 非對稱加密
      • 加密 與解密使用不一樣的祕鑰 (公鑰私鑰)
      • RSA
相關文章
相關標籤/搜索