using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reactive; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Amazon.DynamoDBv2.DocumentModel; using Renci.SshNet.Security; using StackExchange.Redis; namespace ServerCore; public static class RedisWithLuaScriptExecutorHelper { //============================================================================================= // 랭킹 맴버를 추가하고 최고 순위자를 반환하고 차순위자들을 제거하지 않는다. - kangms //============================================================================================= public static async Task<(bool, string, double, string, double, bool, int)> tryPlaceTopRank( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string rankKey, string memberId, double memberPoint , string blockKey ) { // Lua 스크립트 정의 var lua_script_name = "placeTopRank"; var lua_script = @" local blockKey = KEYS[2] local rankKey = KEYS[1] local rankerId = ARGV[1] local rankerPoint = tonumber(ARGV[2]) local blockKeyExists = redis.call('EXISTS', blockKey) if blockKeyExists == 1 then return { 1, '', 0, '', 0 } end redis.call('ZADD', rankKey, rankerPoint, rankerId) local currentHighestRanker = redis.call('ZREVRANGE', rankKey, 0, 0, 'WITHSCORES') local result = {} local rankerTotalCount = redis.call('ZCARD', rankKey) if currentHighestRanker[1] ~= nil then local currentHighestPoint = tonumber(currentHighestRanker[2]) local currentHighestRankerId = currentHighestRanker[1] if rankerPoint > currentHighestPoint then rankerTotalCount = redis.call('ZCARD', rankKey) result = { 0, rankerId, rankerPoint, currentHighestRankerId, currentHighestPoint, rankerTotalCount } else result = { 0, currentHighestRankerId, currentHighestPoint, '', 0, rankerTotalCount } end else totalRankers = redis.call('ZCARD', rankKey) result = { 0, rankerId, rankerPoint, '', 0, rankerTotalCount } end return result "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if(false == is_success) { return (false, string.Empty, 0, string.Empty, 0, false, 0); } bool is_blocked = false; string curr_ranker_id = string.Empty; double curr_ranker_point = 0; string old_ranker_id = string.Empty; double old_ranker_point = 0; bool is_top_change = false; int ranker_count = 0; try { // 루아 스크립트 실행, KEYS[1]에 rankKey, ARGV[1]에 memberId, ARGV[2]에 memberPoint 전달 (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { rankKey, blockKey } , new RedisValue[] { memberId, memberPoint }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : rankkey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint} - LuaScriptName:{lua_script_name}"); return (false, string.Empty, 0, string.Empty, 0, is_top_change, 0); } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!"); // 반환된 Lua 스크립트 결과: 최고가 입찰자 정보, 차순위 입찰자 정보, 순위자 개수 is_blocked = (false == redis_result[0].IsNull) ? (bool)redis_result[0] : true; curr_ranker_id = (false == redis_result[1].IsNull) ? redis_result[1].ToString() : string.Empty; curr_ranker_point = (false == redis_result[2].IsNull) ? (double)redis_result[2] : 0; old_ranker_id = (false == redis_result[3].IsNull) ? redis_result[3].ToString() : string.Empty; old_ranker_point = (false == redis_result[4].IsNull) ? (double)redis_result[4] : 0; ranker_count = (false == redis_result[5].IsNull) ? (int)redis_result[5] : 0; if (true == is_blocked) { var err_msg = $"Raking is Blocked !!! : rankkey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}"; Log.getLogger().warn(err_msg); return (false, curr_ranker_id, curr_ranker_point, old_ranker_id, old_ranker_point, is_top_change, ranker_count); } // 탑랭커 변경 여부 if (false == old_ranker_id.isNullOrWhiteSpace() && curr_ranker_id != old_ranker_id) { is_top_change = true; } } catch (Exception e) { var err_msg = $"Exception !!!, Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, rankKey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint}"; Log.getLogger().error(err_msg); return (false, string.Empty, 0, string.Empty, 0, is_top_change, 0); } return (true, curr_ranker_id, curr_ranker_point, old_ranker_id, old_ranker_point, is_top_change, ranker_count); } //============================================================================================= // 랭킹 맴버를 추가하고 최고 순위자와 직전 최고 순위자가 있다면 반환 하며, 차순위자는 제거 한다. - kangms //============================================================================================= public static async Task<(bool, string, double, string, double, bool)> tryPlaceTopRankOnly( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string rankKey, string memberId, double memberPoint , string blockKey ) { // Lua 스크립트 정의 var lua_script_name = "placeTopRankOnly"; var lua_script = @" local blockKey = KEYS[2] local rankKey = KEYS[1] local rankerId = ARGV[1] local rankerPoint = tonumber(ARGV[2]) local blockKeyExists = redis.call('EXISTS', blockKey) if blockKeyExists == 1 then return { 1, '', 0, '', 0, 0 } end local currentHighestRanker = redis.call('ZREVRANGE', rankKey, 0, 0, 'WITHSCORES') local result = {} if currentHighestRanker[1] ~= nil then local currentHighestPoint = tonumber(currentHighestRanker[2]) local currentHighestRankerId = currentHighestRanker[1] if rankerPoint > currentHighestPoint then redis.call('ZREM', rankKey, currentHighestRankerId) redis.call('ZADD', rankKey, rankerPoint, rankerId) result = { 0, rankerId, rankerPoint, currentHighestRankerId, currentHighestPoint, 1 } else result = { 0, currentHighestRankerId, currentHighestPoint, '', 0, 0 } end else redis.call('ZADD', rankKey, rankerPoint, rankerId) result = { 0, rankerId, rankerPoint, '', 0, 1 } end return result "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return (false, string.Empty, 0, string.Empty, 0, false); } bool is_blocked = false; string curr_ranker_id = string.Empty; double curr_ranker_point = 0; string old_ranker_id = string.Empty; double old_ranker_point = 0; bool is_top_change = false; // 첫번째로 등록되는 랭커일 경우에도 true 로 반환 !!! try { // 루아 스크립트 실행, KEYS[1]에 rankKey, ARGV[1]에 memberId, ARGV[2]에 memberPoint 전달 (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { rankKey, blockKey } , new RedisValue[] { memberId, memberPoint }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : rankkey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}"); return (false, string.Empty, 0, string.Empty, 0, false); } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!"); // 반환된 Lua 스크립트 결과: 최고가 입찰자 정보, 차순위 입찰자 정보 is_blocked = (false == redis_result[0].IsNull) ? (bool)redis_result[0] : true; curr_ranker_id = (false == redis_result[1].IsNull) ? redis_result[1].ToString() : string.Empty; curr_ranker_point = (false == redis_result[2].IsNull) ? (double)redis_result[2] : 0; old_ranker_id = (false == redis_result[3].IsNull) ? redis_result[3].ToString() : string.Empty; old_ranker_point = (false == redis_result[4].IsNull) ? (double)redis_result[4] : 0; is_top_change = (false == redis_result[5].IsNull) ? (bool)redis_result[5] : false; if (true == is_blocked) { var err_msg = $"Raking is Blocked !!! : rankkey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}"; Log.getLogger().warn(err_msg); return (false, curr_ranker_id, curr_ranker_point, old_ranker_id, old_ranker_point, is_top_change); } } catch (Exception e) { var err_msg = $"Exception !!!, Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, rankKey:{rankKey}, memberId:{memberId}, memberPoint:{memberPoint} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return (false, string.Empty, 0, string.Empty, 0, false); } return (true, curr_ranker_id, curr_ranker_point, old_ranker_id, old_ranker_point, is_top_change); } //============================================================================================= // 탑랭커 정보를 반환하고, 랭킹 정보를 블록 상태로 설정한다. - kangms //============================================================================================= public static async Task<(bool, bool, string, double)> tryGetTopRankAndSetBlock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string rankKey, string blockKey ) { // Lua 스크립트 정의 var lua_script_name = "getTopRankAndSetBlock"; var lua_script = @" local blockKey = KEYS[2] local rankKey = KEYS[1] -- 블록 상태 설정 (무제한 TTL) redis.call('SET', blockKey, 1) -- 현재 최고 랭커 조회 local currentHighestRanker = redis.call('ZREVRANGE', rankKey, 0, 0, 'WITHSCORES') if currentHighestRanker[1] == nil then return { 0, '', 0 } -- 최고 랭커가 없을 경우 end local currentHighestRankerId = currentHighestRanker[1] local currentHighestPoint = tonumber(currentHighestRanker[2]) -- 결과 반환 (성공, 최고 랭커 ID, 점수) return { 1, currentHighestRankerId, currentHighestPoint } "; // Lua 스크립트를 Redis 서버에 로드 var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return (false, false, string.Empty, 0); } bool is_top_found = false; string top_ranker_id = string.Empty; double top_ranker_point = 0; try { // Lua 스크립트 실행, KEYS[1]에 rankKey, KEYS[2]에 blockKey 전달 (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { rankKey, blockKey } , new RedisValue[] { } ); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : rankKey:{rankKey}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}"); return (false, false, string.Empty, 0); } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!"); // Lua 스크립트 결과 처리: 최고 랭커 정보 is_top_found = (bool)redis_result[0]; top_ranker_id = redis_result[1]?.ToString() ?? string.Empty; top_ranker_point = redis_result[2].IsNull ? 0 : (double)redis_result[2]; } catch (Exception e) { var err_msg = $"Exception !!!, Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, rankKey:{rankKey}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return (false, false, string.Empty, 0); } return (is_success, is_top_found, top_ranker_id, top_ranker_point); } //============================================================================================= // 랭킹 키와 블록 키를 제거 한다. - kangms //============================================================================================= public static async Task tryRemoveTopRankAndBlock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string rankKey, string blockKey ) { // Lua 스크립트 정의 var lua_script_name = "removeTopRankAndBlock"; var lua_script = @" local rankKey = KEYS[1] local blockKey = KEYS[2] redis.call('DEL', rankKey) redis.call('DEL', blockKey) return 1 "; // Lua 스크립트를 Redis 서버에 로드 var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return false; } bool is_result = false; try { // Lua 스크립트 실행, KEYS[1]에 rankKey, KEYS[2]에 blockKey 전달 (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { rankKey, blockKey } , new RedisValue[] { }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : rankKey:{rankKey}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}"); return false; } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!"); is_result = (bool)redis_result; } catch (Exception e) { var err_msg = $"Exception !!!, Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, rankKey:{rankKey}, blockKey:{blockKey} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return false; } return is_success; } //============================================================================================= // 데이터를 저장한 후, 즉시 Lock을 해제합니다. kangms //============================================================================================= public static async Task tryWriteAndReleaseLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string lockKey, string lockValue , string toWriteDataKey, string toWriteDataValue ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "writeAndReleaseLock"; var lua_script = @" local currentLock = redis.call('GET', KEYS[1]) -- Get current lock value (write lock key) local ownershipKey = ARGV[1] -- Ownership key (e.g., user or session ID) local newData = ARGV[2] -- New data to store if currentLock == false then -- Lock doesn't exist, set lock, store data redis.call('SET', KEYS[1], ownershipKey) redis.call('SET', KEYS[2], newData) -- Store new data redis.call('DEL', KEYS[1]) -- Immediately release lock return 1 elseif currentLock == ownershipKey then -- Lock exists and ownership matches, update data redis.call('SET', KEYS[2], newData) redis.call('DEL', KEYS[1]) -- Immediately release lock return 1 else -- Lock exists but ownership doesn't match return 0 end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return false; } try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { lockKey, toWriteDataKey } , new RedisValue[] { lockValue, toWriteDataValue }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, toWriteDataKey:{toWriteDataKey} - LuaScriptName:{lua_script_name}"); return false; } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}"); var return_value = (int)redis_result; if (0 == return_value) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}"); return false; } } catch (Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, toWriteDataKey:{toWriteDataKey} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return false; } return true; } //============================================================================================= // 데이터를 저장하면서 WriteLock을 설정하고 TTL을 유지한다. WriteLock을 해제하지 않고 유지한다. - kangms //============================================================================================= public static async Task tryWriteWithLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string lockKey, string lockValue , string toWriteDataKey, string toWriteDataValue , Int32 ttlSec ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "writeWithLock"; var lua_script = @" local currentLock = redis.call('GET', KEYS[1]) -- Get current lock value (write lock key) local ownershipKey = ARGV[1] -- Ownership key (e.g., user or session ID) local newData = ARGV[2] -- New data to store local ttl = tonumber(ARGV[3]) -- TTL 값 (초 단위) if currentLock == false then -- Lock doesn't exist, set lock, store data, and set TTL redis.call('SET', KEYS[1], ownershipKey) redis.call('EXPIRE', KEYS[1], ttl) -- Set TTL for the lock redis.call('SET', KEYS[2], newData) -- Store new data return 1 -- 성공시 1 반환 elseif currentLock == ownershipKey then -- Lock exists and ownership matches, update data redis.call('SET', KEYS[2], newData) return 1 -- 성공시 1 반환 else -- Lock exists but ownership doesn't match return 0 -- 실패시 0 반환 end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return false; } try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { lockKey, toWriteDataKey } , new RedisValue[] { lockValue, toWriteDataValue, ttlSec }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, toWriteDataKey:{toWriteDataKey} - LuaScriptName:{lua_script_name}"); return false; } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}"); var return_value = (int)redis_result; if (0 == return_value) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}"); return false; } } catch (Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, toWriteDataKey:{toWriteDataKey}, ttlSec:{ttlSec} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return false; } return true; } //============================================================================================= // WriteLock 소유 여부와 상관없이 toReadDataKey로 데이터를 읽기만 한다. - kangms //============================================================================================= public static async Task<(bool, string)> tryReadWithLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string lockKey, string lockValue , string toReadDataKey , Int32 ttlSec ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "readWithLock"; var lua_script = @" local currentLock = redis.call('GET', KEYS[1]) -- 현재 잠금 상태 확인 local ownershipKey = ARGV[1] -- 소유권 키 (예: 사용자 ID 또는 세션 ID) local ttl = tonumber(ARGV[2]) -- TTL 값 (초 단위) if currentLock == ownershipKey then -- 소유권이 일치하면 TTL 갱신 및 데이터 반환 redis.call('EXPIRE', KEYS[1], ttl) -- TTL 갱신 local data = redis.call('GET', KEYS[2]) -- 데이터 가져오기 (nil 일수 있다) return { 1, data } -- 성공: {1, 데이터} 형태로 반환 else -- 소유권이 일치하지 않으면 실패 메시지 반환 return { 0, nil } end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return (false, string.Empty); } var return_data = string.Empty; try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { lockKey, toReadDataKey } , new RedisValue[] { lockValue, ttlSec } ); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, toReadDataKey:{toReadDataKey} - LuaScriptName:{lua_script_name}"); return (false, string.Empty); } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}"); var return_result = (false == redis_result[0].IsNull) ? (int)redis_result[0] : 0; return_data = (false == redis_result[1].IsNull) ? redis_result[1].ToString() : string.Empty; if (0 == return_result) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_result} - LuaScriptName:{lua_script_name}"); return (false, string.Empty); } } catch(Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, toReadDataKey:{toReadDataKey}, ttlSec:{ttlSec} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return (false, string.Empty); } return (true, return_data); } //============================================================================================= // WriteLock 소유 여부와 상관없이 toReadDataKey로 데이터를 읽기만 한다. - kangms //============================================================================================= public static async Task<(bool, string)> tryReadWithNoLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string toReadDataKey ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "readWithNoLock"; var lua_script = @" local data = redis.call('GET', KEYS[1]) -- key에 해당하는 data 반환 if data then return { 1, data } -- 성공시 1과 데이터 반환 else return { 0, nil } -- 실패시 0과 nil 반환 end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return (false, string.Empty); } var return_data = string.Empty; try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { toReadDataKey } ); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : toReadDataKey:{toReadDataKey} - LuaScriptName:{lua_script_name}"); return (false, string.Empty); } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}"); var return_result = (false == redis_result[0].IsNull) ? (int)redis_result[0] : 0; return_data = (false == redis_result[1].IsNull) ? redis_result[1].ToString() : string.Empty; if (0 == return_result) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_result} - LuaScriptName:{lua_script_name}"); return (false, string.Empty); } } catch(Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, toReadDataKey:{toReadDataKey} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return (false, string.Empty); } return (true, return_data); } //============================================================================================= // 현재 키를 비교하고 같다면 새로운 값으로 변경 한다. - kangms //============================================================================================= public static async Task compareAndSetWithLua( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string key , string expectedValue, string newValue ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "compareAndSet"; var lua_script = @" local current = redis.call('GET', KEYS[1]) if current == nil or current == false or current == ARGV[1] then redis.call('SET', KEYS[1], ARGV[2]) return 1 -- 성공시 1 반환 else return 0 -- 실패시 0 반환 end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return false; } try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { key } , new RedisValue[] { expectedValue, newValue }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : key:{key}, expectedValue:{expectedValue}, newValue:{newValue} - LuaScriptName:{lua_script_name}"); return false; } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!! - LuaScriptName:{lua_script_name}"); var return_value = (int)redis_result; if (0 == return_value) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}"); return false; } } catch(Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, key:{key}, expectedValue:{expectedValue}, newValue:{newValue} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return false; } return true; } //============================================================================================= // 현재 lockKey가 존재하는지 체크하고 lockKey가 없거나, lockKey의 lockValue값이 같을 경우 true 반환, 그 외에는 false-를 반환 한다. - kangms //============================================================================================= public static async Task tryAcquireLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string lockKey, string lockValue , Int32 ttlSec ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "acquireLock"; var lua_script = @" local ttl = tonumber(ARGV[2]) -- TTL 값 (초 단위) local current = redis.call('GET', KEYS[1]) if current == nil or current == false or current == ARGV[1] then redis.call('SET', KEYS[1], ARGV[1], 'EX', ttl) return 1 -- 성공시 1 반환 else return 0 -- 실패시 0 반환 end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return false; } try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { lockKey } , new RedisValue[] { lockValue, ttlSec }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, lockValue:{lockValue}, ttlSec:{ttlSec} - LuaScriptName:{lua_script_name}"); return false; } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!"); var return_value = (int)redis_result; if (0 == return_value) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}"); return false; } } catch(Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, lockValue:{lockValue}, ttlSec:{ttlSec} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); return false; } return true; } //============================================================================================= // 현재 lockKey가 존재하는지 체크하고 lockKey가 있고 lockValue가 같다면 true 를 반환하고, 그 외에는 false를 반환 한다. - kangms //============================================================================================= public static async Task tyrReleaseLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string lockKey, string lockValue ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "releaseLock"; var lua_script = @" local current = redis.call('GET', KEYS[1]) if current == ARGV[1] then redis.call('DEL', KEYS[1]) return 1 -- 성공시 1 반환 else return 0 -- 실패시 0 반환 end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return false; } try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { lockKey } , new RedisValue[] { lockValue }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, lockValue:{lockValue} - LuaScriptName:{lua_script_name}"); return false; } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!"); var return_value = (int)redis_result; if (0 == return_value) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}"); return false; } } catch (Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, lockValue:{lockValue} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); } return true; } //============================================================================================= // 현재 lockKey가 존재하는지 체크하고 lockKey의 lockValue값이 같을 경우 ttlMSec 설정 한다. 성공시 1, 실패시 0을 반환 한다. - kangms //============================================================================================= public static async Task tyrResetTTLWithLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string lockKey, string lockValue , Int32 ttlMSec ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "resetTtlWithLock"; var lua_script = @" local currentLock = redis.call('GET', KEYS[1]) -- Get current lock value (write lock key) local ownershipKey = ARGV[1] -- Ownership key (e.g., user or session ID) local ttl = tonumber(ARGV[2]) -- New TTL in seconds if currentLock == ownershipKey then -- 소유권이 일치하면 TTL 갱신 redis.call('EXPIRE', KEYS[1], ttl) return 1 -- Success else -- 소유권이 일치하지 않음 return 0 -- Failure end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return false; } try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { lockKey } , new RedisValue[] { lockValue, ttlMSec }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : lockKey:{lockKey}, ttl:{ttlMSec} - LuaScriptName:{lua_script_name}"); return false; } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!"); var return_value = (int)redis_result; if (0 == return_value) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}"); return false; } } catch (Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, lockKey:{lockKey}, ttl:{ttlMSec} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); } return true; } //============================================================================================= // targetKey로 TTL을 재설정할 키를 전달받고, ttl로 새로운 TTL 값을 전달받습니다. 성공시 true, 실패시 false을 반환 한다. - kangms //============================================================================================= public static async Task tyrResetTTLWithNoLock( this RedisWithLuaScriptExecutorBase luaScriptExecutor , string targetKey, Int32 ttlMSec ) { // lua 스크립트 레디스 set 구문 실행 구성 var lua_script_name = "resetTtlWithNoLock"; var lua_script = @" local ttl = tonumber(ARGV[1]) -- TTL in seconds local exists = redis.call('EXISTS', KEYS[1]) if exists == 1 then -- 키가 존재할 경우 TTL을 재설정 redis.call('EXPIRE', KEYS[1], ttl) return 1 -- 성공 시 1 반환 else -- 키가 존재하지 않으면 실패 return 0 -- 실패 시 0 반환 end "; var is_success = await luaScriptExecutor.tryLoadLuaScriptToRedisServer(lua_script_name, lua_script); if (false == is_success) { return false; } try { (is_success, var redis_result) = await luaScriptExecutor.tryExecuteLuaScriptAsync( lua_script_name , new RedisKey[] { targetKey } , new RedisValue[] { ttlMSec }); if (false == is_success) { Log.getLogger().error($"Failed to RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : targetKey:{targetKey}, ttl:{ttlMSec} - LuaScriptName:{lua_script_name}"); return false; } NullReferenceCheckHelper.throwIfNull(redis_result, () => $"redis_result is null !!!"); var return_value = (int)redis_result; if (0 == return_value) { Log.getLogger().error($"Failed to execute LuaScript in Redis !!! : retrunValue:{return_value} - LuaScriptName:{lua_script_name}"); return false; } } catch (Exception e) { var err_msg = $"Exception !!!, RedisWithLuaScriptExecutorBase.tryExecuteLuaScriptAsync() !!! : exception:{e}, targetKey:{targetKey}, ttl:{ttlMSec} - LuaScriptName:{lua_script_name}"; Log.getLogger().error(err_msg); } return true; } }