广告
返回顶部
首页 > 资讯 > 数据库 >Redis键值设计的实践
  • 852
分享到

Redis键值设计的实践

Redis键值Redis键值设计 2023-01-30 10:01:18 852人浏览 安东尼
摘要

目录1 优雅的key结构2 拒绝BigKey2.1 判断BigKey2.2 BigKey的危害2.3 如何发现BigKey2.4 如何删除BigKey3 恰当的数据类型3.1 存储对象3.2 Hash优化在Redis中,

Redis中,良好的键值设计可以达成事半功倍的效果,而不好的键值设计可能会带来Redis服务停滞,网络阻塞,CPU使用率飙升等一系列问题,今天就教大家如何设计一个良好的key-value

1 优雅的key结构

Redis的Key虽然可以自定义,但最好遵循下面的几个最佳实践约定:

遵循基本格式[业务名称]:[数据名]:[id],例如我们的登录业务,需要保存用户信息,其key可以设计成如下格式

在这里插入图片描述

这种设计的好处不仅在于可读性强,还在于可以避免key的冲突问题,而且方便管理

Key的长度不超过44字节

无论是哪种数据类型, key都是string类型,string类型的底层编码包含int、embstr和raw三种。如果key中全是数字,那么就会直接以int类型去存储,而int占用的空间也是最小的,当然出于业务需求,我们不可能将key设计为一个全数字的,而如果不是纯数字,底层存储的就是SDS内容,如果小于44字节,就会使用embstr类型,embstr在内存中是一段连续的存储空间,内存占用相对raw来说较小,而当字节数大于44字节时,会转为raw模式存储,在raw模式下,内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,在这段空间里存储SDS内容,这样空间不连续,访问的时候性能也就会收到影响,还有可能产生内存碎片

需要注意的是,如果你的redis版本低于4.0,那么界限是39字节而非44字节

Key中不包含一些特殊字符

2 拒绝BigKey

2.1 判断BigKey

BigKey顾名思义就是一个很大的Key,这里的大并不是指Key本身很大,而是指包括这个Key的Value在内的一整个键值对很大

BigKey通常以Key-Value的大小或者Key中成员的数量来综合判定,例如:

  • Key的Value过大:例如一个String类型的Key,它的Value为5MB
  • Key中的成员数过多:例如一个ZSET类型的Key,它的成员数量为10000个
  • Key中成员的Value过大:例如一个Hash类型的Key,它的成员数量虽然只有1000个,但这些成员的Value总大小为100 MB

那么如何判断元素的大小呢?redis中为我们提供了相应的命令,语法如下:

memory usage 键名

这条命令会返回一条数据占用内存的总大小,这个大小不仅包括Key和Value的大小,还包括数据存储时的一些元信息,因此可能你的Key与Value只占用了几十个字节,但最终的返回结果是几百个字节

但是我们一般不推荐使用memory指令,因为这个指令对CPU的占用率是很高的,实际开发中我们一般只需要衡量Value的大小或者Key中的成员数即可

例如如果我们使用的数据类型是String,就可以使用以下命令,返回的结果是Value的长度

strlen 键名

如果我们使用的数据类型是List,就可以使用以下命令,返回的结果是List中成员的个数

llen 键名

一般我们推荐,单个key的value小于10KB,集合类型的key元素数量小于1000

2.2 BigKey的危害

网络阻塞

当我们对一个BigKey发起读请求时,只需少量的QPS就可能导致带宽使用率被占满,导致Redis实例乃至所在物理机变慢,例如一个bigkey占用5M内存,只需要QPS达到20,那么1秒钟就会占100M的带宽

数据倾斜

集群环境下,由于所有插槽一开始都是均衡分配的,因此BigKey所在的Redis实例内存使用率会远超其他实例,从而无法使数据分片的内存资源达到均衡,最后不得不手动重新分配插槽,增加运维人员的负担

Redis阻塞

对元素较多的hash、list、zset等做运算会耗时较久,而且由于Redis是单线程的,在运算过程中会导致服务阻塞,无法接收其他用户请求

CPU压力

对BigKey的数据进行序列化或反序列化都会导致CPU的使用率飙升,影响Redis实例和本机其它应用

2.3 如何发现BigKey

既然我们知道了什么叫BigKey以及BigKey的危害,那么如何去快速发现Redis中所有的BigKey呢?这里为大家提供以下几种方案:

1)利用Redis本身提供的命令

利用以下命令,可以遍历分析所有key,并返回Key的整体统计信息与每种数据类型中Top1的BigKey

redis-cli -a 密码 --bigkeys

演示如下(这里我的redis没有设置密码,如果你的redis设置了密码,则需要使用 -a 密码 进行连接)

在这里插入图片描述

2)自己手动编写程序进行扫描

我们可以通过自己编写程序,将Redis中所有的数据查询出来并一一统计长度来找出BigKey,这里不建议使用keys *来查询所有数据,因为keys * 是一次将所有的数据全部查找出来,如果数据量很大,key *一次可能要几十秒甚至几分钟,在如此长的时间内,Redis的主线程会因为执行该命令而被阻塞。

这里建议使用redis提供的scan命令,语法如下:

scan 起始位置 count 数量

scan扫描有点类似于分页查询,而被分页的对象是redis中所有的数据,scan命令调用一次只会从指定的起始位置开始返回指定数量的数据,以及此次扫描结束时光标所在的位置,下一次扫描时就需要从这个光标开始继续往下扫描

这里提供一个已经编写好的查找BigKey的测试类,大家可以参考一下

import com.heima.jedis.util.JedisConnectionFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanResult;

import Java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JedisTest {
    private Jedis jedis;

    @BeforeEach
    void setUp() {
        // 1.建立连接
        // jedis = new Jedis("192.168.150.101", 6379);
        jedis = JedisConnectionFactory.getJedis();
        // 2.设置密码
        jedis.auth("123321");
        // 3.选择库
        jedis.select(0);
    }

    //设置string类型的长度上限,超过这个上限就判断为BigKey
    final static int STR_MAX_LEN = 10 * 1024;
    //设置集合类型允许的成员数量上限,超过这个上限就判断为BigKey
    final static int HASH_MAX_LEN = 500;

    @Test
    void testScan() {
        int maxLen = 0;
        long len = 0;

        String cursor = "0";
        do {
            // 扫描并获取一部分key
            ScanResult<String> result = jedis.scan(cursor);
            // 记录cursor
            cursor = result.getCursor();
            List<String> list = result.getResult();
            if (list == null || list.isEmpty()) {
                break;
            }
            // 遍历
            for (String key : list) {
                // 判断key的类型
                String type = jedis.type(key);
                switch (type) {
                    case "string":
                        len = jedis.strlen(key);
                        maxLen = STR_MAX_LEN;
                        break;
                    case "hash":
                        len = jedis.hlen(key);
                        maxLen = HASH_MAX_LEN;
                        break;
                    case "list":
                        len = jedis.llen(key);
                        maxLen = HASH_MAX_LEN;
                        break;
                    case "set":
                        len = jedis.scard(key);
                        maxLen = HASH_MAX_LEN;
                        break;
                    case "zset":
                        len = jedis.zcard(key);
                        maxLen = HASH_MAX_LEN;
                        break;
                    default:
                        break;
                }
                if (len >= maxLen) {
                    System.out.printf("Found big key : %s, type: %s, length or size: %d %n", key, type, len);
                }
            }
        } while (!cursor.equals("0"));
    }
    
    @AfterEach
    void tearDown() {
        if (jedis != null) {
            jedis.close();
        }
    }

}

3)第三方工具

利用第三方工具,这里推荐Redis-Rdb-Tools,它会针对Redis的RDB快照文件来分析内存使用情况,由于分析的是快照文件,因此不会占用Redis服务的任何性能,但是时效性相对较差

Redis-Rdb-Tools的GitHub网址:https://github.com/sripathikrishnan/redis-rdb-tools

4)网络监控

使用自定义工具,监控进出Redis的网络数据,超出预警值时主动告警。一般阿里云搭建的云服务器就有相关监控页面:

在这里插入图片描述

2.4 如何删除BigKey

BigKey内存占用较多,因此即便我们使用的是删除操作,删除BigKey也需要耗费很长时间,导致Redis主线程阻塞,引发一系列问题。

如果redis版本在4.0之后,我们可以通过异步删除命令unlink来删除一个BigKey,该命令会先把数据标记为已删除,然后再异步执行删除操作。

如果redis版本在4.0之前,针对集合类型,我们可以先遍历BigKey中所有的元素,先将子元素逐个删除,最后再删除BigKey。至于如何遍历,针对不同的集合类型,可以参考以下不同的命令

在这里插入图片描述

3 恰当的数据类型

找出BigKey中,我们应该如何对BigKey进行优化呢?这里我们需要选择恰当的数据类型

3.1 存储对象

如果我们要存储一个User对象,有三种存储方式:

1)jsON字符串

将一整个对象转成JSON格式进行存储

user:1{“name”: “Jack”, “age”: 21}

优点:实现简单粗暴

缺点:数据耦合,不够灵活,且需要维护jsON结构,占用内存相对较大

2)字段打散

将对象的不同属性存储到不同的key中

keyvalue
user:1:nameJack
user:1:age21

优点:可以灵活访问对象任意字段

缺点:由于每条数据都会有一些元信息需要存储,因此将一个Key分成多个Key进行存储,占用的内存会变的更大,且由于字段分散,当我们需要做统一控制时会变得很困难

3)hash(推荐)

使用hash结构来存储对象,对象的一个属性对应集合中的一个成员

user:1namejack
age21

优点:hash结构底层会使用ziplist压缩列表,空间占用小,且可以灵活访问对象的任意字段

缺点:代码编写时相对复杂

3.2 Hash优化

假如有一个hash类型的key,其中有100万对field和value,field是自增id,这个key存在什么问题?如何优化?

keyfieldvalue
someKeyid:0value0
..........
id:999999value999999

当hash的entry数量超过500时,底层会使用哈希表存储而不是ZipList,内存占用会变得比较高,虽然这个数量限制我们是可以通过以下命令进行修改的

config set hash-max-ziplist-entries 数量

但是entry数量如果实在太大了还是会导致BigKey问题,这是需要优化的,这里提供以下两种解决思路:

1)拆分为String类型(不推荐)

将Hash中的每个成员单独使用一个String类型的key进行存储

keyvalue
id:0value0
..........
id:999999value999999

这种方案是不推荐的,存在的问题如下

  • string结构底层没有太多内存优化的,且存储这些key的同时也会存储大量的元信息,虽然数据打散了,但是整体内存占用更多了
  • 如果我们想要批量获取这些数据,会变得格外麻烦

2)拆分成多个Hash类型

拆分为小的hash,将 id / 100 作为key, 将id % 100 作为field,这样每100个元素为一个Hash,这种方式相对上面两种来说内存占用会少很多,而且解决了Bigkey的问题,当然多少个元素作为一个Hash是自己定义的,这里建议数量不要超过500

keyfieldvalue
key:0id:00value0
..........
id:99value99
key:1id:00value100
..........
id:99value199
....
key:9999id:00value999900
..........
id:99value999999

到此这篇关于Redis键值设计的实践的文章就介绍到这了,更多相关Redis键值设计内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

您可能感兴趣的文档:

--结束END--

本文标题: Redis键值设计的实践

本文链接: https://www.lsjlt.com/news/178708.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Redis键值设计的实践
    目录1 优雅的key结构2 拒绝BigKey2.1 判断BigKey2.2 BigKey的危害2.3 如何发现BigKey2.4 如何删除BigKey3 恰当的数据类型3.1 存储对象3.2 Hash优化在Redis中,...
    99+
    2023-01-30
    Redis键值 Redis键值设计
  • Redis 键值设计使用总结
    目录前言Redis使用中不规范的现象Redis 使用业务场景推荐与建议如何设计出优雅的key一、遵循如下几个最佳实践约定二、尽量避免bigkey三、使用恰当的数据类型Redis 缓存在实际应用中的使用建议使用业务规范前言...
    99+
    2023-04-07
    Redis 键值设计 Redis 键值 Redis 键值使用
  • Redis键值设计使用总结
    目录前言Redis使用中不规范的现象Redis 使用业务场景推荐与建议如何设计出优雅的key一、遵循如下几个最佳实践约定二、尽量避免bigkey三、使用恰当的数据类型Redis 缓存...
    99+
    2023-05-14
    Redis 键值设计 Redis 键值 Redis 键值使用
  • Redis键值设计使用的方法是什么
    这篇文章主要介绍了Redis键值设计使用的方法是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Redis键值设计使用的方法是什么文章都会有所收获,下面我们一起来看看吧。Redis使用中不规范的现象Redis...
    99+
    2023-07-05
  • Redis数据库常见的键值设计有哪些
    这篇文章将为大家详细讲解有关Redis数据库常见的键值设计有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。  用户登录系统  记录用户登录信息的一个系统,我们简化业务...
    99+
    2022-10-19
  • 如何设置redis键值永不超时
    设置redis键值永不超时的方法:expire命令可以设置key的存活时间。命令格式例如:expire key seconds意思:设置指定key 多少秒后过期,seconds 为“-1”时表示永不过期。...
    99+
    2022-10-15
  • 《Redis设计与实现》
    由浅到深,逐步讲解Redis 本书主要分为四大部分。 第一部分"数据结构与对象": 介绍了Redis中的各种对象及其数据结构,并说明这些数据结构如何影响对象的功能和性能。 第二部分"单机数据库的实现": 对Redis实现单机数据库的方法进...
    99+
    2020-06-17
    《Redis设计与实现》
  • redis怎么获取所有的键值
    在Redis中,要获取所有的键值可以使用以下两个命令:1. KEYS命令:该命令可以返回匹配指定模式的所有键。语法如下:```KEY...
    99+
    2023-08-24
    redis
  • 怎么在Redis中实现键值过期操作
    这篇文章将为大家详细讲解有关怎么在Redis中实现键值过期操作,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。1.过期设置Redis 中设置过期时间主要通过以...
    99+
    2022-10-18
  • PHP核心的设计模式与实践
    PHP核心的设计模式与实践引言:设计模式是软件开发中常用的解决问题的模板,它们提供了一种可重用的解决方案,可以帮助我们在开发过程中遵循最佳实践和良好的软件设计原则。PHP作为一种广泛应用的编程语言,也有许多常见和有用的设计模式可以在核心开发...
    99+
    2023-11-09
    PHP设计模式 PHP核心 核心实践
  • redis中过期键的设置
    EXPIRE key seconds用来对一个键设置一个过期时间,第二个参数表示经过多少秒后键过期。 一个键过期后, 这个键将会被自动删除。 在Redis术语中,带有过期时间的键经常被称作volatile(...
    99+
    2022-10-18
  • Redis | 第4章 Redis中的数据库《Redis设计与实现》
    目录前言1. Redis中的数据库2. 数据库的键空间3. 键的生成时间与过期时间4. Redis中的过期键删除策略5. AOF、RDB和复制功能对过期键的处理5.1 生成 RDB 文件5.2 载入 RDB 文件5.3 AOF 文件写入5...
    99+
    2020-04-14
    Redis | 第4章 Redis中的数据库《Redis设计与实现》
  • 怎么设计与实现Redis
    本篇内容主要讲解“怎么设计与实现Redis”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么设计与实现Redis”吧!Redis的设计与实现其实 Redis 主要是通过三个方面来满足这样高效吞吐...
    99+
    2023-06-16
  • Redis | 第7章 Redis 服务器《Redis设计与实现》
    目录前言1. 命令请求的执行过程1.1 发送命令请求1.2 读取命令请求1.3 命令执行器(1):查找命令实现1.4 命令执行器(2):执行预备操作1.5 命令执行器(3):调用命令的实现函数1.6 命令执行器(4):执行后续工作1.7 ...
    99+
    2022-04-27
    Redis | 第7章 Redis 服务器《Redis设计与实现》
  • Redis | 第3章 对象《Redis设计与实现》
    目录前言1. Redis对象概述1.1 对象的定义2. 字符串对象3. 列表对象3.1 quicklist 快速链表4. 哈希对象5. 集合对象6. 有序集合对象7. Redis对象的特点7.1 类型检查与命令多态7.2 内存回收7.3 ...
    99+
    2016-10-04
    Redis | 第3章 对象《Redis设计与实现》
  • 数据库设计的十个最佳实践
    数据库是应用及计算机的核心元素,负责存储运行软件应用所需的一切重要数据。为了保障应用正常运行,总有一个甚至多个数据库在默默运作。我们可以把数据库视为信息仓库,以结构化的方式存储了大量的相关信息,并合理分类,方便搜索及使用(java项目 f...
    99+
    2018-09-30
    数据库设计的十个最佳实践
  • Redis | 第5章 Redis 中的持久化技术《Redis设计与实现》
    目录前言1. RDB 持久化1.1 RDB 文件的创建与载入1.2 自动间隔性保存1.2.1 设置保存条件1.2.2 dirty 计数器和 lastsave 属性1.2.3 检查保存条件是否满足1.3 RDB 文件1.3.1 RDB 的文...
    99+
    2018-11-05
    Redis | 第5章 Redis 中的持久化技术《Redis设计与实现》
  • MongoDB与边缘计算的结合实践与架构设计
    随着物联网和云计算的快速发展,边缘计算逐渐成为新的热点领域。边缘计算是指将数据处理和计算能力从传统的云计算中心转移到物理设备的边缘节点上,以提高数据处理的效率和减少延迟。而MongoDB作为一种强大的NoSQL数据库,其在边缘计算领域的应用...
    99+
    2023-11-02
    边缘计算 MongoDB 架构设计
  • pygame 键盘事件的实践
    目录Pygame事件事件类型及属性事件处理函数键盘事件及类型的使用键盘事件及属性什么是事件呢?按下键盘某个按键,鼠标移动,包括点击关闭按钮都可以算是事件操作。 Pygame事件 ...
    99+
    2022-11-12
  • NPM响应式设计:Java IDE的最佳实践
    随着移动互联网的普及,响应式设计已经成为了现代Web开发的必备技能。而在Java IDE的开发中,NPM响应式设计也越来越受到关注。在本文中,我们将介绍NPM响应式设计的基础知识,并探讨如何在Java IDE中应用最佳实践。 什么是NPM...
    99+
    2023-09-29
    ide npm 响应
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作