缓存的一些问题和一些加密算法【缓存问题】

缓存

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
相关文章
相关标签/搜索