Redis 简介
2008 年,意大利的一家创业公司 Merzia 推出了一款基于 MySQL 的网站实时统计系统 LLOOGG ,然而没过多久该公司的创始人 Salvatore Sanfilippo 便开始对 MySQL 的性能感到失望,于是他决定亲自为 LLOOGG 量身定做一个数据库,并于 2009 年开发完成,这个数据库就是 Redis。
什么是 Redis
Redis 是一个使用 ANSI C 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。
Redis 不是简单的 Key-Value 数据库,它还支持数据结构,例如
- 字符串
- 哈希
- 列表
- 集合
- 带范围查询的排序集合
- 位图
- 超日志
- 带有半径查询和流的地理空间索引
Redis 具有内置的复制功能,解析执行 Lua 脚本,LRU 缓存控制,事务和不同级别的磁盘持久性,并通过 Redis Sentinel 和 Redis Cluster 自动分区提供高可用性。
Redis 的存储结构
Redis 是 REmote DIctionary Server(远程字典
服务器)的缩写,它以字典结构存储数据,并允许其他应用通过 TCP 协议读写字典中的内容。
Redis 字典中的键值除了可以是字符串,还可以是其它数据类型。
内存存储与持久化
Redis 数据库中所有数据都存储在内存
中。相对于磁盘,内存的数据读/写速度要快得多,所以我们通常用 Redis 做缓存数据库,在一台普通电脑上,Redis 可以在一秒内读写超过 10 万个键值。
将数据存储在内存中也有问题,比如程序退出后内存中的数据会丢失。不过 Redis 提供了对持久化的支持,即可以将内存中的数据异步写入到硬盘中,同时不影响继续提供服务。
Redis特点
- 读写性能优异
- 持久化
- 数据类型丰富
- 单线程
- 数据自动过期
- 发布订阅
- 分布式
简单稳定
Redis 提供了 250 多个命令,听起来很多,但是常用的也就几十个,并且每个命令都很容易记忆。
Redis 命令列表
Redis 应用场景
Redis是一个 Key-Value 存储系统,大部分情况下是因为其高性能的特性,被当做缓存使用,这里介绍下Redis经常遇到的使用场景。
- 缓存系统
- 排行榜
- 计数器
- 分布式会话
- 分布式锁
- 社交网络
- 最新列表
- 消息系统
相关资源
Redis的安装
Redis 官方不支持 Windows。2011 年微软向 Redis 提交了一个补丁,以使 Redis 可以在 Windows 下编译运行。但被作者拒绝了,原因是在服务器领域上 Linux 已经得到了广泛的使用,让 Redis 能在 Windows 下运行相比而言显得不那么重要。并且 Redis 使用了如写时复制等很多操作系统相关的特性,兼容 Windows 会耗费天大的精力而影响 Redis 的开发。
我这里是在Docker上部署的redis
关于docker的概念与使用可以看这里
我这里的window10还需要安装一个WPL
接下来需要生成一个image文件,Docker 把应用程序及其依赖,打包在 image 文件里面。那么如何可以生成 image 文件?这就需要用到 Dockerfile 文件。它是一个文本文件,用来配置 image。Docker 根据 该文件生成二进制的 image 文件。
新建Dockerfile文件,内容如下:
FROM node
#拷贝当前目录文件到app
COPY . /app
#创建app文件夹
WORKDIR /app
#下载 Redis 源码
RUN wget https://download.redis.io/releases/redis-6.0.9.tar.gz
#解压 Redis 压缩包
RUN tar xzf redis-6.0.9.tar.gz
#将容器 5000 端口暴露出来, 允许外部连接这个端口
EXPOSE 5000/tcp
有了 Dockerfile 文件以后,就可以使用docker image build命令创建 image 文件了。
# 创建 image 文件
docker image build -t docker-redis:0.0.1 .
# docker container run命令会从 image 文件生成容器。
docker container run -p 6379:6379 -it docker-redis:0.0.1 /bin/bash
or => docker container run --rm -p 6379:6379 -it docker-redis:0.0.1 /bin/bash
ls
cd redis-6.0.9
# 编译安装
make
# 开启redis服务
cd ./src/redis-server
# 登录远程docker账号,并上传
docker login
image tag koa-demo:0.0.1 conor007a/koa-demo:0.0.1
docker image push conor007a/koa-demo:0.0.1
# 其它命令
docker container ls
docker container ls -all
docker container kill 50b2aa067544
docker container rm 50b2aa067544
运行 Redis
redis-server
带端口
redis-server --port 1234
如果需要在后台运行 Redis:
redis-server --daemonize yes
查看 Redis 后端运行进程
ps -ef | grep -i redis
要将 Redis 二进制文件安装到 /usr/local/bin 中,只需使用:
make install
停止 Redis
考虑到 Redis 有可能正在将内存中的数据同步到硬盘中,强行终止 Redis 进程可能会导致数据丢失。
所有正确停止 Redis 的方式应该是向 Redis 发送 SHUTDOWN 命令:
redis-cli shutdown
通过进程号停止 Redis 效果与发送 SHUTDOWN 命令一样。
kill -9 4684
连接 Redis
redis-cli
redis-cli -h 127.0.0.1 -p 1234
不出差错的话,此时已经连接上 Redis 数据库,我们通过 Redis 提供的 PING 命令来测试与 Redis 是否连接正常:
127.0.0.1:6379> PING
PONG
127.0.0.1:6379>
Redis 返回 PONG,证明连接正常。
如果想要断开连接:
- 命令:quit
- 快捷键:Ctrl + C
Redis 配置
通过命令行传递参数
redis-server --port 6380 --host 127.0.0.1
我们建议把该文件放到 /etc/redis 目录中(该目录需要手动创建),以端口号命令,例如 6379.conf。
启用配置文件的方法是在启动时将配置文件的路径作为启动参数传递给 redis-server:
redis-server 配置文件路径
在服务器运行时更改 Redis 配置
还可以在 Redis 运行时通过 CONFIG SET 命令在不重新启动 Redis 的清空下动态修改部分 Redis 配置。就像这样:
CONFIG SET logLevel warning
同样在运行的时候也可以使用 CONFIG GET 命令获得 Redis 当前的配置情况:
CONFIG GET logLevel
Redis 中的多数据库
一个 Redis 实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库中可以创建多个数据库类似,所有可以将其中的每个字典都理解成一个独立的数据库。
Redis 默认支持 16 个数据库,分别编号为 0、1、2、…14、15
- Redis 不支持自定义数据库名字
- 因为每个数据库都以编号命名,所有开发者必须要明确哪个数据库存放了哪些数据
- 可以通过配置参数 databases 修改支持的数据库个数
每个数据库都是独立的,也就是说你在 0 号数据库中插入的数据在 1 号数据库是访问不到的。
客户端与 Redis 建立连接后自动选择 0 号数据库,我们可以使用 SELECT 命令来更换数据库。
127.0.0.1:6379> SET a 1
OK
127.0.0.1:6379> KEYS *
1) "a"
127.0.0.1:6379> SELECT 16
(error) ERR DB index is out of range
127.0.0.1:6379> SELECT 15
OK
127.0.0.1:6379[15]> SET b 2
OK
127.0.0.1:6379[15]> KEYS *
1) "b"
127.0.0.1:6379[15]> SELECT 0
OK
127.0.0.1:6379> KEYS *
1) "a"
127.0.0.1:6379>
# 将指定 key 移动到指定数据库
move key db
Redis 不支持为每个数据库设置不同的访问密码,所有一个客户端要么可以访问全部数据库,要么一个数据库也没有权限访问。
存储的数据应该是不区分数据库的。
**如果是不同的应用就要使用不同的 Redis 实例存储数据。**由于 Redis 非常轻量级,一个空的 Redis 占用的内存只有 1 MB 作用,所以不用担心多个 Redis 实例会额外占用很多内存的问题
Redis 常用数据类型及操作命令(CRUD)
Redis 不是简单的键值存储,它实际上是一个数据结构服务器,支持不同类型的值。这意味着在传统键值存储中,您将字符串键与字符串值相关联,而在 Redis 中,该值不仅限于简单的字符串,还可以容纳更复杂的数据结构。以下是 Redis 中支持的所有数据结构的列表
Redis 中的键如何命名
Redis 密钥是二进制安全的,这意味着您可以使用任何二进制序列作为 key
- 太长不好,占用内存空间
- 太短也不好,没有可读性
- 尝试坚持使用固定规则
- 允许的最大大小为 512 MB
Redis中的字符串操作
注意:在 Redis 中命令不区分大小写。也就是说 SET foo bar 和 set foo bar 是一样的,但是一般约定使用大写表示它是一个 Redis 命令。
字符串类型是 Redis 中最基本的数据类型,也是其它数据类型的基础。
- 它能存储任何形式的字符串,包括二进制数据。
- 你可以用它存储用户的邮箱、JSON 化的对象,甚至是一张图片
- value 最多可以容纳数据大小为 512 MB
添加
# 设置指定 key 的值
SET key value
# 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
GETSET key value
# 只有在 key 不存在时设置 key 的值
SETNX key value
# 同时设置一个或多个 key-value 对
MSET key value [key value ...]
# 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
MSETNX key value [key value ...]
# 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
APPEND key value
查询
# 获取指定 key 的值
GET key
# 返回 key 中字符串值的子字符
GETRANGE key start end
# 获取所有(一个或多个)给定 key 的值
MGET key [key ...]
# 返回 key 所储存的字符串值的长度。
STRLEN key
# 通用命令:查询集合中是否有指定的 key
EXISTS key [key ...]
# 通用命令,查询 key 的类型
TYPE key
删除
# 通用命令:删除1个或多个指定的 key
DEL key [key ...]
修改
# 设置指定 key 的值
SET key value
# 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
GETSET key value
# 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
APPEND key value
Redis
数字值在 Redis 中以字符串保存。
# 将 key 中储存的数字值增一
INCR key
# 将 key 所储存的值加上给定的增量值(increment)
INCRBY key increment
# 将 key 中储存的数字值减一
DECR key
# key 所储存的值减去给定的减量值(decrement)
DECRBY key decrement
Redis中的哈希(Hash)操作
哈希(也叫散列)类型也是一种字典结构,其存储了字段和字段值的映射,但字符值只能是字符串,不能其它数据类型,换句话说,散列类型不能嵌套其它数据类型。一个哈希类型可以包含至少 232 - 1 个字段。
提示:除了散列类型,Redis 的其它数据类型同样不支持数据类型嵌套。
添加
# 将哈希表 key 中的字段 field 的值设为 value
HSET key field value [field value ...]
# 同时将多个 field-value (域-值)对设置到哈希表 key 中
HMSET key field value [field value ...]
# 只有在字段 field 不存在时,设置哈希表字段的值
HSETNX key field value
查询
# 获取所有哈希表中的字段
HKEYS key
# 获取哈希表中字段的数量
HLEN key
# 获取所有给定字段的值
HMGET key field1 [field2]
# 获取存储在哈希表中指定字段的值
HGET key field
# 获取在哈希表中指定 key 的所有字段和值
HGETALL key
# 查看哈希表 key 中,指定的字段是否存在
HEXISTS key field
# 获取哈希表中所有值
HVALS key
# 迭代哈希表中的键值对
HSCAN key cursor [MATCH pattern] [COUNT count]
修改
# 将哈希表 key 中的字段 field 的值设为 value
HSET key field value [field value ...]
# 为哈希表 key 中的指定字段的整数值加上增量 increment
HINCRBY key field increment
删除
# 删除一个或多个哈希表字段
HDEL key field1 [field2]
# 删除整个数据字段
DEL key [key ...]
Redis中的列表(List)操作
列表类型类似于编程语言中的数组,可以存储一个有序的字符串列表,常用的操作就是向列表两端添加元素,或者获得列表的某一个片段。
列表类型内部使用双向链表实现的,所有向列表两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度就越快。这意味着即时是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的(和从只有20个元素的列表中获取头部或尾部的10条记录的速度是一样的)。
添加
# 将一个或多个值插入到列表头部
LPUSH key element [element ...]
# 在列表的元素前或者后插入元素
LINSERT key BEFORE|AFTER pivot value
# 将一个值插入到已存在的列表头部
LPUSHX key value
# 通过索引设置列表元素的值
LSET key index value
# 在列表中添加一个或多个值
RPUSH key value1 [value2]
# 为已存在的列表添加值
RPUSHX key value
查询
# 通过索引获取列表中的元素
LINDEX key index
# 获取列表长度
LLEN key
# 获取列表指定范围内的元素
LRANGE key start stop
删除
# 移出并获取列表的第一个元素
LPOP key
# 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
BLPOP key1 [key2 ] timeout
# 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
BRPOP key1 [key2 ] timeout
# 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
BRPOPLPUSH source destination timeout
# 移除列表元素
# 如果 count > 0,则从头向尾遍历删除元素
# 如果 count < 0,则从后面向前面删除元素
# 如果 count = 0,则删除所有匹配的元素
LREM key count value
# 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
LTRIM key start stop
# 移除列表的最后一个元素,返回值为移除的元素
RPOP key
# 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
RPOPLPUSH source destination
集合
集合类型和数学中的集合概念相似,集合中的元素是唯一的、无序的,简单理解集合就是没有顺序且不重复的列表。
一个集合类型可以存储至多 232 - 1 个字符串。
集合类型和列表类型有相似之处,它们的主要区别是:
- 列表是有序的,集合是无序的
- 列表数据可以重复,集合中没有重复数据
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等。由于集合类型在 Redis 内部是使用值为空的散列表实现的,所以这些操作的时间复杂度都是O(1)。
最方便的是多个集合之间还可以进行并集、交集和差集运算。
使用场景
- 跟踪一些唯一性数据
- 比如访问网站的唯一 IP 地址信息,每次访问网站的时候记录用户 IP 地址,SET 自动保证数据的唯一不重复
- 充分利用 SET 聚合操作方便高效的特性,用于维护数据对象之间的关联关系
- 比如所有购买A商品的客户 ID 存储到指定的 SET 中,所有购买B商品的客户 ID 存储到指定的 SET 中,如果我们想要获取有哪个客户同时购买了这两个商品,我们只需要使用交集操作就可以轻松的查出来
添加
# 向集合添加一个或多个成员
SADD key member1 [member2]
查询
# 返回集合中的所有成员
SMEMBERS key
# 获取集合的成员数
SCARD key
# 判断 member 元素是否是集合 key 的成员
SISMEMBER key member
# 返回集合中一个或多个随机数
SRANDMEMBER key [count]
删除
# 移除集合中一个或多个成员
SREM key member1 [member2]
# 移除并返回集合中的一个随机元素
SPOP key
# 将 member 元素从 source 集合移动到 destination 集合
SMOVE source destination member
集合间聚合运算
多个集合之间还可以进行并集、交集和差集运算。
# 返回第一个集合与其他集合之间的差异。
SDIFF key1 [key2]
# 返回给定所有集合的交集
SINTER key1 [key2]
# 返回所有给定集合的并集
SUNION key1 [key2]
# 返回给定所有集合的差集并存储在 destination 中
SDIFFSTORE destination key1 [key2]
# 返回给定所有集合的交集并存储在 destination 中
SINTERSTORE destination key1 [key2]
# 所有给定集合的并集存储在 destination 集合中
SUNIONSTORE destination key1 [key2]
有序集合(Sorted Set)
有序集合是一种类似于集合和哈希之间的混合数据类型。
- 与集合一样,排序集合由唯一的非重复字符串元素组成
- 有序集合中的元素不排序,但有序集合中的每个元素都关联了一个分数(这就是为什么类型也类似于哈希,因为每个元素都映射到一个值)
- 虽然集合中每个元素都是不同的,但是它们的分数确可以相同
每个元素都会关联一个 double 类型的分数。Redis 正是通过分数来为集合中的成员进行从小到大的排序。
与列表类型比较
相同点:
- 两者都是有序的
- 两者都可以获得某一范围的元素
不同点:
- 列表类型通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用
- 有序集合类似是使用哈希表实现的,所以即使读取位于中间部分的数据速度也很快
- 列表中不能简单的调整某个元素的位置,但是有序集合可以(通过更改元素的分数)
- 有序集合要比列表类型更耗费内存
应用场景
排行榜
例如一个大型在线游戏的积分排行榜,每当玩家的分数发生变化时,可以执行 ZADD 命令更新玩家的分数,此后再通过 ZRANGE 命令获取积分 TOPTEN 的用户信息。当然我们也可以利用 ZRANK 命令通过 username 来获取玩家的排行信息。最后我们将组合使用 ZRANGE 和 ZRANK 命令快速的获取和某个玩家积分相近的其他用户的信息。
微博热搜
假设我们现在要获取热门的帖子或搜索,比如我们常用的微博热搜。
首先,我们需要一个衡量的标准,定量的量度热搜的热门程度。假设我们有一个字段叫回复量,回复量越高就越热门。
如果我们用关系型数据库来获取的话,用 SQL 语句实现很简单:
SELECT * FROM message ORDER BY backsum LIMIT 10
但是当数据量很大的时候,效率很低,同时如果建立索引又要消耗大量的资源,同时增加负载。
使用 Redis 的时候,我们不需要存储多余的信息,只需要存储帖子 id 和回复量两个信息就可以了。
添加
# 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZADD key score member [score member ...]
查询
# 通过索引区间返回有序集合指定区间内的成员,分数从低到高排序
ZRANGE key start stop [WITHSCORES]
# 通过索引区间返回有序集合指定区间内的成员,分数从高到低排序
ZREVRANGE key start stop [WITHSCORES]
# 返回有序集中指定分数区间内的成员,分数从低到高排序
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
# 返回有序集中指定分数区间内的成员,分数从高到低排序
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
# 返回有序集合中指定成员的排名,有序集成员按分数值(从小到大)排序
ZRANK key member
# 返回有序集合中指定成员的排名,有序集成员按分数值(从大到小)排序
ZREVRANK key member
# 获取有序集合的成员数
ZCARD key
# 返回有序集中,成员的分数值
ZSCORE key member
# 计算在有序集合中指定区间分数的成员数
ZCOUNT key min max
修改
# 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZADD key score member [score member ...]
# 有序集合中对指定成员的分数加上增量 increment
ZINCRBY key increment member
删除
# 移除有序集合中的一个或多个成员
ZREM key member [member ...]
# 移除有序集合中给定的排名区间的所有成员
ZREMRANGEBYRANK key start stop
# 移除有序集合中给定的分数区间的所有成员
ZREMRANGEBYSCORE key min max
有序集合间聚合运算
# 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中
ZINTERSTORE destination numkeys key [key ...]
# 计算给定的一个或多个有序集的并集,并存储在新的 key 中
ZUNIONSTORE destination numkeys key [key ...]
通用命令
# 返回所有 key
KEYS *
# 返回所有以 my 开头的 key
KEYS my*
# 获取 key 的类型
TYPE key
# 查询某个 key 是否存在
EXISTS key [key ...]
# 将 key 改名为 newkey
RENAME key newkey
# 删除指定 key
DEL key [key ...]
# 从当前数据库中随机返回(不删除)一个 key
RANDOMKEY
# 对 key 进行重命名
RENAME key newkey
# 清空当前数据库所有内容
FLUSHDB
# 清空所有数据库内容
FLUSHALL
# 将当前数据库的 key 移动到给定的数据库 db 当中
MOVE key db
Redis 过期时间
在实际开发中经常会遇到一些有时效的数据,比如限时优惠活动、缓存或验证码等,过了一定时间就需要删除这些数据。在关系数据库中一般需要额外的一个字段记录到期时间,然后定期检测删除过期数据。而在 Redis 中可以设置一个键的过期时间,到时间后 Redis 会自动删除它。
设置键的过期时间
# 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
EXPIRE key seconds
# 和 EXPIRE 一样,但是它以毫秒为单位
PEXPIRE key milliseconds
# EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。
# 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
EXPIREAT key timestamp
# 这个命令和 EXPIREAT 命令类似,但它以毫秒为单位设置 key 的过期 unix 时间戳,而不是像 EXPIREAT 那样,以秒为单位。
PEXPIREAT key milliseconds-timestamp
上面这4个命令只是单位和表现形式上的不同,但实际上 EXPIRE、PEXPIRE 以及 EXPIREAT 命令的执行最后都会使用 PEXPIREAT 来实行。
比如使用 EXPIRE 来设置 KEY 的生存时间为 N 秒,那么后台是如何运行的呢:
- 它会调用 PEXPIRE 命令把 N 秒转换为M毫秒
- 然后获取当前的 UNIX 时间单位也是毫秒
- 把当前 UNIX 时间加上 M 毫秒传递给 PEXPREAT
另外给键设置了过期时间,这个时间保存在一个字典里,也是键值结构,键是一个指针,指向真实的键,而值这是一个长整型的 UNIX 时间。
获取键的过期时间
# 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
TTL key
# 类似于 TTL,但它以毫秒为单位返回 key 的剩余生存时间。
PTTL key
过期时间返回值说明:
清除键的过期时间
# 移除给定 key 的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。
PERSIST key
注意:
● 使用 SET 或 GETSET 命令为键赋值也会同时清除键的过期时间。
● 其它只对键值进行操作的命令(如 INCR、LPUSH、HSET、ZREM)不会影响键的过期时间。