Files
caliverse_server/ServerCore/Redis/RedisConnectorBase.cs
2025-11-28 16:54:56 +09:00

264 lines
8.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Renci.SshNet;
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Core;
using StackExchange.Redis.Extensions.Core.Abstractions;
using StackExchange.Redis.Extensions.Core.Configuration;
using StackExchange.Redis.Extensions.Core.Implementations;
namespace ServerCore;
// HANDOVER: AWS S3 SDK Wrapper 클래스 이다.
// S3 저장소에 관리할 Bucket 정보를 관리해준다.
public abstract class RedisConnectorBase
{
private readonly string m_connection_string = string.Empty;
private readonly RedisConfiguration m_redis_config;
private ConnectionMultiplexer? m_connector;
private SshClient? m_ssh_tuneling;
private ForwardedPortLocal? m_forwarded_port_local;
public RedisConnectorBase(string connectionString)
{
m_redis_config = new RedisConfiguration()
{
ConnectionString = connectionString,
};
m_connection_string = connectionString;
}
public async Task<bool> tryConnectAsync()
{
if (false == isConfigureConnectionInfo())
{
return false;
}
try
{
if (false == isConnected())
{
ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true);
var config_options = ConfigurationOptions.Parse(m_connection_string);
var connection = await ConnectionMultiplexer.ConnectAsync(config_options);
if (null == connection)
{
var err_msg = $"Failed to connectAsync RedisServer !!! : ConnectString:{m_connection_string}";
throw new Exception(err_msg);
}
m_connector = connection;
connection.ConnectionFailed += onConnectfailed;
connection.ErrorMessage += onError;
connection.InternalError += onInternalError;
connection.ConnectionRestored += onConnectionRestored;
connection.ConfigurationChanged += onConfigurationChanged;
connection.ConfigurationChangedBroadcast += onMasterSlaveChanged;
}
}
catch(Exception e)
{
var err_msg = $"Exception !!!, Failed to perform in tryConnectAsync() !!! : exception:{e}, connectionString:{m_connection_string} - {toBasicString()}";
Log.getLogger().error(err_msg);
return false;
}
return true;
}
protected void setConnectionMultiplexer(ConnectionMultiplexer connector) => m_connector = connector;
public bool tryConnect()
{
if (false == isConfigureConnectionInfo())
{
return false;
}
try
{
if (false == isConnected())
{
var config_options = ConfigurationOptions.Parse(m_connection_string);
var connection = ConnectionMultiplexer.Connect(config_options);
if (null == connection)
{
var err_msg = $"Failed to connect RedisServer !!! : ConnectString:{m_connection_string}";
throw new Exception(err_msg);
}
m_connector = connection;
connection.ConnectionFailed += onConnectfailed;
connection.ErrorMessage += onError;
connection.InternalError += onInternalError;
connection.ConnectionRestored += onConnectionRestored;
connection.ConfigurationChanged += onConfigurationChanged;
connection.ConfigurationChangedBroadcast += onMasterSlaveChanged;
}
}
catch (Exception e)
{
var err_msg = $"Exception !!!, try connectAsync failed !!! : Exception:{e} - {toBasicString()}";
Log.getLogger().error(err_msg);
return false;
}
return true;
}
//=========================================================================================
// sshHost : ec2-54-69-223-245.us-west-2.compute.amazonaws.com // SSH 서버 (Jump Host or Bastion)
// sshPort : 2211 // SSH 포트
// sshUsername : "ubuntu" // SSH 사용자 이름
// privateKeyPath : @"D:\SVN\Caliverse\Metaverse\trunk\Caliverse\Server\Security\SSH-Keys\pem\USWest2-KeyPair.pem" // Private Key 보안 파일 참조 경로
// localPort : 16379 // 로컬에서 사용할 포트
// redisHost : "clustercfg.metaverse-qa-cluster.ocif0u.usw2.cache.amazonaws.com" // Redis 클러스터 호스트
// redisPort : 6379 // Redis 사용 포트
// redisPassword : "wiUaVvNwX4PhBj&8"
//=========================================================================================
public bool startTuneling( string sshHost, int sshPort, string sshUserName, string privateKeyPath
, int localPort, string redisHost, int redisPort, string redisPassword )
{
// SSH Private Key 설정
PrivateKeyFile privateKey = new PrivateKeyFile(privateKeyPath);
var keyFiles = new[] { privateKey };
try
{
var ssh_client = new SshClient(sshHost, sshPort, sshUserName, keyFiles);
// SSH 연결 시도
ssh_client.Connect();
// 포트 포워딩 설정 (로컬 포트 -> 원격 Redis 호스트)
var portForward = new ForwardedPortLocal("127.0.0.1", (uint)localPort, redisHost, (uint)redisPort);
ssh_client.AddForwardedPort(portForward);
portForward.Start();
m_forwarded_port_local = portForward;
m_ssh_tuneling = ssh_client;
}
catch(Exception e)
{
var err_msg = $"Exception !!!, Failed to function in startTuneling() !!! : Exception:{e}";
Log.getLogger().error(err_msg);
return false;
}
return true;
}
public void stopTuneling()
{
if (null != m_forwarded_port_local)
{
m_forwarded_port_local.Stop();
m_forwarded_port_local = null;
}
if(null != m_ssh_tuneling)
{
// SSH 연결 종료
m_ssh_tuneling.Disconnect();
m_ssh_tuneling = null;
}
}
protected bool isConfigureConnectionInfo()
{
if ( 0 >= m_connection_string.Length )
{
var err_msg = $"Invalid Configure of RedisConnector, Empty m_connectionString !!! - {toBasicString()}";
Log.getLogger().error(err_msg);
return false;
}
return true;
}
public bool isConnected()
{
if( null == m_connector
|| false == m_connector.IsConnected)
{
return false;
}
return true;
}
public IDatabase? getDatabase()
{
if(false == isConnected())
{
return null;
}
return m_connector?.GetDatabase();
}
public ConnectionMultiplexer? getConnectionMultiplexer() => m_connector;
#region event
protected virtual void onConnectionRestored(object? sender, ConnectionFailedEventArgs e)
{
Log.getLogger().warn($"Redis connection restored !!! : {e} - {toBasicString()}");
}
protected void onConnectfailed(object? sender, ConnectionFailedEventArgs e)
{
Log.getLogger().error($"Redis connect failed !!! : ConnectionFailed:{e.Exception} - {toBasicString()}");
}
protected void onError(object? sender, RedisErrorEventArgs e)
{
Log.getLogger().error($"Redis connection restored !!! : RedisError:{e} - {toBasicString()}");
}
protected void onInternalError(object? sender, InternalErrorEventArgs e)
{
Log.getLogger().error($"Redis internal error !!! : InternalError:{e} - {toBasicString()}");
}
protected void onConfigurationChanged(object? sender, EndPointEventArgs e)
{
Log.getLogger().warn($"Redis configuration changed !!! : EndPoint:{e} - {toBasicString()}");
}
protected void onMasterSlaveChanged(object? sender, EndPointEventArgs e)
{
Log.getLogger().warn($"Redis changed master-slave !!! : EndPoint:{e.EndPoint} - {toBasicString()}");
}
#endregion event
public string toBasicString()
{
return $"RedisConnector - Class:{this.getTypeName()}, ConnectString:{m_connection_string}";
}
}