티스토리 뷰

Backend

Redis를 통한 현재 접속 유저 파악하기

지마켓박명훈 2021. 2. 18. 09:36

안녕하세요. Sell POD팀에서 판매자 대상으로 업무를 하고 있는 개발자 박명훈입니다. 오늘은 좀 더 기술적인 글을 작성해보려고 합니다.

 

최근 만든 서비스 중 하나는 지마켓과 옥션의 판매자 플랫픔 ESM+에서 접속 중인 사용자를 확인하고 다른 사용자를 로그아웃 시킬 수 있는 기능을 개발하였습니다. 다른 시니어 개발자 분 한분과 함께 개발을 했는데, 저는 서비스에서 사용하는 API을 주로 개발하였습니다.

 

이 기능을 통해서 현재 접속하고 있는 로그인 유저를 확인할 수 있고, 중복 로그인이 발생하였는지를 알 수 있고 이를 통해 다른 기능을 제공할 수 있습니다.

 

개발 고민

최초 해당 업무를 받았을 때, 어떤 식으로 개발을 해야할 지 많은 고민을 했습니다. 당연히 로그인에 대한 로직이고, 해당 데이터 접근이 많고 데이터를 수정하는 경우가 많기 때문에 기존의 RDBMS에서 해당 로직이 접근한다면 DB에 많은 부담이 가는 작업으로 판단했습니다. 그렇기 때문에 RDBMS 보다는 NoSQL을 사용하기로 하였습니다. DBA팀도 RDBMS보다 No SQL이 적합하다라고 코멘트를 주셨고, 여러 상황을 고려했을 때 로그인 키를 바탕으로 판단을 내리기 때문에 Key-value Nosql의 대표인 Redis를 사용하기로 결정하였습니다.

Redis는 대표적인 Key-value 스토리지 형의 NoSQL입니다.

 

그러나, 로그인 유저에 대한 값을 key로 하여 저장을 할 때 어떠한 방식으로 저장할지에 대해 많은 고민을 가졌습니다.

최초의 생각은 다음과 같았습니다.

  • 1안 - 로그인 factor를 모두 구분자로 구분해서 이를 키로 잡아서, 부분적으로 scan을 합니다
  • 2안 - 로그인 아이디를 키로 잡고, 로그인 factor를 리스트로 저장합니다.

각 방법마다 단점이나 고려사항이 있었는데 1안의 경우, scan을 써야 하기 때문에 시간이 O(n)이 걸리게 됩니다. Redis에서 Scan의 경우는 Redis 키 전체에 대해서 동작을 하기 때문에 이를 사용할 수는 없습니다. 일반적인 사용자 애플리케이션에서는 사용을 하는 것은 매우 좋지 않기 때문에 이 방법은 포기하였습니다.

 

따라서 2안을 선택하였는데 2안의 가장 큰 문제는 사용자에게 유효시간을 어떻게 줄 것인지에 대한 내용이었습니다. Redis에 key값에는 TTL을 줄 수 있지만, data는 TTL을 줄 수 없습니다. 그렇기 때문에 임의의 사용자가 로그인했을 때, 해당 유저가 나갔을 때 그 사용자에 대한 데이터는 남게 됩니다.

 

다음과 같은 문제가 발생 할 수 있습니다.

EX) 동일한 Login Id 에서
- A에서 로그인
- B에서 로그인 (중복 로그인)
- B에서 로그아웃
- A에서 로그아웃
- C에서 로그인 (중복 로그인 : 버그 발생)

 

A와 B가 들어왔다가 나갔을 때, 데이터는 남아있어 C가 로그인을 하는 경우, 남아있는 데이터로 인해서 C도 중복 로그인으로 판단하게 됩니다. 로그인 키값에 TTL을 주더라도 A와 B가 로그인을 했을 때, A는 잠시 후 나가고, B는 계속 있는 상태인 경우에 A의 데이터는 계속 남아있게 됩니다. 이러한 찌꺼기 데이터가 남아있기 때문에 로직적으로도 문제가 있고 메모리적으로도 낭비가 발생합니다.

 

아이디어 발견

그러던 중 하나의 글을 보게 되었는데, 그 글에서는 레디스의 value 값에 score(일종의 TTL)을 주는 방법을 확인 했습니다.

 

특정 사용자 값의 key를 가지고 있을 때, 해당 사용자 키안에 여러 사용자 데이터를 넣어놓고, 그 사용자의 데이터에는 score라고 하는 일종의 TTL을 임의로 넣습니다.

 

현재의 TimeValue 값에 TTL을 더해서 저장을 하고, 다음에 이 데이터에 접근을 했을 때, 해당 데이터 값의 Score 값이 현재의 TimeValue값보다 적은 경우, 이미 로그아웃한 유저로 판단을 하고 헤딩 데이터를 삭제시키고 로그아웃으로 판단합니다.

이러한 사용자의 데이터는 사이트에서 활동을 하는 경우에는 꾸준하게 score값을 업데이트해줍니다. 이런 로직을 통해서 활동 중 로그아웃이 일어나지 않게 되고, 또한 특정 시간 동안 활동이 없으면 로그아웃 시킬 수 있는 기능을 만들었습니다.

 

개발하기.

Redis에 저장을 할 때는 Hash의 형태로 저장하였습니다.

아래는 모두 예시를 위해 임의로 만든 것입니다. 실제 서비스에서는 좀 더 복잡하고 섬세하게 구성했습니다.

 

Redis HASH로 저장할 때 hash key, sub-key, value로 구성되며 다음과 같이 구성합니다.

  • Hash Key : 로그인 아이디, 해당 Key안에 가지고 있는 정보로 몇 명이 로그인 중인지 확인 가능합니다.
  • Sub Key : 로그인 아이디에 접속한 사용자를 구분할 수 있는 Unique 한 값들입니다.
    • Ex) IP address, Browser Type, Os Type, Country
  • Value : Sub Key와 매칭 되는 데이터입니다. 이 정보를 통해서 멀티로그인 상세 기능을 구현합니다.
    • Ex) Score, Login Date, Message Code

 

Redis에는 다음과 같이 저장됩니다. 

  • Hash Key는 다음과 같이 구성합니다.
EsmLoginUnqiueValue1:EsmLoginUnqiueValue2:{SiteLoginId}
  • 하나의 Hash Key에는 여러 로그인 사용자가 존재합니다. 이를 Sub Key로 구별합니다.
...{ipAddress}:{browserType}:{osType}:{countryCode}...

ip주소나, 브라우저 타입, os 타입 등을 통해서 현재 사용자가 같은 유저인지 혹은 같은 유저가 아닌지에 대해 판단합니다.

  • 하나의 Sub Key에는 매칭 되는 value값이 존재합니다.
data : {
	...
	score :
	loginDate :
	messageCode :
}

 

data는 사용자에 대한 여러 정보를 보관하고 있습니다. 이 중 score를 통해서 사용자의 유효시간을 판단합니다.

 

코드 구성

Spring에서 저장하는 코드는 다음과 같이 구성하였습니다. 자바 HashMap 구조를 사용하여 다음과 같이 Redis 데이터를 만들었습니다.

public LinkedHashMap createRedisValue(LoginRequestDto request){
  LinkedHashMap hashMap = new LinkedHashMap();
  hashMap.put("score", System.currentTimeMillis() + TTL);
  hashMap.put("loginDate", System.currentTimeMillis());
  hashMap.put("messageCode", request.getMessageCode());
  return hashMap;
}

이를 이후에 아래처럼 저장합니다.

redisTemplate.opsForHash().put(redisKey, redisUniqueKey, redisValue)

 

이러한 저장 정보를 통해서 다른 사이트에서 api 요청을 통해 사용자 로그인 정보를 확인하고 유지합니다. 또한 메시지 코드 등을 통해서 해당 사용자가 로그아웃 해야 하는지에 대해서 판단하고, 또한 강제 로그아웃 등의 기능을 구현할 수 있습니다.

 

댓글