专栏目录
Redis
文章目录
1. 安装Redis
1.1 本地安装redis
1.2 docker安装redis
①创建目录以及文件
创建目录/root/redis/conf
和/root/redis/data
在conf
目录下创建文件redis.conf
②安装镜像
docker run -d \
--name redis \
-p 6379:6379 \
-v /root/redis/data:/data \
-v /root/redis/conf/redis.conf:/etc/redis/redis.conf \
redis redis-server /etc/redis/redis.conf
③修改配置文件
# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes后即可后台运行
daemonize yes
# 密码,设置后访问Redis必须输入密码
requirepass passwd
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
2. Redis命令
2.1常见命令
指令格式:redis-cli [options] [commonds]
如果使用docker进入:
docker exec -it redis redis-cli -h [ip] -p [port] -a [passwd]
-h [ip] 指定要连接redis节点的IP地址,默认127.0.0.1
-p [port] 指定要连接redis节点的端口,默认6379
-a [passwd] 指定redis的访问密码
命令 | 说明 |
---|---|
ping | 与redis服务器做心跳测试,服务器正常则返回pong |
AUTH | 指定用户名和密码 |
select [dbNum] | 根据标号选择数据库 |
keys [pattern] | 查看符合模板的所有key |
del [key] | 删除指定key |
exists [key] | 判断key是否存在 |
expire [key] | 设置key的有效期,过期删除 |
ttl [key] | 查看一个key的剩余有效期 |
2.2 String命令
命令 | 说明 |
---|---|
SET [key] [value] | 设置键值对 |
GET [key] | 查找指定key的value |
MSET | 批量添加多个String类型键值对 |
MGET | 根据多个key批量获取value |
INCR [key] | 让一个整型的key自增1 |
INCRBY [key] [num] | 让一个整型的key自增并指定步长 |
INCRBYFLOAT [key] [num] | 让一个浮点类型的数字自增并指定步长 |
SETNX | 添加一个String类型的键值对,前提是这个key不存在,否则不执行 |
SETEX | 添加一个String类型的键值对,并且指定有效期 |
2.3 Key结构
Redis没有类似MySQL中的Table的概念,我们该如何区分不同类型的key呢?
例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1,此时如果使用id作为key,那就会冲突了,该怎么办?
我们可以通过给key添加前缀加以区分,不过这个前缀不是随便加的,有一定的规范:
Redis的key允许有多个单词形成层级结构,多个单词之间用’:'隔开,格式如下:
项目名:业务名:类型:id
这个格式并非固定,也可以根据自己的需求来删除或添加词条。这样以来,我们就可以把不同类型的数据区分开了。从而避免了key的冲突问题。
如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储:
KEY | VALUE |
---|---|
nwu:user:1 | {“id”:1, “name”: “Jack”, “age”: 21} |
nwu:product:1 | {“id”:1, “name”: “小米11”, “price”: 4999} |
2.4 Hash命令
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String结构是将对象序列化为JSON字符串后存储,Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD。
命令 | 说明 |
---|---|
HSET [key] [value] | 添加或者修改hash类型key的field的值 |
HGET [key] | 获取一个hash类型key的field的值 |
HMSET | 批量添加多个hash类型key的field的值 |
HMGET | 批量获取多个hash类型key的field的值 |
HGETALL | 获取一个hash类型的key中的所有的field和value |
HKEYS | 获取一个hash类型的key中的所有的field |
HINCRBY | 让一个hash类型key的字段值自增并指定步长 |
HSETNX | 添加一个hash类型的key的field值,前提是这个field不存在,否则不执行 |
2.5 List命令
命令 | 说明 |
---|---|
LPUSH [key] [element…] | 向列表左侧插入一个或多个元素 |
LPOP [key] | 移除并返回列表左侧的第一个元素,没有则返回nil |
RPUSH [key] [element…] | 向列表右侧插入一个或多个元素 |
RPOP [key] | 移除并返回列表右侧的第一个元素,没有则返回nil |
LRANGE [key] [star] [end] | 返回一段角标范围内的所有元素 |
BLPOP [key] | 移除并返回列表左侧的第一个元素,没有元素时等待指定时间 |
BRPOP [key] | 移除并返回列表右侧的第一个元素,没有元素时等待指定时间 |
2.6 Set命令
命令 | 说明 |
---|---|
SADD [key] [member] | 向set中添加一个或多个元素 |
SREM [key] [member] | 移除set中的指定元素 |
SCARD [key] | 返回set中元素的个数 |
SISMEMBER [key] [member] | 判断一个元素是否存在于set中 |
SMEMBERS | 获取set中的所有元素 |
SINTER [key1] [key2] | 求key1与key2的交集 |
SDIFF [key1] [key2] | 求key1与key2的差集 |
2.7 Zset类型
命令 | 说明 |
---|---|
ZADD [key] [score] [member] | 添加一个或多个元素到Zset ,如果已经存在则更新其score值 |
ZREM [key] [member] | 删除Zset中的一个指定元素 |
ZSCORE [key] [member] | 获取Zset中的指定元素的score值 |
ZRANK [key] [member] | 获取Zset 中的指定元素的排名 |
ZCARD [key] | 获取Zset中的元素个数 |
ZCOUNT [key] [min] [max] | 统计score值在给定范围内的所有元素的个数 |
ZINCRBY [key] [increment] [member] | 让Zset中的指定元素自增,步长为指定的increment值 |
ZRANGE [key] [min] [max] | 按照score排序后,获取指定排名范围内的元素 |
ZRANGEBYSCORE [key] [min] [max] | 按照score排序后,获取指定score范围内的元素 |
ZDIFF [key1] [key2] | 求key1与key2的差集 |
ZINTER [key1] [key2] | 求key1与key2的交集 |
ZUNION [key1] [key2] | 求key1与key2的并集 |
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可,例如:
升序获取Zset中的指定元素的排名:ZRANK key member
降序获取Zset中的指定元素的排名:ZREVRANK key memeber
3. Java客户端
业务开发中,Redis还是要以编码的形式进行使用,Redis官方也提供了各种语言的客户端。
推荐使用的java客户端,包括:
- Jedis和Lettuce:这两个主要是提供了Redis命令对应的API,方便我们操作Redis,而SpringDataRedis又对这两种做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习。
- Redisson:是在Redis基础上实现了分布式的可伸缩的java数据结构,例如Map、Queue等,而且支持跨进程的同步机制:Lock、Semaphore等待,比较适合用来实现特殊的功能需求。
3.1 Jedis客户端
3.1.1 相关地址
3.1.2 快速入门
①引入依赖
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
②建立连接
void setUp() {
// 1.建立连接
// jedis = new Jedis("192.168.150.101", 6379);
jedis = JedisConnectionFactory.getJedis();
// 2.设置密码
jedis.auth("123321");
// 3.选择库
jedis.select(0);
}
③使用
@Test
void innerString() {
// 存入数据
String result = jedis.set("name", "Tom");
System.out.println("result = " + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}
void innerHash() {
// 插入hash数据
jedis.hset("user:1", "name", "Jack");
jedis.hset("user:1", "age", "21");
// 获取
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}
④释放连接
void release() {
if (jedis != null) {
jedis.close();
}
}
3.1.3 连接池
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此可以使用Jedis连接池代替Jedis的直连方式。
import redis.clients.jedis.*;
public class JedisConnectionFactory {
private static JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8); //连接池中总连接的最大数量
poolConfig.setMaxIdle(8); // 最大空闲连接数
poolConfig.setMinIdle(0); // 最小空闲连接数
poolConfig.setMaxWaitMillis(1000); // 最大等待时间,-1表示无限制
// 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
jedisPool = new JedisPool(poolConfig, "192.168.18.129", 6379, 1000, "yu123");
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
3.2 SpringDataRedis客户端
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。
- 提供了对不同Redis客户端的整合(Lettuce和Jedis)
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
- 支持Redis哨兵和Redis集群
- 支持基于Lettuce的响应式编程
- 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
- 支持基于Redis的JDKCollection实现
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:
API | 返回值类型 | 说明 |
---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作String类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作Hash类型数据 |
redisTemplate.opsForList() | ListOperations | 操作List类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作Set类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作ZSet类型数据 |
redisTemplate | 通用命令 |
3.2.1 相关地址
3.2.2 快速入门
①引入依赖
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
②在application.yaml
配置Redis
spring:
redis:
host: 192.168.18.129
port: 6379
password: 123
lettuce:
pool:
max-active: 10 # 最大活动连接数
max-idle: 5 # 最大空闲连接数
min-idle: 2 # 最小空闲连接数
max-wait: 1000ms # 最大等待时间,-1表示无限制
shutdown-timeout: 100ms # 关闭连接的超时时间
③Spring注入RedisTemplate
使用SpringBoot的自动装配
class RedisString {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testString() {
// 写入一条String数据
redisTemplate.opsForValue().set("name", "Tom");
// 获取string数据
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}
}
3.2.3 自定义序列化
RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,但存在可读性差、内存占用大的问题。
因此采用JSON序列化来代替默认的JDK序列化方式,能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象
编写RedisConfig配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();
// 设置Key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}
3.2.4 StringRedisTemplate
JSON序列化会将类名一致存入,以便自动进行反序列化,但是也带来了额外的内存开销。
为了节省内存空间,可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。
因此SpringDataRedis提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。
省去了自定义RedisTemplate序列化方式的步骤,可以直接使用:
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("Tom", 21);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:200", json);
// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);
System.out.println("user1 = " + user1);
}
3.2.5 总结
RedisTemplate的两种序列化实践方案:
-
方案一:
- 自定义RedisTemplate
- 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
-
方案二:
- 使用StringRedisTemplate
- 写入Redis时,手动把对象序列化为JSON
- 读取Redis时,手动把读取到的JSON反序列化为对象