目录
Redis如何实现延时队列
延时队列的组成
生产消息
消费消息
实现细节
Redis集群
Integer.compare(a[1], b[1]))与a[1] - b[1])
设计模式编辑
算法
Redis如何实现延时队列
使用 sortedset ,拿时间戳作为 score ,消息内容作为 key 调用 zadd 来生产消息,消费者用
zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。
延时队列的组成
- Sorted Set:在Redis中,Sorted Set是一个集合,其中的每个元素都关联一个分数(score),这使得集合中的元素能够根据分数进行排序。
生产消息
- 选择Sorted Set:
-
- 选择一个或多个Sorted Set来作为延时队列的数据结构。
- 定义分数:
-
- 分数(score)通常设置为消息需要被处理的时间戳。例如,如果想要消息在5秒后被处理,那么分数就是当前时间戳加上5秒。
添加消息:
-
- 使用
ZADD
命令将消息添加到Sorted Set中。消息内容作为Sorted Set的成员(member),而消息需要被处理的时间戳作为分数。
- 使用
- shell复制
ZADD queue_name timestamp message
-
queue_name
是Sorted Set的键名。timestamp
是消息应该被处理的时间戳。message
是消息的内容。
消费消息
- 轮询检查:
-
- 消费者需要定期检查Sorted Set,以确定是否有消息已经到了处理时间。
获取可处理的消息:
-
- 使用
ZRANGEBYSCORE
命令获取所有分数小于或等于当前时间戳的成员。
- 使用
- shell复制
ZRANGEBYSCORE queue_name -inf $(current_timestamp)
-
-inf
表示分数的下界,这里表示从最小分数开始。$(current_timestamp)
是当前时间戳,表示分数的上界。
- 处理消息:
-
- 从
ZRANGEBYSCORE
返回的列表中,取出每个消息进行处理。
- 从
移除已处理的消息:
-
- 处理完消息后,使用
ZREM
命令从Sorted Set中移除该消息。
- 处理完消息后,使用
- shell复制
ZREM queue_name message
-
message
是已经处理完毕的消息内容。
实现细节
- 定时轮询:消费者可以使用定时任务(如cron job)或循环逻辑来定期执行
ZRANGEBYSCORE
命令。 - 多消费者:如果有多个消费者,可以使用分布式锁或其他同步机制来避免消息被重复处理。
- 性能考虑:
ZRANGEBYSCORE
和ZREM
命令在处理大量数据时可能会影响性能,因此需要合理设计消息的大小和处理频率。 - 消息持久化:Redis的Sorted Set支持持久化,可以确保即使Redis重启,消息也不会丢失。
Redis集群
Redis集群是一个提供在多个Redis节点之间进行数据分片(sharding)和复制的分布式系统。
Redis集群是一种分布式Redis实现,它可以将数据分布在多个Redis实例上,以提高Redis的性能和可扩展性。Redis集群通过分片(sharding)技术将数据分散存储在不同的Redis实例上,每个实例负责存储数据的一部分。Redis集群由多个主节点和从节点组成,主节点负责处理客户端的写入和读取请求,从节点则负责同步主节点的数据。
- 分片(Sharding):Redis集群将数据分片存储在不同的Redis实例上,每个实例存储数据的一部分。这样可以将数据分散到多个节点上,提高Redis的性能和可扩展性。
- 主从复制(Master-Slave Replication):Redis集群中的每个主节点都有一个或多个从节点。主节点负责处理客户端的写入和读取请求,并将数据同步到从节点。从节点则负责接收主节点的数据,并可以在主节点故障时提升为主节点。
- 故障转移(Failover):当主节点发生故障时,从节点可以提升为主节点,并继续处理客户端的请求。其他从节点会继续同步提升后的主节点。
- 自动分片和扩展:Redis集群可以自动进行分片和扩展,无需停机。当添加新节点时,Redis集群会自动将数据分片到新节点上,以提高性能和可扩展性。
- 高可用性(High Availability):Redis集群具有高可用性,即使某些节点发生故障,集群仍然可以继续处理客户端的请求。
- 一致性(Consistency):Redis集群提供了强一致性保证,所有写入操作都保证在主节点和从节点之间同步。
- 多线程和网络优化:Redis集群使用多线程和网络优化技术,以提高性能和响应速度。
在Redis集群中,Redis实例可以分为主节点(Master)和从节点(Slave)。
redis集群模式:
Redis集群模式主要包括以下几种:
- 主从复制(Master-Slave Replication):
-
- 在主从复制模式中,一个主节点(Master)负责处理写入和读取请求,并生成内存快照和命令日志。
- 多个从节点(Slaves)从主节点同步数据,并保持与主节点的数据一致性。
- 主从复制模式提高了Redis的可用性和故障转移能力,同时也支持读写分离,提高了读取性能。
- 哨兵(Sentinel):
-
- 哨兵是一个分布式系统,用于监控主从复制模式的Redis集群。
- 哨兵可以监控主节点的运行状态,并在主节点故障时选举出一个从节点作为新的主节点。
- 哨兵模式确保了Redis集群的高可用性,即使主节点发生故障,集群仍然可以继续处理客户端的请求。
- Redis Cluster:
-
- Redis Cluster是Redis官方提供的分布式Redis实现,支持自动分片和故障转移。
- 在Redis Cluster模式中,数据被分片存储在多个Redis实例上,每个实例负责存储数据的一部分。
- Redis Cluster支持自动扩展和负载均衡,可以动态地添加或移除节点,以满足不断增长的业务需求。
- Redis Cluster提供了强一致性保证,所有写入操作都保证在主节点和从节点之间同步。
- Redis-Cluster-Sentinel:
-
- Redis-Cluster-Sentinel结合了Redis Cluster和哨兵的功能。
- 在Redis-Cluster-Sentinel模式中,哨兵用于监控Redis Cluster的运行状态,并在主节点故障时选举出一个从节点作为新的主节点。
- Redis-Cluster-Sentinel模式确保了Redis Cluster的高可用性和数据一致性。
读写分离:
主从复制模式(Master-Slave Replication)通过将Redis实例分为主节点(Master)和从节点(Slave)来实现读写分离,从而提高系统的可用性和性能。
主从复制模式支持读写分离的原理:
- 主节点(Master):
-
- 负责处理写入请求,即客户端的写入操作(SET、DEL等)。
- 生成内存快照(RDB)和记录写入命令(AOF),用于从节点的同步。
- 主节点会定期将快照和增量命令发送给从节点。
- 从节点(Slave):
-
- 负责处理读取请求,即客户端的读取操作(GET、KEYS等)。
- 从节点不会处理写入请求,它们只会执行从主节点接收到的命令。
- 从节点会定期从主节点接收快照和增量命令,以保持与主节点的数据一致性。
通过这种模式,客户端可以将读取请求发送给从节点,将写入请求发送给主节点。由于从节点只负责处理读取请求,它们通常会部署在性能较低的节点上,而主节点则处理写入请求,并负责数据的完整性和一致性。
主从复制和哨兵模式可以一起使用,以实现更高级的可用性和故障转移能力。例如,一个Redis集群可以包含多个主节点和从节点,每个主节点都配备有哨兵,以确保在任何主节点故障时,集群都可以继续工作。
Redis使用哈希槽:
在Redis集群模式中,数据被分片存储在不同的Redis实例上,每个实例负责存储数据的一部分。为了实现这种分片存储,Redis集群使用了哈希槽(Hash Slot)的概念。
哈希槽是Redis集群中的一个抽象概念,用于将数据分布到不同的节点上。每个哈希槽负责存储一个哈希值范围内的所有数据。Redis集群总共有16384个哈希槽,这意味着每个哈希槽负责处理大约65536个不同的键(因为一个键的哈希值范围是0到65535)。
当一个键被添加到Redis集群时,首先需要计算这个键的CRC16哈希值。CRC16是一种循环冗余校验(CRC)算法,用于生成一个16位的校验和,这个校验和可以用来确定数据传输过程中是否有错误发生。
在Redis集群中,计算给定密钥的哈希槽的步骤如下:
- 对密钥计算CRC16哈希值。
- 将得到的16位哈希值对16384取模。
- 得到的模数就是该键所属的哈希槽的编号。
- 将键存储在编号为该模数的哈希槽对应的主节点上。
通过这种方式,Redis集群可以自动将数据分散存储到不同的节点上,每个节点负责管理一部分哈希槽,从而实现数据的分片存储和负载均衡。
Integer.compare(a[1], b[1]))与a[1] - b[1])
在Java中,Arrays.sort
方法可以接受一个比较器(Comparator),用于定义数组元素的排序规则。两种写法都是用来对数组进行排序的,但是它们在某些情况下的行为可能会有所不同。
Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]))
:
-
- 这里使用的是
Integer.compare
方法,它是一个静态方法,用于比较两个int
值。如果a[1]
小于b[1]
,它返回一个负数;如果a[1]
等于b[1]
,它返回 0;如果a[1]
大于b[1]
,它返回一个正数。这种写法是类型安全的,因为它明确地处理了int
值的比较,并且可以正确处理Integer.MIN_VALUE
的情况。
- 这里使用的是
Arrays.sort(points, (a, b) -> a[1] - b[1])
:
-
- 这里直接返回两个
int
值相减的结果。这种写法在大多数情况下工作正常,但是如果a[1]
和b[1]
的值接近Integer.MIN_VALUE
,相减的结果可能会超出int
的范围,导致Integer.MIN_VALUE
被错误地转换为一个正数,从而破坏排序。这是因为int
类型的减法可能会产生溢出,而Integer.compare
会避免这种情况。
- 这里直接返回两个
总结来说,Integer.compare
是一个更安全的选择,因为它可以正确处理所有 int
值的比较,包括边界情况,而直接相减可能会导致整数溢出的问题。在实际编程中,推荐使用 Integer.compare
来避免潜在的溢出问题。
设计模式
算法
2.小红书推荐系统
小红书有一个推荐系统,可以根据用户搜索的关键词推荐用户希望获取的内容。
现在给定小红的搜索记录(记录为分词后的结果),我们认为当一个单词出现的次数不少于3次时,该单词为“用户期望搜索的单词”,即称为关键词。请你根据小红的记录,输出小红的用户画像对应的所有关键词。
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 256M,其他语言512M
输入描述:
一行字符串,仅由小写字母和空格组成。代表小红的搜索记录。
字符串长度不超过100000。
输出描述:
小红所有的关键词。每行输入一个。你需要按照搜索频次从高到低输出。频次相同的,你需要按字典序升序输出。
示例1
输入例子:
kou red game red ok who game red karaoke yukari kou red red nani kou can koukou ongakugame game
输出例子:
red
game
kou
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);// 读取输入字符串String input = sc.nextLine();// 使用 HashMap 统计单词频率Map<String, Integer> frequency = new HashMap<>();String[] words = input.split(" ");// 统计每个单词的频率for (String word : words) {frequency.put(word, frequency.getOrDefault(word, 0) + 1);}// 筛选出频率大于等于3的关键词List<String> keywords = new ArrayList<>();for (Map.Entry<String, Integer> entry : frequency.entrySet()) {if (entry.getValue() >= 3) {keywords.add(entry.getKey());}}// 按照频率降序和字典序升序排序keywords.sort((a, b) -> {int freqCompare = frequency.get(b).compareTo(frequency.get(a));if (freqCompare != 0) {return freqCompare; // 按频率降序}return a.compareTo(b); // 按字典序升序});// 输出结果for (String keyword : keywords) {System.out.println(keyword);}sc.close();}
}
近日总结:
身体不舒服,好烦