Redis原子性写入HASH结构数据并设置过期时间

更新时间:2023-06-25 18:36:47 阅读: 评论:0

Redis原⼦性写⼊HASH结构数据并设置过期时间
Redis中提供了原⼦性命令或SET来写⼊STRING类型数据并设置Key的过期时间:
> SET key value EX 60 NX
ok
> SETEX key 60 value
ok
但对于HASH结构则没有这样的命令,只能先写⼊数据然后设置过期时间:
> HSET key field value
ok
> EXPIRE key 60
ok
这样就带了⼀个问题:HSET命令执⾏成功⽽EXPIRE命令执⾏失败(如命令未能成功发送到Redis服务器),那么数据将不会过期。针对这个问题,本⽂提供了⼏种解决⽅案:
Lua脚本
向Redis中写⼊HASH结构的Lua脚本如下:
local fieldIndex=3
local valueIndex=4
local key=KEYS[1]
local fieldCount=ARGV[1]
local expired=ARGV[2]
for i=1,fieldCount,1do
redis.pcall('HSET',key,ARGV[fieldIndex],ARGV[valueIndex])
fieldIndex=fieldIndex+2
valueIndex=valueIndex+2
end
redis.pcall('EXPIRE',key,expired)
,需要将脚本内容单⾏化,并以分号间隔不同的命令:
>  SCRIPT LOAD "local fieldIndex=3;local valueIndex=4;local key=KEYS[1];local fieldCount=ARGV[1];local expired=ARGV[2];for i=1,fieldCount,1 do redis.pcall('HSET',key,ARGV[fieldIndex],ARGV[valueIndex]) fieldIndex=fieldIndex+2 valueInde "e03e7868920b7669d1c8c8b16dcee86ebfac650d"
> evalsha e03e7868920b7669d1c8c8b16dcee86ebfac650d 1 key 21000 field1 value1 field2 value2
nil
写⼊结果:
使⽤执⾏Lua脚本:
public async Task WriteAsync(string key, IDictionary<string, string> valueDict, TimeSpan expiry)
{
async Task func()
{
if (valueDict.Empty())
{
return;
}
var luaScriptPath = $"{AppDomain.CurrentDomain.BaDirectory}/Lua/HSET.lua";
var script = File.ReadAllText(luaScriptPath);
var conds = (int)Math.Ceiling(expiry.TotalSeconds);
var fieldCount = valueDict.Count;
var redisValues = new RedisValue[fieldCount * 2 + 2];
redisValues[0] = fieldCount;
redisValues[1] = conds;
var i = 2;
foreach (var item in valueDict)
{
redisValues[i] = item.Key;
redisValues[i + 1] = item.Value;
i += 2;
}
//await Databa.ScriptEvaluateAsync(script, new RedisKey[] { key, fieldCount.ToString(), conds.ToString() }, redisValues);
await Databa.ScriptEvaluateAsync(script, new RedisKey[] { key }, redisValues);
}
await ExecuteCommandAsync(func, $"redisError:hashWrite:{key}");
}
事务
在事务⼀节中指出:Redis命令只会在有语法错误或对Key使⽤了错误的数据类型时执⾏失败。因此,只要我们保证将正确的写数据和设置过期时间的命令作为⼀个整体发送到服
务器端即可,使⽤Lua脚本正式基于此。
StackExchange.Redis官⽅⽂档中关于事务的说明,参见:
以下是代码实现:
public async Task<bool> WriteAsync(string key, IDictionary<string, string> valueDict, TimeSpan expiry)
{
var tranc = Databa.CreateTransaction();
foreach (var item in valueDict)
{
tranc.HashSetAsync(key, item.Key, item.Value);
}
tranc.KeyExpireAsync(key, expiry);
return await tranc.ExecuteAsync();
占位符
这种⽅案⽐较差,思路如下,共分为4步,每⼀步都有可能失败:
先写⼊⼀个特殊的值,如Nil表⽰⽆数据
若第⼀步操作成功,则Key被写⼊Redis。然后对Key设置过期时间。若第⼀步失败,则Key未写⼊Redis,设置过期时间会失败若成功设置Key的过期时间则像Redis中写⼊有效数据
删除第⼀步中设置的特殊值
在读取Hash的值时,判断读到的field的值是否是Nil,若是则删除并忽略,若不是则处理。
代码如下:
namespace RedisClient.Imples
{
public class RedisHashOperator : RedisCommandExecutor, IRedisHashOperator
{
private readonly string KeyExpiryPlaceHolder = "expiryPlaceHolder";
public RedisHashOperator(ILogger<RedisHashOperator> logger, IRedisConnection redisConnection)
: ba(logger, redisConnection)
{
}
public async Task WriteAsync(string key, IDictionary<string, string> valueDict, TimeSpan expiry)
{
async Task action()
{
if (valueDict.Empty())
相提并论造句{
return;
}
var hashList = new List<HashEntry>();
foreach (var value in valueDict)
{
hashList.Add(new HashEntry(value.Key, value.Value));
}
await Databa.HashSetAsync(key, hashList.ToArray());
}
async Task succesd()
手机电源键坏了怎么开机{
await ExecuteCommandAsync(action, $"redisEorror:hashWrite:{key}");
客服周报
}
await SetKeyExpireAsync(key, expiry, succesd);
}
public async Task<RedisReadResult<IDictionary<string, string>>> ReadAllFieldsAsync(string key)
{
async Task<RedisReadResult<IDictionary<string, string>>> func()
{
var redisReadResult = new RedisReadResult<IDictionary<string, string>>();
if (Databa.KeyExists(key) == fal)
{
return redisReadResult.Failed();
}
气可以组什么词语
var resultList = await Databa.HashGetAllAsync(key);
if (resultList == null)
{
return redisReadResult.Failed();
}
瓦缸
var dict = new Dictionary<string, string>();
if (resultList.Any())
{
foreach (var result in resultList)
{
if (result.Name == KeyExpiryPlaceHolder || result.Value == KeyExpiryPlaceHolder)
{
await RemoveKeyExpiryPlaceHolderAsync(key);
continue;
}
dict[result.Name] = result.Value;
}
}
return redisReadResult.Success(dict);
}
return await ExecuteCommandAsync(func, $"redisError:hashReadAll:{key}");
}
#region private
///<summary>
///设置HASH结构KEY的过期时间
///</summary>
///<param name="succesd">设置过期时间成功之后的回调函数</param>
private async Task SetKeyExpireAsync(string key, TimeSpan expiry, Func<Task> succesd)
{
// 确保KEY的过期时间写⼊成功之后再执其它的操作
await Databa.HashSetAsync(key, new HashEntry[] { new HashEntry(KeyExpiryPlaceHolder, KeyExpiryPlaceHolder) });
if (Databa.KeyExpire(key, expiry))
{
await succesd();
}
await Databa.HashDeleteAsync(key, KeyExpiryPlaceHolder);
}
private async Task RemoveKeyExpiryPlaceHolderAsync(string key)
{
await Databa.HashDeleteAsync(key, KeyExpiryPlaceHolder);
}
#endregion
}
}
⽂中多次出现的ExecuteCommandAsync⽅法主要⽬的是实现针对异常情况的统⼀处理,实现如下:
namespace RedisClient.Imples
{
public class RedisCommandExecutor
{
private readonly ILogger Logger;
protected readonly IDataba Databa;
public RedisCommandExecutor(ILogger<RedisCommandExecutor> logger, IRedisConnection redisConnection)
Logger = logger;
Databa = redisConnection.GetDataba();
}
protected async Task ExecuteCommandAsync(Func<Task> func, string errorMessage = null)
{
try
{
await func();
先绪}
catch (Exception ex)
{
李健歌手
if (string.IsNullOrEmpty(errorMessage))
{
errorMessage = ex.Message;
}
Logger.LogError(errorMessage, ex);
}
}
protected async Task<T> ExecuteCommandAsync<T>(Func<Task<T>> func, string errorMessage = null)        {
小211工程try
{
return await func();
}
catch (Exception ex)
{
if (string.IsNullOrEmpty(errorMessage))
{
errorMessage = ex.Message;
}
Logger.LogError(errorMessage, ex);
return default(T);
}
}
}
}

本文发布于:2023-06-25 18:36:47,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/1038174.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:设置   时间   命令   数据
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图