最近深入的学习了一下Redis ,在此记录一下 共同学习。
目录
- 一、redis的数据结构和常用命令
- 五种数据结构
- 常用命令
- 1.字符串string常用操作
- 2.哈希hash常用操作
- 3.列表list常用操作
- 4.集合set常用操作
- 5.有序集合zset常用命令
- 其他命令
- 二、redis的持久化
- 1.RDB快照(snapshot)
- 2.AOF(append-only file)
- 3.混合持久化
- 三、redis的主从架构、哨兵机制和集群搭建
- 1. Redis的主从架构如何搭建:
- 2.Redis哨兵高可用架构
- 3.高可用集群搭建
- 四、通过源码看底层结构
- 1.string
- 2.list
- 3.hash
- 4.set
- 5.zset
- 五、Redis常见问题及解决思路
一、redis的数据结构和常用命令
五种数据结构
字符串string
哈希hash
列表list
集合set
有序集合zset
常用命令
推荐一个Redis命令很全的中文参考网站,而且有详细的命令介绍和使用示例。
下面,我列出一些常用的命令:
1.字符串string常用操作
SET key value //存入字符串键值对
GET key //获取一个字符串键值
应用场景: 单值缓存
MSET key value [key value …] //批量存储字符串键值对
MGET key [key …] //批量获取字符串键值
应用场景:对象缓存 如:MSET user:1:name xiaoming user:1:age 18
SETNX key value //存入一个不存在的字符串键值对,如果key已经存在,会不成功
应用场景:分布式锁 SETNX product:10001 true //返回1代表获取锁成功
INCR key //将key中储存的数字值加1 (有原子性)
DECR key //将key中储存的数字值减1(有原子性)
应用场景:计数器,如:微博点赞、文章浏览次数等。
DEL key [key …] //删除一个键
EXPIRE key seconds //设置一个键的过期时间(秒)
INCRBY key increment //将key所储存的值加上increment(有原子性)
DECRBY key decrement //将key所储存的值减去decrement(有原子性)
2.哈希hash常用操作
HSET key field value //存储一个哈希表key的键值
HSETNX key field value //存储一个不存在的哈希表key的键值,如果key已经存在,会不成功
HMSET key field value [field value …] //在一个哈希表key中存储多个键值对
HGET key field //获取哈希表key对应的field键值
HMGET key field [field …] //批量获取哈希表key中多个field键值
HDEL key field [field …] //删除哈希表key中的field键值
HLEN key //返回哈希表key中field的数量
HGETALL key //返回哈希表key中所有的键值
HINCRBY key field increment //为哈希表key中field键的值加上增量increment
应用场景:
对象缓存:
存储:HMSET user 1:name xiaoming 1:age 18
读取:HMGET user 1:name (可以只读取用户的名字信息,这是String数据结构做不到的)
如:购物车功能:key是购物车ID(cart:1001), field是商品id(88),商品数量为value
添加商品:hset cart:1001 88 1
增加数量:hincrby cart:1001 88 1
商品总数:hlen cart:1001
删除商品:hdel cart:1001 88
获取购物车所有商品:hgetall cart:1001
3.列表list常用操作
LPUSH key value [value …] //将一个或多个值value插入到key列表的表头(最左边)
RPUSH key value [value …] //将一个或多个值value插入到key列表的表尾(最右边)
LPOP key //移除并返回key列表的头元素
RPOP key //移除并返回key列表的尾元素
LRANGE key start stop //返回列表key中指定区间内的元素,区间以偏移量start和stop指定
BLPOP key [key …] timeout //从key列表表头弹出一个元素,若列表中没有元素,阻塞等待 timeout秒,如果timeout=0,一直阻塞等待
BRPOP key [key …] timeout //从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待 timeout秒,如果timeout=0,一直阻塞等待
应用场景:
通过上面的命令,我们可以随意操作一个value在list中,左进、左出、右进、右出,通过这些方法的组合,我们可以想到什么数据结构? 栈结构:先进后出, 队列:先进先出,阻塞队列:用BLPOP取出就是阻塞队列。
4.集合set常用操作
SADD key member [member …] //往集合key中存入元素,元素存在则忽略,若key不存在则新建
SREM key member [member …] //从集合key中删除元素
SMEMBERS key //获取集合key中所有元素
SCARD key //获取集合key的元素个数
SISMEMBER key member //判断member元素是否存在于集合key中
SRANDMEMBER key [count] //从集合key中选出count个元素,元素不从key中删除
SPOP key [count] //从集合key中选出count个元素,元素从key中删除
运算操作
SINTER key [key …] //交集运算
SINTERSTORE destination key [key …] //将交集结果存入新集合destination中
SUNION key [key …] //并集运算
SUNIONSTORE destination key [key …] //将并集结果存入新集合destination中
SDIFF key [key …] //差集运算
SDIFFSTORE destination key [key …] //将差集结果存入新集合destination中
应用场景:
在社交APP中,如知乎或者微博,有一个功能,“共同关注”,就是求两个集合的交集。
5.有序集合zset常用命令
ZADD key score member [[score member]…] //往有序集合key中加入带分值元素
ZREM key member [member …] //从有序集合key中删除元素
ZSCORE key member //返回有序集合key中元素member的分值
ZINCRBY key increment member //为有序集合key中元素member的分值加上increment
ZCARD key //返回有序集合key中元素个数
ZRANGE key start stop [WITHSCORES] //正序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES] //倒序获取有序集合key从start下标到stop下标的元素
ZUNIONSTORE destkey numkeys key [key …] //并集计算
ZINTERSTORE destkey numkeys key [key …] //交集计算
应用场景:
热门新闻top10排行:
点击新闻加1分值 : ZINCRBY hotNews:20211204 1 新闻名字
展示当日排行前十: ZREVRANGE hotNews:20211204 0 9 WITHSCORES
其他命令
keys [MATCH pattern] 全量遍历键
用来列出所有满足特定正则字符串规则的key,当redis数据量比较大时, 性能比较差,要避免使用。
SCAN cursor [MATCH pattern] [COUNT count] 渐进式遍历键 是一个基于游标的迭代器
scan 参数提供了三个参数,第一个是 cursor 整数值(hash桶的索引值),第二个是 key 的正则模式, 第三个是一次遍历的key的数量(参考值,底层遍历的数量不一定),并不是符合条件的结果数量。第 一次遍历时,cursor 值为 0,然后将返回结果中第一个整数值作为下一次遍历的 cursor。一直遍历 到返回的 cursor 值为 0 时结束。
Info:查看redis服务运行信息
二、redis的持久化
Redis 启动服务: src/redis‐server redis.conf
redis.conf文件是 启动redis的配置文件,我们持久化相关的配置也都在这个文件里。
Redis的持久化配置目前有3中:
1、RDB快照(snapshot) 这是Redis默认的持久化方式。
2、AOF(append-only file)
3、混合持久化
1.RDB快照(snapshot)
Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。
配置解释:
save 60 1000
60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次rdb文件。
还可以手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件, 每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。
配置自动生成rdb文件后台使用的是bgsave方式。
2.AOF(append-only file)
AOF 持久化,将修改的每一条指令记录进文件appendonly.aof中(先写入os cache,每隔一段时间 fsync到磁盘);
可以通过修改配置文件来打开 AOF 功能:
appendonly yes #打开AOF持久化
比如执行命令“set test 666”,aof文件里会记录如下数据:
*3
$3
set
$5
test
$3
666
这是一种resp协议格式数据,星号后面的数字代表命令有多少个参数,$号后面的数字代表这个参数有几 个字符
也可以配置 Redis 多久才将数据 fsync 到磁盘一次:
appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。 (默认的,且推荐这个)
appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
AOF重写:
AOF文件里可能有太多没用指令,所以AOF会定期根据内存的最新数据生成整理后的aof文件。
配置可以控制AOF自动重写频率:
auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就 很快,重写的意义不大
auto‐aof‐rewrite‐percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写
AOF还可以通过命令去重写,进入redis客户端执行命令bgrewriteaof重写AOF。重写也是新的线程去处理的,不会影响客户端发送Redis的请求。
RDB 和 AOF 对比:
因为RDB文件是二进制的,所以它占用空间小、恢复速度快。但是相比数据安全性,AOF的数据会更全更安全。如果你同时启用了这两个持久化方式,Redis在启动恢复的时候,会优先选择AOF的数据,因为它一般情况下数据会更完整。
3.混合持久化
什么事混合持久化,其实就是AOF的一种文件, 在重写的时候,不再是单纯将内存数据转换为RESP命令写入AOF文件,而是将写数据这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一 起,都写入新的AOF文件。
所以混合持久化AOF文件appendonly.aof的结构:RDB格式+AOF格式。
因为RDB是二进制的 空间小、读取速度很快。所以 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。
通过如下配置可以开启混合持久化(必须先开启aof):
aof‐use‐rdb‐preamble yes
三、redis的主从架构、哨兵机制和集群搭建
1. Redis的主从架构如何搭建:
一个Redis主节点master和多个Redis从节点slave,组合到一起,slave节点从master节点复制数据保持数据同步,master节点可以为客户端提供读写,slaver只能提供读的服务。
如何操作:
首先正常启动主节点,然后再启动从节点之前在redis.conf文件中加一些配置,让从节点的数据从主节点里复制,所以要配置主节点的IP和端口。
从节点要修改配置 redis.conf文件:
// 配置主从复制
replicaof 192.168.0.60 6379 # 从本机6379的redis实例复制数据,Redis 5.0之前使用slaveof
replica‐read‐only yes # 配置从节点只读
启动从节点,就OK了
redis‐server redis.conf
可以看到,配置启动主从架构很简单,只需要从节点中配置一个主节点的地址就OK了。
2.Redis哨兵高可用架构
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点,master节点挂了,就选举slave节点作为master节点。
哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点。当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis 主节点通知给client端。
搭建步骤:
在sentinel.conf文件中修改配置(注意这个文件名字叫sentinel 翻译成中文就是哨兵,所以我们都说Redis的哨兵架构)
将相关配置修改为如下值:
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
dir "/usr/local/redis-5.0.3/data"
# sentinel monitor <master-redis-name> <master-redis-ip> <master-redis-port> <quorum>
# quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2 # mymaster这个名字随便取,客户端访问时会用到
启动sentinel哨兵实例
src/redis-sentinel sentinel-26379.conf`
查看sentinel的info信息
src/redis-cli -p 26379
127.0.0.1:26379>info
sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面)。当redis主节点如果挂了,哨兵集群会重新选举出新的redis主节点,
同时会修改所有sentinel节点配置文件的集群元数据信息,
同时还会修改sentinel文件里之前配置的mymaster对应的6379端口。
所以我们可以在sentinel配置文件中查看历史的节点选举情况。
哨兵的Jedis连接代码:
public class JedisSentinelTest {
public static void main(String[] args) throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
String masterName = "mymaster";
Set<String> sentinels = new HashSet<String>();
sentinels.add(new HostAndPort("192.168.0.60",26379).toString());
sentinels.add(new HostAndPort("192.168.0.60",26380).toString());
sentinels.add(new HostAndPort("192.168.0.60",26381).toString());
//JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池
//JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, config, 3000, null);
Jedis jedis = null;
try {
jedis = jedisSentinelPool.getResource();
System.out.println(jedis.set("test", "testvalue"));
System.out.println(jedis.get("test"));
} catch (Exception e) {
e.printStackTrace();
} finally {
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
if (jedis != null)
jedis.close();
}
}
}
注意,以上哨兵机制, 性能和高可用性等各方面表现 一般,特别是在主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持 很高的并发,且单个主节点内存也不宜设置得过大。 所以就有了下面的高可用的集群架构。
3.高可用集群搭建
这是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中 心节点,可水平扩展。
redis集群需要至少三个master节点。
这里搭建三个master节点,并且给每个master再搭建一个slave节 点,总共6个redis节点,这里用三台机器部署6个redis实例,每台机器一主一从,搭建集群的步骤如下:
在redis.conf 文件中修改
把之前的redis.conf配置文件copy到8001下,修改如下内容:
(1)daemonize yes
(2)port 8001(分别对每个机器的端口号进行设置)
(3)pidfile /var/run/redis_8001.pid # 把pid进程号写入pidfile配置的文件
(4)dir /usr/local/redis-cluster/8001/(指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据)
(5)cluster-enabled yes(启动集群模式)
(6)cluster-config-file nodes-8001.conf(集群节点信息文件,这里800x最好和port对应上)
(7)cluster-node-timeout 10000
(8)# bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
(9)protected-mode no (关闭保护模式)
(10)appendonly yes
如果要设置密码需要增加如下配置:
(11)requirepass 123456 (设置redis访问密码)
(12)masterauth 123456 (设置集群节点间访问密码,跟上面一致)
把6台Redis都修改好这样的配置之后,分别启动6个redis实例,然后检查是否启动成功。
用redis-cli创建整个redis集群:
执行这条命令需要确认三台机器之间的redis实例要能相互访问,可以先简单把所有机器防火墙关掉,如果不关闭防火墙则需要打开redis服务端口和集群节点gossip通信端口16379(默认是在redis端口号上加1W)
关闭防火墙
systemctl stop firewalld # 临时关闭防火墙
systemctl disable firewalld # 禁止开机启动
用这个命令启动集群:
/usr/local/redis-5.0.3/src/redis-cli -a 123456 --cluster create --cluster-replicas 1 192.168.0.61:8001 192.168.0.62:8002 192.168.0.63:8003 192.168.0.61:8004 192.168.0.62:8005 192.168.0.63:8006
最后查看集群信息:
连接任意一个客户端即可:./redis-cli -c -h -p (-a访问服务端密码,-c表示集群模式,指定ip地址和端口号)
cluster info(查看集群信息)
cluster nodes(查看节点列表)
关闭集群则需要逐个进行关闭,使用命令:
/usr/local/redis-5.0.3/src/redis-cli -a 123456 -c -h 192.168.0.60 -p 800* shutdown
集群分析:
Redis Cluster 将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每 个节点中。
槽位定位算法: 会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模 来得到具体槽位。
所以我们要尽量避免bigkey,也就是一个key对应的value非常大,这个key存在某一个槽位中,必然会导致数据倾斜。
四、通过源码看底层结构
redis的源码使用C语言编写的。这里简单的记录一下它底层的数据结构 共同深入学习。
1.string
string数据结构底层存储是: 简单动态字符串(SDS,simple dynamic string)
详细讲解可以看这个博客:javascript:void(0) SDS数据结构的主要优点是:
1、二进制的安全的数据结构
2、提供了内存预分配机制、避免频繁的内存分配
3、兼容C语言的函数库
2.list
List是一个有序(按加入的时序排序)的数据结构,Redis采用quicklist(双端链表) 和 ziplist 作为List的底层实现。
3.hash
Hash 数据结构底层实现为一个字典( dict )来存储K-V的数据结构,当数据量比较小,或者单个元素比较小时,底层用ziplist存储。
4.set
Set 为无序的,自动去重的集合数据类型,Set 数据结构底层实现为一个value 为 null 的 字典( dict ),当数据可以用整形表示时,Set集合将被编码为intset数据结构。两个条件任意满足时
Set将用hashtable存储数据。
5.zset
ZSet 为有序的,自动去重的集合数据类型,ZSet 数据结构底层实现为 字典(dict) + 跳表(skiplist) ,当数据比较少时,用ziplist编码结构存储。
五、Redis常见问题及解决思路
缓存穿透: 在缓存上没有匹配到,就去查数据库了。
方案1: 给无效ID设置缓存且加上过期时间。
缓存失效(击穿): 缓存突然失效,但是大量请求还在。
方案1:商品失效时间,不要统一设置,要在一个范围设置随机失效时间
缓存雪崩: 如果Redis宕机,所有请求都到数据库,导致数据库挂了,所有网站崩溃。
方案1: 可以做限流,防止雪崩,可能会有些体验不好,但是至少可以保证服务器不宕机。
热点缓存key重建优化: 冷数据突然变成热点数据,但是我们还没有缓存。
方案:用分布式锁,重建缓存。
缓存数据库双写不一致: 非常高并发情况,才有可能出现这个问题。
方案: 用队列 串行化执行, 延时删除, 推荐加分布式锁,用读写锁。
canal是阿里云的开源中间件,会监听binlog去更新缓存
常用优化小提示:
拒绝bigkey: value值很大。 解决:拆分,取模拆分。
用缓存尽量加上超时时间。
禁用危险的命令 如keys
Redis有16个DB,不同的业务不要用不同的DB,这样对并发没有效果,可以用不同的Redis
Redis的事务功能较弱,如果用到事务就用lua脚本