巩义自助建站优化,做企业官网的公司,东莞软件开发培训,陕西网站制作商目录
1、Big Key的产生
2、BigKey场景分析
3、Big Key的危害
4、检测 BigKey
5、解决 BigKey 问题
Big Key拆分
#xff08;1#xff09;按时间/业务拆分
#xff08;2#xff09;按哈希#xff08;Hash#xff09;拆分
#xff08;3#xff09;按前缀树拆分…目录
1、Big Key的产生
2、BigKey场景分析
3、Big Key的危害
4、检测 BigKey
5、解决 BigKey 问题
Big Key拆分
1按时间/业务拆分
2按哈希Hash拆分
3按前缀树拆分
Big Key定期清理
Big key压缩
Big Key批处理
优化持久化配置
6、总结 Big Key 问题是指某个键key的值value过大这会导致 Redis 的性能下降
1、Big Key的产生
业务设计不合理
在设计应用时如果对数据结构的设计不够精细可能会导致单个 key 存储的数据量过大没有合理地拆分成多个较小的 key
如存储了大量数据的字符串、哈希表hash、列表list等这会导致在执行涉及该键的操作时消耗更多的时间和资源。
未及时清理无用数据
如忘记设置过期时间没有定期删除过期或不再需要的数据List结构中数据持续增加而没有弹出数据的机制那么数据会越来越多
数据类型选择不当
不同的数据类型有不同的内存使用特点。例如使用字符串类型存储大量数据可能不如使用哈希HASH类型来得高效。 选择不适合的数据类型可能导致内存使用效率低下进而产生 Big Key
如文件二进制数据不使用 String 保存、使用 HyperLogLog 统计页面 UV、Bitmap 保存状态信息0/1
动态增长管理不当
某些数据结构可能随着时间和用户行为的变化而不断增长如果没有适当的管理机制来控制其增长就可能导致 Big Key 的出现。
如日志记录、历史数据存储、某个明星热点粉丝列表或者评论的列表等功能如果没有合理的清理策略可能会导致 key 的数据量逐渐增大
2、BigKey场景分析
以以下常见的场景分析Big Key的问题以及解决方案
1. 用户行为日志记录
场景描述应用程序记录用户的行为日志如点击事件、浏览历史等通常会将这些数据存储在一个用户的键中。 问题产生随着用户行为的增多单一用户的日志数据量会逐渐增大形成 BigKey。 解决方案分割日志数据到多个键中例如按照日期分割。定期归档或清理旧的日志数据
2. 产品目录或商品信息
场景描述电商平台或零售系统中每个商品可能包含大量的属性信息如规格、价格、库存等。 问题产生如果将所有商品信息都存储在一个键中随着商品数量的增加键的大小也会增加。 解决方案将商品信息拆分为多个键例如按类别或品牌存储。使用更高效的数据结构如使用哈希表存储商品的属性
3. 社交网络的好友关系
场景描述社交应用中每个用户都有自己的好友列表。 问题产生当用户的好友数量非常多时存储好友列表的键可能会变得非常大。 解决方案将好友列表拆分为多个键例如按照字母顺序或好友活跃度进行分组。使用有序集合Sorted Set存储好友列表便于快速查找和排序
4. 会话状态存储
场景描述Web 应用程序中会话状态通常需要在服务器端存储。 问题产生如果每个用户的会话状态包含了大量数据如购物车、登录信息等会话状态键可能会变得非常大。 解决方案将会话状态拆分为多个键例如将购物车和其他状态分开。定期清理过期的会话状态。
3、Big Key的危害
性能下降
对 BigKey 执行批量操作如 HGETALL 或 HSETALL这些命令的时间复杂度通常是 O(N)其中 N 是键中元素的数量。当 N 很大时这些操作会变得非常耗时尤其是在 Redis 的单线程模型下
内存占用
BigKey 占用过多的内存可能导致 Redis 的整体内存使用过高从而触发内存淘汰策略影响其他键的性能
网络拥塞
BigKey 在网络上传输时会占用较多带宽特别是当客户端频繁读取或写入 BigKey 时可能会导致网络拥塞。
超时阻塞
Redis 的主要命令执行是在单线程中完成的这意味着在执行 BigKey 相关操作时其他客户端的请求会被阻塞直到该操作完成
备份和恢复时间增加
Big Key 会使 RDB 快照文件或 AOF 日志文件增大从而增加备份和恢复所需的时间。
大量的 Big Key 存在时可能会导致备份和恢复过程变得非常缓慢
复制延迟/删除异常
在主从复制场景中Big Key 的复制会消耗更多的时间导致从节点的延迟增加
当Big Key 过期需要删除时由于数据量过大可能发生主库较响应时间过长主从数据同步异常删除掉的数据从库还在使用
4、检测 BigKey 使用 redis-cli 工具可以通过 redis-cli 工具来检测 BigKey使用 Redis 自带的命令Redis 提供了 BIGKEYS 命令来查询当前 Redis 中所有 key 的信息帮助统计分析键值对的大小情况 使用以下命令扫描Redis中所有的键并返回前1000个Big Key
redis-cli --scan --pattern * --count 1000
redis-cli -a password -- bigkeys
5、解决 BigKey 问题 Big Key拆分
将大键拆分成多个小键使用更合适的数据结构来存储数据
拆分方式
1按时间/业务拆分
如果 Big Key 包含的是按时间顺序排列的数据可以考虑按时间范围拆分
2按哈希Hash拆分
使用哈希函数将大 Key 拆分成多个小 Key并将其存储在不同的 Redis 实例中
3按前缀树拆分
使用前缀树将大 Key 拆分成多个层级结构每个层级的 Key 都更小
前缀树是一种树形数据结构用于高效地存储和检索字符串集合
假设有一个 Big Key 用于存储每天的用户登录记录按照Hash拆分
按照Hash拆分 HashExample中- 使用 HashMap 来存储键值对。 - insert 方法用于插入以 user: 开头的键值对。 - get 方法用于查找特定键的值。 - printKeysWithPrefix 方法用于打印所有以特定前缀开头的键
public class HashExample {private MapString, String hashTable;public HashExample() {hashTable new HashMap();}// 插入键值对public void insert(String key, String value) {if (key.startsWith(user:)) {hashTable.put(key, value);} else {System.out.println(只允许以 user: 开头的键。);}}// 查找键public String get(String key) {return hashTable.get(key);}// 查找以特定前缀开头的所有键public void printKeysWithPrefix(String prefix) {System.out.println(以 prefix 开头的键:);for (String key : hashTable.keySet()) {if (key.startsWith(prefix)) {System.out.println(key : hashTable.get(key));}}}public static void main(String[] args) {HashExample hashExample new HashExample();hashExample.insert(user:123, User 123 Data);hashExample.insert(user:456, User 456 Data);hashExample.insert(admin:001, Admin Data); // 不会插入// 查找以 user: 开头的所有键hashExample.printKeysWithPrefix(user:);}
}
按照前缀树拆分
那么按照前缀树实现来存储和查询以 user: 开头的键该如何实现呢
首先需要定义一个前缀树节点的类
TrieNode类中每个节点包含一个字符的子节点映射 children 和一个布尔值 isEndOfKey 指示该节点是否为一个完整的键
class TrieNode {MapCharacter, TrieNode children;boolean isEndOfKey;public TrieNode() {children new HashMap();isEndOfKey false;}
}
接下来定义一个前缀树类 Trie 类 - insert 方法用于插入一个键 - collectKeysWithPrefix 方法用于查找以特定前缀开头的所有键 - collectKeys 方法使用深度优先搜索DFS遍历节点收集所有完整的键
class Trie {private TrieNode root;public Trie() {root new TrieNode();}// 插入键public void insert(String key) {TrieNode node root;for (char ch : key.toCharArray()) {node.children.putIfAbsent(ch, new TrieNode());node node.children.get(ch);}node.isEndOfKey true;}// 查找以特定前缀开头的所有键public ListString collectKeysWithPrefix(String prefix) {TrieNode node root;for (char ch : prefix.toCharArray()) {if (!node.children.containsKey(ch)) {return new ArrayList(); // 如果前缀不存在返回空列表}node node.children.get(ch);}ListString keys new ArrayList();collectKeys(node, prefix, keys);return keys;}// 深度优先搜索收集所有以特定前缀开头的键private void collectKeys(TrieNode node, String currentPrefix, ListString keys) {if (node.isEndOfKey) {keys.add(currentPrefix);}for (Map.EntryCharacter, TrieNode entry : node.children.entrySet()) {collectKeys(entry.getValue(), currentPrefix entry.getKey(), keys);}}
}
最后使用前缀树来存储和查询以 user: 开头的键
public class Main {public static void main(String[] args) {Trie trie new Trie();trie.insert(user:1, value1);trie.insert(user:2, value2);trie.insert(user:3, value3);trie.insert(admin:1, value4);// 查找以 user: 开头的所有键ListString userKeys trie.collectKeysWithPrefix(user:);System.out.println(以 user: 开头的键: userKeys);}
}Big Key定期清理
异步删除定期清理不再使用的 BigKey以释放内存空间
Redis 4.0 可以使用 UNLINK 命令来异步删除一个或多个指定的 key。Redis 4.0 以下可以考虑使用 SCAN 命令结合 DEL 命令来分批次删除
设置TTL对于经常访问的大键可以考虑使用缓存策略LRU最近最少使用来减轻 Redis 的负担
关于缓存策略可以看这篇文章Redis的过期策略以及内存淘汰机制-CSDN博客
Big key压缩
对于文本数据可以考虑使用压缩算法来减小存储空间
Redis 支持使用 LZF、QUICKLZ 和 GZIP 算法进行压缩
Big Key批处理
如果 Big Key 不能避免可以考虑使用分批处理的方式来读取和更新数据比如使用 SCAN 命令
优化持久化配置
RDB调整 RDB 的备份频率和条件减少 Big Key 对 RDB 文件大小的影响。 AOF启用 AOF 的压缩功能减少 AOF 文件的大小
建议
避免使用非常长的 Key理想情况下Key 的长度应小于 100 字节。使用复合键使用多个字段例如用户 ID 和帖子 ID组合成一个键可以减少单个键的长度。使用子列表将大列表拆分成多个子列表每个子列表的元素较少。使用 HyperLogLogHyperLogLog 是一种概率数据结构可以近似计算大集合中的唯一元素数量它占用的空间非常小例如统计页面 UV、网站访问者的唯一 IP 地址、用户的唯一 ID等
6、总结
Redis的Big Key会给Redis带来的意想不到的危害需要监控、及时发现和处理Big Key。在开发过程中需要选用适当的Redis数据结构开发业务尽量避免big key