Java和Redis实现一个简单的热搜功能

1. 前言

我们有一个简单的需求:

  • 搜索栏展示当前登陆的个人用户的搜索历史记录,删除个人历史记录。
  • 用户在搜索栏输入某字符,则将该字符记录下来 以zset格式存储的redis中,记录该字符被搜索的个数以及当前的时间戳 (用了DFA算法)。
  • 每当用户查询了已在redis存在了的字符时,则直接累加个数, 用来获取平台上最热查询的十条数据。(可以自己写接口或者直接在redis中添加一些预备好的关键词)。
  • 做不雅文字的过滤功能。

在这里插入图片描述

2. 实现

2.1 引入依赖

<dependencies>  <dependency>  <groupId>redis.clients</groupId>  <artifactId>jedis</artifactId>  <version>3.7.0</version> <!-- 使用你需要的版本 -->  </dependency>  
</dependencies>

2.2 实现代码

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.IntStream;public class HotSearch {private static final String REDIS_HOST = "localhost";private static final int REDIS_PORT = 6379;private static final String HISTORY_SET = "history";private static final String ZSET_PREFIX = "zset:";private static final int TOP_TEN = 10;private static final String BAD_WORDS = "bad"; // 替换为需要过滤的关键词  private static final String FILTERED_WORD = "***"; // 替换为过滤后的关键词  private static final int BAD_WORD_THRESHOLD = 100; // 替换为过滤的阈值,超过则认为是不雅文字  private static final List<String> BAD_WORD_LIST = IntStream.range(0, BAD_WORDS.length()).mapToObj(i -> BAD_WORDS.substring(i, i + 1)).collect(Collectors.toList()); // 将BAD_WORDS转为List,方便后续操作  public static void main(String[] args) {Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);String userId = "user1"; // 当前登陆的个人用户ID,需要根据实际情况获取  String searchWord = "test"; // 需要搜索的字符  hotSearch(jedis, userId, searchWord);}public static void hotSearch(Jedis jedis, String userId, String searchWord) {// 获取当前用户的搜索历史记录  Set<String> history = jedis.smembers(HISTORY_SET + ":" + userId);if (history == null) history = new HashSet<>();history.add(searchWord); // 将新搜索词加入历史记录  jedis.sadd(HISTORY_SET + ":" + userId, history); // 将历史记录存入redis中  history.remove(searchWord); // 去掉新搜索词,只保留旧的历史记录  // 将搜索词加入zset中,记录该字符被搜索的个数以及当前的时间戳   jedis.zadd(ZSET_PREFIX + userId, getScore(searchWord), searchWord);System.out.println("Added " + searchWord + " to hot search with score " + getScore(searchWord));// 过滤不雅文字,如果是不雅文字则替换为***,并累加不雅文字的搜索次数  if (BAD_WORD_LIST.contains(searchWord)) {if (jedis.zscore(ZSET_PREFIX + userId, FILTERED_WORD) == null) { // 如果该词在zset中不存在,则加入并设置得分  jedis.zadd(ZSET_PREFIX + userId, BAD_WORD_THRESHOLD, FILTERED_WORD); // 设置得分为BAD_WORD_THRESHOLD,表示这是一个不雅文字  jedis.incrBy(HISTORY_SET + ":bad", 1); // 累加不雅文字的搜索次数,存储在bad历史的集合中,方便后续统计和过滤处理  } else { // 如果该词在zset中已存在,则只累加搜索次数,并更新得分(得分+1)  jedis.zincrby(ZSET_PREFIX + userId, 1, FILTERED_WORD); // 得分为当前得分+1,表示这是一个不雅文字的再次搜索  jedis.incrBy(HISTORY_SET + ":bad", 1); // 累加不雅文字的搜索次数,存储在bad历史的集合中,方便后续统计和过滤处理  }System.out.println("The word " + searchWord + " is filtered and replaced with " + FILTERED_WORD); // 输出过滤后的结果  } else { // 如果不是不雅文字,则正常加入热搜列表并设置得分  jedis.zadd(ZSET_PREFIX + userId, getScore(searchWord), searchWord); // 正常加入热搜列表并设置得分  System.out.println("Added normal word " + searchWord + " to hot search with score " + getScore(searchWord)); // 输出正常加入热搜列表的结果}// 获取平台上最热搜索的十条数据  Set<Tuple> hotData = jedis.zrevrangeWithScores(ZSET_PREFIX + userId, 0, TOP_TEN - 1);List<String> hotWords = hotData.stream().map(Tuple::getElement).collect(Collectors.toList());List<Integer> hotScores = hotData.stream().map(Tuple::getScore).collect(Collectors.toList());System.out.println("Top " + TOP_TEN + " hot searches are: " + hotWords + " with scores: " + hotScores);}// 用于计算得分的方法,这里采用了最简单的得分方式,只考虑了搜索频率和时间戳,实际情况可能需要更复杂的算法 private static int getScore(String word) {return 1;}
}

2.3 实现原理

  1. 搜索历史记录
    • 我们使用Redis的set数据结构来存储用户的搜索历史。每个用户都有自己的历史记录集合,通过HISTORY_SET + ":" + userId来区分不同用户的搜索历史。
    • jedis.sadd方法用于添加新搜索词到历史记录集合中。
    • 删除操作没有直接在代码中体现,但可以通过jedis.srem方法从集合中移除某个元素来实现。
  2. 更新热搜列表
    • 我们使用Redis的有序集合(zset)来存储热搜数据。每个用户都有自己的有序集合,通过ZSET_PREFIX + userId来区分不同用户的热搜数据。
    • 每个搜索词都与一个得分相关联,该得分由函数getScore计算得出。新搜索词得分为1,旧搜索词得分为0。这个得分代表了搜索的频率和时间戳。
    • jedis.zadd方法用于向有序集合中添加新元素,并设置其得分。
  3. 获取平台上最热查询的十条数据
    • 我们使用jedis.zrevrangeWithScores方法获取有序集合中的前十个元素(得分最高的十个搜索词)。
    • 返回的结果是一个包含元素和得分的集合,我们通过流处理将其转换为列表。
  4. 不雅文字过滤
    • 这部分功能在代码中有直接实现,其原理是当用户输入搜索词时,系统会检查该词是否在预定义的BAD_WORDS列表中。
    • 如果在列表中,并且该词的搜索频率超过BAD_WORD_THRESHOLD,则认为这是一个不雅文字,将其替换为FILTERED_WORD
    • 注意:在实际应用中,可能需要更复杂的不雅文字过滤算法和策略,而不仅仅是基于频率的检查。

3. 注意事项

  1. 安全性
    • 确保Redis服务器的安全性。这包括使用强密码、配置防火墙规则、使用SSL连接等。不要将敏感数据暴露给不必要的用户或应用程序。
    • 在存储和传输用户搜索数据时,考虑到数据的机密性和隐私保护。根据当地的隐私法律和政策,可能需要采取额外的措施来保护用户数据。
  2. 性能监控和调优
    • 监控Redis的性能指标,如内存使用情况、连接数、查询速度等。根据实际负载情况,可能需要调整Redis的配置参数或增加硬件资源。
    • 定期检查代码的性能,确保在大量请求下能够保持稳定的性能。对于瓶颈部分,可能需要优化算法或调整数据结构。
  3. 异常处理
    • 添加适当的异常处理逻辑,以处理Redis连接失败、查询错误等情况。确保应用程序能够优雅地处理这些异常,并为用户提供适当的错误消息。
    • 对于可能出现的Redis故障或维护时段,考虑实现一种回退机制或通知系统,以便及时通知相关人员并采取措施。
  4. 数据一致性和备份
    • 确保Redis中的数据与应用程序中的其他数据源保持一致。在写入数据时,要确保幂等性以避免数据冲突。
    • 定期备份Redis中的数据,以防数据丢失。考虑使用快照或追加日志的方式来备份数据。
  5. 扩展性和高可用性
    • 如果应用程序需要处理大量的搜索请求,考虑使用Redis集群来分担负载和提高可用性。确保集群配置正确,并能够自动处理节点故障转移。
    • 在设计系统时,考虑到未来的扩展需求。使用可扩展的数据结构或算法,以便在需要时轻松地增加功能和优化性能。
  6. 日志和监控
    • 配置适当的日志记录系统,记录Redis的操作和关键事件。这有助于故障排查和性能分析。
    • 使用监控工具来实时跟踪Redis的性能指标和应用程序的健康状况。这样可以在问题发生时迅速采取行动。
  7. 测试和验证
    • 在将代码部署到生产环境之前,进行充分的测试和验证。确保代码的功能正确、性能良好,并且没有安全漏洞。
    • 考虑使用集成测试、单元测试和负载测试来评估代码的健壮性和稳定性。确保代码能够承受实际工作负载和各种边界条件。
  8. 代码维护和文档
    • 为代码添加适当的注释和文档,以帮助其他开发人员理解其工作原理和维护方式。这也有助于未来的代码审查和维护工作。
    • 保持代码的清洁和可维护性,遵循最佳实践和编码规范。定期重构代码以消除冗余和提高可读性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/640391.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt5.12.0 与 VS2017 在 .pro文件转.vcxproj文件

一、参考资料 stackoverflow qt - How to generate .sln/.vcproj using qmake - Stack Overflowhttps://stackoverflow.com/questions/2339832/how-to-generate-sln-vcproj-using-qmake?answertabtrending#tab-topqt - 如何使用 qmake 生成 .sln/.vcproj - IT工具网 (coder.wo…

《WebKit 技术内幕》学习之七(2): 渲染基础

2 网页层次和RenderLayer树 2.1 层次和RenderLayer对象 前面章节介绍了网页的层次结构&#xff0c;也就是说网页是可以分层的&#xff0c;这有两点原因&#xff0c;一是为了方便网页开发者开发网页并设置网页的层次&#xff0c;二是为了WebKit处理上的便利&#xff0c;也就是…

数据库复试—关系数据库标准语言SQL

数据库复试—关系数据库标准语言SQL SQL&#xff1a;结构化查询语言 以教材中的学生-课程数据库为例进行SQL基础语法的复习 数据库实验环境选择SQLServer 11 关系模式 学生表Student(Sno,Sname,Ssex,Sage,Sdept) 课程表Course(Cno,Cname,Cpno,Ccredit) 学生选课表SC&#xf…

【Java】学习一门开发语言,从TA的Hello World开始

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《Java》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握…

【Android12】Android Framework系列---Adb和PMS安装apk源码流程

Adb和PMS安装apk源码流程 adb install命令 通过adb install命令可以将apk安装到Android系统&#xff08;注意&#xff1a;特定类型的apk&#xff0c;比如persist类型是无法通过adb安装的&#xff09; 下述命令中adb解析install命令&#xff0c;并调用Android PackageManagerS…

eNSP学习——部分VLAN间互通、部分VLAN间隔离、VLAN内用户隔离(MUX-VLAN)

MUX VLAN&#xff08;Multiplex VLAN&#xff09;提供了一种通过VLAN进行网络资源控制 的机制。通过MUX VLAN提供的二层流量隔离的机制可以实现企业内部员 工之间互相通信&#xff0c;而企业外来访客之间的互访是隔离的。 特点&#xff1a; 一、主VLAN端口可以和所有VLAN通信 二…

基本控件(一)

目录 控件的定义&#xff1a;使用控件的好处&#xff1a; setupUiwidget.hwidget.cppmain.cpp 控件的定义&#xff1a; QT控件是在QT框架下实现的一组用户界面元素&#xff0c;包括按钮、标签、输入框、列表框、滚动条、菜单等等&#xff0c;它们用于快速构建各种应用程序的图…

【Linux】gcc与g++的认识

gcc与g是linux下的编译器&#xff0c; 我们都知道写好了源文件gcc一下就可以得到.exe文件 那么其中会经过几个过程呢&#xff1f;也就是源文件翻译过程&#xff0c; 预处理&#xff08;预编译&#xff09;编译汇编链接 本篇文章的侧重点是围绕链接进行理解C语言标准库 目录 …

Java项目:ssm框架基于spring+springmvc+mybatis框架的民宿预订管理系统设计与实现(ssm+B/S架构+源码+数据库+毕业论文)

一、项目简介 本项目是一套ssm827基于SSM框架的民宿预订管理系统设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调…

POI及EasyExcel学习笔记

POI及EasyExcel学习笔记 组件、工具 POI-Excel概述 Apache POI 结构&#xff1a; HSSF &#xff0d; 提供读写[Microsoft Excel](https://baike.baidu.com/item/Microsoft Excel)格式档案的功能。XSSF &#xff0d; 提供读写Microsoft Excel OOXML格式档案的功能。HWPF &am…

93. 复原 IP 地址 - 力扣(LeetCode)

题目描述 有效 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 ‘.’ 分隔。 例如&#xff1a;“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址&#xff0c;但是 “0.011.255.245”、“…

matlab appdesigner系列-常用16-状态按钮

状态按钮&#xff0c;有两个状态&#xff0c;按下状态&#xff0c;返回值为1或true&#xff1b;未按下状态&#xff0c;返回值为0或false 示例&#xff1a;设置一状态按钮&#xff0c;用文本记录其状态 操作步骤&#xff1a; 1&#xff09;将状态按钮、文本区域拖拽到画布上…

LeetCode 热题 100 | 哈希

目录 1 基础知识 1.1 定义哈希表 1.2 遍历哈希表 1.3 查找某一个键 1.4 插入键值对 1.5 获取键值对的值 1.6 搜索功能 2 三道题 2.1 1. 两数之和 2.2 49. 字母异位词分组 2.3 128. 最长连续序列 菜鸟做题第一周&#xff0c;语言是 C 1 基础知识 1.1 定…

对网站进行打点(不要有主动扫描行为)

什么是打点&#xff1f; 简单来说就是获取一个演习方服务器的控制权限。 目的&#xff1a; 1. 上传一个一句话木马 2. 挖到命令执行 3. 挖到反序列化漏洞 4. 钓鱼 假设对“千峰”网站进行打点&#xff1a; 1. 利用平台 1. 利用各类平台&#xff1a; 天眼查-商业查询平…

Unity SnapScrollRect 滚动 匹配 列表 整页

展示效果 原理: 当停止滑动时 判断Contet的horizontalNormalizedPosition 与子Item的缓存值 相减,并得到最小值&#xff0c;然后将Content horizontalNormalizedPosition滚动过去 使用方式&#xff1a; 直接将脚本挂到ScrollRect上 注意&#xff1a;在创建Content子物体时…

Mysql运维篇(二) 主从复制

一路走来,所有遇到的人,帮助过我的、伤害过我的都是朋友,没有一个是敌人,如有侵权请留言,我及时删除。 一、主从复制的原理 主库会生成一个I/O操作线程进去写的的操作,而从库则生成两个线程,其一是I/O读取线程,其二是一个SQL线程。 1、主库将数据的操作记录到一个二进…

C++的流库

1.流的概念 “流”&#xff0c;即“流动”的意思&#xff0c;是物质从一处向另一处流动的过程。在计算机这边通常是指对一种有序连续且具有方向性的数据的抽象描述。 C 中的流一般指两个过程的统一&#xff1a; 信息从外部输入设备&#xff08;键盘&#xff09;向计算机内部…

【数据结构与算法】之字符串系列-20240121

这里写目录标题 一、344. 反转字符串二、125. 验证回文串三、205. 同构字符串四、242. 有效的字母异位词五、290. 单词规律 一、344. 反转字符串 简单 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额…

k8s集群加入一个master2--kubeadm方式

已经有一个集群&#xff1a; 192.168.206.138 master 192.168.206.136 k8s-node1 192.168.206.137 k8s-node2 kubectl get nodes -o wide 新加入一个master2节点 192.168.206.139 master2 一、初始化系统参数 139 master2 上 #在136、137、138上添加hosts“” echo "…

贪心算法笔记

贪心 1.序列问题1.1摆动序列1.2单调递增的数字 2.多维度权衡2.1分发糖果2.2根据身高重构队列 3.区间问题3.1跳跃问题3.2跳跃问题 II3.3用最小数量的箭引爆气球3.4无重叠区间3.5划分字母区间3.6合并区间 4.其他4.1最大子数组和4.2加油站4.3监控二叉树 正如这个算法的名字一样&am…