Redis 从入门到入坟
一、NoSQL 数据库
NoSQL (Not Only SQL)
,泛指非关系型数据库。NoSQL
不依赖业务逻辑方式存储,而以简单的key-value
模式存储。因此大大的增加了数据库的扩展能力。
- 不遵循
SQL
标准- 不支持
ACID
- 远超于
SQL
性能
1.1 适用场景
- 对数据高并发的读写
- 海量数据的读写
- 对数据高可扩展性的要求
1.2 不适用场景
- 需要事务支持
- 基于
SQL
的结构化查询存储,处理复杂的关系,需要即席查询
二、Redis基本介绍
默认端口:6379
默认有16个数据库,类似数组下标从0开始,初始默认使用 0 号库
统一密码管理,所有库的密码相同
2.1 单线程 + 多路 IO 复用
多路复用是指使用一个线程来检查多个文件描述符
Socket
的就绪状态,比如调用select()
和pool()
,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超市。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如线程池)
2.2 安装(CentOS7 + Redis6)
- 前置准备
1
2
3
4 yum -y install gcc tcl
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
echo -e "\nsource /opt/rh/devtoolset-9/enable" >>/etc/profile
- 安装
1
2
3
4
5
6
7 cd /usr/local/
wget http://download.redis.io/releases/redis-6.0.9.tar.gz
tar -zxvf redis-6.0.9.tar.gz
cd redis-6.0.9
make && make install
等待安装完成
cp redis.conf /etc/redis.conf
- 修改配置文件
1
2
3
4
5
6 vim /etc/redis.conf
bind 127.0.0.1 # 将这行代码注释,监听所有的ip地址,外网可以访问
protected-mode no # 把yes改成no,允许外网访问
daemonize yes # 把no改成yes,后台运行
appendonly yes # 开启aof备份
- 设置开机启动
1
2
3
4 启动服务器
/usr/local/bin/redis-server /etc/redis.conf
启动客户端
/usr/local/bin/redis-cli
2.3 Key 的相关操作
*keys * * 查看当前库所有 key
exists key 判断某个 key 是否存在
type key 查看 key 的类型
del key 删除指定的 key
unlink key 根据 value 选择非阻塞删除(表面上已经删除,实际上并没有,会等后期慢慢删除)
expire key N 给 key 设置过期时间(N秒)
ttl key 查看 key 还有多少秒过期
select N 使用第 N 号数据库
dbsize 查看当前数据库 key 的数量
flushdb 清空当前库
flushall 清空所有库
2.4 配置文件
2.4.1 文件头
主要是单位的设置方式
2.4.2 INCLUDE
可以导入其他文件
2.4.3 NETWORK
仅绑定的 ip 可以连接到 redis
yes 状态下仅本机可连接
端口号配置
设置 Tcp 的 backlog,backlog 是一个连接队列,队列总和 = 未完成三次握手队列 + 已完成三次握手队列。在高并发下需要一个高 backlog 来避免慢客户端连接问题
空闲等待时间,单位为秒,0 为永不超时
心跳检测时间,单位秒
3.4.4 GENERAL
允许后台启动
进程号文件
日志级别
日志文件输出路径
数据库个数
3.4.5 SECURITY
密码设置
3.4.6 CLIENTS
客户端最大连接数
三、基本五大类型
3.1 String
3.1.1 命令行操作
get [key] 查询 key 的值
append [key] [value] 将 value 添加到指定 key 尾
strlen [key] 计算 value 的长度
setnx [key] [value] 当 db 中不存在 key 时才加入
incr/decr [key] 将数字类型的字符串加/减1
incrby/decrby [key] [step] 将数字类型的字符串加/减step
mset [key1] [value1] [key2] [value2] … 同时添加多个 k-v 键值对
mget [key1] [key2] … 同时获取多个 value
msetnx [key1] [value1] [key2] [value2] … 同时添加多个 k-v 键值对,但如果 db 中存在一个 key,那么所有数据都将添加失败
getrange [key] [start] [end] 截取字串
setrange [key] [start] [value] 从 start 开始将原字符替换为 value
setex [key] [timeout] [value] 设置 k-v 键值对的有效时间
getset [key] [value] 获取旧值,设置新值
3.1.2 数据结构
String 的数据结构为简单动态字符串(Simple Dynamic String, SDS)。是可以修改的字符串,结构上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
如图,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len, 当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次指挥多扩 1M 的空间,最大 512M
3.2 List
3.2.1 命令行操作
lpush/rpush [key] [value1] [value2] … 从左边/右边插入一个或多个值
lpop/rpop [key] 从左边/右边吐出一个值。值在键在,值毁键亡
rpoplpush [key1] [key2] 从 key1 右边吐出一个值,加入到 key2 列表左边
lrange [key] [start] [end] 按照索引获取值
lindex [key] [index] 按照索引下标获取值
llen [key] 获取列表长度
linsert [key] before/after [element] [newElement] 在 element 前/后插入一个新 element
lrem [key] [n] [value] 从左边删除 n 个 value
lset [key] [index] [value] 设置指定下标处的值
3.2.2 数据结构
单键多值。Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素的头部或尾部。
底层是一个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能可能会较差
数据结构为快速链表 quickList。在列表元素较少的情况下会使用一块连续的内存存储,这个结构是压缩链表
(zipList)
。它将所有的元素紧挨在一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quickList。因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是 int 类型的数据,结构上还需要额外的指针 prev 和 next。
Redis 将链表和 zipList 结合起来组成 quickList。也就是将多个 zipList 使用双向指针串起来。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
3.3 Set
3.3.1 命令行操作
sadd [key] [value1] [value2] … 将一个或多个元素加入到集合 key 中,已经存在的元素将被忽略。
smembers [key] 取出该集合的所有值
sismember [key] [value] 判断集合 key 中是否存在 value
scard [key] 返回集合元素个数
**srem [key] [value1] [value2] …**删除集合中的某个元素
**spop [key]**随机从集合中弹出一个值
srandmember [key] [N] 随即从集合中取出 N 个值,不会删除
**smove [source] [destination] [value]**把 value 从 source 集合移动到 destination 集合
**sinter [key1] [key2]**交集
**sunion [key1] [key2]**并集
**sdiff [key1] [key2]**差集
3.3.2 数据结构
Redis set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择。
是 string 类型的无序集合。它的底层其实是一个 value 为 null 的 hash 表,所以添加,删除,查找的**复杂度都是
O(1)
**。一个算法,随着数据的增加,执行时间的长短,如果是 O(1),数据增加,查找数据的时间不变。
set 数据结构是 dict 字典,用 hash 表实现
3.4 Hash
3.4.1 命令行操作
**hset [key] [field] [value]**给 key 集合中的 field 键赋值
**hget [key1] [field]**从 key1 集合 field 取出 value
**hmset [key] [field1] [value1] [field2] [value2] …**批量设置 hash 的值
hexists [key1] [field] 查看 key 中是否有 field
hkeys [key] 列出 key 中的所有 field
**hvals [key]**列出 key 中的所有 value
hincrby [key] [field] [step] 给 key 中的字段加上 step
**hsetnx [key] [field] [value]**将哈希表 key 中的域 field 的值设置为 value,当且仅当 field 不存在
3.4.2 数据结构
Redis hash 是一个键值对集合,是一个 string 类型的 filed 和 value 的映射表,hash 特别适合用于存储对象,类似 Java 里面的 Map<String, Object>
hash 类型对应的数据结构有 zipList,hashTable 两种。当 field-value 长度较短且个数比较少时,使用 zipList,反之使用 hashTable
3.5 Zset
3.5.1 命令行操作
**zadd [key] [score1] [value1] [score2] [value2] …**将一个或多个元素及评分加入有序集合中
**zrange [key] [start] [end] [withscores]**返回有序集合 key 中下标在 [start, stop] 的元素
3.5.2 数据结构
有序集合 zset 与普通集合 set 非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分(
score
)**,被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但评分可以重复**因为元素是有序的,所以也可以很快的根据评分或次序来获取一个范围的元素
数据结构采用跳表
四、发布与订阅
4.1 subscribe
订阅
4.2 publish
发布
五、Jedis
5.1 普通使用
Jedis 为命令行中的每一个命令都创建了与其名字相对应的方法,可以直接调用
导入依赖
1
2
3
4
5 <dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.1</version>
</dependency>建立连接
1
2
3
4
5
6
7
8
9
10
11 import redis.clients.jedis.Jedis;
public class Connection {
private final static Jedis jedis = new Jedis("127.0.0.1", 6379);
public static Jedis getJedis() {
return jedis;
}
}测试连接
1
2
3
4
5
6
7
8 public class Ping {
public static void main(String[] args) {
Jedis jedis = Connection.getJedis();
System.out.println(jedis.ping());
}
}开始使用
1
2
3
4
5
6
7
8
9
10 public class JedisDemo {
public static void main(String[] args) {
Jedis jedis = Connection.getJedis();
jedis.sadd("jedis", "v1", "v2", "v3");
Set<String> smembers = jedis.smembers("jedis");
System.out.println(smembers);
}
}
5.2 整合 SpringBoot
导入依赖
1
2
3
4
5
6
7
8
9
10
11 <dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.10.0</version>
</dependency>配置启动文件
1
2
3
4
5
6
7
8
9
10
11
12 spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 1800000
lettuce:
pool:
max-active: 20
max-wait: -1
max-idle: 5
min-idle: 0书写配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
public class RedisConfig {
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
// key采用String的方式进行序列化
template.setKeySerializer(redisSerializer);
// hash的key也采用String的方式进行序列化
template.setHashKeySerializer(redisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
六、事务和锁机制
Redis 事务是一个单独的隔离操作:食物中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。Redis 事务的主要作用就是串联多个命令防止别的命令插队
6.1 Multi、Exec、Discard
从输入 Multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入 Exec 之后,Redis 才会将之前的命令队列中的命令依次执行。组队过程中可以通过 Discard 来取消
6.2 异常处理
- 情况一:在 multi - exec 组队过程中命令就已经出错 => 所有命令都不会执行
- 情况二:在执行的过程中出现异常 => 只有出现异常的命令不会被执行
6.3 事务冲突
6.3.1 事务三特性
- 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送过来的命令请求所打断
- 没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
- 不保证原子性:事务中如果有一条命令失败,其后的命令仍会被执行,没有回滚
6.3.2 命令行操作
- watch [key1] [key2]… 可以监视一个或多个 key,如果在事务执行之前这个 key 被其他命令所改动,那么事务将被打断
- unwatch [key1] [key2]… 取消监视