Redis
端口号:6379
NoSQL
全称为Not Only SQL
,即为不仅仅是SQL的含义,指非关系型数据库
用来解决性能问题的,Redis
是一种NoSQL
可以作为一个缓存数据库,例如服务端缓存session
中的token
使用key-value
的方式进行存储的
适用于:
- 对数据的高并发读写
- 海量数据的读写
并不适用于:
- 事务
- 结构化查询
Redis:
- 数据存储在内存中
- 支持持久化
- 支持多种数据类型
使用
安装后,可以使用redis-cli
运行客户端
登录redis-cli -a 密码
Redis默认有16个数据库,下标从0开始,默认用的第0个,所有库的密码相同
使用的是单线程+多路IO复用
切换数据库
select 下标
dbsize
可以查看当前数据库的大小
flushdb
代表清空当前数据库
flushall
代表清空所有数据库
常用五大数据类型
字符串String
列表List
集合Set
哈希Hash
有序集合Zset
key操作
keys *
查看当前库中的所有的键
exists key名
可以查看是否存在一个key
:
- 如果返回
1
,代表存在 - 如果返回
0
,代表不存在
type key名
可以查看这个key
的数据类型
del key名
删除一个key
:
- 返回
1
代表成功 0
代表失败
unlink
也可以删除,选择非阻塞删除,是异步删除
expire key名 秒数
可以设置一个key
的过期时间:
expire
中文为失效,读音为ɪkˈspaɪər
ttl key名
可以查看这个key
还有多少秒过期
-1
代表永不过期-2
代表已过期
String 字符串
是一个最基本的数据类型,是二进制安全的,可以存储任何数据,例如图片、Java中的可序列化对象,一个Redis
字符串中的value
最大是512MB
常用命令
设置值:
set key名 value具体的值
get key名
可以取出值,如果不存在则返回(nil)
append key名 值
,会将值添加到已存在的key名
值的后边:
- 并返回这个
key
的长度 - 如果这个
key
不存在,将会新建一个key
,值为追加的部分的值
strlen key名
,可以获取这个key
中value
的长度
setnx 键 值
,当这个键不存在时才设置相应的值
- 设置成功返回
1
- 设置失败返回
0
针对于整型数值,当值为数字时才能使用:
incr 键
会使相应键的值自增1
decr 键
会使相应的值减1
incrby 键 步长
会使相关键的值增加相应的步长decrby 键 步长
会使相关键的值减少相应的步长
Java中的i++
的操作过程:不断地 取值、加1,如果有两个线程对i++
(100次)进行操作,那么这个时候i
的值的范围是2-200
mset key1 value1 key2 value2....keyn valuen
可以批量的设置值
mget key1 .... keyn
批量获取值
- 当有一个失败时,所有的都失败
msetnx key1 value1 key2 value2....keyn valuen
可以批量设置如果一个值不存在就插入
-
如果有一个键存在,那么此时所有的数据都不会被插入
-
127.0.0.1:6379> keys * 1) "key" 2) "kk" 3) "key2" 4) "key1" 5) "key3" # 执行 msetnx kk vv k1 v1 # 返回结果: (integer) 0 # 查询所有的键 127.0.0.1:6379> keys * 1) "key" 2) "kk" 3) "key2" 4) "key1" 5) "key3"
-
因为此时的
kk
键存在,导致k1
没有插入进去
getrange key名 开始位置 结束位置
,相当于subString(开始, 结束)
,从下标0
开始
setrange key名 位置 值
会将key
相应位置的值设置为值,如果值的长度大于1,则会往后覆盖
setex key名 秒 值
设置键值的同时设置过期时间,秒数必须大于0
getset key名 新值
返回旧的值,设置为新的值
String底层数据结构
采用的动态字符串,类似于Java
中的ArrayList
List 列表
单键多值
列表是一个字符串列表,按照插入的顺序排序,可以将一个元素插入到头部或者尾部,底层采用的是双向循环链表,随机访问的效率比较差
lpush key名 值1 值2 .... 值n
,在key名
的左侧插入一系列值,并将值1...值n
按照值n...值1
的顺序插入
-
127.0.0.1:6379> lpush key 11 22 33 44 55 (integer) 5 127.0.0.1:6379> lrange key 0 1000 1) "55" 2) "44" 3) "33" 4) "22" 5) "11" 127.0.0.1:6379> lrange key 0 -1 1) "55" 2) "44" 3) "33" 4) "22" 5) "11" 127.0.0.1:6379> lpush key 888 999 (integer) 7 127.0.0.1:6379> lrange key 0 -1 1) "999" 2) "888" 3) "55" 4) "44" 5) "33" 6) "22" 7) "11"
rpush key名 值1 值2 .... 值n
,在key名的右侧插入一系列值,并将值1...值n
按照值1...值n
的顺序插入
-
127.0.0.1:6379> lrange key 0 -1 1) "999" 2) "888" 3) "55" 4) "44" 5) "33" 6) "22" 7) "11" 127.0.0.1:6379> rpush key 00 -11 -22 -33 -44 (integer) 12 127.0.0.1:6379> lrange key 0 -1 1) "999" 2) "888" 3) "55" 4) "44" 5) "33" 6) "22" 7) "11" 8) "00" 9) "-11" 10) "-22" 11) "-33" 12) "-44"
lpop/rpop key
在左侧或者右侧弹出一个值,如果一个列表中没有值,那么这个键将会被删除
rpoplpush key1 key2
在key1
的右侧弹出一个值并且插入到key2
的左侧
lrange key 开始位置 结束位置
,从左到右按照下标获取值
lrange key 0 -1
代表取出所有的值
lindex key 索引
,获取索引处的值
llen key
获取列表的大小
linsert key名 before/after 旧值 新值
在旧值之前/之后
插入新值
- 如果有多个相同的旧值,将会从
0-n
以第一个旧值为准 - 如果不存在旧值,将不会插入
lrem key名 n 值
从左边删除n
个值,无论这几个值是否连续
-
127.0.0.1:6379> lrange key 0 -1 1) "00" 2) "11" 3) "22" 4) "33" 5) "44" 6) "44" 7) "55" 8) "66" 9) "44" 10) "77" 11) "88" 12) "99" 13) "44" 127.0.0.1:6379> lrem key 4 44 (integer) 4 127.0.0.1:6379> lrange key 0 -1 1) "00" 2) "11" 3) "22" 4) "33" 5) "55" 6) "66" 7) "77" 8) "88" 9) "99"
lset key 下标 值
List底层数据结构
如果元素比较少,将会使用一块连续的内存存储,如果数据比较多时将不会使用连续内存方式存储,会将多个连续的内容作为链表串起来
Set集合
功能和list差不多,但会自动去重,是一个底层时String
类型的无需集合,并且是一个value
为空的hash
表,添加、删除、查找的时间都是***O(1)***
sadd key 值1 ... 值n
将一个或者多个元素放入到集合中
smembers key
取出集合中所有的值
sismember key 值
判断集合中是否有相应的值
- 如果有,返回
1
- 如果没有,返回
0
scard key
返回集合的大小
srem key 值1 ... 值n
删除相应的值
spop key
将会随机的在集合中弹出一个值
srandmember key n
随机在集合中取出n
个值,不会从集合中删除
smove key1名 key2名 值
将key1
集合中的值
移动到key2
集合
sinter key1 key2
返回两个集合的交集
sunion key1 key2
返回两个集合的交集
sdiff key1 key2
返回差集,相当于key1 - key2
-
127.0.0.1:6379> smembers key 1) "44" 2) "66" 3) "88" 4) "99" 127.0.0.1:6379> smembers key2 1) "11" 2) "22" 3) "33" 4) "44" 5) "55" 6) "66" 127.0.0.1:6379> sdiff key key2 1) "88" 2) "99" 127.0.0.1:6379> sdiff key2 key 1) "11" 2) "22" 3) "33" 4) "55" 127.0.0.1:6379> sdiff key2 key 1) "11" 2) "22" 3) "33" 4) "55"
hash 哈希
是一个键值对的集合,是一个String
类型的映射表,但值中可以存储一个对象类似于Map<String, Object>
hset key 属性名 值
给key中的属性值设置为值
hget key 属性名
从key中获取属性值
hmset key 属性1 值1 ... 属性n 值n
,批量设置值
hexists key 属性
查看属性是否存在
hkeys key
查看该key
所有的属性
hvals key
查看该hash集合的所有值
hincrby key 属性名 值
将属性名中的值增加或者减少相应的值
- 值大于0,代表增加相应的值
- 值小于0,代表减少相应的值
hsetnx key 属性 值
当相关的属性不存在时才给其设置值
hash数据结构
如果属性中field-value
比较少时,使用列表
如果比较多时,使用hashtable
Zset 有序集合
也是没有重复元素的集合,每个元素都被关联了一个评分/权值,这个评分/权值将作为排序的依据,按照这个评分/权值从高到低进行排序,评分/权值可以是重复的
zadd key 权值 值 ... 权值 值
可以将一个或者多个元素放入到key中
zrange key 开始位置 结束位置
输出开始位置到结束位置之间的元素
-
zrange key 0 -1
代表输出所有的元素 -
默认是从小到大进行排名
-
还有一个可选参数
withscores
代表把权重一并输出 -
127.0.0.1:6379> zrange key 0 -1 withscores 1) "bb" 2) "0" 3) "cc" 4) "2" 5) "a" 6) "11" 7) "dd" 8) "99"
zrangebyscore key 开始权值 结束权值
表示输出开始权值 - 结束权值
之间的元素,按照权值由小到大排序
withscores
也是一个可选参数,有这个参数代表把权重一并输出- 需要保证开始权值要比结束权值小
zrevrangebyscore key 开始权值 结束权值
表示输出开始权值 - 结束权值
之间的元素,按照权值由大到小排序
withscores
也是一个可选参数,有这个参数代表把权重一并输出- 需要保证开始权值要比结束权值大
zincrby key 值 元素
表示把元素的权值增加/减少相应的值
- 值为正数表示增加
- 值为负数表示减少
zrem key 元素1 ... 元素n
表示删除这个集合中的相应的元素
zcount key 开始权值 结束权值
统计区间中的值的个数(开始权值 - 结束权值
)
zrank key 值
返回这个值在该集合中的排名,从0
开始,rank
中文为等级

Redis配置文件
开始位置定义了一些度量单位,只支持bytes
,大小写不敏感
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
下方是INCLUDES
部分,表示这个文件中可以包含其他的子文件
NETWORK
部分
- 会有类似于
bind 127.0.0.1
的配置项,表示只能本地连接 protected-mode yes
代表只能本机访问,改为no
之后其他设备都能访问port 6379
用来指定端口tcp-backlog 511
指定tcp
的握手队列的大小timeout 0
,设置超时时间,如果一定时间连接不上就不再连接了,如果为0表示不限制tcp-keepalive 300
心跳时间,如果超过相应秒数没有操作,就会断开连接
General
部分
loglevel xxx
用来指定日志等级logfile "路径"
用来指定日志的路径databases 16
用来设置数据库的个数
SECURITY
部分:
requirepass 密码
用来设置密码
CLIENTS
部分:
maxclients 10000
用来设置最大的客户端连接数
MEMORY MANAGEMENT
部分
maxmemory <bytes>
设置最大的内存
新数据类型
Bitmap

专门用于位操作的字符串,位时以字符串的形式进行表示
偏移量offset
从0
开始
setbit key 偏移量 值
将key
中的某一位设置值,值只能取0/1
,上图可以表示为:
setbit key 5 1
setbit key 11 1
getbit key 偏移量
可以获取这个位上的值:
127.0.0.1:6379> getbit key 5
(integer) 1
127.0.0.1:6379> getbit key 11
(integer) 1
如果第一次初始化bitmap
时,指定的偏移量非常大时,整个初始化过程会非常缓慢,会造成redis的阻塞
bitcount key
可以获取设置位1的位的数量
- 有两个可选参数:
bitcount key 开始字节 结束字节
表示查看这个字节范围内1的个数,也是从0开始- 上图中,查看
0-7
位的1的个数可以表示为bitcount key 0 0
,同理8-17
位可以表示为bitcount key 1 1
bitop 操作 结果key key1 ... keyn
位操作,全称:bitoperator
,可以做多个key的按位与、按位或、按位取反、按位异或
-
操作:
and
按位与、or
按位或、not
按位取反、xor
按位异或 -
not
按位取反是按照最大字节进行取反的-
例如
-
setbit key 4 1 # 再执行 bitop not result key # 此时的result中的值 1 1 1 1 0 1 1 1 offset 0 1 2 3 4 5 6 7
-
setbit key 12 1 # 再执行 bitop not result key # 此时的result中的值 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
-
setbit key10 32 0 # 再执行 bitop not result key10 # 此时的result中的值 0-39位都是1
-
百万级别用户的访问记录的存储空间:
-
HyperLogLog
可以记录网站访问量,也可以用来去重
pfadd key 值1 .... 值n
添加值
pfcount key1 ... keyn
获取个数
- 如果有多个
key
,那么将会合并起来,看这些所有key
中的不重复的元素的个数
pfmerge 新key名 key1 ... keyn
将多个key
合并到一个新键中
这个类型主要适用于去重,放入其中的值不能够取出
Geospatial
地理中的基本经纬度
Jedis 操作 Redis
使用Java操作Redis
引入依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.1</version>
</dependency>
Jedis jedis = new Jedis("ip", 端口);
// 密码,如果有
jedis.auth("密码");
验证码的一个例子
- 生成6位数验证码
- 70秒内有效
- 24小时内最多验证3次
//检查手机号是否正确
public boolean checkPhoneNumber(String number){
return number.matches("^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$");
}
@RequestMapping("/sms")
public Map<String, String> getSMS(@RequestParam String phone) {
HashMap<String, String> map = new HashMap<>();
if (!checkPhoneNumber(phone)) {
map.put("code", "手机号不正确");
return map;
}
map.put("phone", phone);
Jedis jedis = new Jedis("localhost", 6379);
// 表2为验证次数
jedis.select(2);
boolean isUpperLimit = "3".equals(jedis.get(phone));
// 表1为当前的验证码
jedis.select(1);
boolean existsCode = jedis.exists(phone);
if (!isUpperLimit && !existsCode) {
Random random = new Random();
String code = "";
for (int i = 0; i < 6; i++) {
code += random.nextInt(10);
}
jedis.setex(phone, 70, code);
map.put("code", code);
jedis.select(2);
if (jedis.exists(phone)) {
jedis.incr(phone);
} else {
jedis.setex(phone, 24 * 3600, "1");
}
} else if (isUpperLimit) {
jedis.select(2);
map.put("code", "超过每天的验证码发送次数限制,请等待" + jedis.ttl(phone) + "秒后再试");
} else {
map.put("code", "您已经发送过验证码了,最后的验证码是:" + jedis.get(phone) + ",还剩余:" + jedis.ttl(phone) + "秒");
}
return map;
}
@RequestMapping("/checkcode")
public Map<String, String> verificationCodeCheck(@RequestParam String phone, @RequestParam String code) {
HashMap<String, String> map = new HashMap<>();
if (!checkPhoneNumber(phone)) {
map.put("info", "手机号格式不正确");
} else if (code.length() != 6){
map.put("info", "验证码不符合要求");
} else {
Jedis jedis = new Jedis("localhost", 6379);
jedis.select(1);
String verificationCode = jedis.get(phone);
if (verificationCode != null && verificationCode.equals(code)) {
map.put("info", "验证成功");
jedis.del(phone);
} else {
map.put("info", "验证失败");
}
}
return map;
}
Spring Boot整合Redis
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.6</version>
</dependency>
在application.xml
中进行配置
spring:
redis:
host: ip
port: 端口
默认的底层操作客户端使用的是Lettuce
,也是可以切换成Jredis
的:
-
方式1:移除掉
Lettuce
-
方式2:配置文件指定
-
redis: client-type: jedis
-
切换数据库:
以下代码缺一不可
@Autowired
StringRedisTemplate redisTemplate;
LettuceConnectionFactory connectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
connectionFactory.setDatabase(3);
redisTemplate.setConnectionFactory(connectionFactory);
connectionFactory.afterPropertiesSet();
connectionFactory.resetConnection();
事务
单独的隔离操作,使命令按照顺序执行
multi
表示开启事务- 可以理解为将命令放到队列中,等待执行
exec
- 执行阶段,提交事务
discard
- 放弃执行,中文为丢弃
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 v1
QUEUED
127.0.0.1:6379> set key2 v2
QUEUED
127.0.0.1:6379> set key3 v3
QUEUED
127.0.0.1:6379> set key4 v4
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
4) OK
队列中,只要有一条失败,在执行exec
时队列中所有的都会失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 v1
QUEUED
127.0.0.1:6379> set key2 v2
QUEUED
127.0.0.1:6379> set key3 v3
QUEUED
127.0.0.1:6379> set kk
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> set key66666 k
QUEUED
127.0.0.1:6379> set key7777 kkkk
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
情况2:组队时成功,执行时失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 0
QUEUED
127.0.0.1:6379> incr key1
QUEUED
127.0.0.1:6379> incr key1
QUEUED
127.0.0.1:6379> set key2 vvv
QUEUED
127.0.0.1:6379> incr key2
QUEUED
127.0.0.1:6379> incr key2
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 1
3) (integer) 2
4) OK
5) (error) ERR value is not an integer or out of range
6) (error) ERR value is not an integer or out of range
事务冲突
悲观锁:认为每次的操作都不安全,即每次操作都加锁
乐观锁:带有版本号的操作,认为每次操作都是安全的,不直接上锁
乐观锁在Redis中的应用
watch key1 ... keyn
命令,在执行multi
开启事务之前可以执行watch
用来监视一个或者多个值
如果在事务执行之前这几个key
发生了任何改变,都会导致整个事务执行失败
在执行exec
或者discard
命令后,将会自动取消本次对所有key
的监视
也可以使用unwatch
命令手动的取消监视所有的key
,但必须是在执行multi
之前
应用场景:商品秒杀
将商品的库存作为监视的key
,开启事务,在事务中写上库存-1
等一系列的操作语句
最后执行事务
Redis事务特性
- 单独的隔离操作
- 没有事务的隔离级别的概念
- 不能保证原子性,即有一条命令执行失败,其余命令继续执行,将不会回滚
持久化操作
提供了两种方式RDB
和AOF
RDB
- redis database
AOF
- append of file
RDB
在指定的时间间隔内,将内存的数据快照写入到磁盘
过程:
- 单独新建一个子进程作为持久化的进行
- 会有一个临时文件,会先将数据写入到临时文件中
- 是为了数据写入时安全的考虑
- 如果在临时文件中写完了,将会在此写入到磁盘中,覆盖之前的持久化文件
优点:适合大规模的数据恢复,对数据完整性和一致性不高的时候适合使用,节省磁盘空间,恢复速度快
缺点:最后一次持久化后的数据可能会丢失
默认的持久化文件为dump.rdb
可以在配置文件中设置,例如指定的秒数内有n
个key
发生变化就会自动触发持久化的操作
备份和恢复
将dump.rdb
复制即可完成备份
恢复:
- 关闭
redis
:redis-cli shutdown
- 将
dump.rdb
移到指定的目录下 - 启动
redis
AOF
以日志的形式记录每一个写操作,包括增删改,但不包括查询,每次重启都会根据日志的内容逐行执行完成数据的恢复
AOF
默认不开启,需要手动开启,文件保存的路径和RDB
的路径一致,如果两者同时开启,那么将以AOF
为准
默认的文件名为appendonly.aof
备份和恢复的过程和RDB
相同
可以修复持久化文件redis-check-rdb/aof
命令
redis-check-aof --fix 文件名.aof
即可快速修复有问题的文件

AOF
支持压缩操作:
-
将两个指令合并到一块,例如
-
set a a1 set b b1 # 压缩合并为 set a a1 b b1
-
压缩重新也是采用的子进程
持久化过程:
- 所有的写的命令会被追加到缓冲区中
- 缓冲区根据持久化策略将操作同步到
AOF
文件中 - 如果文件带线啊哦超过重写策略或者手动重写时,会对
AOF
进行rewrite
进行压缩重写
优点:
- 数据丢失概率低
- 可读的日志文本
缺点:
- 更耗费磁盘空间
- 备份速度慢
- 每次读写有性能压力
Q.E.D.