2881099 / FreeRedis

🦄 FreeRedis is .NET40+ redis client. supports cluster, sentinel, master-slave, pub-sub, lua, pipeline, transaction, streams, client-side-caching, and pooling.
MIT License
913 stars 163 forks source link

HMGET with client side caching #123

Closed tky753 closed 1 year ago

tky753 commented 1 year ago

I use script something like

local rst = redis.call('HSETNX', KEYS[1], 'absexp', ARGV[1])
if rst ~= 1 then
  return rst
end
redis.call('HSET', KEYS[1], 'sldexp', ARGV[2], 'data', ARGV[4])
if ARGV[3] ~= '-1' then
  redis.call('EXPIRE', KEYS[1], ARGV[3])
end
return 1

to set cache data. And I need to use HMGET to get data, Now I want to use redis 6 client side caching to cache 'data': ARGV[4], does FreeRedis support this?

tky753 commented 1 year ago

If not, does FreeRedis support independent server for receiving key expire message from redis

2881099 commented 1 year ago

提供测试 demo

tky753 commented 1 year ago
var cli = new RedisClient("192.168.1.9:6379,password=xxxxxx,defaultDatabase=xx");
bool CheckExpired(string key, DateTime dt) => DateTime.Now.Subtract(dt) > TimeSpan.FromSeconds(5);
bool KeyFilter(string key) => key.StartsWith("k");
cli.UseClientSideCaching(new ClientSideCachingOptions
{
    //本地缓存的容量
    Capacity = 3,
    //过滤哪些键能被本地缓存
    KeyFilter = KeyFilter,
    //检查长期未使用的缓存
    CheckExpired = CheckExpired
});
const string script = """
local rst = redis.call('HSETNX', KEYS[1], 'absexp', ARGV[1])
if rst ~= 1 then
return rst
end
redis.call('HSET', KEYS[1], 'sldexp', ARGV[2], 'data', ARGV[4])
if ARGV[3] ~= '-1' then
redis.call('EXPIRE', KEYS[1], ARGV[3])
end
return 1
""";
var rst = cli.Eval(script, new[] { "key" }, 1, 1, 5, "{}");
var d1 = cli.HMGet("key");
Thread.Sleep(TimeSpan.FromSeconds(1));
var d2 = cli.HMGet("key"); //should use client side cache
tky753 commented 1 year ago

If I invoke this function,

void ClientTracking(bool on_off, long? redirect, string[] prefix, bool bcast, bool optin, bool optout, bool noloop)

how can i receive tracked information?

2881099 commented 1 year ago

如果我没记错,ClientSideCaching 只针对 strings 类型。

hash 及其他复合类型不支持。

tky753 commented 1 year ago

如果我没记错,ClientSideCaching 只针对 strings 类型。

hash 及其他复合类型不支持。

我尝试这样调用

var cli = new RedisClient("192.168.1.9:6379,password=xxxxxx,defaultDatabase=xx");
bool CheckExpired(string key, DateTime dt)
{
    return DateTime.Now.Subtract(dt) > TimeSpan.FromSeconds(1);
}

bool KeyFilter(string key)
{
    return key.StartsWith("k");
}

cli.UseClientSideCaching(new ClientSideCachingOptions
{
    //本地缓存的容量
    Capacity = 3,
    //过滤哪些键能被本地缓存
    KeyFilter = KeyFilter,
    //检查长期未使用的缓存
    CheckExpired = CheckExpired
});
cli.HSet("k:1", "f1", "v");
cli.Expire("k:1", 10);
var h = cli.HGet("k:1", "f1");

然后在FreeRedis\src\FreeRedis\ClientSideCaching.cs

void InValidate(string chan, object msg)
{
    if (msg == null)
    {
        //flushall
        lock (_dictLock) _dictSort.Clear();
        _dict.Clear();
        return;
    }
    var keys = msg as object[];
    if (keys != null)
    {
        foreach (var key in keys)
            RemoveCache(string.Concat(key));
    }
}

这个方法设断点,在10秒后,我发现是会调用到InValidate这个方法的

tky753 commented 1 year ago

能否在InValidate方法内 触发一个事件,让使用者可以进行一定程度地自定义处理expire key事件 比如我已经自定义了一个InMemoryCache,想要在用它来存储 client side cache,这样我只需要一个InValidate事件就够了。 不需要FreeRedis默认的UseClientSideCaching实现

tky753 commented 1 year ago

能否提供一个例子,我想要用 client tracking on bcast prefix 前缀 这种方式来使用 client side caching, 用freeredis应该怎么写?

tky753 commented 1 year ago

我尝试这样写:

cli.ClientTracking(true, null, new []{"k"}, true, false, false, false);
cli.Subscribe("__redis__:invalidate", InValidate);

void InValidate(string chan, object msg)
{
    _output.WriteLine($"{chan}:{JsonSerializer.Serialize(msg)}");
}

但是收不到消息,是不是因为redirect传空了?

tky753 commented 1 year ago

成了:

var sub = cli.Subscribe("__redis__:invalidate", InValidate) as IPubSubSubscriber;
cli.ClientTracking(true, sub.RedisSocket.ClientId, new []{"k"}, true, false, false, false);
tky753 commented 1 year ago

上面的写法有个严重的问题:调用 HSet,调用Expire 以及真expire时 都会触发一次 __redis__:invalidate 要改成这样写就只在 真expire 的时候触发一次了:

var sub = cli.Subscribe("__redis__:invalidate", InValidate) as IPubSubSubscriber;
cli.ClientTracking(true, sub.RedisSocket.ClientId, new []{"k"}, true, false, false, true);

区别就是ClientTracking最后那个参数 noloop要设置为true