03: redis高级

1.1 布隆过滤器  

  1、布隆过滤器是什么?(判断某个key一定不存在)

      1. 本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构

      2. 特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。

      3. 相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

      使用:

        1. 布隆过滤器在NoSQL数据库领域中应用的非常广泛

        2. 当用户来查询某一个row时,可以先通过内存中的布隆过滤器过滤掉大量不存在的row请求,然后去再磁盘进行查询

        3. 布隆过滤器说某个值不存在时,那肯定就是不存在,可以显著降低数据库IO请求数量

  2、应用场景

    1)场景1(给用户推荐新闻)

        1. 当用户看过的新闻,肯定会被过滤掉,对于没有看多的新闻,可能会过滤极少的一部分(误判)。

        2. 这样可以完全保证推送给用户的新闻都是无重复的。

    2)场景2(爬虫url去重)

        1. 在爬虫系统中,我们需要对url去重,已经爬取的页面不再爬取

        2. 当url高达几千万时,如果一个集合去装下这些URL地址非常浪费空间

        3. 使用布隆过滤器可以大幅降低去重存储消耗,只不过也会使爬虫系统错过少量页面

  3、布隆过滤器原理

      1. 每个布隆过滤器对应到Redis的数据结构是一个大型的数组和几个不一样的无偏hash函数

      2. 如下图:f、g、h就是这样的hash函数(无偏差指让hash映射到数组的位置比较随机)

      添加:值到布隆过滤器

        1)向布隆过滤器添加key,会使用 f、g、h hash函数对key算出一个整数索引,然后对长度取余

        2)每个hash函数都会算出一个不同的位置,把算出的位置都设置成1就完成了布隆过滤器添加过程

      查询:布隆过滤器值

        1)当查询某个key时,先用hash函数算出一个整数索引,然后对长度取余

        2)当你有一个不为1时肯定不存在这个key,当全部都为1时可能有这个key

        3)这样内存中的布隆过滤器过滤掉大量不存在的row请求,然后去再磁盘进行查询,减少IO操作

      删除:不支持

        1)目前我们知道布隆过滤器可以支持 add 和 isExist 操作

        2)如何解决这个问题,答案是计数删除,但是计数删除需要存储一个数值,而不是原先的 bit 位,会增大占用的内存大小。

        3)增加一个值就是将对应索引槽上存储的值加一,删除则是减一,判断是否存在则是看值是否大于0。

      

1.2 redis事物

  1、redis事物介绍

      1. redis事物是可以一次执行多个命令,本质是一组命令的集合。

      2. 一个事务中的所有命令都会序列化,按顺序串行化的执行而不会被其他命令插入

      作用:一个队列中,一次性、顺序性、排他性的执行一系列命令 

  2、redis事物基本使用

      1. 下面指令演示了一个完整的事物过程,所有指令在exec前不执行,而是缓存在服务器的一个事物队列中

      2. 服务器一旦收到exec指令才开始执行事物队列,执行完毕后一次性返回所有结果

      3. 因为redis是单线程的,所以不必担心自己在执行队列是被打断,可以保证这样的“原子性”

      注:redis事物在遇到指令失败后,后面的指令会继续执行

      mysql的rollback与redis的discard的区别:

          1) mysql回滚为sql全部成功才执行,一条sql失败则全部失败,执行rollback后所有语句造成的影响消失

          2) redis的discard只是结束本次事务,正确命令造成的影响仍然还在.

# Multi 命令用于标记一个事务块的开始事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性( atomic )地执行
> multi(开始一个redis事物) incr books incr books > exec (执行事物) > discard (丢弃事物)
[root@redis ~]# redis-cli
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set test 123
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get test
"123"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set test 456
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get test
"123"
127.0.0.1:6379> 
在命令行测试redis事物
#定义ip
host = 'localhost'

#建立服务连接

r = redis.Redis(host=host)
pipe = r.pipeline()

#开启事务
pipe.multi()
#存储子命令
pipe.set('key2', 4)
#执行事务
pipe.execute()

print(r.get('key2'))
使用python测试redis事物

  3、watch指令

      实质:WATCH 只会在数据被其他客户端抢先修改了的情况下通知执行命令的这个客户端(通过 WatchError 异常)但不会阻止其他客户端对数据的修改

      1. watch其实就是redis提供的一种乐观锁,可以解决并发修改问题

      2. watch会在事物开始前盯住一个或多个关键变量,当服务器收到exec指令要顺序执行缓存中的事物队列时

      3. redis会检查关键变量自watch后是否被修改(包括当前事物所在的客户端)

      4. 如果关键变量被人改动过,exec指令就会返回null回复告知客户端事物执行失败,这个时候客户端会选择重试

      注:redis禁用在multi和exec之间执行watch指令,必须在multi之前盯住关键变量,否则会出错

1.3 redis事物与分布式锁 

  1、redis事物

      1. 严格意义来讲,Redis的事务和我们理解的传统数据库(如mysql)的事务是不一样的;

      2. Redis的事务实质上是命令的集合,在一个事务中要么所有命令都被执行,要么所有命令都不执行。

      需要注意的是:

        1.Redis的事务没有关系数据库事务提供的回滚(rollback),所以开发者必须在事务执行失败后进行后续的处理;
        2.如果在一个事务中的命令出现错误,那么所有的命令都不会执行
        3.如果在一个事务中出现运行错误,那么正确的命令会被执行

  2、redis原子操作

      1. 原子操作是指不会被线程调度机制打断的操作

      2. 这种操作一旦开始,就会一直运行到结束,中间不会切换任何进程

  3、分布式锁

      1. 分布式锁本质是占一个坑,当别的进程也要来占坑时发现已经被占,就会放弃或者稍后重试

      2. 占坑一般使用 setnx(set if not exists)指令,只允许一个客户端占坑

      3. 先来先占,用完了在调用del指令释放坑

> setnx lock:codehole true
.... do something critical ....
> del lock:codehole

      4. 但是这样有一个问题,如果逻辑执行到中间出现异常,可能导致del指令没有被调用,这样就会陷入死锁,锁永远无法释放

      5. 为了解决死锁问题,我们拿到锁时可以加上一个expire过期时间,这样即使出现异常,当到达过期时间也会自动释放锁

> setnx lock:codehole true
> expire lock:codehole 5
.... do something critical ....
> del lock:codehole

      6. 这样又有一个问题,setnx和expire是两条指令而不是原子指令,如果两条指令之间进程挂掉依然会出现死锁

      7. 为了治理上面乱象,在redis 2.8中加入了set指令的扩展参数,使setnx和expire指令可以一起执行

> set lock:codehole true ex 5 nx
''' do something '''
> del lock:codehole 

1.4 redis五种数据结构

  说明:

      1. redis中所有数据结构都以唯一的key字符串作为名称,然后通过这个唯一的key来获取对应的value

      2. 不同的数据类型数据结构差异就在于value的结构不一样

  1、 字符串(string)

    1)value的数据结构(数组)

        1. 字符串value数据结构类似于数组,采用与分配容易空间来减少内存频繁分配

        2. 当字符串长度小于1M时,扩容就是加倍现有空间

        3. 如果字符串长度操作1M时,扩容时最多扩容1M空间,字符串最大长度为 512M

    2)字符串的使用场景(缓存)

        1. 字符串一个常见的用途是缓存用户信息,我们将用户信息使用JSON序列化成字符串

        2. 取用户信息时会经过一次反序列化的过程

   2、list(列表)

     1)value的数据结构(双向链表)

        1. 列表的数据结构是双向链表,这意味着插入和删除的时间复杂度是0(1),索引的时间复杂度位0(n)

        2. 当列表弹出最后一个元素后,该数据结构会被自动删除,内存被回手

    2)列表的使用场景(队列、栈)

  3、hash(字典)

    1)value的数据结构(HashMap)

        1. redis中的字典也是HashMap(数组+列表)的二维结构

        2. 不同的是redis的字典的值只能是字符串

    2)hash的使用场景(缓存)

        1. hash结构也可以用来缓存用户信息,与字符串一次性全部序列化整个对象不同,hash可以对每个字段进行单独存储

        2. 这样可以部分获取用户信息,节约网络流量

        3. hash也有缺点,hash结构的存储消耗要高于单个字符串

  4、set(集合)

    1)value的数据结构(字典)

        1. redis中的集合相当于一个特殊的字典,字典的所有value都位null

        2. 当集合中的最后一个元素被移除后,数据结构会被自动删除,内存被回收

     2)set使用场景

        1. set结构可以用来存储某个活动中中奖的用户ID,因为有去重功能,可以保证同一用户不会中间两次

  5、zset(有序集合)

    1)value的数据结构(跳跃列表)

        1. zset一方面是一个set,保证了内部的唯一性

        2. 另一方面它可以给每一个value赋予一个score,代表这个value的权重 

        3. zset内部实现用的是一种叫做“跳跃列表”的数据结构

        4. zset最后一个元素被移除后,数据结构就会被自动删除,内存也会被回收

    2)zset应用场景

        1. 粉丝列表:value(粉丝ID),score(关注时间),这样可以轻松按关注事件排序

        2. 学生成绩:value(学生ID),score(考试成绩),这样可以轻松对成绩排序

 1.5 redis雪崩&穿透&击穿

   1、缓存穿透

    1)定义

        1. 缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,接着查询数据库也无法查询出结果,

        2. 虽然也不会写入到缓存中,但是这将会导致每个查询都会去请求数据库,造成缓存穿透;

    2)解决方法 :布隆过滤

        1. 对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

  2、缓存雪崩

    1)定义      

        1. 缓存雪崩是指,由于缓存层承载着大量请求,有效的保护了存储层,但是如果缓存层由于某些原因整体不能提供服务

        2. 于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

    2)解决方法

        1. 保证缓存层服务高可用性:比如 Redis Sentinel 和 Redis Cluster 都实现了高可用

        2. 依赖隔离组件为后端限流并降级:比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

  3、缓存击穿

    1)定义:

        1. 缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况

        2. 当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

    2)解决方法

        1. 解决方式也很简单,可以将热点数据设置为永远不过期;

        2. 或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。

 

 

 

 

 

 

 

11111

 

posted @ 2019-09-25 18:27  不做大哥好多年  阅读(567)  评论(0编辑  收藏  举报