文章目录
- 数据结构
- 面试
-
- Redis中模糊搜索
-
- [1. KEYS 命令](#1. KEYS 命令)
- [2. SCAN 命令](#2. SCAN 命令)
- [3. ~~SORT 命令 和 Alpha 修改符~~](#3.
SORT 命令 和 Alpha 修改符) - [~~4. 自定义数据结构和算法~~](#
4. 自定义数据结构和算法) - [~~5. Redis Modules~~](#
5. Redis Modules) - [6. 利用 Lua 脚本](#6. 利用 Lua 脚本)
- [~~7. 利用集合和哈希的特性~~](#
7. 利用集合和哈希的特性) - 总结:
- 总结:
简介
提示:理论+方法+小总结
Redis基本介绍:
Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能键值存储的内存数据库 。这里要区分开MySQL:
它支持多种基础数据结构
1、字符串(Strings)
2、哈希(Hashes)
3、列表(Lists)
4、集合(Sets
5、有序集合(Sorted Sets)
以及更高级的数据结构如
1、位图(Bitmaps)
2、超日志(Hyperloglogs)
4、地理空间索引(Geo indexes)
性能:
作为内存数据库,Redis的读写性能极高,能够支持每秒万级 甚至百万级 的读写操作,非常适合需要快速读写操作的场景。
持久性和复制:
虽然Redis是基于内存的,但它支持数据的持久化。可以通过RDB(快照)或AOF(append-only file)方式将内存中的数据定期或实时保存到磁盘上,确保数据安全。此外,Redis支持主从复制功能,提高数据的可用性和冗余。
补充------重点:
抛开Redis在java项目开发中:不论是elasticsearch还是Rocketmq等等诸多中间件和框架只要是涉及到会存储的那么都是如下的思想:
1、定时
2、定量
这两种在和:同步、异步进行组合:
最终结果为:
定时-同步刷盘
定时-异步刷盘
定量-同步刷盘
定量-异步刷盘
从性能角度来讲:定量-异步刷盘性能最高,从数据安全性角度来讲:定时-同步刷盘安全性最高性能最低。
Redis额外支持的操作:
Redis不但支持基本的键值操作,还提供复杂的数据结构操作,如事务、管道(pipeline)、Lua脚本、发布/订阅模式等
使用场景:
Redis由于其高性能的特性,通常被用于以下场景:
1、缓存系统 :减少数据库负载,提供快速响应;
2、会话存储 :如Web应用中的用户会话管理;
3、消息队列 :因其支持发布订阅模式(pub/sub);
4、实时分析 :如计算在线用户数、排行榜等;
5、地理空间数据:支持地理空间索引和范围查询。
与Java的集成:
在Java应用中,可以通过使用Jedis、Redisson、redisTemplate、Lettuce等客户端库来和Redis进行交互。这些库提供API来执行Redis命令,处理连接池,以及实现复杂的功能如分布式锁等。
但是通常建议使用Redisson ,这里提供的功能更多,如看门狗机制
Redis集群
Redis拥有多种集群解决方案,以提供高可用性和水平扩展功能。以下是几种常见的Redis集群配置和它们的特点:
Redis Sentinel
描述:主从复制的模式增加了Redis Sentinel系统以实现高可用性。Sentinel负责监控所有Redis节点,并在主节点故障时自动进行故障转移(Process Failover)。
优点:
1、故障发现和自动故障转移。
2、可以继续使用单个Redis实例,无需修改客户端代码。
缺点:
不提供自动分片,因此不能直接用于数据水平扩展;由于使用异步复制,仍有数据丢失的风险。
适用场景:
要求高可用但数据量不是特别巨大的场景。
Redis Cluster
描述:一个分布式数据库系统,通过自动将数据分片到多个Redis实例中来提供水平扩展和高可用性。
优点:
1、自动分片,可水平扩展;
2、支持主从复制,并提供故障转移功能;
3、无单点故障(No single point of failure,NSPF)。
缺点:
1、客户端需要支持Redis Cluster协议;
2、分片机制可能会限制某些复杂操作,例如,有些事务和键操作要求数据位于同一节点上。
适用场景:
大规模数据集和高吞吐量要求场景。
除了以上两种官方支持的解决方案外,还有一些社区或云服务提供商实现的Redis高可用和分布式解决方案:
Codis
描述:基于代理的Redis分片解决方案,通过增加一个代理层来实现Redis的水平扩展。
优点:
1、对客户端透明,不需要特别的客户端支持;
2、提供图形界面管理工具;
3、动态扩缩容。
缺点:
1、新增了代理层,增加了系统复杂性;
2、不是Redis官方支持的,社区维护可能不如官方活跃。
适用场景:
操作简单,需要容易管理的环境下的中小型Redis集群。
Redis Enterprise
描述:Redis官方推出的企业版本,提供自动分片、持久化、备份、高可用等功能。
优点:
1、官方支持,稳定性和性能有保障;
2、集成了许多高级功能,例如自动重平衡、热升级;
3、提供图形界面的管理平台。
缺点:
1、商业化产品,需要付费;
2、对开源社区选型有一定限制。
适用场景:
对性能和稳定性有非常高要求的企业级应用。
选择不同的Redis集群方案时,需要权衡数据规模、易用性、成本、客户端支持等因素。对于大部分应用来说,Redis官方提供的Sentinel和Cluster方案已经能够很好地满足需求。在一些特定的、对集群管理有更复杂要求的场景下,则可以考虑使用如Codis或Redis Enterprise等其他解决方案。
补充
我之前没有单独列出"主从复制"作为一种集群配置,但实际上它是Redis提供高可用性的基础,并且在Redis Sentinel和Redis Cluster中都扮演重要的角色。我们可以将主从复制单独说明:
主从复制(Replication)
主从复制是Redis支持的一种数据复制方式,它允许一个或多个副本(从服务器),实时地复制主服务器的数据。它的配置简单,主要用于数据的冗余备份、读写分离和提高数据安全性。
优点:
1、数据冗余:提供数据的多个副本,可以用于灾难恢复。
2、读写分离:读操作可以分布到多个从节点,减轻主节点压力。
3、数据备份:可在从节点上进行持久化操作,避免影响主节点性能。
4、可扩展性:通过增加从节点来水平扩展读取能力。
缺点:
1、写操作不可扩展:所有写操作仍然需要通过主节点,主节点成为瓶颈。
2、不自动故障转移:如果主节点宕机,需要人工介入或额外的工具(如Sentinel)进行故障转移。
3、数据存在延迟:从节点的数据同步可能存在延迟,不能保证强一致性。
适用场景:
1、数据备份和灾难恢复。
2、当应用对读取性能要求高,而对写入性能和强一致性要求不高时,可以搭配多从节点以分散读压力。
3、作为复杂高可用解决方案(如Sentinel和Cluster)的一部分。
4、主从复制通常是高可用和负载均衡配置的基础。
比如,在Redis Sentinel中,Sentinel负责监控主从服务器并自动执行故障转移操作。在Redis Cluster中,各个分片(shard)内部也会使用主从复制来确保数据的高可用性。
因此,即使没有单独列出主从复制作为一个集群配置选项,它实际上是构成其他高可用集群解决方案的关键组件。
数据结构
数据结构分析------操作
提示:先操作后思想+面试
Redis提供了多种数据结构,每种结构都适合不同的使用场景。下面是Redis支持的常用数据结构及其典型使用场景:
字符串(String)
场景:缓存用户信息、会话(Session)信息、计数器、分布式锁的值。
描述:可以包含任何数据,例如文本、数字或者序列化的对象。
操作
java
# 增
SET user:1 "John Doe"
# 删
DEL user:1
# 改
SET user:1 "Jane Doe"
# 查
GET user:1
底层数据结构
字符串在Redis中是最基本的数据类型,实际上是动态字符串sds(simple dynamic string)。
Redis的字符串是二进制安全的,可以包含任意数据,比如jpeg图片或序列化的对象。
特点:动态字符串意味着字符串长度可以在运行时修改,不像C语言的字符串以空字符终止,支持高效的长度计算和字符串追加操作。
哈希(Hash)
场景:存储对象数据,如用户的属性,避免频繁的加载和存储整个对象。
描述:键值对集合,适合存储对象。
操作
java
# 增
HSET user:2 name "Alice" age "30" city "Wonderland"
# 删
# 删除特定字段
HDEL user:2 city
# 删除整个哈希
DEL user:2
# 改
HSET user:2 age "31"
# 查
HGET user:2 name
HGETALL user:2
底层数据结构
哈希在小数据集下使用ziplist(压缩列表),在数据量变大时转而使用hashtable。ziplist是Redis为内存优化设计的数据结构,压缩空间但对于元素的增删改查可能相对较慢。
特点:当哈希内的字段数量增长,或单个字段值超过某个阈值时,底层数据结构从ziplist升级到hashtable。
列表(List)
场景:实现队列(FIFO)或栈(LIFO)结构,比如消息队列。
描述:有序集合,按插入顺序排序。
操作
java
# 增
LPUSH friends:1 "Bob"
RPUSH friends:1 "Charlie"
# 删
LREM friends:1 0 "Charlie" # 移除所有Charlie
LPOP friends:1
RPOP friends:1
# 改
LSET friends:1 0 "David"
# 查
LINDEX friends:1 0
LRANGE friends:1 0 -1
底层数据结构
列表类型使用ziplist或linkedlist(双向链表)。
列表操作连贯性较强时例如对列表的两端插入和删除,常用linkedlist。当列表中的字符串长度较短,元素数量较少时,为了节省空间使用ziplist。
特点:Redis 的列表允许在列表的两端进行插入或删除操作,实现了时间复杂度为O(1)的列表头尾操作。
集合(Set)
场景:存储不允许重复的元素,如标签、好友关系。
描述:unordered collection of unique items。集合中的元素是不重复的,且不保留元素的插入顺序。
操作
java
# 增
SADD hobbies:1 "reading" "traveling" "cooking"
# 删
SREM hobbies:1 "cooking"
# 改
# 不提供直接修改操作,可以先删除再添加
SREM hobbies:1 "reading"
SADD hobbies:1 "hiking"
# 查
SISMEMBER hobbies:1 "traveling"
SMEMBERS hobbies:1
底层数据结构
小集合使用intset(整数集合),它是一个有序的数组,元素之间没有重复。较大集合或有字符串值的集合使用hashtable。
特点:intset由于元素类型的一致和良好的内存连续性,保证了高效的内存使用和查询速度。
有序集合(Sorted Set)
场景:排行榜、数据按范围查询,如游戏得分榜。
描述:与集合相似,但是每个元素都会关联一个浮点数分值,元素按分值有序排列。
操作
java
# 增
ZADD scores 10 "player1" 20 "player2"
# 删
ZREM scores "player1"
# 改
ZADD scores 15 "player2" # 更新player2的得分
# 查
ZRANGE scores 0 -1 WITHSCORES
ZSCORE scores "player2"
底层数据结构
有序集合小数据集使用ziplist,数据量较大则使用skiplist(跳表)和hashtable的组合结构。跳表用于保持元素有序,哈希表用来快速查找元素。
特点:跳表提供了有序数据快速访问的功能,但其内存消耗比纯哈希集合要高。
位图(Bitmaps)
场景:进行位级操作,比如统计在线用户数、特征标记。
描述:更高效地存储大量的布尔值,如标志位。
操作
java
# 增/改
SETBIT featureFlags 7 1 # Set the 8th bit (0-indexed) to 1
# 删
DEL featureFlags
# 查
GETBIT featureFlags 7
BITCOUNT featureFlags
底层数据结构
位图本质上是字符串,通过位操作命令来处理。本身不是独立的数据结构,而是建立在字符串之上的一层抽象。
特点:由于底层是字符串,位图可以非常节省空间,并且可以很快地进行操作,非常适合于统计和运算。
超日志(HyperLogLog)
场景:大量数据的基数统计(如独立IP访问计数)时,能够以极小的空间误差提供近似值。
描述:用于近似计算集合的基数(即不同元素的数量)。
操作
java
# 增/改
SETBIT featureFlags 7 1 # Set the 8th bit (0-indexed) to 1
# 删
DEL featureFlags
# 查
GETBIT featureFlags 7
BITCOUNT featureFlags
底层数据结构
超日志使用稀疏矩阵压缩编码,并伴随着特殊的算法来保持计数的唯一性和准确性。
这是一种概率数据结构,用于估算集合中唯一元素的数量。
特点:特别适合处理大量数据,其空间占用总是固定的,并且独立于输入元素的数量。
地理空间索引(Geospatial)
场景:存储地理位置信息,并进行范围查询、附近位置查询等。
描述:可以存储地理空间信息,并进行半径查询和邻近成员查询。
操作
java
# 增
GEOADD locations 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
# 删
ZREM locations "Catania"
# 改
GEOADD locations 13.356778 38.115219 "Palermo" # 更新Palermo的坐标
# 查
GEORADIUS locations 15 37 100 km WITHCOORD
GEODIST locations "Palermo" "Catania" km
底层数据结构
地理空间类型实际上使用了有序集合(sorted sets)来存储数据,每个元素的分数是通过将地理位置信息(经度和纬度)编码成一个64位的整数实现的。
特点 :利用Sorted Sets的分值来实现区域查询,在两个地理位置点之间进行快速的范围查询。
每种数据类型的特点和底层实现使得Redis在处理不同类型的数据时都提供了优越的性能表现。在日常开发中,了解这些底层实现可以帮助我们更好地了解每种类型可能的性能瓶颈和最佳使用场景。
流(Stream)
场景:用于构建消息队列,实现发布订阅系统,记录事件流。
描述:是一种日志数据结构,每个条目由一个ID和一系列键值对组成。
使用这些数据结构时,需要根据应用场景和需求中的数据特征来选择最合适的结构。例如,如果要存储社交应用中的好友关系,可以考虑使用 Set。如果要存储和获取用户的个人信息对象,Hash 比较适合。为高性能排行榜提供支持时,可以使用 Sorted Set。选择正确的数据结构可以大大提高性能和存储效率。
操作
java
# 增
XADD mystream * name "John" email "john.doe@example.com"
# 删
# 假设ID是从XADD返回的或之前已知的
XDEL mystream ID
# 改
# 流的条目不直接支持修改,通常是删除再插入
XDEL mystream ID
XADD mystream ID name "Jane" email "jane.doe@example.com"
# 查
XRANGE mystream - + # 查看全部消息
XREVRANGE mystream + - # 查看全部消息,逆序
底层数据结构
Redis流的底层采用的是一组linked lists,即多个链表组成。每个链表节点包含了多个字段,称为记录(record)或条目(entry)。每个条目由一个唯一的ID(通常是时间戳)和一系列键值对组成。在Redis版本5以上引入流之后,流的底层实现专门设计了一种叫做listpack的紧凑数据结构来存储这些记录。listpack类似于ziplist,是一种为小型集合优化的数据结构,它存储在每个链表节点中,通过连续的内存存储条目数据。
特点:
流结构支撑高性能的插入,同时保留了插入顺序。
自动条目删除,可以通过配置基于时间或最大长度来自动逸出老的数据。
消费群组(Consumer Groups),允许多个消费者同时读取和处理数据,类似于Kafka的使用场景。
通过ID进行消息定位,允许消费者在任意位置开始消息读取。
流适合用于实现发布/订阅模式,聊天应用程序,实时消息队列等。与其他消息队列系统相比,Redis流因为将数据存储在内存中,因此能提供更低的延迟和更高的吞吐量。另外,它的自动删除机制可以帮助控制内存使用,并且消费者群组使得构建复杂的消息传递架构成为可能。
补充:
这些例子展示了如何在Redis命令行中直接对各种数据结构进行操作。在Java开发环境中,你需要使用Redis客户端来执行类似的操作,并处理返回结果或异常情况,以及考虑连接管理、线程安全和性能优化等相关问题。
面试
聊到这里:如果把上边的都看了,并且将主要的几个常用的数据结构理解了那么面试Redis这块问题不大
再就是无非就是对他的增啥改查:不知道何时起,Redis的模糊搜索也成名面试会问到的:
那就卷一下吧,看前两个就行了:
Redis中模糊搜索
1. KEYS 命令
KEYS 命令可以在处理小型数据集时用于模糊搜索键名,例如:
java
KEYS user:*
这个命令会返回所有以 "user:" 为前缀的键。
优点:
- 简单直接,易于使用。
- 可以使用多种通配符如 *(匹配任意数量的字符)、?(匹配单个字符)和字符范围 [a-z]。
缺点:
- 在大型数据库上运行时效率低下,因为它是一个阻塞操作,扫描整个键空间可能会导致性能问题。
- 不适用于生产环境中的大型数据集。
2. SCAN 命令
使用 SCAN 命令是一种更高效且不阻塞的方式来迭代数据库中的键,可以与模式结合使用:
java
SCAN 0 MATCH user:* COUNT 100
优点:
- 迭代过程是非阻塞的,不会像KEYS命令那样对性能产生负面影响。
- 具有MATCH参数,可以指定键的模式。
- 可以逐渐和重复地调用,对于应用程序来说更加友好。
- 提供了游标概念实现非阻塞式迭代。
- 与 KEYS 类似,支持模糊匹配。
缺点:
- 可能需要多次执行才能遍历整个键空间,对于需立刻获得所有结果的场景不太适用。
- 使用时可能会得到重复的键,需要在客户端进行去重。
- COUNT 参数可以控制每次迭代返回键的数量,但不保证每次都返回该数量的键。通常需要多次调用来遍历整个键空间。
3. SORT 命令 和 Alpha 修改符
如果键按某种模式存储(例如列表或集合),可以使用 SORT 命令配合 ALPHA 修改符,对键执行字母排序并通过模式搜索:
java
SORT user:ids BY nosort GET user:*->name ALPHA
优点:
- 在获取排序结果的同时能进行模式匹配。
- 可以通过操作列表、集合或有序集合中的元素来实现更复杂的搜索。
缺点:
- 只适用于字符串类型的元素。
- 依赖于存储数据的Redis数据结构类型。
- 需要预先知道键的部分名称。
- ALPHA 修改符用于符合字母排序。
- 适用于数据结构是列表、集合或有序集合。
4. 自定义数据结构和算法
在某些情况下,可以自己设计如前缀树(Trie)等复杂的数据结构来实现模糊搜索,可能需要额外在 Redis 之外实现数据结构逻辑。
优点:
高度可定制,可以为特定场景构建索引。
提供排序,范围查询等复杂操作。
缺点:
复杂性增加,需要维护代码和逻辑。
需要额外的存储空间来维护索引。
5. Redis Modules
RediSearch,它是一个建立在 Redis 之上的全文搜索引擎模块,能够提供先进的搜索功能。
优点:
提供全面的搜索功能,包括模糊匹配、全文搜索、复杂的查询语法等。
性能优化,专为搜索场景设计。
缺点:
需要额外安装和维护redisseach模块。
可能增加系统的复杂性。
6. 利用 Lua 脚本
可以编写 Lua 脚本来定制模糊搜索的行为,比如对集合进行一些过滤和处理后返回结果。
优点:
高度可定制。
服务器端执行,节省网络传输成本。
缺点:
没有内置的文字匹配功能,需要编写逻辑。
特点:
7. 利用集合和哈希的特性
有时候可以通过将项存储为集合(SET)成员或哈希(HASH)字段来实现限定的匹配。
优点:
只适用于精确匹配。
缺点:
需要巧妙地设计键的模式以实现模糊匹配的需求~~ 。
总结:
每种模糊搜索方式都有其适用范围与限制。通常,KEYS 和 SCAN 更适合于键名的搜索,而 RediSearch 等模块则为内容搜索提供了更加专业的解决方案。在选择合适的搜索方法时,开发者应该考虑数据集的大小,性能需求以及搜索复杂性。
特点:
总结:
Redis本身并不是java语言实现的,这里其实面试题还有很多,尝试的去理解就好了:
比如多路IO复用,为什么是单线程(存在疑问,仅仅是某一点使用了单线程),分析一下其好处和坏处,等等诸多奇怪的问题,目的是什么呢,增加门槛高度。