Files
interactive/rcts/rc_sscd/src/TrafficControlThread.cpp
T
2026-08-07 17:38:18 +09:00

496 lines
19 KiB
C++

#include "TrafficControlThread.h"
#include "Logger.h"
#include "ProcessConfig.h"
#include "RcdbInfo.h"
#include "Database.h"
#include "ReportLog.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>
// 네트워크 통계 모듈의 sqlite SQLITE_BUSY 잦은 발생으로 INSERT 지연 발생 (#21440)
// 이로 인해 Traffic 통계 합산이 정상적으로 처리되지 않을 수 있으므로 다음과 같이 조정 처리
// - Timestamp delay 15 sec -> 16 sec 로 변경 처리 ( 기존 3.4 rc_statd 는 15 sec 였으나.. 신규 fimngd 적용에 따라 조정 처리)
// - sqlite lock timeout 값을 3 sec -> 1 sec 로 변경 처리
#define TIMESTAMP_DELAY_SEC 16 // 5분단위 timestamp 변경시... 변경 후.. 어느만큼 delay 를 허용할지 시간값.
#define SQLITE_DB_LOCK_TIMEOUT 1000 // 세션 중에 SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. (1 sec)
#define TRAFFIC_CONTROL_LOW_LIMIT 25 // 트래픽 제한 관련 전송률의 최소값(%), 해당값 이하로는 떨어지지 않도록 처리.
#define REPORT_TRAFFIC_CONTROL_LIMIT 50 // 트래픽 제한 관련 전송률이 해당 값(%) 이하로 떨어질 경우.. report 로그에 기록 처리한다.
// 생성자..
CTrafficControlThread::CTrafficControlThread()
{
// Thread Handle 초기화
m_threadHandle = 0;
}
// 소멸자..
CTrafficControlThread::~CTrafficControlThread()
{
// Thread 가 동작 중인 경우 Thread 동작 정지 처리.
if( m_threadHandle != 0 )
pthread_cancel(m_threadHandle);
}
// Thread 를 생성하여 작업을 시작한다...
bool CTrafficControlThread::Start()
{
int nResult = pthread_create( &m_threadHandle, NULL, CTrafficControlThread::threadFunc, this );
if( nResult != 0 )
{
// Thread 생성 실패시...
int errorNum = errno;
LOG( LERR, "TrafficControlThread: Thread create failed.[%d][%s]", errorNum, strerror(errorNum) );
return false;
}
return true;
}
// Thread 함수...
void * CTrafficControlThread::threadFunc( void * arg )
{
CTrafficControlThread * pObject = reinterpret_cast<CTrafficControlThread *>(arg);
pthread_detach( pthread_self() );
// 초기 기동시...통계 정보 및 timestamp 처리 관련 문제로 인해....
// 지정된 시간 sleep 했다가.. 작업을 시작한다.
sleep( TIMESTAMP_DELAY_SEC );
// 본 Thread 에서는
// RCDB t_sms_sp_svc_product_band 테이블에 저장된 서비스별 트래픽 제어 정보를 조회하여..
// Local DB 에 저장된 서비스별 Network 통계 정보를 바탕으로... 현재 5분간 트래픽을 어느 수준으로 제어할 것인지 판단하여...
// RCDB t_sms_sp_svc_product_band 테이블에 UPDATE 처리를 주기적으로 수행한다.
// 실제 본 thread function 에서 loop 를 동작하는 것이 아니라....
// 객체 execute 함수 내에서 loop 로 동작하도록 한다.
pthread_testcancel();
pObject->Execute();
pthread_testcancel();
// Thread 종료시 Handle 초기화
pObject->m_threadHandle = 0;
return NULL;
}
// RCDB t_sms_sp_svc_product_band 테이블에 저장된 서비스별 트래픽 제어 정보를 조회하여..
// Local DB 에 저장된 Network 통계 정보와 계산을 통해.. 향후 5분간 서비스 트래픽을 어느수준으로 제어할 것인지 계산...
// 해당 결과를 RCDB t_sms_sp_svc_product_band 테이블에 업데이트 처리한다.
void CTrafficControlThread::Execute()
{
// 변수 선언 및 초기화..
time_t currentTime; // 현재 시간 정보를 저장하기 위한 변수..
unsigned long nowTimestamp = 0; // 현재 시간 기준으로 계산된 timestamp;
unsigned long delayTimestamp = 0; // 지정된 시간동안 delay 를 통해 보정 처리된 timestamp;
// RCDB 접속 정보 저장 관련 객체 생성 및 초기화...
CRcdbInfo rcdbInfo;
rcdbInfo.Load();
// Local DB 관련 변수 초기화.
sqlite3 * db = NULL;
sqlite3_stmt * stmt = NULL;
int db_result;
int nRetryCount;
const char * select_sql = "SELECT sum(down_traffic)*8/300 FROM network_stat WHERE timestamp = ? AND user_seq = ? AND svc_seq = ?";
// RCDB 조회 결과를 저장하기 위한 임시 변수.
struct traffic_control_info stTraffic;
int nResult;
// Logging
_LOG( LINF, "TrafficControlThread: start...");
// 주기적으로 Network 통계 정보를 바탕으로 RCDB 트래픽 제어 정보를 업데이트 한다.
while( 1 )
{
// FHS 는 장비의 시간을 기준으로.. timestamp 값이 변경 후 20 sec 후에... 트래픽 제한 정보를 refresh 처리한다.
// 기존 rc_statd(3.4 이전) 는 대략 timestamp 변경 후 12 sec 후에 RCDB 에 트래픽 제한 정보를 업데이트 처리한다.
// 따라서.. 본 loop 에서는 timestamp 값 변경 후 약 TIMESTAMP_DELAY_SEC sec 후에 RCDB 에 트래픽 제한 정보를 업데이트토록 처리한다.
// 1. 현재 시간 정보 추출..
currentTime = time(0);
// 2. 현재 시간 정보를 기준으로 timestamp 값을 계산한다...
// 계산시.. FHS 장비들이 시간 동기화 오차로... 통계 정보가 늦게 올라와 저장될 수 있으므로...
// 위에서 언급한대로 TIMESTAMP_DELAY_SEC sec 보정 처리하여 5분 단위 timestamp 값을 추출한다.
nowTimestamp = (currentTime + 299)/300;
delayTimestamp = (currentTime - TIMESTAMP_DELAY_SEC + 299)/300;
// 3. 현재 timestamp 와 보정 처리된 delayTimestamp 값이 동일한 경우....
if( nowTimestamp == delayTimestamp )
{
// 1 sec 대기 후 timestamp 변경이 되었는지 다시 계산하도록 처리...
sleep(1);
continue;
}
else
{
// nowTimestamp 와 delayTimestamp 는 5분단위 정각에 값이 변경되어 서로 값이 달라지므로....
// 지정된 시간 동안 sleep 처리 후... 실제 작업을 수행한다.
sleep( TIMESTAMP_DELAY_SEC );
}
_LOG( LINF, "TrafficControlThread: Traffic Control update job start. timestamp[%lu]", nowTimestamp );
// 이제 실제 작업을 수행한다.
// 1. RCDB 와 연결을 수행한다.
// - RCDB 연결은 5분마다 발생하므로.. 세션을 유지하지 않고.. 필요할 경우에만 연결/연결해제 하도록 처리한다.
DataBase rcdb;
m_vecTraffic.clear();
// RCDB 와 연결을 시도한다.
if( rcdb.PgOpenDB( rcdbInfo.m_strRcdbIp, rcdbInfo.m_nRcdbPort, rcdbInfo.m_strRcdbName, rcdbInfo.m_strRcdbAcct, rcdbInfo.m_strRcdbAcctPw) == NULL )
{
// 연결 실패시... 5분 후 재시도하도록 한다.
LOG( LERR, "TrafficControlThread: RCDB connection failed.");
}
else
{
// RCDB 와 연결 성공시....
// t_sms_sp_svc_product_band 테이블을 조회하여 트래픽 제어가 필요한 서비스 관련 정보를 추출, 멤버 변수에 저장한다.
char szQuery[1024];
snprintf( szQuery, 1023,
"SELECT A.sp_user_seq, A.sp_svc_tran_id, B.volume_bandwidth_limit, B.volume_bandwidth_current, B.volume_bandwidth_control_current "
"FROM t_sms_sp_svc_product A, t_sms_sp_svc_product_band B "
"WHERE A.sp_user_seq = B.sp_user_seq "
" AND A.sp_svc_tran_id = B.sp_svc_tran_id "
" AND A.bandwidth_ctrl_yn = 'Y'");
// 조회 Query 수행.
rcdb.PgDoExec(szQuery);
// 수행 결과 확인
if( rcdb.PgResult( DataBase::NOT_CLEAR ) < 0 )
{
// Query 수행 결과 오류 발생시...
LOG( LERR, "TrafficControlThread: RCDB select query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery );
}
else
{
// query 수행이 정상인 경우.
nResult = rcdb.GetNoTuples();
// 조회 결과를 멤버변수 vector 에 저장한다.
for( int i = 0; i < nResult; i++ )
{
stTraffic.user_seq = atoi ( rcdb.GetValue( i, 0 ) );
stTraffic.svc_seq = atoi ( rcdb.GetValue( i, 1 ) );
stTraffic.limit = atoll( rcdb.GetValue( i, 2 ) );
stTraffic.previous_traffic = atoll( rcdb.GetValue( i, 3 ) );
stTraffic.previous_control_precent = atoi ( rcdb.GetValue( i, 4 ) );
// 나머지 변수도 초기화 처리
stTraffic.current_traffic = 0; // Local db 조회시 정보가 없을 수 있으므로 0 으로 설정.
stTraffic.current_control_precent = 10000; // Local db 조회시 정보가 없을 수 있으므로... 100 % 의미의 값을 default 로 설정.
// vector 에 push 처리
m_vecTraffic.push_back( stTraffic );
}
}
// RCDB 조회 결과 set 를 clear 처리한다.
rcdb.PgClear();
// 트래픽 제어 설정된 서비스가 존재하는 경우..
if( m_vecTraffic.empty() == false )
{
// Local DB 에서 해당 서비스 및 timestamp 에 맞는 트래픽 정보를 추출한다.
std::vector<struct traffic_control_info>::iterator it;
// Local DB 와 연결을 수행한다.
db_result = sqlite3_open( CProcessConfig::GetInstance()->GetLocalDbFileName(), &db);
if( db_result != SQLITE_OK )
{
LOG( LERR, "TrafficControlThread: sqlite3 open failed.[%s][%d][%s]", CProcessConfig::GetInstance()->GetLocalDbFileName(), db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
}
else
{
// DB File 이 open 된 경우...
nRetryCount = 0;
// bind 관련 Query 를 수행할 stmt 객체 생성.
// - 만약 sqlite DB File 이 사전에 생성되어 있지 않은 경우... 위의 open 함수에서 0 파일을 생성되어 정상적으로 open 처리되지만..
// sqlite3_prepare 함수에서 table 이 생성되어 있지 않으므로 오류가 발생함.
// 따라서 loop 중에 재시도하도록 한다.
db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL );
// sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다.
while( db_result == SQLITE_BUSY && nRetryCount < 30 )
{
// sqlite 에서 SQLITE_BUSY 반환 관련 lock 대기 timeout 을 설정.
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "TrafficControlThread: sqlite3 prepare SQLITE_BUSY, Retry[%d]", nRetryCount );
db_result = sqlite3_prepare(db, select_sql, strlen(select_sql), &stmt, NULL );
}
if( db_result != SQLITE_OK )
{
LOG( LERR, "TrafficControlThread: sqlite3 prepare failed.[%d][%s]", db_result, sqlite3_errmsg(db));
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
}
// sqlite 접속이 정상적으로 된 경우...
if( stmt != NULL )
{
// vector 에 저장된 서비스 정보에 맞는 트래픽 정보를 추출한다.
for( it = m_vecTraffic.begin(); it != m_vecTraffic.end(); ++it )
{
/*
_LOG( LDBG, "TrafficControlThread: RCDB result. svc[%d][%d] traffic[%llu/%llu] control[%u]"
, it->user_seq, it->svc_seq, it->previous_traffic, it->limit, it->previous_control_precent );
*/
sqlite3_bind_int64( stmt, 1, nowTimestamp-1 );
sqlite3_bind_int ( stmt, 2, it->user_seq );
sqlite3_bind_int ( stmt, 3, it->svc_seq );
// Query 수행
db_result = sqlite3_step(stmt);
nRetryCount = 0;
// 만약 BUSY 상태로 인해 오류가 발생하면..20번 재시도
while( db_result == SQLITE_BUSY && nRetryCount < 15 )
{
sqlite3_busy_timeout( db, SQLITE_DB_LOCK_TIMEOUT );
++nRetryCount;
LOG( LWAR, "TrafficControlThread: sqlite3 step SQLITE_BUSY, Retry[%d]",nRetryCount );
// query 재시도
db_result = sqlite3_step(stmt);
}
// select 조회 결과가 존재하는 경우... SQLITE_ROW (100 ) 을 반환...
// select 조회 결과가 없는 경우.. SQLITE_DONE (101) 을 반환.
// select query 수행시 오류가 발생한 경우...
if( db_result != SQLITE_ROW && db_result != SQLITE_DONE )
{
LOG( LERR, "TrafficControlThread: sqlite3 step error.[%d][%s]", db_result, sqlite3_errmsg(db));
// 조회 loop 를 벗어나.. 마무리 작업을 한 후 다음 5분 후에 다시 시도하도록 한다.
break;
}
else
{
// select 조회 결과가 존재하는 경우... 1개 Row 만 존재하므로...
if( db_result == SQLITE_ROW )
{
it->current_traffic = sqlite3_column_int64(stmt, 0);
}
}
// Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다.
sqlite3_reset(stmt);
}
// Local DB 와의 연결을 종료 처리한다.
sqlite3_finalize(stmt);
sqlite3_close(db);
db = NULL;
stmt = NULL;
}
// vector 에 수집된 정보를 바탕으로
// 계산을 통해 현재 5분간 트래픽을 어느 수준을 제어할지 결정한다.
for( it = m_vecTraffic.begin(); it != m_vecTraffic.end(); ++it )
{
// 기본 조건
// 1. 제한값이 0 인 경우.. 전송률은 0.01% (1) 로 설정한다.
// 2. 트래픽 전송율은 서비스 품질을 위해 최대 25% 수준까지만 제한한다.
// - 일반적으로 50% 이하로 제한되면...트래픽 증설을 권고할 수 있도록... report 로그에 기록 처리.
// 3. 트래픽 전송율은 감소시킬 경우... Slow 하게... 원복시킬 경우에는 Fast 하게 처리될 수 있도록 한다.
// 제한값이 0인 경우.. -> 운영자가 트래픽을 0으로 제한해 버린 경우...
if( it->limit == 0 )
{
it->current_control_precent = 1; // 0.01 % 로 설정 처리.
}
else
{
// limit 값이 0 을 초과하는 경우...
// 현재 트래픽이 0 인 경우.. 전송율은 default 100% 로 설정처리.
// - 해당 사항은 실제 traffic 전송한 것이 없거나....
// - local db 조회 결과가 존재하지 않는 경우에 발생한다.
if( it->current_traffic == 0 )
{
it->current_control_precent = 10000;
}
else
{
// 현재 트래픽이 존재하는 경우....
if( it->current_traffic > it->limit )
{
// 현재 트래픽이... 제한 트래픽을 초과한 경우...
// - 초과된 비율을 계산한 후..
// - 기존 비율에서 초과된 비율만큼 감소시키되.. 최대 20% 로 제한.
unsigned int nOverPercent = (it->current_traffic - it->limit ) * 10000 / it->limit ;
if( nOverPercent > 2000 )
it->current_control_precent = it->previous_control_precent - 2000;
else
it->current_control_precent = it->previous_control_precent - nOverPercent;
}
else if( it->current_traffic < it->limit )
{
// 현재 트래픽이 제한 트래픽을 초과하지 않은 경우...
// - 현재 트래픽이 제한 트래픽의 90% 초과 수준인 경우에는 현재 제한율 유지...
// - 현재 트래픽이 제한 트래픽의 90% 이하인 경우...10 % 씩 증설 처리 ( 최대 100 % )
unsigned int nUsedPercent = it->current_traffic * 10000 / it->limit;
if( nUsedPercent > 9000 )
it->current_control_precent = it->previous_control_precent;
else
it->current_control_precent = it->previous_control_precent + 1000;
}
else
{
// 제한 트래픽과 현재 트래픽이 동일한 경우...
// 기존 전송율을 유지.
it->current_control_precent = it->previous_control_precent;
}
}
// 마지막으로 전송율 최소값 25%, 최대값 100% 이상을 초과하는 경우.. 보정 처리한다.
if( it->current_control_precent < (TRAFFIC_CONTROL_LOW_LIMIT * 100) )
{
it->current_control_precent = (TRAFFIC_CONTROL_LOW_LIMIT * 100);
}
else if( it->current_control_precent > 10000 )
{
it->current_control_precent = 10000;
}
}
// 전송율이 50% 미만으로 떨어질 경우.... report log 에 기록하여.. 운영자가 확인할 수 있도록 한다.
if( it->limit > 0 && it->current_control_precent < (REPORT_TRAFFIC_CONTROL_LIMIT * 100) )
{
_LOG( LWAR, "TrafficControlThread: service traffic control value set below %d%%. svc[%d][%d] limit[%llu] traffic[%llu->%llu] control[%5.2f->%5.2f%%]"
, REPORT_TRAFFIC_CONTROL_LIMIT, it->user_seq, it->svc_seq, it->limit
, it->previous_traffic, it->current_traffic, it->previous_control_precent/100.0, it->current_control_precent/100.0 );
// report 로그에 해당 내역 기록 처리...
CReportLog reportLog;
std::string strErrorMessage;
strErrorMessage.clear();
if( reportLog.Init( CProcessConfig::GetInstance()->GetLogPath() ) == false )
{
reportLog.GetLastError( strErrorMessage );
LOG( LERR, "TrafficControlThread: report log init failed.[%s]", strErrorMessage.c_str() );
}
else
{
reportLog.Write( "WAR", "[%s][service traffic control value set below %d%%. svc[%d][%d] limit[%llu] traffic[%llu->%llu] control[%5.2f->%5.2f%%]]"
, PROG_NAME, REPORT_TRAFFIC_CONTROL_LIMIT
, it->user_seq, it->svc_seq, it->limit
, it->previous_traffic, it->current_traffic, it->previous_control_precent/100.0, it->current_control_precent/100.0 );
}
}
}
// vector 에 저장된 최종 트래픽 제어 정보를 RCDB 에 UPDATE 처리한다.
for( it = m_vecTraffic.begin(); it != m_vecTraffic.end(); ++it )
{
snprintf( szQuery, 1023,
"UPDATE t_sms_sp_svc_product_band "
"SET "
" volume_bandwidth_current = '%llu' "
" , volume_bandwidth_before = '%llu' "
" , volume_bandwidth_control_current = '%u' "
" , volume_bandwidth_control_before = '%u' "
" , volume_bandwidth_update_datetime = now() "
"WHERE sp_user_seq = %d AND sp_svc_tran_id = %d "
, it->current_traffic, it->previous_traffic, it->current_control_precent, it->previous_control_precent
, it->user_seq, it->svc_seq );
// update query 수행.
rcdb.PgDoExec(szQuery);
// update 실패 발생시...
if( rcdb.GetCmdTuples() <= 0 )
{
LOG( LERR, "TrafficControlThread: RCDB update query failed.[%s][%s]", rcdb.GetErrorMessage().c_str(), szQuery);
}
else
{
_LOG( LINF, "TrafficControlThread: RCDB update OK..svc[%d][%d] limit[%llu] traffic[%llu->%llu] control[%5.2f->%5.2f%%]"
, it->user_seq, it->svc_seq, it->limit
, it->previous_traffic, it->current_traffic, it->previous_control_precent/100.0, it->current_control_precent/100.0 );
}
// Update 처리 정상 수행시... RCDB 결과 Set 을 Clear 처리한다.
rcdb.PgClear();
}
}// RCDB 조회 결과가 존재하는 경우...
// RCDB 관련 종료 처리 작업 수행.
rcdb.PgCloseDB();
}
// 작업 완료 후.. 불필요하게 loop 를 동작시킬 필요가 없고.. timestamp 체크 관련 루프 계산 문제로 인해서...
// 5분 단위로 동작해야 하므로... 약 3분 정도 sleep 한다.
sleep(180);
}
// 종료 처리 코드
return;
}