# 마이 룸 서버 - 동시성 문제 트러블 슈팅
### 발견하게된 계기
- 하나의 서버로만 개발하다가 Jmeter로 테스트를 해보고 싶어 4명의 유저를 동시에 접속시키고 테스트해봄
- 하나의 서버로, 하나의 계정으로만 테스트할 때는 문제가 없었는데, 비어있는 자료에 접근하거나, 데이터가 불일치하는 문제가 발생
- 이를 통해 동시성 처리 문제를 인지
### 멀티 스레드 static 인스턴스 접근 동시성 문제
- 서버 내에서 유저 위치, 세션, 방 정보 등을 Map 자료구조를 이용해 저장해놓고 사용합니다.
- ``` java
// sessionId : userDetails
private static final Map<String, UserDetails> userData = new HashMap<>();
// sessionId : roomId
private static final Map<String, String> session_room = new HashMap<>();
// roomId : room
private static final Map<String, Room> lobby = new HashMap<>();
```
- 여러 유저가 접속한다고 했을 때, 스레드 여러 개가 동시에 Map 자료구조를 참조하고 수정하게 됩니다.
- 이 과정에서 정보가 누락되거나, 이미 사라진 정보를 참조하게 되는 문제가 발생했습니다.
- 
- 따라서 동시성 문제를 해결할 방법을 찾았습니다.
- Synchronized
- Concurrent 패키지의 ConcurrentHashMap 자료구조
- [Synchronized 원리 참고자료](https://qqqqqq.tistory.com/entry/Synchronized%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC)
- [Concurrent 패키지 참고자료](https://devlog-wjdrbs96.tistory.com/269)
- 위 자료를 통해 Concurrent 패키지로 HashMap 자료형에 대해 부분적인 락 처리를 활용하는 것이 빠를 것이라는 생각이었습니다.
- 두 방식 중 어떤 것이 더 좋을지 확인해보고자 특정 요청에 대해 Jmeter를 이용해 테스트해봤습니다.
- 기본 메소드
- 
- Synchronized
- 
- ConcurrentHashMap
- 
- 위와 같은 결과를 통해 인스턴스에 대한 동기화 처리는 ConcurrentMap 자료구조를 이용하고 메소드 범위에서 동기화 처리는 Synchronized를 이용하기로 했습니다.
### 멀티 서버 Redis 접근 동시성 문제
- 서버 내에서 멀티스레드에 대해 동기화처리를 하고 나서 여러 서버가 동시에 Redis에 접근할 때, 동시성 오류가 난다는 것을 발견했습니다.
- Queue형태로 명령을 받아서 처리하여 문제가 없다고 생각했지만, 조회 시점, 업데이트 시점을
- 이후 조언을 얻어 상태관리 서버와 소켓 연결을 해 Redis에 대한 동기화 처리를 해줄 수 있다는 것을 알게 됐습니다.
- 하지만 시간이 부족하다고 판단하여 다른 방법을 찾았고, 그 중 Redisson을 이용한 분산 락 처리에 대해 알게 됐습니다.
- [Redisson 참고자료](https://kkambi.tistory.com/196)
- 이후 동기화가 진행됐지만, 여러 데이터를 변경하는 작업이 트랜잭션으로 묶이지 않아서 따로 `redisTemplate.execute()`를 이용해 한번에 명령을 수행하도록 했습니다.
- ``` java
RLock roomLock = redissonClient.getLock(PREFIX_OF_LOCK + roomId);
try {
roomLock.lockInterruptibly(EXPIRE_TIME_OF_LOCK, TIME_UNIT);
if(Boolean.TRUE.equals(redisTemplate.opsForHash().hasKey(mainKey, hashKey + "nickname"))) {
redisTemplate.execute(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
operations.multi();
HashOperations<K, String, Object> hash = operations.opsForHash();
// 트랜잭션 코드
operations.exec();
return null;
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
roomLock.unlock();
}
```