使用Redis处理JSON数据的比较:RedisString与RedisJSON
一. 引言
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文档的部分更新和检索能力,提供了更高效的解决方案。
关键要点
-
RedisString适用于简单用例,但在处理大型或频繁更新的JSON对象时效率较低。 -
RedisJSON提供部分更新、更高效的内存使用、原子操作和更快的查询,适合现代应用处理复杂嵌套数据结构。 -
通过理解和分析RedisString与RedisJSON的性能,您可以更明智地决定如何根据特定应用需求在Redis中存储和管理JSON数据。