Redis(Remote Dictionary Server)是一种支持key-value等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供String、List、Set、Zset、Hash结构直接存取,基于内存,可持久化。
1.Redis为什么快
- 纯内存访问,读写性能优异
- 支持数据类型丰富
- 单线程避免上下文切换
- 渐进式ReHash,缓存时间戳(获取时间会有上下文切换,redis无法承受,要对时间缓存。所以redis有定时任务,每ms更新时间缓存,所以获取时间就直接从缓存获得,避免上下文切换)
- 使用了多路IO复用模型,非阻塞IO
2.基础数据结构详解
String字符串
String是redis中最基本的数据类型,一个key对应一个value。
实战场景:
- 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
- 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
- session:常见方案spring session + redis实现session共享
List列表
Redis中的List其实就是链表(Redis用双端链表实现List)。
实战场景:
- 微博TimeLine: 有人发布微博,用lpush加入时间轴,展示新的列表信息。
- 消息队列
Set集合
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
实战场景:
- 标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
- 点赞,或点踩,收藏等,可以放到set中实现
Hash散列
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
实战场景:
- 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等。
Zset有序集合:
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
实战场景:
- 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
3.特殊类型详解
HyperLogLogs(基数统计)
举个例子,A = {1, 2, 3, 4, 5}, B = {3, 5, 6, 7, 9};那么基数(不重复的元素)= 1, 2, 4, 6, 7, 9; (允许容错,即可以接受一定误差)
实战场景:
这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数,共同好友数等。
Bitmap (位存储)
Bitmap 即位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态。
实战场景:
统计用户信息,活跃,不活跃! 登录,未登录! 打卡,不打卡! 两个状态的,都可以使用 Bitmaps!
geospatial (地理位置)
实战场景:
推算地理位置的信息: 两地之间的距离, 方圆几里的人
4.Redis key 的过期时间和永久有效分别怎么设置
EXPIRE 和 PERSIST 命令
5.什么是redis事务
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
6.SpringBoot集成redisTemplate
1.增加maven依赖
<!-- redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2.增加redis配置
spring:
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: localhost
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
# 连接超时时间(毫秒)
timeout: 1000
3.写一个redis配置类
- 以上是 RedisAutoConfiguration 类中的源码片段,可以看出 SpringBoot 对 Redis 做自动化配置的时候,在容器中注入了 redisTemplate 和 stringRedisTemplate
- 其中,RedisTemplate<Object, Object> 表示,key 的类型为 Object,value 的类型为 Object,但是我们往往需要的是 RedisTemplate<String, Object>,这就需要我们重新注入一个 RedisTemplate 的 Bean,它的泛型为 RedisTemplate<String, Object>,并设置 key和value 的序列化方式
- 看到这个@ConditionalOnMissingBean注解后,就知道如果Spring容器中有了RedisTemplate对象了,这个自动配置的RedisTemplate不会实例化。因此我们可以直接自己写个配置类,配置RedisTemplate。
4.主要的业务代码
public ResponseResult<Void> deleteByIdRedis(Long userId) {
int result = userService.deleteById(userId).getCode();
String key = "user_"+userId;
if(result == ResponseCode.SERVER_SUCCESS.id){
boolean hasKey = redisTemplate.hasKey(key);
if(hasKey){
redisTemplate.delete(key);
System.out.println("删除了Redis中的key:"+key);
}
}
return ResponseResult.ok();
}
public ResponseResult<Void> updateByIdRedis(Long userId, UserUpdateRequest updateRequest) {
ValueOperations<String, User> operations = redisTemplate.opsForValue();
// 更新对象 转成 实体
User user = userDTOAssembler.assembler(updateRequest);
// 设置主键
user.setId(userId);
int result = userService.updateById(user).getCode();
if (result == ResponseCode.SERVER_SUCCESS.id) {
String key = "user_" +userId;
boolean haskey = redisTemplate.hasKey(key);
if (haskey) {
redisTemplate.delete(key);
System.out.println("删除Redis中的key-----------> " + key);
}
// 再将更新后的数据加入缓存
UserResponse userNew = this.queryByIdRedis(userId).getData();
if (!ObjectUtils.isEmpty(userNew)) {
operations.set(key, user, 3, TimeUnit.HOURS);
}
}
return ResponseResult.ok();
}
public ResponseResult<UserResponse> queryByIdRedis(Long userId) {
String key = "user_" + userId;
ValueOperations<String, User> operations = redisTemplate.opsForValue();
//判断redis中是否有键为key的缓存
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
User user = operations.get(key);
log.info("从Redis中获取数据:" + user.getName());
return ResponseResult.success(userDTOAssembler.toResponse(user));
} else {
User user = userService.queryById(userId).getData();
log.info("查询数据库获取数据:" + user.getName());
//写入缓存
operations.set(key, user, 1000, TimeUnit.HOURS);
return ResponseResult.success(userDTOAssembler.toResponse(user));
}
}
5.启动成功,但是调用接口报错
出现原因:实体类中的LocalDate类型的数据不能序列化
解决办法:
①导入依赖
<!-- localdate序列化依赖-->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.2</version>
</dependency>
②在相关字段上加注解
/**
* 出生日期
*/
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate birth;
7.启动成功,调用查询接口