Redis

1.redis概述
  1. Redis 是一个基于内存的  NoSQL 存储系统,以 key-value 形式存储。
  2. 存储的类型包括 string、list 、set、zset和 hash等 。
  3. 支持数据持久化。
  4. 支持集群模式和分布式存储。
 2.应用场景
  1. 作高速缓存,降低 db 压力。
  2. 作分布式锁和分布式 session 。
  3. 多样的数据结构提供功能,包括排行榜、计数器、秒杀、队列、发布订阅消息系统等。
3.相关知识

        默认使用16个数据库,类似数组下标从0开始,默认使用0号数据库,所有库同样的密码。

4.Redis 使用的是单线程 + 多路 IO 复用技术

        多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用 select 和 poll 函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。

5.基本命令

keys * 查看当前库所有的key

set key value  存放数据

setnx key value 不存在时存放数据

append key value 追加数据

get key 取数据

incr key 数据自增1

decrkey 数据自减1

incrby/decrby key <步长> 将key存储的数字增减自定义的步长。

strlen key 获取数据长度

exists key 判断是否存在某个key 返回0 和 1

type key 查看key是什么类型

del key 删除key

unlink key 根据value选择非阻塞式删除,仅将key从keyspace中删除,真正的删除会在后续异步操作

expire key 10 表示为给定的key设置过期时间10秒

ttl key 表示查看key的过期时间,-1表示永不过期,-2表示已经过期

select dbid来切换数据库,例如:select 6

dbsize 查看当前库的key数量

flushdb清空当前库

flushall通杀全部库

6.原子操作 

        在单线程中,能够在单条指令完成的操作都可以认为是原子操作,因为中断只能发生在指令之间。在多线程中,不会被其他线程打断的操作就叫原子操作。 Redis的单命令的原子性得益于Redis的单线程。

7.数据类型
        7.1数据类型之string

        String 类型可以包含任何数据。例如jpg 图片或者序列化对象,但是最多可以存储 512M。

            数据结构:

        String 的数据结构为简单动态字符串 (Simple Dynamic String, 缩写 SDS),是可以修改的,内部结构实现类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存分配次数,字符串实际分配的空间一般要多于实际字符串长度。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容一次只会多扩 1M 的空间,需要注意的是字符串上限为 512M。

        7.2数据类型之LIst 

       有序的字符串列表,你可以添加一个元素到列表的头部(左边)或者尾部(右边)。底层是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

        数据结构:

        数据结构为以ziplist为元素组成的链表,即quicklist。

        在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist(压缩列表)。数据量比较多的时候才会改成 quicklist。因为普通的链表需要的附加两个额外的指针 prev 和 next,会比较浪费空间。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

        7.3数据类型之set

        功能与 list 类似,但是还可以自动排重,可以判断成员是否存在。

        数据结构:

                类似于 Java 的 HashSet,内部结构实现为值为 null 的 hash 表。

        7.4数据类型之zset

         zset 与set 非常相似,不同之处是 zset的每个成员都关联了一个评分(score),可以被用来排序,成员是唯一的,但是评分可以是重复的,可以根据评分获取范围元素。

         数据结构:

                包含哈希表和跳表,哈希表的作用就是关联元素 value 和权重 score,保障元素 value 的唯一性,可以通过元素 value 找到相应的 score 值。跳跃表的目的在于给元素 value 排序。这两种数据结构会通过指针共享相同元素的成员和分值,避免造成内存的浪费。

        对比:

                链表要查找值为 51 的元素,需要从第一个元素开始依次查找、比较才能找到。共需要 6 次比较。

                跳表要查找值为 51 的元素,从第 2 层开始,对比两次找到21,后面为null,直接下沉,对比两次,找到41~61的区间下沉,在对比51,共对比5次,跳跃表比有序链表查询效率要高。

        7.5数据类型之hash

        是一个 string 类型的 field 和 value 的键值对集合,特别适合用于存储对象。

        数据结构:

                类似 Java 里面的 Map<String,Object>,对应的数据结构有两种:ziplist(压缩列表),哈希表。当 field-value 长度较短且个数较少时,使用 ziplist,否则使用哈希表。

         7.6 数据类型之Bitmaps

        实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。

        一个以位为单位的数组, 数组的每个单元只能存储 0 和 1, 数组的下标在 Bitmaps 中叫做偏移量。

         7.7 数据类型之HyperLogLog

       解决去重计数问题

        Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

        在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

        但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

        7.8 数据类型之Geospatial

        Redis 3.2 中增加了对 GEO 类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的 2 维坐标,在地图上就是经纬度。redis 基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度 Hash 等常见操作。

8.发布订阅

        什么式发布订阅?  

                Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息,Redis 客户端可以订阅任意数量的频道。

                                    

9.redis事物      

        Redis的事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断,主要作用就是串联多个命令防止别的命令插队。

        Redis 事务中有 Multi、Exec 和 discard 三个指令,在 Redis 中,从输入 Multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入 Exec 后,Redis 会将之前的命令队列中的命令依次执行。而组队的过程中可以通过 discard 来放弃组队。组队阶段某个命令报错,则会提交失败,整个的所有队列都会被取消;组队成功,进入执行阶段,则只有错误命令不会被执行,而其他的命令都会执行,不会回滚。

        场景:同一账户,同时去参加双十一抢购,不能全部抢购失败。        

        悲观锁 (Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁。

        乐观锁 (Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

        在执行 multi 之前,先执行 watch key1 [key2],可以监视一个 (或多个) key ,如果在事务执行之前这个 (或这些) key 被其他命令所改动,那么事务将被打断

        取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后,EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。

        Redis 事务三特性

  1. 单独的隔离操作 :事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  2. 没有隔离级别的概念 :队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
  3. 不保证原子性 :事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚 。
10.redis秒杀超卖和遗留库存案例

       核心代码如下,在 java多线程下会造成超卖的问题。

//6 判断如果商品数量,库存数量小于1,秒杀结束
		if(Integer.parseInt(kc)<=0) {
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		}


//7.1 库存-1
jedis.decr(kcKey);
//7.2 把秒杀成功用户添加清单里面
jedis.sadd(userKey,uid);

        使用redis事物改进后,在 java多线程下会造成遗留库存的问题。

jedis.watch(kcKey);
//使用事务
Transaction multi = jedis.multi();
//组队操作
multi.decr(kcKey);
multi.sadd(userKey,uid);
multi.exec();


        使用LUA脚本可以解决遗留库存的问题:

  1. 将复杂的或者多步的 redis 操作,写为一个脚本,一次提交给 redis 执行,减少反复连接 redis 的次数,提升性能。
  2. LUA 脚本是类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成一些 redis 事务性的操作。
  3. 通过 lua 脚本解决争抢问题,实际上是 redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。
11.Redis持久化策略

        Redis持久化策略有两种,RDB 和 AOF。

        AOF 和 RDB 同时开启,系统默认取 AOF 的数据(数据不会存在丢失)

        用哪个好?

  1. 如果对数据不敏感,可以选单独用 RDB。     
  2. 不建议单独用 AOF,因为可能会出现 Bug。
  3. 如果只是做纯内存缓存,可以都不用。
        11.1 RDB(Redis DataBase)

                在指定的时间间隔内将内存中的数据集快照写入磁盘,默认名为dump.rdb,恢复时是将快照文件直接读到内存里。

                备份是如何执行的?

                Redis 会单独创建(fork)一个子进程来进行持久化,首先会将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。RDB 的缺点是最后一次需要持久化的数据可能会丢失。

                Fork

  1. Fork 的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
  2. 在 Linux 程序中,fork () 会产生一个和父进程完全相同的子进程,但子进程在此后多会 exec 系统调用,出于效率考虑,Linux 中引入了 “写时复制技术”。
  3. 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

                如何触发 RDB 快照的持久化策略

                       例 save 20 1,在 20秒内至少有一个key变更,则触发。

                RDB优势与劣势

                       优势:适合大规模的数据恢复,节省磁盘空间,恢复速度快。

                       劣势:数据不完整。

        11.2 AOF(Append Only File)

        以日志的形式来记录每个写操作(增量保存),将 Redis 执行过的所有写指令记录下来 , 只许追加文件但不可以改写文件。

         AOF 持久化流程:

  1. 客户端的请求写命令会被 append 追加到 AOF 缓冲区内;
  2. 缓冲区根据 AOF 持久化策略 [always,everysec,no] 将操作同步到磁盘appendonly.aof 文件中;
  3. AOF 文件大小超过重写策略或手动重写时,会对AOF文件重写,压缩 AOF 文件容量;

        AOF 同步频率设置:

  1. appendfsync always:始终同步,每次 Redis 的写入都会立刻记入日志;性能较差但数据完整性比较好。
  2. appendfsync everysec:每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
  3. appendfsync no:redis 不主动进行同步,把同步时机交给操作系统。

        Rewrite 压缩是什么

        AOF 采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当 AOF 文件的大小超过所设定的阈值时,Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令 bgrewriteaof。

        重写原理,如何实现重写

        AOF 文件持续增长而过大时,会 fork 出一条新进程来将文件重写 (也是先写临时文件最后再 rename),redis4.0 版本后的重写,是指把 rdb 的快照,以二进制的形式附在新的 aof 头部,作为已有的历史数据,替换掉原来的流水账操作。

        no-appendfsync-on-rewrite:

        如果 no-appendfsync-on-rewrite=yes ,不写入 aof 文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)

        如果 no-appendfsync-on-rewrite=no,还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)

        触发机制,何时重写

        Redis 会记录上次重写时的 AOF 大小,默认配置是当 AOF 文件大小是上次 rewrite 后大小的一倍且文件大于 64M 时触发。

        重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定 Redis 要满足一定条件才会进行重写。

        auto-aof-rewrite-percentage:设置重写的基准值,文件达到 100% 时开始重写(文件是原来重写后文件的 2 倍时触发)。

        auto-aof-rewrite-min-size:设置重写的基准值,最小文件 64MB。达到这个值开始重写。

系统载入时或者上次重写完毕时,Redis 会记录此时 AOF 大小,设为 base_size,

        如果 Redis 的 AOF 当前大小 >= base_size +base_size*100% (默认) 且当前大小 >=64mb (默认) 的情况下,Redis 会对 AOF 进行重写。

        例如:文件达到 70MB 开始重写,降到 50MB,下次什么时候开始重写?100MB

        重写流程

        bgrewriteaof 触发重写,判断是否当前有 bgsave 或 bgrewriteaof 在运行,如果有,则等待该命令结束后再继续执行;

        主进程 fork 出子进程执行重写操作,保证主进程不会阻塞;

        子进程遍历 redis 内存中数据到临时文件,客户端的写请求同时写入 aof_buf 缓冲区和 aof_rewrite_buf 重写缓冲区,保证原 AOF 文件完整以及新 AOF 文件生成期间的新的数据修改动作不会丢失;

        子进程写完新的 AOF 文件后,向主进程发信号,父进程更新统计信息。主进程把 aof_rewrite_buf 中的数据写入到新的 AOF 文件;

        使用新的 AOF 文件覆盖旧的 AOF 文件,完成 AOF 重写。

        AOF优势与劣势

  1.         优势:备份机制更稳健,丢失数据概率更低,可读的日志文本,通过操作 AOF 稳健,可以处理误操作。
  2.         劣势:比起 RDB 占用更多的磁盘空间,恢复备份速度要慢,每次读写都同步的话,有一定的性能压力。
12.主从复制 

        主机数据更新后根据配置和策略, 自动同步到备机的 master/slaver 机制,Master 以写为主,Slave 以读为主,主从复制节点间数据是全量的。

        作用:

        读写分离,性能扩展,容灾快速恢复

        复制原理:

  1. Slave 启动成功连接到 master 后会发送一个 sync 命令;
  2. Master 接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master 将传送整个数据文件到 slave,以完成一次完全同步。
  3. 全量复制:slave 服务器在接收到数据库文件数据后,将其存盘并加载到内存中。
  4. 增量复制:Master 继续将新的所有收集到的修改命令依次传给 slave,完成同步。
  5. 但是只要是重新连接 master,一次完全同步(全量复制) 将被自动执行。

        哨兵模式 (sentinel):

        反客为主:当一个 master 宕机后,后面的 slave 可以立刻升为 master,其后面的 slave 不用做任何修改。用 slaveof no one 指令将从机变为主机。而哨兵模式是反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

        当主机挂掉,从机选举产生新的主机

哪个从机会被选举为主机呢?根据优先级别:slave-priority /最大偏移量/最小runnid。

原主机重启后会变为从机。

13 Redis集群(cluster 模式)

        优点:扩容、分摊读写压力、无中心配置

        Redis 集群实现了对 Redis 的无中心化集群配置和水平扩容,即启动 N 个 redis 节点,将整个数据库分布存储在这 N 个节点中,每个节点存储总数据的 1/N。 通过分区(partition)来提供可用性, 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理理求。集群至少要有三个主节点,分配原则尽量保证每个主数据库运行在不同的 IP 地址,每个从库和主库不在一个 IP 地址上。

         slots

        一个 Redis 集群包含 16384 个插槽(hash slot), Redis 中的每个键都属于这 16384 个插槽的其中一个。集群使用公式 CRC16 (key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16 (key) 语句用于计算键 key 的 CRC16 校验和 。集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:

        节点 A 负责处理 0 号至 5460 号插槽。
        节点 B 负责处理 5461 号至 10922 号插槽。
        节点 C 负责处理 10923 号至 16383 号插槽。

        注意 :

        主节点下线,从节点可自动升为主节点,注意:15 秒超时。主节点恢复后,主节点回来变成从机。如果所有某一段插槽的主从节点都宕掉,而 cluster-require-full-coverage 为 yes ,那么整个集群都挂掉;而 cluster-require-full-coverage 为 no ,那么,该插槽数据全都不能使用,也无法存储。

14. Redis应用问题及解决
        14.1缓存穿透 

                请求数据不在redis 和 db,高并发请求瞬间把后端数据库压垮。

                解决方案:

  1. 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
  2. 设置可访问的名单(白名单):使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,不允许访问。
  3. 采用布隆过滤器:布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量 (位图) 和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
  4. 进行实时监控:当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。
        14.2缓存击穿 

               redis 中热点key过期,高并发请求瞬间把后端数据库压垮。

        解决方案:

  1. 预先设置热门数据:在 redis 高峰访问之前,把一些热门数据提前存入到 redis 里面,加大这些热门数据 key 的时长。
  2. 实时调整:现场监控哪些数据热门,实时调整 key 的过期时长。
  3. 使用锁:
    1.  就是在缓存失效的时候(判断拿出来的值为空),不是立即去 load db。
    2.  先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX)去 set 一个 mutex key。
    3. 当操作返回成功时,再进行 load db 的操作,并回设缓存,最后删除 mutex key;
    4. 当操作返回失败,证明有线程在 load db,当前线程睡眠一段时间再重试整个 get 缓存的方法。
        14.3缓存雪崩 

                Redis中大量key同时过期,高并发请求瞬间把后端数据库压垮。

                解决方案:

  1. 构建多级缓存架构:nginx 缓存 + redis 缓存 + 其他缓存(ehcache 等)。
  2. 使用锁或队列:用加锁或者队列的方式来保证不会有大量的线程同时对数据库进行读写,从而避免压垮数据库,该方法不适用高并发情况。
  3. 设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际 key 的缓存。
  4. 散列缓存失效时间:比如可以设置一定时间区间内的随机值,这样每个缓存的过期时间的重复率就会降低。
15. 分布式锁

        分布式系统中 ,JVM原生锁机制不支持跨JVM调用,故产生分布式锁。

        分布式锁主流的实现方案:

  1. 基于数据库实现分布式锁。
  2. 基于缓存(Redis 等),性能好
  3. 基于 Zookeeper,可靠性好

         推荐使用Redisson。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值