890 lines
45 KiB
C#
890 lines
45 KiB
C#
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;
|
|
|
|
|
|
// HANDOVER: RedisWithLuaScriptExecutorBase 참조하고, Redis에 저장하고자 하는 정보의 무결성을 보장해 주기위한 구조를 제공 한다.
|
|
|
|
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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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<bool> 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;
|
|
}
|
|
}
|