背景信息
概念
Redis在内存中存储数据的中间件,用于作为数据库,数据缓存。主要应用于分布式系统中(因为单机系统可以将数据通过变量保存到内存中,而分布式系统中多个服务器无法直接共享内存中的变量,只能通过网络来同步数据)
特点
- 使用键值对格式在内存中存储数据,属于非关系型数据库,Key都是String,Value可以是字符串,哈希表,列表,集合这样的数据结构
- 可编程性:可以通过简单的交互式命令进行操作,也可使用一些脚本的方式进行批量操作
- 可扩展性:Redis提供了一组API,可以在Redis原有功能的基础上使用(C、C++、RUST)编写扩展。比如让Redis支持更多的数据结构,以及支持更多的命令
- 持久化:Redis不仅会将数据保存在内存中,还会在硬盘中保存一份备份数据。正常使用时,访问的都是内存中的数据,当服务器重启,内存中的数据丢失时。就会将硬盘中的数据加载到内存中,使Redis恢复到服务器重启之前的状态
- 支持集群/主从复制:可以引入多台主机,部署多个Redis节点,每个Redis节点存储数据的一部分。Redis自身也支持主从复制,从节点相当于主节点的备份
- 在网络角度上,使用了IO多路复用的方式
- 使用单线程模型,减少了不必要的线程之间的竞争开销(因为Redis的核心功能都是一些简单的逻辑,主要是操作内存中的数据结构,不会吃很多CPU,所以使用单线程结构)
使用场景
- 作为数据库:在广告搜索的业务中,由于对性能的高要求,所以会将需要检索的数据保存到Redis中
- 作为Cache:将关系型数据库中经常访问的数据,保存到Redis中。这些经常访问的数据,足以满足绝大部分的业务场景
- 会话保存:将用户登录信息保存到Redis中。解决在分布式应用服务器的情况下,同一用户的不同请求路由到不同服务器时,造成的登录信息不同步的问题
注意:
redis的快,是相对于MySQL这种关系型数据库的。如果和直接在内存中操作变量相比,那就是慢了,因为存在网络上的开销。也就是说,当目前的业务采用分布式架构,或者希望内存中的数据可以持久化保存,才推荐使用Redis。盲目使用反倒会适得其反
Redis客户端
Redis是一个客户端-服务器结构的程序(服务器本体负责存储和管理数据,用于使用客户端通过网络来访问和操作服务器中的数据)。客户端可以有一个,也可存在多个
-
自带的命令行客户端
redis -cli -
图形化界面的客户端
-
基于redis的api自行开发客户端
通用命令
可以搭配任意一个数据结构来使用的命令
https://redis.io/docs/latest/commands
Keys
获取匹配的key,返回符合要求的key O(N)
redis
Keys pattern
pattern:
- ? 匹配任意一个字符
- * 匹配0个或多个任意字符
- abcd 只匹配a b c d其中一个
- \^e 排除e的任意一个
- a-c 只能匹配a b c
exists
判断某个key是否存在,返回key的个数 O(1)
exists key [key...]
del
删除指定的key ,返回删除key的个数 O(1)
del key [key...]
expire
为指定的key添加秒级的过期时间 返回1(成功) 0(失败) O(1)
expire key seconds
ttl
ttl key
获取指定key的过期时间,秒级。返回剩余过期时间,-1(没有过期时间),-2(表示Key不存在)
pexpire/pttl
使用毫秒为单位,具体使用方法与expire和ttl相同
定时器
在某个时间达到之后,执行指定任务
-
基于优先级队列/堆实现:队首元素为过期时间最早的元素,为定时器分配一个线程,让这个线程根据队首元素的过期时间来设置线程等待,避免频繁检查队首元素而造成CPU忙等。当新插入一个元素时,唤醒定时器进程,继续维护优先级队列
-
基于时间轮实现:将时间划分为一个个的小段,具体大小根据实际需求来确定。每一小段时间挂着一段链表,表示要处理的事件

每隔100ms,时间轮转动一次,开始处理下一个时间段的事件
key的过期策略
一个redis中存在多个key,这些key大部分存在过期时间,此时redis就需要通过某种方法来知道哪些key已经过期
不能直接遍历所有的key,因为这样会消耗大量时间;而且redis是单线程程序,在这段时间,只能进行遍历,无法对外提供服务。
- 定期删除:每次抽取一部分,验证过期时间,保证这个抽取的过程足够快
- 惰性删除:当某个key已经到了过期时间,暂时不删除。当这个key再次被访问时,进行删除,并返回nil
type
返回key对应的value的类型 O(1)
type key
数据类型与编码方式
数据类型
redis所有的key都是字符串,只有value的类型存在差别,这里讨论的也是value的类型

string
redis 的string类型就是直接按照二进制数据的方式进行存储,不会做任何的编码转化,存进去是什么编码,取出来就是什么编码。可以存储文本数据、整数、字符串、JSON、二进制数据(图片,视频)等等
set/get:

- 不加nx/xx:key不存在则创建,存在则更新value
- 当更新value的值时:value的数据类型可能发生改变,原有的ttl实现
- 可以使用setnx、setex、psetex代替
mset/mget:
一次操作多组键值对

incr/incrby/decr/decrby/incrbyfloat:

- incr/incrby/decr/decrby:对应的n必须为整数
- incrbyfloat:对应的n可以为浮点数(redis内部使用string类型保存浮点数)
- 当操作的key不存在时,会先创建key - 0的键值对,然后对其进行操作
append:

在key对应的value后面追加字符串,如果key不存在,则相当于set语句
getrange/setrange:

- start/end允许传负数,表示倒数第几个元素
- setrange对于不存在的key也可以操作,会将offset前面的值都设为0X00
strlen:

返回字符串的长度,单位是字节。对应的value类型不对,报错
hash

-
hset/hget/hexists/hdel:

-
hkeys/hvals:

-
hgetall/hmget:

-
hlen/hsetnx/hincrby/hincrbyfloat:

List
相当于数组或者顺序表,列表中的数据是有序的(将元素顺序调换,得到的新的List与之前的List不等价),可重复的

阻塞操作:

- 如果队列为空,则阻塞一段时间,在这段时间redis可以执行其他命令。期间如果有元素插入,则立刻返回。如果没有,则在阻塞时间之后,返回nil
- 时间单位是秒,redis6之前,只能设置整数,redis6以及之后可以设置小数
- 如果命令中设置了多个键,那么会从左往右进行遍历,一旦有一个键可以弹出元素,命令立刻返回
- 如果多个客户端同时对一个键进行操作,那么最先执行该命令的客户端会得到弹出的元素
set
把一些有关联的数据,放到一起,作为集合。集合中元素是无序的,且不允许重复。一个集合中最多可以存储2^32-1个元素。元素只能是字符串类型
普通命令:

集合操作:集合的交集、并集、差集

zset
有序集合,且元素不能重复。为每个元素引入一个score属性,存储的是浮点数。依据元素对应的score进行排序


- inf 可以表示无穷大,-inf表示无穷小
- 分数相同则按照字典序排序
- zpopmax 底层调用的通用删除函数(传下标),所以时间复杂度为O(log(N)*M)
阻塞操作:

- 集合为空,则发生阻塞。若指定多个key,则删除第一个非空key的元素后,直接返回
集合操作:

编码方式
value的各个数据类型内部实现不一定是标准的哈希,列表...。redis 会对其实现进行优化
- string
- raw:最基本的字符串编码方式,底层就是一个字符数组
- int:value为一个整数时,redis直接使用int来保存
- embstr:对短字符串进行的优化
- hash
- hashtable:最基本的哈希表
- ziplist压缩列表:当哈希表的元素比较少时,会使用列表存储,以节省空间
- list
- linkedlist:链表
- ziplist压缩列表:元素个数少时,使用压缩列表实现
- quicklist:从redis3.2开始,使用quicklist代替以上两种。quicklist是一个链表,每个元素是一个ziplist
- set
- hashtable:哈希表
- intset:集合里存放的都是整数
- zset
- skiplist跳表:每个节点上有多个指针域,巧妙搭配这些指针域的指向,就可以达到从跳表上查询元素的时间复杂度是O(logN)
- ziplist压缩列表
查看key对应的value的数据类型:
object encoding key
使用场景
String
-
作为缓存:先查redis,如果存在则直接返回,不存在则查数据库,然后写入redis并返回。为防止redis内部的数据过多,可以设置过期时间

-
计数器:视频网站的播放次数统计就可以使用redis实现,每播放一次视频redis对应的key自增一

-
共享会话:使用redis同一管理session会话,以解决同一用户请求被路由到不同服务器时,session会话无法统一的问题
-
手机验证码:为验证码设置过期时间,或者设置单位时间内验证码的请求次数
hash
-
保存关系型数据库的信息

list
-
作为数组这样的结构,用来存储多个元素。比如,每个班级作为一个列表,列表中存储学生id来实现学生和班级的绑定
-
作为阻塞消息队列模型,实现类似"轮询"的效果

-
分频道阻塞消息队列模型,一个频道只有一个消费者能够抢到元素

-
模拟栈或队列
set
- 使用set保存用户的标签,计算共同好友等集合操作
zset
- 因为zset是天生有序并且数据唯一的。所以可以作为排行榜,分数实时变化,排名也可以实时变化
渐进式遍历
用于解决Keys pattern 命令导致的阻塞问题,渐进式遍历,可以指定每次遍历的key的个数

- match:匹配语法与keys命令的语法相同
- count:用于指定每次遍历的key个数。redis不一定完全按照count数去遍历,但是是一个邻近的值
- type:获取指定value类型的Key
Redis的单线程模型
只使用一个线程去处理所有命令请求(不包括网络请求,网络请求仍使用多线程进行处理)。所有的客户端请求都串行执行,不用考虑线程安全问题
为什么效率高
这里的效率高是与关系型数据库比较
-
redis访问内存,且核心功能简单,只需保存对应的key和value或者根据key取出对应的value。没有复杂查询
-
redis采取单线程模型,避免一些不必要的线程竞争开销。因为redis每个基本操作都是短平快的,即使使用多线程提升也不大,反而会增加额外的开销
-
网络IO多路复用:对于redis来说,同一时刻,只有少数的socket是活跃的。所以不需要为每个socket都安排一个线程来处理请求,可以使用一个线程来处理多个socket,这就是IO多路复用
对于TCP协议,服务器每次要访问一个客户端,都需要给这个客户端安排一个socket。如果是网络游戏,或者直播平台这类频繁与服务器交互的场景,服务器会给每个socket安排单独的线程
数据库管理
redis默认提供了16个database,每个database之间相互隔离。默认使用0号database

redis客户端
Java客户端:
创建maven项目,添加Jedis依赖
java
JedisPool jedisPool=new JedisPool("tcp://jedis服务器地址");
try(Jedis jedis=jedisPool.getResource()){
//在这里使用jedis敲redis代码
}
Spring客户端:
配置文件:
yaml
spring:
data:
redis:
host: 127.0.0.1
port: 8888
注入StringRedisTemplate:
java
@Autowired
private StringRedisTemplate redisTemplate;
//执行某些数据类型特有的命令,Spring对Redis命令作了进一步封装
//要先获取对应的对象,然后执行操作逻辑
redisTemplate.opsForValue().set("key1","1");
//执行数据库命令以及通用命令
redisTemplate.execute((RedisConnection connection)->{
connection.serverCommands().flushAll();
return null;
});
