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

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]