0%

Redis核心数据结构

Redis的核心数据结构介绍和使用,以及Redis高性能原理剖析


Linux环境下Redis的安装

环境搭建

本文以CentOS Linux release 7.9.2009 (Core)为例,演示安装过程。

Redis下载地址:https://redis.io/download/#redis-downloads

因为官网下载下来的是redis源码,不能直接解压使用,需要用gcc编译器进行编译,才会生成可执行文件。

服务器安装gcc编译环境

1
yum install gcc -y

查看gcc的版本

1
gcc --version

将下载好的压缩包,复制到服务器的目录中。或者直接使用wget,将压缩包下载到服务器上。

1
wget https://download.redis.io/releases/redis-6.2.12.tar.gz

解压压缩包到当前目录,进入目录,并进行编译

1
2
3
tar -zxvf redis-6.2.12.tar.gz
cd redis-6.2.12
make

如果编译期间出错,使用

1
make distclean

清除编译生成的文件,处理错误后,再次使用 make 重新编译

配置修改

使用vim修改redis.conf文件

1
2
3
4
5
6
#设置后台启动
daemonize yes
#设置redis密码,默认无密码
requirepass 123456
#不限制IP访问
bind 0.0.0.0

启动redis

1
2
cd src
./redis-server ../redis.conf

查看redis是否启动

1
ps -ef|grep redis

使用redis客户端,连接redis

1
./redis-cli

输入密码,就可以执行redis命令了

1
auth 123456

Redis基础数据类型

Redis有五种基本数据类型

String

字符串常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 存入字符串键值对
SET key value
// 获取一个字符串键值
GET key
// 删除一个键
DEL key [key ...]
// 存入一个不存在的字符串键值对
SETNX key value
// 批量存储字符串键值对
MSET key value [key value ...]
// 批量获取字符串键值
MGET key [key ...]
// 设置一个键的过期时间(秒)
EXPIRE key seconds

原子加减

1
2
3
4
5
6
7
8
// 将key中储存的数字值加1
INCR key
// 将key中储存的数字值减1
DECR key
// 将key所储存的值加上increment
INCRBY key increment
// 将key所储存的值减去decrement
DECRBY key decrement

String应用场景

单值缓存
1
2
set key value
get key
对象缓存
1
2
3
set user:1 value(json数据)
mset user:1:name zhangsan user:1:age 18
mget user:1:name user:1:age
分布式锁
1
2
3
4
5
6
7
8
// 返回1代表加锁成功1
setnx order:1 true
// 返回0代表加锁失败
setnx order:1 true
// 业务执行完毕释放锁
del order:1
// 加锁并设置过期时间,防止死锁
set order:1 true ex 10 nx
计数器
1
2
3
// 文章阅读次数+1
INCR article:readcount:{文章id}
GET article:readcount:{文章id}

session共享
1
spring session + redis实现session共享
分布式序列号
1
2
// redis批量生成序列号提升性能
INCRBY orderId 1

Hash

Hash常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 存储一个哈希表key的键值
HSET key field value
// 存储一个不存在的哈希表key的键值
HSETNX key field value
// 在一个哈希表key中存储多个键值对
HMSET key field value [field value ...]
// 获取哈希表key对应的field键值
HGET key field
// 批量获取哈希表key中多个field键值
HMGET key field [field ...]
// 删除哈希表key中的field键值
HDEL key field [field ...]
// 返回哈希表key中field的数量
HLEN key
// 返回哈希表key中所有的键值
HGETALL key
// 为哈希表key中field键的值加上增量increment
HINCRBY key field increment

Hash应用场景

对象缓存
1
2
HMSET user 1:name zhuge 1:balance 1888
HMGET user 1:name 1:balance

优点

  1. 同类数据归类整合储存,方便数据管理
  2. 相比string操作消耗内存与cpu更小
  3. 相比string储存更节省空间

缺点

  1. 过期功能不能使用在field上,只能用在key上
  2. Redis集群架构下不适合大规模使用

List

List常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将一个或多个值value插入到key列表的表头(最左边)
LPUSH key value [value ...]
// 将一个或多个值value插入到key列表的表尾(最右边)
RPUSH key value [value ...]
// 移除并返回key列表的头元素
LPOP key
// 移除并返回key列表的尾元素
RPOP key
// 返回列表key中指定区间内的元素,区间以偏移量start和stop指定
LRANGE key start stop
// 从key列表表头弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
BLPOP key [key ...] timeout
// 从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
BRPOP key [key ...] timeout

List应用场景

1
2
3
Stack(栈) = LPUSH + LPOP
Queue(队列)= LPUSH + RPOP
Blocking MQ(阻塞队列)= LPUSH + BRPOP

Set

Set常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 往集合key中存入元素,元素存在则忽略,若key不存在则新建
SADD key member [member ...]
// 从集合key中删除元素
SREM key member [member ...]
// 获取集合key中所有元素
SMEMBERS key
// 获取集合key的元素个数
SCARD key
// 判断member元素是否存在于集合key中
SISMEMBER key member
// 从集合key中选出count个元素,元素不从key中删除
SRANDMEMBER key [count]
// 从集合key中选出count个元素,元素从key中删除
SPOP key [count]

Set运算操作

1
2
3
4
5
6
7
8
9
10
11
12
// 交集运算
SINTER key [key ...]
// 将交集结果存入新集合destination中
SINTERSTORE destination key [key ..]
// 并集运算
SUNION key [key ..]
// 将并集结果存入新集合destination中
SUNIONSTORE destination key [key ...]
// 差集运算
SDIFF key [key ...]
// 将差集结果存入新集合destination中
SDIFFSTORE destination key [key ...]

Set应用场景

抽奖小程序
1
2
3
4
5
6
// 点击参与抽奖加入集合
SADD key {userlD}
// 查看参与抽奖所有用户
SMEMBERS key
// 抽取count名中奖者
SRANDMEMBER key [count] / SPOP key [count]

微博或朋友圈点赞
1
2
3
4
5
6
7
8
9
10
// 点赞
SADD like:{消息ID} {用户ID}
// 取消点赞
SREM like:{消息ID} {用户ID}
// 检查用户是否点过赞
SISMEMBER like:{消息ID} {用户ID}
// 获取点赞的用户列表
SMEMBERS like:{消息ID}
// 获取点赞用户数
SCARD like:{消息ID}

集合操作
1
2
3
SINTER set1 set2 set3 -> { c }
SUNION set1 set2 set3 -> { a,b,c,d,e }
SDIFF set1 set2 set3 -> { a }

zset

zset常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 往有序集合key中加入带分值元素
ZADD key score member [[score member]…]
// 从有序集合key中删除元素
ZREM key member [member …]
// 返回有序集合key中元素member的分值
ZSCORE key member
// 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member
// 返回有序集合key中元素个数
ZCARD key
// 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
// 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]

zset集合操作

1
2
3
4
// 并集计算
ZUNIONSTORE destkey numkeys key [key ...]
// 交集计算
ZINTERSTORE destkey numkeys key [key …]

zset应用场景

排行榜
1
2
3
4
5
6
7
8
9
// 点击新闻
ZINCRBY hotNews:20190819 1 守护香港
// 展示当日排行前十
ZREVRANGE hotNews:20190819 0 9 WITHSCORES
// 七日搜索榜单计算
ZUNIONSTORE hotNews:20190813-20190819 7
hotNews:20190813 hotNews:20190814... hotNews:20190819
// 展示七日排行前十
ZREVRANGE hotNews:20190813-20190819 0 9 WITHSCORES

Redis的单线程和高性能

单线程和高性能

Redis是单线程吗?

Redis 的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

Redis 单线程为什么还能这么快?

因为它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令(比如keys),一定要谨慎使用,一不小心就可能会导致 Redis 卡顿。

Redis 单线程如何处理那么多的并发客户端连接?

Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。

1
2
3
4
# 查看redis支持的最大连接数,在redis.conf文件中可修改,# maxclients 10000
127.0.0.1:6379> CONFIG GET maxclients
    ##1) "maxclients"
    ##2) "10000"

其他高级命令

keys

keys:全量遍历键,用来列出所有满足特定正则字符串规则的key,当redis数据量比较大时,性能比较差,要避免使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> set name1 a
OK
127.0.0.1:6379> set name2 b
OK
127.0.0.1:6379> set name3 c
OK
127.0.0.1:6379> set name4 d
OK
127.0.0.1:6379> set name5 e
OK
127.0.0.1:6379> keys name*
1) "name2"
2) "name4"
3) "name5"
4) "name3"
5) "name1"
127.0.0.1:6379>

scan

scan:渐进式遍历键

SCAN cursor [MATCH pattern] [COUNT count]

scan 参数提供了三个参数,第一个是 cursor 整数值(hash桶的索引值),第二个是 key 的正则模式,第三个是一次遍历的key的数量(参考值,底层遍历的数量不一定),并不是符合条件的结果数量。第一次遍历时,cursor 值为 0,然后将返回结果中第一个整数值作为下一次遍历的 cursor。一直遍历到返回的 cursor 值为 0 时结束。

注意:但是scan并非完美无瑕, 如果在scan的过程中如果有键的变化(增加、 删除、 修改) ,那么遍历效果可能会碰到如下问题: 新增的键可能没有遍历到, 遍历出了重复的键等情况, 也就是说scan并不能保证完整的遍历出来所有的键, 这些是我们在开发时需要考虑的。

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> scan 0 match name* count 3
1) "5"
2) 1) "name4"
2) "name5"
3) "name2"
127.0.0.1:6379> scan 5 match name* count 3
1) "0"
2) 1) "name3"
2) "name1"
127.0.0.1:6379>

info

查看redis服务运行信息,分为 9 大块,每个块都有非常多的参数,这 9 个块分别是:

  • Server 服务器运行的环境参数

  • Clients 客户端相关信息

  • Memory 服务器运行内存统计数据

  • Persistence 持久化信息

  • Stats 通用统计数据

  • Replication 主从复制相关信息

  • CPU CPU 使用情况

  • Cluster 集群信息

  • KeySpace 键值对统计数量信息

1
2
3
4
5
6
7
8
9
10
11
12
13
connected_clients:2                  # 正在连接的客户端数量

instantaneous_ops_per_sec:789 # 每秒执行多少次指令

used_memory:929864 # Redis分配的内存总量(byte),包含redis进程内部的开销和数据占用的内存
used_memory_human:908.07K # Redis分配的内存总量(Kb,human会展示出单位)
used_memory_rss_human:2.28M # 向操作系统申请的内存大小(Mb)(这个值一般是大于used_memory的,因为Redis的内存分配策略会产生内存碎片)
used_memory_peak:929864 # redis的内存消耗峰值(byte)
used_memory_peak_human:908.07K # redis的内存消耗峰值(KB)

maxmemory:0 # 配置中设置的最大可使用内存值(byte),默认0,不限制,一般配置为机器物理内存的百分之七八十,需要留一部分给操作系统
maxmemory_human:0B # 配置中设置的最大可使用内存值
maxmemory_policy:noeviction # 当达到maxmemory时的淘汰策略