当前位置: 首页 > article >正文

Redis:Feed流、ZSet点赞排序+滚动分页+滑动窗口限流

目录一、ZSet点赞模块1. 点赞功能实现2. 按照点赞时间将点赞人排序3.定时任务更新点赞量4.总结二、Feed流1.Feed流实现方案1.1 拉模式读扩散1.2 推模式写扩散1.3 读写混合2.推模式实现将用户发布的动态推送给粉丝3.SortedSet滚动分页查询4.总结三、ZSet滑动窗口限流一、ZSet点赞模块1. 点赞功能实现点赞功能也要同时写数据库和写缓存但写的内容不一致所以不属于双写一致性问题。TransactionalpublicResultlikeBlog(Longid){StringuserIdUserHolder.getUser().getId().toString();// 用户是否点赞了DoublescorestringRedisTemplate.opsForZSet().score(like:id,userId);if(scorenull){// 这里直接用null来判断返回值null或空对象区别在于是否分配内存地址String\List\Map判断空对象就要用isEmpty()了stringRedisTemplate.opsForZSet().add(like:id,userId,System.currentTimeMillis());// 用当前ms时间作为排序值update().setSql(liked liked 1).eq(id,id).update();}else{stringRedisTemplate.opsForZSet().remove(like:id,userId);update().setSql(liked liked - 1).eq(id,id).update();}returnResult.ok();}2. 按照点赞时间将点赞人排序publicResultqueryBlogLikes(Longid){// 点赞顺序前5的用户id集合SetStringuserIdsstringRedisTemplate.opsForZSet().range(like:id,0,4);if(userIdsnull||userIds.isEmpty()){// 没有点赞信息returnResult.ok(Collections.emptyList());}/** 查询用户 select * from tb_user where id in (#{userIds}) order by field (id, #{userIds}) * order by field (id, #{userIds}) 是因为in查询的返回结果是按照用户id排序的而不是按照userIds排序导致返回给前端的用户顺序错乱 * userIds.stream().map(Long::valueOf).collect(Collectors.toList())是将String类型的List转为Long类型的List * StrUtil.join(,,userIds)是将userIds拼接成字符串以逗号分隔 */ListUserusersuserService.query().in(id,userIds.stream().map(Long::valueOf).collect(Collectors.toList())).last(ORDER BY FIELD(id,StrUtil.join(,,userIds))).list();ListUserDTOuserDTOSnewArrayList();//为了防止用户信息泄露所以只返回给前端用户基础信息users.forEach(user-{UserDTOuserDTOnewUserDTO();BeanUtil.copyProperties(user,userDTO,true);userDTOS.add(userDTO);});returnResult.ok(userDTOS);}3.定时任务更新点赞量ComponentpublicclassLikeReconciliationTask{AutowiredprivateStringRedisTemplate stringRedisTemplate;AutowiredprivateBlogMapper blogMapper;AutowiredprivateLikeScheduledTaskManager taskManager;// 自定义定时任务线程池privateScheduledExecutorService scheduledExecutorService;PostConstruct// 项目启动时执行publicvoidstartReconciliationTask(){// 实例化任务线程池scheduledExecutorServicenewScheduledThreadPoolExecutor(3,Executors.defaultThreadFactory(),newThreadPoolExecutor.AbortPolicy());// 步骤1计算从当前时间到「凌晨2点」的初始延迟longinitialDelaycalculateDelayTo2AM();// 步骤2每天24小时执行一次对账任务taskManager.getScheduledExecutorService().scheduleAtFixedRate(this::doReconciliation,initialDelay,24,TimeUnit.HOURS);}// 计算当前时间到次日凌晨2点的延迟单位秒privatelongcalculateDelayTo2AM(){longnowSystem.currentTimeMillis();// 今天凌晨2点的时间戳longtoday2AMgetToday2AMTimestamp();// 如果当前时间已过凌晨2点取次日2点if(nowtoday2AM){today2AM24*60*60*1000L;}// 转换为秒ScheduledExecutorService 支持毫秒这里也可以直接返回毫秒return(today2AM-now)/1000;}// 获取今天凌晨2点的时间戳privatelonggetToday2AMTimestamp(){java.util.Calendar caljava.util.Calendar.getInstance();cal.set(java.util.Calendar.HOUR_OF_DAY,2);cal.set(java.util.Calendar.MINUTE,0);cal.set(java.util.Calendar.SECOND,0);cal.set(java.util.Calendar.MILLISECOND,0);returncal.getTimeInMillis();}// 核心对账逻辑privatevoiddoReconciliation(){ListLongblogIdsblogMapper.listAllBlogIds();for(Long blogId:blogIds){try{String likeKeylike:blogId;Long redisLikeCountstringRedisTemplate.opsForZSet().zCard(likeKey);if(redisLikeCountnull){redisLikeCount0L;}Blog blogblogMapper.selectById(blogId);Integer dbLikeCountblog.getLiked()null?0:blog.getLiked();if(!redisLikeCount.equals(dbLikeCount.longValue())){log.warn(博客{}点赞数不一致Redis{}数据库{}开始修正,blogId,redisLikeCount,dbLikeCount);blogMapper.update().set(liked,redisLikeCount).eq(id,blogId).update();}}catch(Exception e){log.error(对账博客{}点赞数失败,blogId,e);}}}}4.总结点赞功能SortedSet进行点赞以及点赞排序点赞结果先更新redis后更新数据库并采用scheduledExecutorService凌晨两点遍历数据库的笔记表并查询redis对应记录不一致则更新数据库。二、Feed流Feed流也叫关注推送为用户持续推送消息的一种方式。Feed流常见有两种实现模式Timeline不做内容筛选推送内容按照内容发布时间排序例如微信朋友圈。智能排序利用推荐算法推送用户感兴趣的内容例如抖音。1.Feed流实现方案在redis中可以为用户创建收件箱和发件箱收件箱用来接收消息推送给用户的消息保存在用户的收件箱中。发件箱用户自己的消息保存在该用户的发件箱中用于发送消息。1.1 拉模式读扩散每个用户只需维护发件箱用户A发消息时服务器都会将该消息保存到用户A的发件箱中。用户B想要查看消息时会从用户A的发件箱中获取消息。优点每条消息只存一份节省redis内存空间缺点读消息要查发件箱延迟高1.2 推模式写扩散每个用户只需维护收件箱用户A发消息时服务器都会将该消息推送到用户B的收件箱中。用户B想要查看消息时只需要从自己的收件箱中获取消息。优点读消息速度快延迟低缺点每条消息保存n份非常占用redis内存空间1.3 读写混合读写混合结合了推模式和拉模式的优点。每个用户需维护发件箱和收件箱。用户A发消息时若接收该消息的用户太多好友太多或粉丝太多那么服务器都会将该消息保存到用户A的发件箱中。更进一步设计若用户A是用户B的特别关注那么将消息保存到用户B的收件箱中。若用户C是用户A的僵尸粉或用户C不经常查看消息那么将消息保存到用户A的发件箱中。若接收该消息的用户很少那么服务器都会将该消息保存到其他用户的收件箱中。用户B想要查看消息时先查看自己的收件箱再查看其他用户的发件箱。具体可以灵活设计例如如果用户是活跃用户那么可以把消息推到他的收件箱中反之不经常刷抖音的用户没必要给他开辟收件箱他想刷视频那么就去其他用户的发件箱中获取视频。新用户的话不能亏待他就用推模式等用久了再杀熟。此外如果用户经常发高质量视频那么完全可以用推模式直接将通知推送到其他用户的手机中反之低质量视频就不用推拉模式节省内存就够了。2.推模式实现将用户发布的动态推送给粉丝Override// 发布动态publicResultsaveBlog(Blogblog){// 获取登录用户UserDTOuserUserHolder.getUser();blog.setUserId(user.getId());// 保存动态save(blog);// 获取粉丝ListFollowfollowsfollowService.query().eq(follow_user_id,user.getId()).list();if(follows!null!follows.isEmpty()){// 获取粉丝idfollows.forEach(follow-{// 推送当前动态的id到所有粉丝的redis收件箱中stringRedisTemplate.opsForZSet().add(receive:follow.getUserId(),blog.getId().toString(),System.currentTimeMillis());});}// 返回idreturnResult.ok(blog.getId());}3.SortedSet滚动分页查询由于SortedSet是有序的当按照插入时间排序时当数据有序时若按照下标分页会出现如下问题t1时刻读取时间最新的5条数据则第一页数据为6~10。t2时刻插入了一条数据11此时t3时刻会读取第二页数据正常应该读取1~5但是由于中途插入数据导致下标发生变化导致数据下标混乱本质会重复的读数据6。如下图所示滚动分页的思想是记录t1时刻读的最后一条数据6t2时刻从6号数据的下一个数据即5号数据开始读。如何记录每次查询的最后一条数据可以按照元素进入集合的先后顺序给每个元素编号元素的编号给定后就固定不变每次记录读的最后一个数据的编号x而非下标然后给定偏移量offset为与x编号相同的元素个数那么下一次从x-offset开始读。Override// lastId:上一页的最小时间戳也就是这一页的最大时间戳开区间;offset:偏移量publicResultofFollow(LonglastId,Integeroffset){// 按照score范围查询查score范围在[0,lastId)即[0,lastId-1]中score最大的3条数据SetZSetOperations.TypedTupleStringtypedTuplesstringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(receive:UserHolder.getUser().getId().toString(),0,lastId,offset,3);if(typedTuplesnull||typedTuples.isEmpty()){returnResult.ok();//关注的用户没有发布动态}// 获取所有动态的blogId以及该页的最小时间戳lastId偏移量offset// blogId用于页面显示lastId和offset作为下一页取数据的依据ListLongblogIdsnewArrayList(typedTuples.size());longlastIdNextPage0;intoffsetNextPage1;for(ZSetOperations.TypedTupleStringtuple:typedTuples){// blogIdblogIds.add(Long.valueOf(tuple.getValue()));// offsetNextPage为与lastIdNextPage相同的元素个数其中lastIdNextPage为该页的最小编号时间其实重复的可能性很小longtimetuple.getScore().longValue();if(timelastIdNextPage){offsetNextPage;}else{offsetNextPage1;}// 记录该页的最小编号时间其实重复的可能性很小lastIdNextPagetime;}// 根据blogIds查对应的动态信息ListBlogblogsquery().in(id,blogIds).last(ORDER BY FIELD(id,StrUtil.join(,,blogIds))).list();returnResult.ok(newScrollResult(blogs,lastIdNextPage,offsetNextPage));}其实可以简单抽象为一个算法问题给定一个包含数字1-10的数组数组元素有序排列从数组末尾向前遍历规定每一轮输出3个元素不同轮次之间输出的元素不能有重复轮次内的元素可以重复输出各轮次的元素。4.总结粉丝推送功能Set记录粉丝集合foreach List将笔记ID保存到粉丝的SortedSet中。粉丝点击关注列表时直接查询SortedSet中的笔记并返回。三、ZSet滑动窗口限流以QPS100为例每个请求到达时首先记录当前时间t统计以t为结尾的前1s的请求数t, zremrangebyscore key, t-1000, t删除1s外的请求得到当前窗口的请求数zcard key。zremrangebyscore时间复杂度O(mlog n)二分/skiplist查找窗口边界删除边界外的m个元素如果前1s的请求数小于100那么放行并zadd key t trandom将当前请求加入集合否则直接return拒绝当前请求的后续业务。上述流程能确保每个请求都是以自身t构造滑动窗口判断是否限流而非静态窗口。为了避免并发问题要使用lua脚本-- 限流核心参数QPS阈值100、时间窗口大小1000ms1秒locallimit100localwindow1000-- 1. 获取当前时间戳毫秒级保证精度localnowtonumber(redis.call(TIME)[1])*1000tonumber(redis.call(TIME)[2])/1000-- 2. 清理窗口外的过期请求删除 [当前时间-1000ms, 当前时间] 之外的请求-- KEYS[1] 限流的Redis Key如rate_limit:apiredis.call(ZREMRANGEBYSCORE,KEYS[1],0,now-window)-- 3. 统计当前1秒窗口内的请求总数localcounttonumber(redis.call(ZCARD,KEYS[1]))-- 4. 限流判断未超过阈值则放行超过则拒绝ifcountlimitthen-- 放行将当前请求加入有序集合score当前时间戳value时间戳随机数避免重复-- 随机数防止同一毫秒多个请求value重复被覆盖localrandommath.random(1,1000)redis.call(ZADD,KEYS[1],now,now.._..random)-- 设置过期时间自动清理闲置key节省内存2秒足够覆盖窗口redis.call(EXPIRE,KEYS[1],2)-- 返回1代表请求放行return1else-- 返回0代表请求被限流拒绝return0end

相关文章:

Redis:Feed流、ZSet点赞排序+滚动分页+滑动窗口限流

目录一、ZSet点赞模块:1. 点赞功能实现:2. 按照点赞时间将点赞人排序:3.定时任务更新点赞量:4.总结:二、Feed流:1.Feed流实现方案:1.1 拉模式(读扩散):1.2 推…...

昇腾NPU小模型推理性能调优实战:从1.5s到0.7s的优化之路

本文目录: 一、问题背景二、调优全流程1.初步问题定位2.采集Profiling数据采集方法 3.用MindStudio分析数据4.根因分析5.针对性优化方案5.1换框架5.2PyTorch原地优化 三、优化效果四、经验总结工具推荐 一、问题背景 最近做了个模型迁移的项目,遇到了个…...

5个关键步骤彻底掌握Dynamic-Datasource组序列验证终极指南

5个关键步骤彻底掌握Dynamic-Datasource组序列验证终极指南 【免费下载链接】dynamic-datasource dynamic datasource for springboot 多数据源 动态数据源 主从分离 读写分离 分布式事务 项目地址: https://gitcode.com/gh_mirrors/dy/dynamic-datasource Dynamic-Dat…...

FireRed-OCR Studio参数详解:layout-aware attention对齐精度提升方法

FireRed-OCR Studio参数详解:layout-aware attention对齐精度提升方法 1. 引言 如果你用过传统的OCR工具,可能会遇到这样的烦恼:识别出来的文字虽然都对,但表格结构全乱了,标题和正文混在一起,数学公式变…...

STEP3-VL-10B环境配置:CUDA 12.4+PyTorch 2.3+FlashAttention-2适配指南

STEP3-VL-10B环境配置:CUDA 12.4PyTorch 2.3FlashAttention-2适配指南 1. 引言:为什么需要这份配置指南? 如果你最近关注多模态大模型,一定听说过STEP3-VL-10B这个名字。这个由阶跃星辰开源的10B参数模型,在多个评测…...

QWEN-AUDIO开源大模型部署:企业私有化语音合成平台建设指南

QWEN-AUDIO开源大模型部署:企业私有化语音合成平台建设指南 1. 项目概述与核心价值 QWEN-AUDIO是基于通义千问Qwen3-Audio架构构建的新一代智能语音合成系统,专为企业级私有化部署设计。这个系统不仅能生成高质量语音,还能通过情感指令微调…...

人脸识别OOD模型完整指南:支持考勤、门禁、1:1核验的生产级部署

人脸识别OOD模型完整指南:支持考勤、门禁、1:1核验的生产级部署 1. 引言:为什么你需要一个“聪明”的人脸识别系统? 想象一下这个场景:公司前台安装了一套人脸识别考勤机。员工小王早上匆匆赶来,戴着口罩、头发凌乱&…...

Qwen3.5-27B多图理解实战:电商主图+详情图联合分析生成营销文案

Qwen3.5-27B多图理解实战:电商主图详情图联合分析生成营销文案 你是不是也遇到过这样的烦恼?做电商运营,每天要面对几十上百个商品,每个商品都得写营销文案。主图要突出卖点,详情图要讲清楚细节,光是看图片…...

MusePublic镜像免配置实战:Docker一键拉起艺术创作WebUI

MusePublic镜像免配置实战:Docker一键拉起艺术创作WebUI 艺术创作从未如此简单——无需复杂配置,不用研究命令行,Docker一键部署,浏览器直接创作专业级艺术人像 1. 项目简介:专为艺术人像而生的智能创作引擎 MusePubl…...

mPLUG视觉问答保姆级教程:Mac M1/M2芯片本地部署与Metal加速适配

mPLUG视觉问答保姆级教程:Mac M1/M2芯片本地部署与Metal加速适配 1. 项目简介 今天给大家带来一个超级实用的本地视觉问答工具——基于mPLUG模型的视觉问答系统。这个工具可以让你在本地电脑上实现图片理解和问答功能,完全不需要联网,保护隐…...

yz-bijini-cosplay开源可部署:纯本地运行无网络依赖的Cosplay生成方案

yz-bijini-cosplay开源可部署:纯本地运行无网络依赖的Cosplay生成方案 1. 项目概述 yz-bijini-cosplay是一个专为RTX 4090显卡优化的Cosplay风格文生图解决方案,基于通义千问Z-Image底座和专属LoRA权重,提供完全本地化的高质量图像生成体验…...

[特殊字符] mPLUG-Owl3-2B多模态交互工具:从安装到多轮视觉问答的完整实操手册

🦉 mPLUG-Owl3-2B多模态交互工具:从安装到多轮视觉问答的完整实操手册 1. 工具简介:你的本地图文对话助手 今天给大家介绍一个特别实用的工具——mPLUG-Owl3-2B多模态交互工具。简单来说,这是一个能看懂图片并回答问题的本地AI助…...

乙巳马年春联生成终端开源模型:spring_couplet_generation调用详解

乙巳马年春联生成终端开源模型:spring_couplet_generation调用详解 1. 引言:当AI遇见传统年味 春节贴春联,是刻在我们文化基因里的仪式感。但你想过吗?如果让AI来写春联,会是什么体验?不是那种生硬的拼凑…...

JavaWeb(后端实战)

登录功能: 需求: 在登录界面中输入用户的用户名以及密码,点击 "登录" 按钮请求服务器,服务端判断用户输入的用户名或者密码是否正确,如果正确,则返回成功结果,前端跳转至系统首页面…...

深入解析:DisplayLink 是如何把“视频”变成 USB 数据再还原成显示信号的?

前言 DisplayLink 技术近年来成为突破设备原生视频输出限制的重要方案。它依靠软件驱动配合硬件芯片,在 USB通信通道中实现对视频信号的传输和解码,从而让原本无法多屏输出的电脑也能实现更多显示器扩展。本文将从技术层面深入解析 DisplayLink的工作原理…...

Leather Dress Collection惊艳效果:Leather Bodycon Dress紧身剪裁与身体曲线贴合度

Leather Dress Collection惊艳效果:Leather Bodycon Dress紧身剪裁与身体曲线贴合度 1. 引言:当皮革遇见AI,时尚设计的新可能 想象一下,你是一位服装设计师,正在构思下一季的皮革系列。传统的设计流程需要画草图、打…...

Git-RSCLIP生产环境部署:CSDN GPU云实例+Supervisor服务稳定性保障

Git-RSCLIP生产环境部署:CSDN GPU云实例Supervisor服务稳定性保障 1. 引言:从模型到稳定服务 想象一下,你手头有成千上万张遥感图像——卫星拍摄的城市、农田、森林、河流。现在,你需要快速找出所有包含“机场”的图像&#xff…...

all-MiniLM-L6-v2部署教程:WSL2+Ollama+Windows前端三端协同方案

all-MiniLM-L6-v2部署教程:WSL2OllamaWindows前端三端协同方案 你是不是也遇到过这样的问题:想快速搭建一个轻量级语义搜索服务,但又不想折腾复杂的Python环境、PyTorch依赖和GPU驱动?或者手头只有一台普通笔记本,却希…...

Phi-4-reasoning-vision-15B企业应用:ERP系统界面截图→业务流程反向建模

Phi-4-reasoning-vision-15B企业应用:ERP系统界面截图→业务流程反向建模 1. 引言:从截图到流程,企业效率的新解法 想象一下这个场景:你刚接手一个老旧的ERP系统,文档缺失,代码复杂,没人能说清…...

Nano-Banana参数详解:Euler Ancestral调度器为何更适配分解任务

Nano-Banana参数详解:Euler Ancestral调度器为何更适配分解任务 1. 理解Nano-Banana的核心任务 Nano-Banana Studio是一款专门用于生成产品结构拆解图的AI工具,它的核心任务是将复杂的物体分解成各个组件,并以美观的平铺或爆炸视图呈现。这…...

造相-Z-Image创意工作流:中英混合提示词驱动的写实风格内容创作体系

造相-Z-Image创意工作流:中英混合提示词驱动的写实风格内容创作体系 1. 项目概述 造相-Z-Image是一款基于通义千问官方Z-Image模型的本地轻量化文生图系统,专门为RTX 4090显卡深度优化设计。这个系统主打BF16高精度推理、显存极致防爆、本地无网络依赖…...

JavaEE进阶2.0

目录 一、 spring core 1.0 Ioc简介 (1)Ioc简介 (2)Ioc的引入 (3)spring IoC和DI 2.0 详解Ioc (1)Bean简介 (2)Bean name规则 (3)三种不同语义的Bean获取方式 (4)注解 3.0 DI (1)DI简介 (2)依赖注入的方式 (3)Autowired存在的问题 (4)Ioc和DI总结 4.0 常见面试题…...

Qwen3-TTS语音合成实战:为无障碍阅读设备提供多语种TTS支持

Qwen3-TTS语音合成实战:为无障碍阅读设备提供多语种TTS支持 技术前沿:Qwen3-TTS-12Hz-1.7B-CustomVoice 是一款革命性的语音合成模型,专为全球化应用场景设计,特别适合无障碍阅读设备的多语言语音支持需求。 1. 为什么无障碍阅读需…...

Stable Yogi Leather-Dress-Collection惊艳案例:皮衣金属拉链+哑光皮革+高光反射三重质感

Stable Yogi Leather-Dress-Collection惊艳案例:皮衣金属拉链哑光皮革高光反射三重质感 想象一下,一件皮衣在动漫世界里能有多酷?是金属拉链的冰冷光泽,哑光皮革的细腻纹理,还是皮革表面恰到好处的高光反射&#xff1…...

Qwen3-32B私有化部署效果展示:Clawdbot中支持正则提取与结构化清洗

Qwen3-32B私有化部署效果展示:Clawdbot中支持正则提取与结构化清洗 内容安全声明:本文仅讨论技术实现方案与应用效果展示,所有内容均基于公开技术文档与测试数据,不涉及任何敏感信息与特殊网络配置。 1. 项目概述:智能…...

Qwen3-4B-Thinking在教育场景的应用:AI助教自动生成编程习题解析与思路引导

Qwen3-4B-Thinking在教育场景的应用:AI助教自动生成编程习题解析与思路引导 1. 引言:当编程教学遇上会“思考”的AI 想象一下这个场景:深夜,一个编程初学者面对一道复杂的算法题,抓耳挠腮,毫无头绪。传统…...

Qwen2.5-1.5B开发者实操手册:基于官方Instruct版本的本地对话服务构建

Qwen2.5-1.5B开发者实操手册:基于官方Instruct版本的本地对话服务构建 1. 项目概述 想要在本地电脑上搭建一个完全私有的智能对话助手吗?今天介绍的方案基于阿里通义千问官方的Qwen2.5-1.5B-Instruct轻量级模型,让你无需复杂配置就能拥有一…...

Fish Speech 1.5开源TTS部署:Kubernetes编排+HPA自动扩缩容

Fish Speech 1.5开源TTS部署:Kubernetes编排HPA自动扩缩容 1. 项目概述与核心价值 Fish Speech 1.5 是一个基于VQ-GAN和Llama架构的先进文本转语音模型,经过超过100万小时的多语言音频数据训练。这个开源TTS系统不仅支持高质量的多语言语音合成&#x…...

SPIRAN ART SUMMONER参数详解:CFG/步数/LoRA权重在幻光UI中的实战意义

SPIRAN ART SUMMONER参数详解:CFG/步数/LoRA权重在幻光UI中的实战意义 你是否曾面对AI绘画工具里一堆陌生的参数感到迷茫?CFG、步数、LoRA权重……这些听起来像工程师黑话的选项,到底该怎么调?调了又有什么用? 在SPI…...

Qwen3-0.6B-FP8惊艳案例:用Chainlit构建可交互式Linux命令学习助手(带执行沙盒)

Qwen3-0.6B-FP8惊艳案例:用Chainlit构建可交互式Linux命令学习助手(带执行沙盒) 1. 引言:当AI助手能“动手”执行命令 想象一下,你正在学习Linux,面对黑漆漆的命令行,敲下ls、cd、grep这些命令…...