Redis一、部署与数据结构
初识redis
Redis是一种基于键值对(key-value)的NoSQL数据库
NoSQL(常被解释为 “Not Only SQL” ),是非关系型数据库的统称
关系型数据库像 “规整的 Excel 表格”,擅长结构化数据、复杂关联和强事务,适合对数据一致性要求高的业务(如银行转账 );
非关系型数据库像 “灵活的文件箱”,支持多样数据、高并发和水平扩展,适合快速迭代、数据格式多变的场景(如社交 App 动态 )。
因为Redis会将所有数据都存放在内存中,所以它的读写性能非常惊人
可以将内存的数据利用快照和日志的形式保存到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据不会丢失
Redis特性
- 速度快
Redis的所有数据都是存放在内存中的
Redis是用C语言实现的,一般来说C语言实现的程序“距离”操作系统更近,执行速度相对会更快
Redis使用了单线程架构,预防了多线程可能产生的竞争问题 - 基于键值对的数据结构服务器
主要提供了5种数据结构:字符串、哈希、列表、集合、有序集合 - 功能丰富,简单稳定
- 客户端语言多
Redis提供了简单的TCP通信协议 - 持久化
Redis提供了两种持久化方式:RDB和AOF,即可以用两种策略将内存的数据保存到硬盘中

- 主从复制

- 高可用和分布式
Redis Sentinel:它能够保证Redis节点的故障发现和故障自动转移
Redis Cluster:Redis真正的分布式实现,提供了高可用、读写和容量的扩展性
数据分为热数据和冷数据,热数据通常是指需要频繁操作的数据,反之为冷数据
冷数据放在Redis中,基本上是对于内存的一种浪费
热数据放在Redis中加速读写,可以减轻后端存储的负载
安装redis
wget http://download.redis.io/releases/redis-3.0.7.tar.gz
tar xzf redis-3.0.7.tar.gz
ln -s redis-3.0.7 redis
cd redis
make
make install
第一,ln -s redis-3.0.7 redis 建立了一个redis目录的软链接,这样做是为了不把redis目录固定在指定版本上,有利于Redis未来版本升级,算是安装软件的一种好习惯。
第二,make install将Redis的相关运行文件放到/usr/local/bin/下,这样就可以在任意目录下执行Redis的命令
配置、启动、操作、关闭Redis
| 可执行文件 | 作用 |
|---|---|
| redis-server | 启动 Redis 服务 |
| redis-cli | Redis 命令行客户端,用于与 Redis 交互 |
| redis-benchmark | Redis 基准测试工具,用于测试性能 |
| redis-check-aof | Redis AOF 持久化文件检测和修复工具 |
| redis-check-dump | Redis RDB 持久化文件检测和修复工具 |
| redis-sentinel | 启动 Redis Sentinel,用于集群监控和故障转移 |
- 启动和关闭Redis
| 启动方式 | 命令 | 说明 |
|----------------|-------------------------------|----------------------------------------------------------------------|
| 默认配置 | redis-server | 无法自定义配置,使用默认参数启动 Redis。 |
| 运行启动 | redis-server --port 6380 | 可通过命令行参数自定义配置(如端口),但配置较多时不建议使用此方式,难以保存配置。 |
| 配置文件启动 | ❤️redis-server /opt/redis/redis.conf | 将配置写入指定文件(如 /opt/redis/redis.conf),通过文件加载配置,适合复杂配置场景。 |
redis-cli shutdown # 断开与客户端的连接、持久化文件生成,是一种相对优雅的关闭方式。
redis-cli shutdown nosave|save # 强制关闭Redis服务,nosave不进行持久化操作,save会先做持久化再关闭。
不要粗暴地使用kill-9强制杀死Redis服务,不但不会做持久化操作,还会造成缓冲区等资源不能被优雅关闭,极端情况会造成AOF和复制丢失数据的情况
- Redis命令行客户端
第一种是交互式方式
redis-cli -h 127.0.0.1 -p 6379
set hello world
OK
get hello
"world"
第二种是命令方式
redis-cli -h 127.0.0.1 -p 6379 get hello
"world"
API的理解和使用
预备
命令
| 命令 | 作用 |
|---|---|
| redis-cli -h 127.0.0.1 -p 6379 | 连接redis 遍历所有键,时间复杂度是O(n) |
| set key value | 配置键值对[新增/修改] |
| keys * | 查看所有的key |
| rpush test_key 1 2 3 4 5 6 7 | 插入一个列表的键值对 |
| dbsize | 返回当前数据库中键的总数 直接获取Redis内置的键总数变量,所以dbsize命令的时间复杂度是O(1) |
| exists test_key | 如果键存在则返回1,不存在则返回0 |
| del hello py | 删除hello和py两个key |
| expire test_key 864000000 | 对键添加过期时间,当超过过期时间后,会自动删除键 |
| ttl test_key | 查看key的过期时间 |
| type test_key | 查看key的数据类型 |
数据结构和内部编码

string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)
object encoding test_key # 查看键的内部编码
Redis这样设计有两个好处
第一,可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令
第二,多种内部编码实现可以在不同场景下发挥各自的优势
- 字符串
字符串类型是Redis最基础的数据结构
键都是字符串类型,值可以是字符串(简单字符串、数字)或者字节数组(复杂对象),甚至是二进制(图片、音频、视频等),值最大不能超过512MB
单线程架构
Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库服务
因为Redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行
FIFO,先进先出
假设3个客户端同时对Redis发起请求,分别是set key1 value1、get key2和del key3,Redis是如何处理这3个请求的
1. 客户端A的set key1 value1命令先到达服务端,并被放入队列中等待处理
2. 客户端B的get key2命令后到达服务端,并被放入队列中等待处理
3. 客户端C的del key3命令后到达服务端,并被放入队列中等待处理
4. Redis服务端依次从队列中取出命令,逐个执行
5. 执行完命令后,将结果返回给客户端
----------
单线程架构长什么样
IO多路复用模型长什么样
为什么单线程还能这么快
第一,纯内存访问,Redis将所有数据放在内存中
第二,非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间
第三,单线程避免了线程切换和竞态产生的消耗
第一,单线程可以简化数据结构和算法的实现
第二,单线程避免了线程切换和竞态产生的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。
但是单线程会有一个问题:对于每个命令的执行时间是有要求的。如果某个命令执行过长,会造成其他命令的阻塞,对于Redis这种高性能的服务来说是致命的,所以Redis是面向快速执行场景的数据库。
字符串
命令
- set
·ex seconds:为键设置秒级过期时间。
·px milliseconds:为键设置毫秒级过期时间。
·nx:键必须不存在,才可以设置成功,用于添加。
·xx:与nx相反,键必须存在,才可以设置成功,用于更新
set hello world
setnx hello redis # (integer) 0 如果键存在,则设置失败
set hello hi xx # (integer) 1 如果键存在,则更新成功
set hello hi ex 10 # 设置键的过期时间,10秒后过期
由于Redis的单线程命令处理机制,如果多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案
使用setnx实现分布式锁的方法:http://redis.io/topics/distlock
-
get
get hello # 获取键的值 -
mset和mget
mset a 1 b 2 c 3 # 同时设置多个键值对
mget a b c # 同时获取多个键的值
mset a 1 b 2 c 3
OK
mget a b c
1) "1"
2) "2"
3) "3"


n次get时间 = n次网络时间 + n次命令时间 --> n个get命令
n次get时间 = 1次网络时间 + n次命令时间 --> 1个mget命令
-
计数
incr key
·值不是整数,返回错误。
·值是整数,返回自增后的结果。
·键不存在,按照值为0自增,返回结果为1。
Redis提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数) -
据说是不常用的
append可以向字符串尾部追加值
strlen可以获取字符串的长度 # 中文占3个字节,英文占1个字节
getset可以将键的值设置为新的值,并返回旧的值
setrange可以将字符串的一部分替换为新的值,并返回被修改后的长度
getrange可以从字符串中获取指定区间的值,区间通过偏移量(字节为单位)来指定
set test good
OK
append test _very # 在字符串尾部追加值
(integer) 9
get test
"good_very"
strlen test # 获取字符串的长度
(integer) 9
getset test test
"good_very"
get test
"test"
getset test well
"test"
get test
"well"
setrange test 0 f # 将字符串的第一个字节替换为f,并返回被修改后的长度
(integer) 4
get test
"fell"
getrange test 1 2 # 获取字符串的第二个字节到第三个字节的值
"el"
del test
(integer) 1
内部编码
字符串类型的内部编码有3种:
·int:8个字节的长整型。 # 当字符串只包含数字,并且数字长度不超过20时,使用int编码。
·embstr:小于等于39个字节的字符串。
·raw:大于39个字节的字符串。
set key 123456789 # 字符串长度为9
OK
object encoding key # 查看键的内部编码
"int"
set key 1234567891011121314
OK
object encoding key
"int"
strlen key
(integer) 19
set key 12345678910111213141
OK
strlen key
(integer) 20
object encoding key
"embstr"
使用场景
- 缓存功能
Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。
由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用

- 计数器
许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源 - 共享session
可以使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。

- 限速
限制用户每分钟获取验证码的频率
phoneNum = "138xxxxxxxx";
key = "shortMsg:limit:" + phoneNum;
// SET key value EX 60 NX
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key) <=5){
// 通过
}else{
// 限速
}
哈希
就是字典,哈希类型中的映射关系叫作field-value
hset dict_name name tom # 添加field-value
(integer) 1
hget dict_name # 获取field的值
hdel dict_name name # 删除field
(integer) 1
hset dict_name age 18
(integer) 1
hset dict_name name tom
(integer) 1
hlen dict_name # 获取field的数量
(integer) 2
hexists dict_name name # 判断field是否存在
(integer) 1
hkeys dict_name # 获取所有的field
1) "age"
2) "name"
hkeys dict_name # 获取所有的field
1) "age"
2) "name"
hgetall dict_name # 获取所有的field和value
1) "age"
2) "18"
3) "name"
4) "tom"
hincrby dict_name age 1 # 自增field的值
(integer) 19
dict_name-->Hash key
name-->field
tom-->value
hmset对应mset
hmget对应mget
hincrby对应incrby
hincrbyfloat对应incrbyfloat
hstrlen key field # 3.2以上
内部编码
哈希类型的内部编码有两种
·ziplist(压缩列表):
field个数小于hash-max-ziplist-entries配置(默认512个)
value都小于hash-max-ziplist-value配置(默认64字节)
ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
·hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
使用场景
哈希类型每个键可以有不同的field,
关系型数据库一旦添加新的列,所有行都要为其设置值(即使为NULL),

列表
列表中的每个字符串称为元素(element),一个列表最多可以存储2^32-1个元素
可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素


第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表
第二、列表中的元素可以是重复的
命令

rpush my_list 1 2 3 4 5 6 7 # 从列表右边插入元素,也用于创建
lpush my_list -2 -1 0 # 从列表左边插入元素,也用于创建
linsert test before 0 a # 在指定元素前插入新元素,如果有重复的元素,只在第一个匹配的元素前插入
lrange my_list 0 -1 # 获取列表中的所有元素
lindex test 3 # 获取指定索引的元素
llen test # 获取列表的长度
lpop test # 弹出列表左边的元素
rpop test # 弹出列表右边的元素
lrem my_list 5 1 # 从最左边开始删除5个1
lrem my_list -5 0 # 从最右边开始删除5个0
ltrim my_list 11 17 # 只保留第12到第18个元素
lset my_list 6 8 # 修改第7个元素为8
blpop和brpop是lpop和rpop的阻塞版本,当列表中没有元素时,客户端可以等待其他客户端插入元素
看了头疼,有需要再补充
内部编码
和哈希类似
·ziplist(压缩列表):
元素个数小于list-max-ziplist-entries配置(默认512个)
每个元素的值都小于list-max-ziplist-value配置时(默认64字节)
Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
·linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
使用场景
- 消息队列

- 文章列表
·lpush+lpop=Stack(栈)
·lpush+rpop=Queue(队列)
·lpsh+ltrim=Capped Collection(有限集合)
·lpush+brpop=Message Queue(消息队列)
集合
集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素
一个集合最多可以存储2^32-1个元素
命令
sadd myset 1 2 3 # 添加元素
srem myset 2 3 # 删除元素
scard myset # 获取集合中的元素数量
sismember myset 1 # 判断元素是否是集合的成员
srandmember myset 2 # 随机获取集合中的元素,2是个数
spop myset # 随机弹出集合中的元素,从3.2版本开始,spop也支持[count]参数
smembers myset # 获取集合中的所有元素
> myset = {1,2,3,4,5} newset = {1,2,3,7,8}
sinter myset newset # 计算两个集合的交集 结果为{1,2,3}
sunion myset newset # 计算两个集合的并集 结果为{1,2,3,4,5,7,8}
sdiff myset newset # 计算两个集合的差集 结果为{4,5},即myset中有,newset中没有的元素
sunionstore unionset myset newset # 计算两个集合的并集,并将结果保存到新集合<unionset>中
sinterstore、sdiffstore同理
内部编码
·intset(整数集合):
当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,
Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
·hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
使用场景:标签
有序集合
保留了集合不能有重复成员的特性
给每个元素设置一个分数(score)作为排序的依据

有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。

命令
zadd xulist 251 tom 91 mike # 添加元素
·nx:member必须不存在,才可以设置成功,用于添加。
·xx:member必须存在,才可以设置成功,用于更新。
·ch:返回此次操作后,有序集合元素和分数发生变化的个数
·incr:对score做增加,相当于后面介绍的zincrby
zcard xulist # 获取有序集合中的元素数量
zscore xulist tom # 获取元素的score
zrank xulist tom # 获取元素的排名,从小到大
zrevrank xulist tom # 获取元素的排名,从大到小
zrem xulist a # 删除元素
zincrby xulist 1 tom # 给元素的score增加1
zrange xulist 1 3 # 获取排名在1到3之间的元素,从小到大
zrevrange xulist 1 3 # 获取排名在1到3之间的元素,从大到小
zrangebyscore xulist 1 600 # 获取score在1到600之间的元素,从小到大
zrevrangebyscore xulist 600 1 # 获取score在600到1之间的元素,从大到小
and so on ...
使用场景:排行榜
内部编码
·ziplist(压缩列表):
元素个数小于zset-max-ziplist-entries配置(默认128个),
元素的值都小于zset-max-ziplist-value配置(默认64字节)时,
Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
·skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降
键管理
单个键管理
set python py # 创建键
rename python php # 重命名键,即使php已经存在,也会被覆盖
renamenx aa bb # 重命名键,如果bb已经存在,则不执行
randomkey # 随机返回一个键
太无聊了不写了,有需要再看


浙公网安备 33010602011771号