Redis基础知识大全
1.Redis1.1 初识Redis1.1.1 Redis特性1.1.2 Redis使用场景1.1.3 Redis常用数据类型1.1.4 单线程1.2 常用API1.2.1 通用命令1.3 数据结构&内部编码1.4 持久化1.4.1 RDB1.4.2 AOF1.4.3 RDB VS AOF1.4.4 持久化存在的问题2.Redi...
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、全量复制
推荐配置:关闭自动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 复制运维
- 读写分离:主从数据复制延迟、读到过期数据(3.2已经解决)
- 配置不一样:例如:maxmemory配置不一致,数据结构优化参数不一致(hash-max-ziplist-entries)
- 规避全量复制:第一次全量复制(不可避免)、runId不一致、复制缓冲区不足(默认1M,可增大该值)
- 复制风暴:假如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
更多推荐
所有评论(0)