Redis的笔记,使用场景个人总结,仔细看完绝对有收获

2024-02-15 16:44

本文主要是介绍Redis的笔记,使用场景个人总结,仔细看完绝对有收获,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Redis

关于Redis我们先要问:什么是Redis?

个人认为它是一种key - value 存储系统

数据结构

String 字符串类型: name: "xxx"

List 列表:names: ["cat", "dog"]

Set 集合:names: ["cat", "dog"](值不能重复)

Hash 哈希:person: { "name": "张三", "age": 18 }

Zset 集合:persons: { person1 - 9, peron2 - 12 }(适合做排行榜)

geo(计算地理位置)

hyperloglog(pv / uv)(可以来做页面访问)

pub / sub(发布订阅,类似消息队列)

BitMap (1001010101010101010101010101)(用来做每月签到)

bloomfilter(布隆过滤器,主要从大量的数据中快速过滤值,比如邮件黑名单拦截)

使用

自定义序列化类

​
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
​
@Configuration
public class RedisTemplateConfig {
​@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(connectionFactory);redisTemplate.setKeySerializer(RedisSerializer.string());return redisTemplate;}
}

Java中的实现方式

Spring Data Redis(推荐)

1、引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.6.4</version>
</dependency>

2、配置Redis地址

spring:# redis 配置redis:port: 6379  //端口号host: localhost //主机名database: 0 //数据存储在redis的哪个数据库上

Jedis

独立于 Spring 操作 Redis 的 Java 客户端

要配合 Jedis Pool 使用

Lettuce

高阶 的操作 Redis 的 Java 客户端

异步、连接池

Redisson(分布式)

分布式操作 Redis 的 Java 客户端,让你像在使用本地的集合一样操作 Redis(分布式 Redis 数据网格)。就是功能更加丰富版本的 RedisTemplate,经常使用为分布式锁(它解决了分布式系统中的多个节点互斥访问的问题)。

  • 互斥

  • 防止死锁

  • 可重入

  • 自动续期(看门狗机制)

  • 高性能

  • ....

Redisson中的看门狗机制
如果线程仍旧没有执行完,那么redisson会自动给redis中的目标key延长超时时间,这在Redisson中称之为 Watch Dog 机制。
​
如果拿到分布式锁的节点宕机,且这个锁正好处于锁住的状态时,会出现锁死的状态,为了避免这种情况的发生,锁都会设置一个过期时间。这样也存在一个问题,加入一个线程拿到了锁设置了30s超时,在30s后这个线程还没有执行完毕,锁超时释放了,就会导致问题,便有了 watch dog 自动延期机制。
​
Redisson提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。
​
默认情况下,看门狗的续期时间是30s,也可以通过修改Config.lockWatchdogTimeout来另行指定。
​
另外Redisson 还提供了可以指定leaseTime参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期
​
阅读源码总结:
1、watch dog 在当前节点存活时每10s给分布式锁的key续期 30s;
2、watch dog 机制启动,且代码中没有释放锁操作时,watch dog 会不断的给锁续期;
3、如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到 finally {} 中;

JetCache

比较:

1. 如果你用的是 Spring,并且没有过多的定制化要求,可以用 Spring Data Redis,最方便
2. 如果你用的不是 SPring,并且追求简单,并且没有过高的性能要求,可以用 Jedis + Jedis Pool
3. 如果你的项目不是 Spring,并且追求高性能、高定制化,z可以用 Lettuce,支持异步、连接池
4. 如果你的项目是分布式的,需要用到一些分布式的特性(比如分布式锁、分布式集合),推荐用 redisson

使用场景

1、普通缓存数据

比如:

1)使用string存储 验证码

//这里是项目中存储后端返回给用户的验证码操作String code = RandomUtil.randomNumbers(6);
//保存验证码到RedisstringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone,code,2, TimeUnit.MINUTES);

2)使用hash存储用户信息(通过这个,加上拦截器中设置,就是redis+session的登录校验)

//1、随机生成token作为登录令牌String token = UUID.randomUUID().toString();
//2、将User对象转为HashMap存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
//3、利用hash存储用户信息,比直接使用 String 结构是更为节省空间String tokenKey = RedisConstants.LOGIN_USER_KEY + token;stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
//4、设置token有效期(缓存是非常珍贵的,一般都要设置有效期)stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL,TimeUnit.MINUTES);

2、消息队列

1)介绍:

消息队列:就是存放消息的队列。它提供了一种高效、可靠、异步的通信方式,能够降低系统的耦合性,提高系统的扩展性、可维护性和可靠性。
个人认为消息队列只是redis的一个补充,而不是重点,这个redis实现消息队列只适合轻量级,而且需要看情况来实现。
​
最简单的消息队列包括3个角色:
1、消息队列:
2、生产者:发送消息到消息队列
3、消费者:从消息队列获取消息并处理
​
生产者 -》 消息队列 -》 消费者
​
生产者:发送消息
消费者:接收消息

2)实现

Redis中提供了3种不同方式实现消息队列:
1、list结构:基于list结构模拟消息队列优点:基于Redis持久化,数据安全有保证;消息有序缺点:无法避免消息丢失;只能被读一次
2、PubSub:基本的点对点消息模型  (一个生产者可对多个消费者)优点:采用发布订阅模型,支持多生产,多消费缺点:不支持数据持久化;无法避免数据丢失;消息堆积有限,超限则丢失
3、Stream:比较完善的消息队列模型(5.0以上引入,专门为消息队列而生)特点:1)可回溯(消息消费完不会被删除)2)可以多消费者争抢消息,加速消费3)可以阻塞读取4)没有消息漏读风险(会标记上次消费的位置)5)有消息确认机制,保证消息至少被消费一次基于Stream的消息队列——消费者组
# * 表示让 Redis 为插入的数据自动生成一个全局唯一的 ID
# 往名称为 mymq 的消息队列中插入一条消息,消息的键是 name,值是 sid10t
127.0.0.1:6379> XADD mymq * name sid10t
"1665058759764-0"
​
#从消息队列mymq中读取消息
127.0.0.1:6379> XREAD STREAMS mymq 1665058759764-0
(nil)
127.0.0.1:6379> XREAD STREAMS mymq 1665058759763-0
1) 1) "mymq"2) 1) 1) "1665058759764-0"2) 1) "name"2) "sid10t"#通过BLOCK实现阻塞读
# 命令最后的 $ 符号表示读取最新的消息
127.0.0.1:6379> XREAD BLOCK 10000 STREAMS mymq $
(nil)
(10.01s)
​
​
# 创建一个名为 group1 的消费组,0-0 表示从第一条消息开始读取。
127.0.0.1:6379> XGROUP CREATE mymq group1 0-0
OK
# 创建一个名为 group2 的消费组,0-0 表示从第一条消息开始读取。
127.0.0.1:6379> XGROUP CREATE mymq group2 0-0
OK
​
​
# 命令最后的参数“>”,表示从第一条尚未被消费的消息开始读取。
#消息队列中的消息一旦被消费组里的一个消费者读取了,就不能再被该消费组内的其他消费者读取了,即同一个消费组里的消费者不能消费同一条消息。但是不同的消费者组可以再去读这条消息。就是说每个消息只可在消费者组中读一次
127.0.0.1:6379> XREADGROUP GROUP group1 consumer1 STREAMS mymq >
1) 1) "mymq"2) 1) 1) "1665058759764-0"2) 1) "name"2) "sid10t"
​
#使用XPENDING来查看一下 group2 中各个消费者已读取、但尚未确认的消息个数
127.0.0.1:6379> XPENDING mymq group2
1) (integer) 4
2) "1665058759764-0"
3) "1665060634962-0"
4) 1) 1) "consumer1"2) "2"2) 1) "consumer2"2) "1"3) 1) "consumer3"2) "1"
​
小结:
1、消息保序:XADD/XREAD
2、阻塞读取:XREAD block
3、重复消息处理:Stream 在使用 XADD 命令,会自动生成全局唯一 ID;
4、消息可靠性:内部使用 PENDING List 自动保存消息,使用 XPENDING 命令查看消费组已经读取但是未被确认的消息,消费者使用 XACK 确认消息;
5、支持消费组形式消费数据

3、计数器

1)点赞统计(比如说这下面是一个博客点赞统计)

//1、获取当前用户idLong userId = UserHolder.getUser().getId();
//2、判断是否点赞String key = RedisConstants.BLOG_LIKED_KEY + id;//获得score值,判断当前用户是否对这个博客点赞Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
//3、未点赞,可以点赞if(score == null){//3.1、数据库的liked点赞数+1boolean success = update().setSql("liked = liked +1").eq("id", id).update();//3.2、保存用户到redis的set集合,方便判断是否点赞if(success){stringRedisTemplate.opsForZSet().add(key,userId.toString(),System.currentTimeMillis());}}else{
//4、已经点赞,取消点赞//4.1、数据库点赞-1boolean success = update().setSql("liked = liked -1").eq("id", id).update();//4.2、把用户从redis的set集合删除if(success) {stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}

2)网站点击量——UV统计

先搞懂两个概念:

  • UV:独立访问量,只记录某个用户单次

  • PV:页面访问量,记录每次访问网站次数

Hyperloglog是loglog算法派生的概率算法。特点:相比于传统的集合统计方法,HyperLogLog 具有更小的内存占用和更快的计算速度。

 //这里是一个模拟网页点击的测试@Testvoid testHyperLogLog(){String[] values = new String[1000];//1000个用户int j = 0;for(int i =0;i<1000000;i++){    //访问页面次数j=i%1000;values[j] = "user_" + i;if(j == 999){stringRedisTemplate.opsForHyperLogLog().add("dj",values);}}Long count = stringRedisTemplate.opsForHyperLogLog().size("dj");System.out.println(count);  //运行结果:997593,所耗空间:12.02kB}

4、分布式锁

借用Redis的SETNX实现分布式锁,确保在分布式系统中的并发安全性

//获取锁
private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}
//释放锁
private void unlock(String key) {stringRedisTemplate.delete(key);}

5、排行榜

这里利用zset实现了一个点赞排行榜,按最先点赞排序

String key = RedisConstants.BLOG_LIKED_KEY + id;
//取出前5个点赞的Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);if(top5 == null || top5.isEmpty()){return Result.ok(Collections.emptyList());}
//这里就是对取出的数据进行操作了List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());String idStr = StrUtil.join(",", ids);List<UserDTO> userDTOs = userService.query().in("id", ids).last("order by field(id," + idStr + ")").list().stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());

6、共同关注

利用set集合实现

//1、获取当前用户Long userId = UserHolder.getUser().getId();String key1 = "follows:" + userId;String key2 = "follows:" + followUserId;
//2、求交集(前提是双方都把自己关注的对象存储到set集合中了)Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);if (intersect.isEmpty() || intersect.size() == 0)return Result.ok();
//3、取出共同关注的对象的ID
List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());

7、地理位置应用GEO

String key = RedisConstants.SHOP_GEO_KEY + typeId;//3、查询redis、按照距离排序、分页。  结果:shopId,distanceGeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo().search(key,GeoReference.fromCoordinate(x, y),new Distance(5000),RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeCoordinates().limit(end));
通过调用 opsForGeo() 方法获取 RedisTemplate 中的 Geo 操作对象,然后使用 search() 方法进行地理位置搜索。其中,GeoReference.fromCoordinate(x, y) 表示搜索以坐标 (x, y) 为中心,半径为 5000 米的范围内的数据;new Distance(5000) 表示搜索的半径为 5000 米;RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeCoordinates().limit(end) 表示搜索结果需要包含坐标,并且限制最多返回 end 条记录。最后,调用 search() 方法返回的结果是一个 GeoResults 对象,其中包含了按照距离排序后的店铺 ID 和与中心点的距离(单位为米)。

8、滚动分页

利用zset集合的分数实现滚动分页

 public IPage<User> recommend(Integer pageNum, Integer pageSize, HttpServletRequest request) {User loginUser = getLoginUser(request);String key = String.format("user:recommend:%s", loginUser.getId());
​long minScore = (pageNum - 1) * pageSize;long maxScore = pageNum * pageSize - 1;
​// 从缓存中获取指定分数范围内的数据,按分数从高到低排序Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, minScore, maxScore);
​if (!typedTuples.isEmpty()) { // 缓存命中,直接返回缓存数据return parseResult(typedTuples);    //parseResult方法是一个将typedTuples解析成想要的数据}
​// 无缓存,查询数据库Page<User> page = new Page<>(pageNum, pageSize);QueryWrapper<User> queryWrapper = new QueryWrapper<>();IPage<User> userPage = page(page, queryWrapper);
​// 将查询结果存入缓存,使用与查询时相同的分数范围int count = 0;for (User user : userPage.getRecords()) {count++;if (count >= 10 && minScore == 0){minScore += 10;}redisTemplate.opsForZSet().add(key, user, minScore); // 使用最小分数存入缓存,每10个分成了一组的数据}redisTemplate.expire(key, 30, TimeUnit.MINUTES);
​return userPage;}

9、每月签到

BitMap实现获取本月签到次数

// 用户签到public void signIn(String userId, String date) {String key = "signin:" + date; // 每天一个 key,用于记录当天的签到情况redisTemplate.opsForValue().setBit(key, Long.parseLong(userId), true);}
​
// 判断用户是否已签到public boolean hasSignedIn(String userId, String date) {String key = "signin:" + date;return redisTemplate.opsForValue().getBit(key, Long.parseLong(userId));}// 获取本月总签到次数public long getTotalSignInCountThisMonth() {LocalDate now = LocalDate.now();int daysInMonth = now.lengthOfMonth();long totalSignInCount = 0;
​for (int day = 1; day <= daysInMonth; day++) {String key = "signin:" + now.format(DateTimeFormatter.ofPattern("yyyyMM")) + String.format("%02d", day);totalSignInCount += redisTemplate.opsForValue().bitCount(key);}
​return totalSignInCount;}

10、用户唯一标识

一共有3种方法实现:

  • 数据库自增

  • UUID

  • redis实现

通过位运算符将时间戳和序列号合并成一个 64 位的二进制数,其中时间戳占据高 32 位,序列号占据低 32 位。

public class RedisIdWorker {private StringRedisTemplate stringRedisTemplate;
​public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}
​private static final long BEGIN_TIMESTAMP = 1702762344L;private static final int COUNT_BITS =32;
​public long nextID(String keyPrefix){//1.生成时间戳LocalDateTime now = LocalDateTime.now();long nowSecond = now.toEpochSecond(ZoneOffset.UTC);long timeStamp = nowSecond - BEGIN_TIMESTAMP;//2.生成序列号//2.1获取当前日期,精确到天String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));//2.2自增长long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);//3.拼接并返回return timeStamp << COUNT_BITS | count;}
}

这篇关于Redis的笔记,使用场景个人总结,仔细看完绝对有收获的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/711938

相关文章

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Qt中QUndoView控件的具体使用

《Qt中QUndoView控件的具体使用》QUndoView是Qt框架中用于可视化显示QUndoStack内容的控件,本文主要介绍了Qt中QUndoView控件的具体使用,具有一定的参考价值,感兴趣的... 目录引言一、QUndoView 的用途二、工作原理三、 如何与 QUnDOStack 配合使用四、自

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

Python虚拟环境终极(含PyCharm的使用教程)

《Python虚拟环境终极(含PyCharm的使用教程)》:本文主要介绍Python虚拟环境终极(含PyCharm的使用教程),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录一、为什么需要虚拟环境?二、虚拟环境创建方式对比三、命令行创建虚拟环境(venv)3.1 基础命令3

Python Transformer 库安装配置及使用方法

《PythonTransformer库安装配置及使用方法》HuggingFaceTransformers是自然语言处理(NLP)领域最流行的开源库之一,支持基于Transformer架构的预训练模... 目录python 中的 Transformer 库及使用方法一、库的概述二、安装与配置三、基础使用:Pi

关于pandas的read_csv方法使用解读

《关于pandas的read_csv方法使用解读》:本文主要介绍关于pandas的read_csv方法使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录pandas的read_csv方法解读read_csv中的参数基本参数通用解析参数空值处理相关参数时间处理相关