使用Redis处理JSON数据的比较:RedisString与RedisJSON

发布:2024-10-25 15:18 阅读:14 点赞:0

一. 引言

Redis是一种快速的内存键值数据存储,广泛应用于缓存、会话存储和实时数据处理。尽管Redis传统上以简单字符串(包括列表、集合和哈希等结构)处理数据,但其已发展支持更复杂的数据类型,尤其是JSON格式,这通常是处理复杂嵌套数据结构的现代应用程序的首选格式。

本文将探讨在Redis中处理JSON数据的两种方法:RedisString和RedisJSON。我们将涵盖这些方法之间的基本区别,为什么在处理大JSON对象时更倾向于使用RedisJSON,并提供实际用例以突出何时以及为何应考虑RedisJSON。目的是评估每种方法的性能、数据处理能力和潜在用例。

二. 什么是RedisString?

RedisString是Redis中最基本且最常用的数据类型。它将数据存储为简单的字符串值,允许进行GET、SET等操作。可以通过将JSON对象序列化为字符串格式,将JSON数据存储在RedisString中。

RedisString 示例代码

public async Task UploadFile()
{
    // 移除Redis缓存
    await _cache.RemoveAsync(cacheKey);

    // 缓存未命中,从JSON文件读取
    var data = await ReadJsonFileAsync();
    if (data.ActionAccessRight.Count > 0)
    {
        // 将数据保存到RedisString
        await _cache.SetStringAsync(cacheKey,
            JsonSerializer.Serialize(data.ActionAccessRight)); // 将数据序列化为字符串并存入Redis
    }
}

以上方法将JSON文件上传至项目目录,并使用RedisString将数据保存到Redis中。

三. 什么是RedisJSON?

RedisJSON是Redis模块,提供原生支持在Redis中存储、查询和操作JSON数据。它允许您存储JSON文档,并对特定字段进行精细化操作,而无需检索整个文档。RedisJSON旨在高效处理大JSON数据,并支持复杂的JSON结构,如嵌套对象、数组和基本数据类型。

RedisJSON 示例代码

public async Task UploadFile()
{
    // 移除Redis JSON
    var res = await _redisDb.JSON().ForgetAsync(cacheKey, path: "$");

    // 缓存未命中,从JSON文件读取
    var data = await ReadJsonFileAsync();

    if (data.ActionAccessRight.Count > 0)
    {
        // 使用根路径将数据保存到RedisJSON
        _redisDb.JSON().Set(cacheKey, "$", data, When.Always); // 将数据存入RedisJSON
    }
}

以上方法将文件上传至项目目录,并使用RedisJSON将数据保存到Redis中。

四. 项目设置

1. 项目设置

请查看附件的zip文件,内容包括:

  • 源代码:包含RedisString和RedisJSON API的代码。
  • 性能测试结果图片:包括延迟和接收数据的关键指标。
  • JSON文件:用于在本地测试代码。
  • 项目运行说明:文本文件,包含运行项目的说明。

2. 先决条件

  • 已安装并运行的Redis服务器(版本x.x.x)
  • 安装的.NET Core SDK
  • ASP.NET Core Web API项目

您需要在ASP.NET Core Web API项目中包含以下依赖项:

  • StackExchange.Redis用于RedisString操作。
  • NRedisStack用于RedisJSON操作。

五. 可用端点

您的Web API暴露多个端点,以执行与RedisString和RedisJSON的不同操作。以下是关键端点及其用法:

  • 上传JSON文件:我们首先上传JSON文件,然后执行所有操作以查看差异。

    • RedisString: POST /api/redisString/upload-file
    • RedisJSON: POST /api/redisJson/upload-file
  • 清除缓存:用于清除缓存的端点。

    • RedisString: POST /api/redisString/CleanCache
    • RedisJSON: POST /api/redisJson/CleanCache
  • 获取数据:获取所有JSON数据的端点。

    • RedisString: POST /api/redisString/get-data
    • RedisJSON: POST /api/redisJson/get-data
  • 按ID获取数据:通过ID获取数据的端点。

    • RedisString: POST /api/redisString/get-dataById
    • RedisJSON: POST /api/redisJson/get-dataById
  • 检查键是否存在:用于检查Redis键是否存在的端点。

    • RedisString: POST /api/redisString/check-KeyExists
    • RedisJSON: POST /api/redisJson/check-KeyExists

六. 性能结果分析

首先,通过upload-file端点上传JSON文件。此端点将文件上传至项目目录,并将数据保存到缓存中。

1. 使用RedisString的示例代码

[HttpPost("upload-file")]
public async Task<IActionResult> UploadJson([FromForm] IFormFile jsonFile)
{
    if (jsonFile == null || jsonFile.Length == 0)
    {
        return BadRequest("无效文件或未上传文件。");
    }

    var fileExtension = Path.GetExtension(jsonFile.FileName);
    if (fileExtension.ToLower() != ".json")
    {
        return BadRequest("只允许上传JSON文件。");
    }

    if (jsonFile.ContentType != "application/json")
    {
        return BadRequest("文件内容类型无效,只允许JSON文件。");
    }

    try
    {
        // 删除项目目录中的旧JSON文件
        var jsonFiles = Directory.GetFiles(_uploadDirectory, "*.json");
        foreach (var file in jsonFiles)
        {
            System.IO.File.Delete(file);
        }

        // 将文件保存到项目目录
        var filePath = Path.Combine(_uploadDirectory, jsonFile.FileName);
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await jsonFile.CopyToAsync(fileStream); // 将上传的文件复制到目标位置
        }

        // 从文件读取内容并使用Redis缓存
        var jsonData = await System.IO.File.ReadAllTextAsync(filePath); // 读取文件内容

        // 反序列化JSON以验证其格式
        var data = JsonSerializer.Deserialize<object>(jsonData); // 反序列化

        // 获取文件大小(以MB为单位)
        var fileSizeKB = jsonFile.Length / (1024.0 * 1024.0);
        await _redisString.UploadFile(); // 调用上传文件方法

        return Ok(new
        {
            Message = "文件上传成功",
            FileSizeMB = fileSizeKB // 返回文件大小
        });
    }
    catch (JsonException)
    {
        return BadRequest("无效的JSON格式。");
    }
    catch (Exception)
    {
        return StatusCode(500"内部服务器错误。");
    }
}

2. 使用RedisJSON的示例代码

[HttpPost("upload-file")]
public async Task<IActionResult> UploadJson([FromForm] IFormFile jsonFile)
{
    if (jsonFile == null || jsonFile.Length == 0)
    {
        return BadRequest("无效文件或未上传文件。");
    }

    var fileExtension = Path.GetExtension(jsonFile.FileName);
    if (fileExtension.ToLower() != ".json")
    {
        return BadRequest("只允许上传JSON文件。");
    }

    if (jsonFile.ContentType != "application/json")
    {
        return BadRequest("文件内容类型无效,只允许JSON文件。");
    }

    try
    {
        // 删除项目目录中的旧JSON文件
        var jsonFiles = Directory.GetFiles(_uploadDirectory, "*.json");
        foreach (var file in jsonFiles)
        {
            System.IO.File.Delete(file);
        }

        // 将文件保存到项目目录
        var filePath = Path.Combine(_uploadDirectory, jsonFile.FileName);
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await jsonFile.CopyToAsync(fileStream); // 将上传的文件复制到目标位置
        }

        // 从文件读取内容并使用Redis缓存
        var jsonData = await System.IO.File.ReadAllTextAsync(filePath); // 读取文件内容

        // 反序列化JSON以验证其格式
        var data = JsonSerializer.Deserialize<object>(jsonData); // 反序列化

        // 获取文件大小(以MB为单位)
        var fileSizeKB = jsonFile.Length / (1024.0 * 1024.0);
        await _accessRepository.UploadFile(); // 调用上传文件方法

        return Ok(new
        {
            Message = "文件上传成功",
            FileSizeMB = fileSizeKB // 返回文件大小
        });
    }
    catch (JsonException)
    {
        return BadRequest("无效的JSON格式。");
    }
    catch (Exception)
    {
        return StatusCode(500"内部服务器错误。");
    }
}

以上代码分别展示了如何使用RedisString和RedisJSON上传文件并保存数据。

七. 性能指标对比

通过对get-dataById

check-KeyExists两个端点的性能测试,我们可以分析RedisString和RedisJSON的表现。

1. 使用RedisString获取数据

public async Task<(List<ActionAccessRight> AccessRights, TimeSpan Latency, int DataSize)> GetAccessRightByIdAsync(long menuId, long accessRightId)
{
    List<ActionAccessRight> accessRights;
    TimeSpan redisLatency = new TimeSpan();
    var stopwatch = new Stopwatch();
    stopwatch.Start(); // 启动计时器

    // 从Redis获取数据
    var result = await _cache.GetStringAsync(cacheKey);
    var res = JsonSerializer.Deserialize<List<ActionAccessRight>>(result.ToString()); // 反序列化数据

    // 使用LINQ获取menuId和accessRightId对应的数据
    accessRights = res.Where(a => a.MenuId == menuId && a.AccessRightId == accessRightId).ToList();
    stopwatch.Stop(); // 停止计时器
    redisLatency = stopwatch.Elapsed; // 记录延迟

    // 处理的数据大小
    int dataSize = (int)ConvertBytesToKB((result?.ToString()?.Length ?? 0) * sizeof(char));

    return (accessRights, redisLatency, dataSize);
}

2. 使用RedisJSON获取数据

public async Task<(List<ActionAccessRight> AccessRights, TimeSpan Latency, int DataSize)> GetAccessRightByIdAsync(long menuId, long accessRightId)
{
    List<ActionAccessRight> accessRights = null;
    var stopwatch = new Stopwatch();
    TimeSpan redisLatency = new TimeSpan();
    var jsonPath = $"$..[?(@.MenuId=={menuId} && @.AccessRightId=={accessRightId})]"// 定义JSON路径

    stopwatch.Start(); // 启动计时器
    var result = await _redisDb.JSON().GetAsync(cacheKey, path: jsonPath); // 获取RedisJSON数据
    stopwatch.Stop(); // 停止计时器
    redisLatency = stopwatch.Elapsed; // 记录延迟

    // 处理的数据大小
    int dataSize = (int)ConvertBytesToKB((result?.ToString()?.Length ?? 0) * sizeof(char));

    if (!result.IsNull)
    {
        accessRights = JsonSerializer.Deserialize<List<ActionAccessRight>>(result.ToString()); // 反序列化数据
    }

    return (accessRights, redisLatency, dataSize);
}

在上述两个示例中,我们比较了使用RedisString和RedisJSON获取数据的性能。

3. 性能对比结果

  RedisString RedisJSON
Latency(ms) 472 305
Data Received(Kb) 37196 50

八. 结论

通过运行此ASP.NET Core Web API项目,可以清晰地看到RedisJSON在处理大型或复杂JSON数据时相较于RedisString的性能优势。RedisString简单易用,但在大规模JSON处理上效率低下,因为它缺乏部分更新功能、内存消耗较高和网络开销增加。而RedisJSON通过其对JSON文档的部分更新和检索能力,提供了更高效的解决方案。

关键要点

  1. RedisString适用于简单用例,但在处理大型或频繁更新的JSON对象时效率较低。
  2. RedisJSON提供部分更新、更高效的内存使用、原子操作和更快的查询,适合现代应用处理复杂嵌套数据结构。
  3. 通过理解和分析RedisString与RedisJSON的性能,您可以更明智地决定如何根据特定应用需求在Redis中存储和管理JSON数据。