art run 맵매칭 개발기 (1/2)

art run 맵매칭 개발기 (1/2)

Tags
Socket
Kafka
Published
Mar 13, 2022
Property

배경

기존 지도 서비스에서는 주로 차량의 이동경로를 보정하는 데 맵매칭 알고리즘을 사용한다.
프로젝트의 주 목적이 GPS 아트였기 때문에, 차도 대신 목표 경로를 기준으로 맵매칭 알고리즘을 적용하여 GPS 오차를 개선해보고자 한다.
현업에서의 복잡한 맵 매칭 알고리즘을 구현하는 것은 어렵겠지만, 분산 가능한 맵매칭 아키텍처를 구축해보는 것이 주 목적이다.
1편에서는 해당 기능의 배경과 설계에 대해 다룬다. 현재 (3월) 프로젝트가 진행 중인데, 구현이 마무리 되면, 코드를 포함하여 2편을 완성할 예정이다.
 

맵매칭 기능 간단 명세

  • 목표 경로 정보와, GPS Point 값(lng, lat)을 서버에 전달하면 목표 경로를 따라 보정된 GPS Point 값을 반환한다.
  • 이용자가 달리는 중 핸드폰으로 확인해야하기 때문에 해당 기능은 실시간으로 이루어져야 한다.
  • 기존에 구축된 쿠버네티스 클러스터 서버에서 작동이 가능해야 한다.
 

카카오 모빌리티에서의 맵매칭

이 발표에서의 아키텍처가 모티브가 되었다.
 

맵매칭 파이프라인 요약

PreFilter - MapMatcher - PostFilter 과정을 거친다.
  1. PreFilter
    1. 속도를 고려하여 일정 범위 이상으로 튀는 GPS는 버퍼를 활용하여 필터링한다.
      notion image
  1. MapMatcher
      • 서비스 자체적 맵매칭 알고리즘
        • 비동기 워커(kafka)를 포함하여 구축하여야한다.
        • 맵 매칭 서버를 별도로 분리하여 MSA의 형태로 구축한다.
        • Hazelcast와 같은 인메모리 DB를 거쳐서 데이터를 저장한다.
        • notion image
  1. PostFilter
    1. 맵매칭 이후에 중복된 좌표가 있으면 제거하는 과정이다.
 

설계 단계에서의 고민거리

이슈1: 서버에서 앱으로 메시지를 실시간으로 전달하려면 어떻게 해야할까?

Realtime으로 맵매칭이 가능하게 구현할 계획이었기 때문에, 서버에서 앱으로 메시지를 전달하는 것이 필요했다.
http만을 이용한다면 폴링이나 스트리밍 방식이 있겠지만, 양방향 통신을 목적으로 만든 socket 통신을 이용하는 것이 더 좋은 해결책 같았다.
좌표값을 보내면 보정된 좌표값을 주는 일종의 채팅이라고 생각하면 참고할 만한 자료들이 많이 있었다.
→ 웹소켓을 이용하여 통신한다.
 
cf. WebSocket의 동작 원리
  1. Opening Handshake
    1. http를 통해 upgrade: websocket을 전송하고, 101(프로토콜 전환) 응답을 받는다.
  1. Data Transfer
  1. Close Handshake
 

기타: 소켓 보안은 별도로 처리해야하는가?

spring security에서 socket 보안도 설정이 가능하다.
sockJS를 사용하면, connect시 http 프로토콜을 이용하므로 일반 spring security config로도 충분한 보안을 갖출 수 있을 것으로 보인다. (현재 서비스 단계에서는)
...for SockJS HTTP transport requests, typically there will already be an authenticated user accessible via HttpServletRequest#getUserPrincipal().
 

이슈2: 어떤 서버로 웹소켓 요청, 응답을 보내줘야할까?

분산 서버 환경에서 소켓 통신은 까다롭다. 로드밸런싱이 되기 때문에 소켓 통신이 제대로 작동하기 어려운데 크게 두가지 문제가 생길 것으로 보인다.
첫째, 서버로 보낸 요청이 로드밸런싱에 의해 handshake한 서버로 전송되지 않을 수 있다
→ sticky session을 통해 해결한다. kubernetes를 사용하고 있으므로, ingress 설정으로 간단하게 적용할 수 있다. (sessionAffinity만 설정해주어도 된다.) 단, sticky session을 설정하면 로드밸런싱 기능이 상쇄되는 trade off가 있다.
 
 
둘째, 맵매칭 처리된 메시지를 어떤 서버의 웹 소켓에 전달해야할 지 알 수 없다.
→ (본 프로젝트에서 이용 중인) Kafka를 이용하여, 달리기 시작 시 소켓 연결과 함께 토픽을 생성하여 구독한다. 워커에서는 gps 보정 작업 후 해당 토픽으로 메시지를 전송한다. Kafka 메시지를 전송 받은 서버에서 소켓을 전송하면 된다. (자신의 서버에 소켓이 열려있을 것이다.)
단, kafka를 메시지 서버로 이용한다면 topic 관리를 해주어야한다. auto.create.topics 가 권장되지 않으므로, 직접 topic을 생성해주어야 하고, 달리기가 끝나면 해당 topic은 더 이상 사용되지 않으므로 삭제해주어야 한다.
 

기타: 메시지 브로드 캐스터: Redis

https://tech.kakao.com/2020/06/08/websocket-part1/
notion image
이슈2의 두번째 문제를 해결하기 위해 레디스를 메시지 브로드캐스터로 사용할 수 있다. kafka보다는 redis가 속도 면에서는 빠를 수 있다. 서비스가 더 커지면 이렇게도 가능할 것 같다. 다만 이번 프로젝트에서는 웹소켓 구현에 집중하기로 했다. Redis를 추가하지 말고, Kafka를 활용할 계획이다.
 

설계 초안

예상 흐름도
예상 흐름도
  1. 달리기 시작 시점에서, Socket 연결과 Kafka Subscribe를 진행한다.
      • socket: connect(), subscribe()
      • kafka: create topic, subscribe topic
  1. GPS 보정 요청을 socket을 통해 전송한다. socket은 메시지를 받으면 kafka로 전송한다.
    1. 워커에서는 메시지를 consume하여 GPS 값을 보정한다.
      • socket: pub/match
      • server: kafka pub/match
      • worker: kafka sub/match
  1. GPS 보정 응답은 kafka를 통해 해당 kafka topic을 구독하는 서버로 전송된다.
    1. 서버는 socket을 통해 연결되어있는 클라이언트에게 보정값을 전달한다.
      • worker: pub match/{routeId}
      • server: listen kafka message, socket send()
 
 

마무리

실제로 코드를 짜보고 부딪혀 가며 만들면서 내린 결론이다. 본 기능 구현을 위해 RabbitMQ 대신 kafka를 사용하는 게 과연 바람직한가? 분산 환경에서 socket과 kafka는 어떻게 테스트해볼 것인가? 등 아직 의문이 남는 지점이 많다. 기능 구현이 마무리되면 다시 돌아볼 예정이다.
0329) 실제 개발해보니 변경 사항이 꽤 많이 있다. 2편에서 다시 정리할 예정이다.
 

참고