1.Redis

1.1 初识Redis

1.1.1 Redis特性

1.1.2 Redis使用场景

1.1.3 Redis常用数据类型

1.1.4 单线程

1.2 常用API

1.2.1 通用命令

1.3 数据结构&内部编码

1.4 持久化

1.4.1 RDB

1.4.2 AOF

1.4.3 RDB VS AOF

1.4.4 持久化存在的问题

2.Redis安装

2.1 linux安装

2.2 常用操作

2.3 配置

2.3.1 配置文件

2.3.4 Redis安装脚本

3. Redis

3.1 慢查询

3.2 pipeline

3.2.1 pipeline VS m操作

3.3 Hyperloglog

3.1 Redis事务

3.1.1 常用命令:

3.1.2 特性:

3.4 发布订阅

3.4.1 订阅消息

3.4.2 发布消息

3.3 主从复制

3.3.1 主从配置

3.3.2 情景分析

3.3.3 薪火相传

3.3.4 反客为主

3.3.5 全量复制

3.3.6 部分复制

3.3.7 复制运维

3.3.8 优缺点

3.4 哨兵模式

3.4.1 哨兵原理

3.4.2 客户端

3.4.3 哨兵内部定时任务

3.4.4 主客观下线

3.4.5 领导者选举

3.4.6 故障转移

1.面试题:

1.1 为什么要用Redis:

1.2 Redis存在的问题

1.3 Redis的文件事件处理器

1.4 Redis过期策略

1.4.1 惰性删除 && 定期删除

1.4.2 内存淘汰机制

1.4.3 LRU算法

1.5 缓存雪崩

1.5.1 缓存雪崩

1.5.2 解决策略

1.6 缓存穿透

1.7 缓存和数据库双写的一致性问题

1.7.1 双写的策略

 

1.Redis

1.1 初识Redis

1.1.1 Redis特性

1.1.1.1 快

1.1.1.2 持久化

AOF + RDB

1.1.1.3 多种数据结构

String、Hash、Set、List、SortedSet、BitMap(位图)、HyperLogLog(超小内存唯一计数)、Geo(地理信息)

1.1.1.4 多种客户端语言

Java、python、Ruby

1.1.1.5 功能丰富

发布订阅、lua脚本、事务、pipeline

1.1.1.6 主从复制

1.1.1.7 高可用、分布式(集群)

1.1.2 Redis使用场景

缓存系统、计数器、排行榜、社交网络、简易消息队列、实时系统(布隆过滤器)

 

1.1.3 Redis常用数据类型

  • String:最基本的类型,最大512M,二进制安全(jpg图片、序列化的对象等)[单key单value]

Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,内存为当前字符串实际分配的空间一般要高于实际字符串长度。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。

使用场景:记录用户页面访问量、缓存基本数据、分布式Id生成器

------------键值对------------- > set key value //设置键值对,重复设置会更新key > get key // > exists key (integer) 1 > del key (integer) 1 > strlen key //获取value的长度 > append key valu //在原先的value后面追加值 ------------批量键值对------------- (可以批量对多个字符串进行读写,节省网络耗时开销。) > mset k1 v1 k2 v2 k3 v3 > mget k1 k2 k3 > msenx k1 v1 k2 v2 //当且仅当key都不存在时,设置成功 ------------过期和 set 命令扩展------------- > set key value > expire key 5 # 5s 后过期 > setex key 5 value # 5s 后过期,等价于 set+expire > setnx key value# 如果 name 不存在就执行 set 创建 (integer) 1 > setnx key value (integer) 0 # 因为 name 已经存在,所以 set 创建不成功 ---------------------计数------------------- > set age 30 > incr age //自增值 decr key // 自减 (integer) 31 > incrby age 5 //decrby key 值 //-值 (integer) 36 -------------------获取----------------------- getrange key startIdx endIdx //获取内容,包括起始位置和终止位置 setrang key startIdx value //从指定位置开始写

还有一个需要特别注意的地方是如果一个key已经设置了过期时间,然后你调用了set 方法修改了它,它的过期时间会消失。

  • Hash :

key:fileld : value ;MapMap结构

Redis 的Hash相当于 Java 语言里面的 HashMap,它是无序字典。内部通过数组 + 链表实现。第一维 hash 的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。不同的是,Redis 的字典的值只能是字符串,另外它们扩容 方式不一样,因为Java 的HashMap 在字典很大时,扩容是个耗时的操作,需要一次性全部 扩容。Redis为了高性能,不能堵塞服务,所以采用了渐进式扩容策略。渐进式 扩容会在 扩容的同时,保留新旧两个hash 结构,查询时会同时查询两个hash 结构,然后在后续的定时任务中以及 hash 的子指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。

hset sunpeipei age 22 //设置修改年龄 hget sunpei age //获取年龄 hmset sunpei name "sunpei" age 23 phone "153..."//批量设置 hmget sunpei name age hgetall sunpei //获取全部key和v hlen sunpei //获取value有多少键值对 hexists sunpei name //判断key存在否 hkeys hash名 //获取所有的key hvals 哈希名 //获取所有的value hincrby hash名 k 数 //将指定的值+数 【整数】 hsetnx sunpei age 22 //当且仅当元素不存在时设置成功

 

  • List : 列表,按照插入顺序排序[先进后出][单值多value]

Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为O(n)。当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符

串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

Redis 底层存储的不是一个简单的 linkedlist,而是称之为快速链表 quicklist 的一个结构。

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。所以 Redis 将链表和 ziplist 结合起来组成了 quicklist。也就是将多个ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

Tips:

LPUSH+LPOP =栈

LPUSH+RPOP =队列

LPUSH+ LTRIM = 固定数量的列表

LPUSH +BRPOP =消息队列

--------------右边进左边出:队列------------- > rpush books python java golang // lpush 列表名 值 (integer) 3 > lpop books //正序弹出一个元素 "python" > lpop books "java" > lpop books "golang" -----------右边进右边出:栈--------------- > rpush books python java golang (integer) 3 > rpop books //倒序弹出一个元素 "golang" > rpop books "java" > rpop books "python" -----------慢操作(慎用)--------------- > lindex 列表名 索引 //获取指定索引的元素 > lrange 列表名 startIdx endIdx //获取指定数据【包括起始索引和结束索引】 > ltrim 列表名 starIdx endIdx //截取指定范围的值再赋值给key 【包括起止索引】 > lset 列表名 index value // > linsert 列表名 before/after v1 v2 //在v1前/后面插入v2 -----------其他--------------- > llen 列表名 //获取列表的长度 > lrem 列表名 个数 值 //删除n个值 > rpoplpush 源列表 目的列表 //将原列表表的最后一个元素添加到目的列表的第一位

 

  • Set :无序集合,不予许重复

sadd friends lilei //成功返回 1 sadd friends lilei //重复返回 0 不可重复设置 smembers set名 //查看元素,返回结果无序 sismember set名 value //查看该集合中是否包含该元素 1-包含 0-不包含 scard set名 //获取集合元素个数 srem set名 值 //移除某元素 srandmember set名 个数 //随机获取指定个数值 spop set名 //随机出栈 smove set1名 set2名 v //将集合1中的某值移动到集合2中 sdiff set1 set2 //在集合1中且不再集合2中的元素 sinter set1 set2 //集合1和集合2的交集部分 sunion set1 set2 //集合1和集合2的并集 //用户关注人 、粉丝 可以求交集、差集、并集操作【共同操作、共同爱好】

Tips:

SADD :标签

SPOP + SRANDMEMBER : 随机数

SADD +SINTER :社交

  • Sorted Set : 有序结合,不允许重复 【在set的基础上加了一个分数】

zset 可能是 Redis 提供的最为特色的数据结构,它类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着「跳跃列表」的数据结构。

zset 要支持随机的插入和删除,所以它不好使用数组来表示。我们先看一个普通的

链表结构。

我们需要这个链表按照 score 值进行排序。这意味着当有新元素需要插入时,要定位到

特定位置的插入点,这样才可以继续保证链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到。

「跳跃列表」之所以「跳跃」,是因为内部的元素可能「身兼数职」,比如上图中间的

这个元素,同时处于 L0、L1 和 L2 层,可以快速在不同层次之间进行「跳跃」。

定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合

适的位置,将新元素插进去。你也许会问,那新插入的元素如何才有机会「身兼数职」呢?跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。

首先 L0 层肯定是 100% 了,L1 层只有 50% 的概率,L2 层只有 25% 的概率,L3

层只有 12.5% 的概率,一直随机到最顶层 L31 层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。

zadd 集合名 分数1 值1 分数2 值2 //添加数据 zrange key startIdx endIdx 【withscores】 //遍历集合 【带分数】 zrangebyscore 集合名 【(】开始socre 【(】 结束score 【limit 开始idx 个数】//获取指定范围的元素 zrem 集合名 元素 // 删除指定元素 zcard 集合名 //统计集合中有多少元素 zcount 集合名 开始score 结束score //统计得分在指定范围的元素有多少个,包括边界 zrank 集合名 k1 //获取元素的下标 zcore 集合名 元素 //获取元素的分数 zrevrank 集合名 元素 //逆序获取元素的下标 zrevrange 集合名 开始idx 结束idx //逆序获取元素 zrevrangebyscore 集合名 结束分数 开始分数 //获取指定分数范围的集合

  • HyperLogLog: 用于计数

 

  • Geo:存储地理位置信息

 

1.1.4 单线程

单线程的理解:

Redis一次只会执行一个命令,因此要避免长命令(keys、flush、flushdb、slow lua script、multi/exec、operate、big value)

单线程为什么这么快?

  • 基于内存
  • 非阻塞IO(IO多路复用)
  • 避免线程切换

 

1.2 常用API

1.2.1 通用命令

keys //keys * keys h[h-l]* keys jav? config get * //获取配置

 

命令

时间复杂度

keys

O(n)

dbsize

O(1)

exists、hexists、hlen

O(1)

del 、hdel

O(1)

expire、ttl、persist

O(1)

type

O(1)

get、set、del、hget、hset、hdel

O(1)

incr、decr、incrby、decrby、incrbyflocat、hincrby、hincrbyflocat

O(1)

setnx(add)、set xx(update)、hsetnx

O(1)

mget、mset、hmget、hmset、

O(n)[1次网络时间+n次命令时间]

getset(设置新值、返回旧值)、append、strlen(中文)

O(1)

getrange、setrange

O(1)

hgetall、hvals、hkeys

O(n)

rpush、lpush

O(1-n)

linsert

O(n)

lpop、rpop、

O(1)

lrem、ltrim

O(n)

lindex、lrange(包含end)、

O(n)

llen

O(1)

lset、

O(n)

blpop、brpop

O(1)

sadd、srem

O(1)

scard、sismember、srandmember、spop

O(1)

smembers

O(n)

sdiff、sinter、sunion、

集合间操作

zadd

O(lgn)

zrem、zscore、zincrby、zcard

O(1)

zrange、zrangebyscore、zcount、zremrangebyrank、zremrangebyscore

O(log(n)+m)

zinterstore、zunionstore

 

 

1.3 数据结构&内部编码

 

这样设计的目的: 空间换时间? 时间换空间

Redis内置对象结构:

 

1.4 持久化

1.4.1 RDB

触发机制:save、bgsave、自动触发

文件策略:新文件覆盖旧文件

1.4.1.1 save命令

1.4.1.2 bgsave命令

1.4.1.3 save VS bgsave

1.4.1.4 自动生成

自动触发:达到配置、shutdown、全量复制

20217uploading.4e448015.gif转存失败重新上传取消

 

推荐配置:关闭自动save、开启文件压缩、开启文件校验

1.4.1.5 总结

  • save会阻塞Redis,bgsave不会长时间阻塞(fork时阻塞)
  • bgsave会fork出子进程

1.4.2 AOF

1.4.2.1 策略

 

 

推荐使用第二种:最多丢失1s数据

1.4.2.2 AOF重写

重写的作用:减少硬盘占用量、加快恢复速度

1.4.2.3 AOF重写实现方式

注意:重写是基于内存重写,不是基于旧的AOF文件;

 

 

 

min-size:第一次重写最小内存

percentage:下一次重写的条件

重写条件:(同时满足)

1.当期内存 > 最小内存

2.(当期尺寸-上次尺寸)/上次尺寸 > 增长率

no-appendfsync-on-rewrite :yes 在往缓存区写重写内容时,是否停止写aof;性能 VS 数据(如果重写失败,会导致aof丢失部分数据),推荐使用yes,提高性能;

 

1.4.3 RDB VS AOF

 

RDB建议:主节点关闭RDB、从节点打开RDB,便于备份数据;

 

1.4.4 持久化存在的问题

1.4.4.1 fork

latest_fork_usec :上次fork花费的时间

1.4.4.2 子进程的开销

Linux在2.6版本后,优化了内存页(内存页更大),然而这对Redis来说并不是一个好消息;echo:nerver > /sys/kernel/mm/transparent_hugepage/enable

1.4.4.3 AOF追加阻塞

 

 

2.Redis安装

2.1 linux安装

下载压缩包:http://download.redis.io/releases/

// 下载 wget http://download.redis.io/releases/+版本 //解压 tar -zxvf 文件名 // 安装gcc[c编译器]: yum install gcc-c++ // 安装Redis 【redis目录下】 make // 出错用 :  make MALLOC=libc  make install // 开启守护线程 daemonize yes // 运行Redis redis-server 配置文件 // 查询线程信息 ps -ef|grep "redis" // 连接客户端: redis-cli -h host-p 端口号

 

2.2 常用操作

性能测试:

redis-benchmark

 

切换库:

select 编号 【从0开始,默认16个库】

 

查看当前数据库key的数量:

DBSIZE

 

查看当前库所有的key

keys * // keys k? 精确查找

 

清空当前库

flushdb

 

清空全库

flushall

 

判断某个key是否存在

exists key //0-不存在 1-存在

 

剪切key到另一个库

move key db //当前库就不存在了

 

给key设置过期时间

expire key 10 //过期时间

 

查看key还有多久过期

ttl key //-1 永久有效,-2 已过期

 

查看key的类型

type key

 

2.3 配置

2.3.1 配置文件

redis.conf 配置项说明如下: 1.daemonize no Redis默认不是以守护进程的方式运行,可以通过该配置项修改, 使用yes启用守护进程 2. pidfile /var/run/redis.pid 当Redis以守护进程方式运行时,Redis默认会把pid 写入/var/run/redis.pid文件,可以通过pidfile指定   3. port 6379 指定Redis监听端口,默认端口为6379,   4. bind 127.0.0.1 绑定的主机地址   5. timeout 300 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能 6. loglevel verbose 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、 warning,默认为verbose   7. logfile stdout 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行, 而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null   8. databases 16 Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享, 并且基于单机才有,如果是集群就没有数据库的概念 。 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令 在连接上指定数据库id   9. save <seconds> <changes> 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件, 可以多个条件配合 save "" //关闭RDB   Redis默认配置文件中提供了三个条件:   save 900 1   save 300 10   save 60 10000   分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及 60秒内有10000个更改。   10. rdbcompression yes 指定存储至本地数据库时是否压缩数据,默认为yes, Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项, 但会导致数据库文件变的巨大   11. dbfilename dump.rdb 指定本地数据库文件名,默认值为dump.rdb   12. dir ./ 指定本地数据库存放目录   13. slaveof <masterip> <masterport> 设置当本机为slave服务时,设置master服务的IP地址及端口, 在Redis启动时,它会自动从master进行数据同步   14. masterauth <master-password> 当master服务设置了密码保护时,slav服务连接master的密码   15. requirepass foobared 设置Redis连接密码,如果配置了连接密码, 客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭   16. maxclients 12 设置同一时间最大客户端连接数,默认无限制, Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符 数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时, Redis会关闭新的连接并向客户端返回max number of clients reached 错误信息 17. maxmemory <bytes> 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中, 达到最大内存后,Redis会先尝试清除已到期或即将到期的Key, 当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作, 但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存, Value会存放在swap区   18. appendonly no 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的 把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。 因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会 在一段时间内只存在于内存中。默认为no   19. appendfilename appendonly.aof 指定更新日志文件名,默认为appendonly.aof    20. appendfsync everysec 指定更新日志条件,共有3个可选值:  no:表示等操作系统进行数据缓存同步到磁盘(快)    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)    everysec:表示每秒同步一次(折衷,默认值)   21. vm-enabled no 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下, VM机制将数据分页存放,由Redis将访问量较少的页即冷数据 swap到磁盘上,访问多的页面由磁盘自动换出到内存中    22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享    vm-swap-file /tmp/redis.swap 23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0    vm-max-memory 0 24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值    vm-page-size 32 25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。    vm-pages 134217728 26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4    vm-max-threads 4 27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启   glueoutputbuf yes 28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法   hash-max-zipmap-entries 64   hash-max-zipmap-value 512 29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)   activerehashing yes 30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件   include /path/to/local.conf

 

2.3.4 Redis安装脚本

#!/bin/bash #检查用户是否已经存在 ############################## ## 描述:创建用户,安装Redis ## 作者:孙培 ## 时间:2019-09-17 ############################## set -o nounset set -o errexit readonly redisDir="/opt/cachecloud/redis" readonly redisTarGz="redis-3.0.7.tar.gz" checkExist(){ #查看passwd里是否有用户记录 local num=`cat /etc/passwd | grep -w ${1}|wc -l` if [ ${num} -ge 1 ] then echo "==>>当前用户已经存在,是否覆盖已有数据?:[y/n]" read replace if [ ${replace} == "y" ] then echo "==>>删除已存在用户: ${1}" userdel -r "${1}" createUser "${1}" init "${1}" return 0 fi else createUser "${1}" init "${1}" fi return 0 } #创建用户 createUser() { # 添加用户 useradd -m -d /home/${1} -s /bin/bash ${1} # 添加密码 echo "==>> 请输入用户密码" passwd ${1} # 密码保存的有效天数 chage -M 9999 ${1} echo "==>> 用户${1}创建成功!" } # 初始化 init() { # 创建目录 mkdir -p /opt/cachecloud/data mkdir -p /opt/cachecloud/conf mkdir -p /opt/cachecloud/logs mkdir -p /opt/cachecloud/redis mkdir -p /tmp/cachecloud echo "==>>初始化文件目录" # 为用户授权 chown -R ${1}:${1} /opt/cachecloud chown -R ${1}:${1} /tmp/cachecloud echo "==>>为${1}用户授权" } # 安装redis installRedis() { # 提示语: echo "===>>开始安装Redis," # 安装GCC yum install -y gcc # 创建目录 mkdir -p ${redisDir} && cd ${redisDir} # 下载解压目录 wget http://download.redis.io/releases/${redisTarGz} && mv ${redisTarGz} redis.tar.gz && tar zxvf redis.tar.gz --strip-component=1 # 安装Redis make && make install if [ $? == 0 ] then echo "==>> Redis安装成功,Redis默认安装目录:${redisDir},Redis默认版本:${redisTarGz},如有需要,请修改脚本" #授权 chown -R $1:$1 ${redisDir} #配置环境变量 export PATH=$PATH:${redisDir}/src return 0 fi echo "==>>Redis安装失败,原因:Redis已经存在" } username=$1 checkExist "${username}" installRedis "${username}"

3. Redis

3.1 慢查询

Redis会将慢查询命令保存在内存队列中,慢查询数据不会持久化;

slowlog get [n] //获取慢查询列表 slowlog len //获取慢查询队列长度 slowlog reset //清理慢查询队列 //动态修改 config set slowlog-max-len 1000 //慢查询队列长度 config set slowlog-log-slower-than 1000 //单位微妙:即1毫秒

tip:

  • slowlog-log-slower-than 默认10ms,通常设置1ms
  • slowlog-max-len 不要太小,通常设置1000左右
  • 查询声明周期:1.发送请求 2.请求排队 3.执行请求(慢查询) 4,返回请求
  • 定期持久化慢查询

3.2 pipeline

1次请求时间 = 1次网络请求时间(发送请求+接收请求) + 命令排队时间+执行时间

注意:Redis执行命令的时间是微妙级别、所以Redis的瓶颈是在网络时间上;北京到上海的一次网 络请求大概需要花费13毫秒。

pipeline就是为了节省网络开销,一次执行多条命令;

3.2.1 pipeline VS m操作

 

原生的M操作是原子性的,pipeline是非原子性的;

 

3.3 Hyperloglog

pfadd key element //添加元素 pfcount key :计算总数 pfmerge destkey sourcekey [sourcekey..] //合并多个key

Tips:

  • 是否容忍错误 (错误率:0.81)
  • 是否需要单条数据

 

3.1 Redis事务

3.1.1 常用命令:

①标记开始一个事务:MULTI

②取消事务:DISCARD

③执行所有的事务:EXEC

④监视一个(或多个)key,如果在事务执行前这个key被改动,那么事务被打断:WATCH key [key...]

⑤取消WATCH命令多所有key的监视: UNWATCH

redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> set k1 v1 QUEUED redis 127.0.0.1:6379> DISCARD OK redis 127.0.0.1:6379> get k1 (nil) redis 127.0.0.1:6379> EXEC 1) OK redis 127.0.0.1:6379> get k1 "v1"

 

3.1.2 特性:

①冤头债主:

部分支持事务,成功的操作不会受错误操作的影响;

redis 127.0.0.1:6379> set k1 v1 QUEUED redis 127.0.0.1:6379> getset k2 (error) ERR wrong number of arguments for 'getset' command redis 127.0.0.1:6379> set k3 v3 QUEUED redis 127.0.0.1:6379> exec 1) OK 3) OK redis 127.0.0.1:6379> get k3 "v3"

 

②WATCH锁

WATCH锁,底层由乐观锁实现;

redis 127.0.0.1:6379> get k1 "800" //设置监视锁 redis 127.0.0.1:6379> watch k1 OK //开启事务 redis 127.0.0.1:6379> multi OK // 修改k1为29 redis 127.0.0.1:6379> set k1 29 QUEUED /----------- 别的线程---------------- redis 127.0.0.1:6379> get k1 "800" redis 127.0.0.1:6379> set k1 200 OK /----------- 别的线程---------------- //执行失败 redis 127.0.0.1:6379> exec (nil) //修改值失败 redis 127.0.0.1:6379> get k1 "200"

 

3.4 发布订阅

Redis消息队列特点:1. 无法获取历史消息 2.消息是非抢占式的(都可以收到)

3.4.1 订阅消息

//订阅多个消息 redis 127.0.0.1:6379> subscribe cctv-1 cctv-2 //订阅多个消息 psubscribe cctv*

3.4.2 发布消息

publish cctv-1 xinweblianbo

 

3.3 主从复制

作用:数据副本(高可用)、扩展读性能(读写分离)

3.3.1 主从配置

3.3.1.1 命令

查看当前节点信息:

info replication

 

作为从节点:【重启无效】

slaveof 主机IP 主机端口号 slaveof no one // 恢复为主节点,旧数据不会清除,跟随新主后会清空数据

3.3.1.2 配置

slaveof ip port slave-read-only yes //从节点只读

3.3.2 情景分析

情景一:先写入数据,后进行主从备份,之前的数据会不会被同步?

主从备份前的数据也会被同步

情景二:从机是否可以写数据?

从机只支持读取数据,不支持写数据

情景三:主机宕机后,从机是否会选举为主机?主机恢复后,从机是否可以正常工作?

主机宕机后,从机不会进行选举;

主机恢复后,从机正常工作;

情景四:从机宕机重启后是否可以正常工作?

命令式:重启会从机不会识别主机,需要重新执行命令;

配置型:重启后从机会识别主机

 

3.3.3 薪火相传

①:中间的节点还是属于从节点,不支持写操作;

②:中间节点宕机,最后的从节点也无法正常工作;中间节点恢复后需要重新执行slaveof命令

 

3.3.4 反客为主

原主机宕机 -->从机执行 slaveof no one -->选出新的主机【剩余的从机不会更新主机(忠心不二);谁执行这条命令谁就会变为主机(谋权篡位)】

原主机恢复---> 新主机不会禅让(反客为主) ---> 丛机会跟随原主机(坚贞不渝)

 

3.3.5 全量复制

全量复制开销:

1.bgsave时间

2.RDB文件网络传输时间

3.从节点清空数据时间

4.从节点加载RDB文件时间

5.从节点AOF重写时间(如果从节点开启了AOF功能)

 

3.3.6 部分复制

3.3.7 复制运维

  1. 读写分离:主从数据复制延迟、读到过期数据(3.2已经解决)
  2. 配置不一样:例如:maxmemory配置不一致,数据结构优化参数不一致(hash-max-ziplist-entries)
  3. 规避全量复制:第一次全量复制(不可避免)、runId不一致、复制缓冲区不足(默认1M,可增大该值)
  4. 复制风暴:假如Master挂了很多Slave,Master重启后,会进行大量全量复制、单机器故障,该机器上有大批主节点(主机分散、从机晋升)

3.3.8 优缺点

优点:1.数据备份 2、读写分离,减轻主机压力

缺点:1.手动故障转移 2.写能力、存储能力有限

3.4 哨兵模式

3.4.1 哨兵原理

 

 

 

新建配置文件:sentinel.conf

添加配置:

sentinel monitor mastser-name(区分不同的组) 主机IP 主机端口 投票数(达到该票时就会选举为主机) daemonized yesse port 端口号 logfile "${port}.log" ###########以下配置会重写################## sentinel down-after-milliseconds 组名 30000(30s,超过该时间,就认为该节点下线) sentinel parallel-syncs 组名 1(新主机成立后,一次可以有几个从机复制主机) sentinel failover-timeout 组名 180000(故障转移时间)

启动哨兵: /Redis/src目录下

redis-sentinel sentinel.conf【配置文件位置】

 

命令查看:

redis-cli -p 26379 //哨兵是一个特殊的 info //查看哨兵信息

 

3.4.2 客户端

3.4.2.1 客户端如何连接Master

step1: 客户端需要提供哨兵集合和组名,遍历哨兵集合,找到一个可用的哨兵

step2:通过组名获取master地址

step3: 验证节点的信息

step4:如果发生故障转移,哨兵通过发布订阅模式通知客户端

3.4.3 哨兵内部定时任务

3.4.4 主客观下线

从节点:因为不涉及故障转移,固只需要主观下线就行;

 

3.4.5 领导者选举

 

 

3.4.6 故障转移

 

 

1.面试题:

1.1 为什么要用Redis:

高性能:Redis读取速率高于DB查找

高并发:缓解数据库压力

1.2 Redis存在的问题

1.3 Redis的文件事件处理器

1.4 Redis过期策略

1.4.1 惰性删除 && 定期删除

定期删除:每隔100ms,就会随机抽取一些设置了过期时间的key,j检查其是否过期,如果过期,就删除;

惰性删除:在获取key时,Redis会检查这个key是否设置过期时间以及是否过期,如果已经过期则删除;

其余:走内存淘汰机制

 

1.4.2 内存淘汰机制

如果Redis内存占用过多,就会进行内存淘汰,对应的策略:

策略一:内存不足时,拒绝写入;(noeviction)

策略二:内存不足时,删除最近最少使用的key[全量key];推荐使用(allkeys-lru)

策略三:内存不足时,随机删除;(allkeys-random)

策略四:内存不足时,删除最近最少使用的key【设置了过期时间的key】;(volatile-lru)

策略五:内存不足时,随机删除;【设置了过期时间的key】 (volatile-random)

策略六:内存不足时,优先删除过期时间早的key; (volatile-ttl)

 

1.4.3 LRU算法

public class MyLRU<K,V> extends LinkedHashMap<K,V>{ /** * 缓存的容量 */ private final int CACHE_SIZE; /** * LinkedHashMap的一个构造函数,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面 * public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { * super(initialCapacity, loadFactor); * this.accessOrder = accessOrder; * } * @param cacheSize */ public MyLRU(int cacheSize) { super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); CACHE_SIZE = cacheSize; } /** * 过期策略: * LinkedHashMap自带的判断是否删除最老的元素方法,默认返回false,即不删除老数据 * 我们要做的就是重写这个方法,当满足一定条件时删除老数据 * @param eldest * @return */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > CACHE_SIZE; } }

 

1.5 缓存雪崩

1.5.1 缓存雪崩

缓存崩溃 -->数据库崩溃 -->系统不可用

 

1.5.2 解决策略

事前:保证Redis的高可用 : Redis集群

事中:本地ehcache+限流组件

事后:根据Redis持久化文件,恢复Redis集群

1.6 缓存穿透

穿透:Redis->DB ->穿透

根本原因:数据库中不存在的数据不会回写到Redis中,进而每次都请求数据库

 

解决方案:查询不到数据时,就写空值到缓存;

 

1.7 缓存和数据库双写的一致性问题

1.7.1 双写的策略

读数据:读取数据时,先从Redis获取,获取不到时,再查数据库,并将结果回写数据库。

更新数据:更新数据时,先删除缓存,再更新数据库。

更新数据时删除缓存原因:懒加载机制,频繁更新不等于频繁查询,只有查询的时候才放入缓存;

1.7.2

 

 

3

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐