해결된 질문
작성
·
64
0
현재 웹소켓 구조는 커넥션당 고루틴이 발생하여 블록되어 있는 구조이고 동시에 많은 트래픽이 들어올 경우에 각 커넥션을 담당하는 고루틴들에서 동시에 요청을 처리하기 때문에, RunInit() 같은 메시지 채널을 관리하는 고루틴을 두게 되면, 구조는 깔끔해지지만, 병목이 생길 포인트인 것 같습니다. 그래서 이를 완화하기 위해서 RunInit() 고루틴을 포문으로 여러 개 돌게 할지, 아니면 굳이 RunInit()과 같은 메시지 채널관리용 고루틴을 만들 필요가 있는 건지 의문이 생깁니다.
RunInit()과 같은 패턴을 사용하는 것은 강사님 소스에서 뿐만 아니라, 다른 프로젝트에서도 많이 봤어서 관련하여 강사님의 답변을 듣고 싶습니다!
답변 5
1
안녕하세요 질문 남겨주셔서 감사합니다. 일단 우선적으로 제가 현재 소스코드를 날려먹은 상태여서... ㅠㅠ 정확한 부분은 코드를 봐야 알수가 있는 부분인거 같습니다. 혹시라도 제가 답변해 드리는 내용에 대해서 이해가 잘 안되신다면 관련하여 코드도 첨부해 주시면 감사하겠습니다.
해당 질문에 대해서는 먼저 스펙에 대한 기준이 필요할꺼같습니다.
플랫폼을 구성하는데에 있어서 큰 서비스를 운영하는지, 아니면 간단한 서비스인지에 대해서 관점이 달라지는거 같습니다.
큰 서비스를 운영한다면, 기본적으로 메시지 처리를 채널로 하지 않습니다. 데이터 유실에 대해서도 어느정도의 책임을 가지고 있어야하기 떄문에, 보통 저런 구조에서의 채널을 Kafka를 적용하여 관리를 하게 됩니다.
물론 내부적으로 코드에서는 채널을 통해 무언가를 처리해도 무방합니다.
이러한 관점이 중요한 부분은 서비스에 대해서 오버 스펙을 굳이 구현 할 필요는 없기 떄문입니다. 회사에서의 비용절감도 어느정도 중요한 부분이기 떄문이에요.
그래서 실제로 Kafka를 안써보신 분들도 꽤나 있는걸로 알고 있습니다.
하지만 작은 서비스라면, 사실 어떻게 관리를 하든 무방합니다. 그래서 저는 개인적으로 장단점이 있는거 같아요.
여러개의 고루틴을 관리하는것은 쉽지가 않지면, 병렬적으로 좀 더 가볍게 처리 가능한 장점이 있습니다. 하지만 그에 따라서 개발자가 고생을 많이 하는 형태라고 생각을 합니다.
하나의 루틴을 통해서 관리하는것은 간단하지만 아무래도 데이터의 사이즈가 커지기 떄문에 병목이나 문제가 생길 수 있는 부분이 꽤나 발생할꺼같습니다.
개인적으로는 최적화를 위해서는 병렬적인 처리가 더 좋다고 생각을 해요. 하지만 실무에서는 최적화도 중요하지만, 유지 관리의 가능성도 잘 잡고 있어야 하기 떄문에, 상황에 따라서 달라지는거 같습니다.
내가 정말 자신있다고 생각을 한다면 병렬적으로 하시는게 맞다고 생각을 합니다. 하지만 어느정도 팀내의 레거시를 따라야한다거나, 팀 내에서의 코드 작성이 자신이 없다면 하나에서 관리를 해도 무방합니다.
하지만 결론적으로는 최선의 방법은 kafka를 통해서 관리하는것입니다. 이 강의에서 사용한 방법은 Kafka까지 가게 된다면 수강하시는 분들이 너무 어려워하실꺼 같아 다루지는 않았습니다.
혹시 제가 잘못된 답변을 드렸거나 엉뚱한 소리를 했다면 한번 더 질문 남겨주시면 감사하겠습니다.
좋은 주말 보내세요 :)
0
0
0
빠른 답변해주셔서 정말 감사합니다.
제 질문은 유저의 메시지를 받아서 c.Room.Forward <- msg
부분 처럼, Foward채널에 송신하고 RunInit() 고루틴에서 처리하도록 하는 부분이 체계적이고 유지보수성이 높아지는 장점이 있는 반면에, 수천, 수만명의 커넥션으로 발생한 고루틴들이 메시지 채널에 동시 다발적으로 데이터를 송신할 경우 이 메시지 채널을 수신하는 단 하나의 RunInit()고루틴이 감당 가능한가에 대한 질문이었습니다. 물론 소규모 서비스에서는 동시 요청 수가 적어서 괜찮겠지만, 스트레스 테스트를 진행한다면, 분명 저부분이 병목 포인트일 거라 생각했습니다. 이러한 구조를 사용하는 것을 강사님 소스코드 포함해서 여러번 봤기 때문에, 왜 굳이 Read()에서 각 요청을 담당하는 고루틴이 직접 처리하면되지 c.Room.Forward <- msg과 같이 채널에 넘기는 걸까?라는 의문이 생겼던 겁니다. 제 질문을 좀더 명확히 하기 위해 소스코드를 첨부드립니다.
func (c client) Read() {
// 클라이언트가 들어오는 메시지를 읽는 함수
defer c.Socket.Close()
for {
var msg message
err := c.Socket.ReadJSON(&msg)
if err != nil {
if !websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
break
} else {
panic(err)
}
} else {
log.Println("READ : ", msg, "client", c.Name)
log.Println()
msg.Time = time.Now().Unix()
msg.Name = c.Name
c.Room.Forward <- msg } } }
func (r *Room) RunInit() {
// Room에 있는 모든 체널값을을 받는 역할
for {
select {
case client := <-r.Join:
r.Clients[client] = true
case client := <-r.Leave:
r.Clients[client] = false
close(client.Send)
delete(r.Clients, client)
case msg := <-r.Forward:
for client := range r.Clients {
client.Send <- msg
}
}
}
}
아하 이해가 되었습니다.
Forward
채널은 일종의 중간 입구역할을 수행합니다.
보시면 들어오는 메시지는 모두 Forward
에 담기게 되고, Forward
에서 다른 clinet에게 메시지를 처리해주는 코드로 보이실 겁니다.
이렇게 구성한 이유는 단일 입구가 반드시 필요하기 떄문입니다.
현재는 간단한 채팅서버를 통해서 채널을 통해 이벤트를 처리하기 위해서 작성이 되어 있지만,
기능이 추가되어서 특정 방에서의 데이터 처리만 필요하거나, 누군가 나갔을 떄, 그 방에서만 이벤트를 띄워주어야 한단던지
이러한 부가적인 처리에 대해서 하나의 입구에서 받아서, 분기처리해 주는 작업이 필요합니다. 해당 작업을 위해서 Forward
가 사용이 된다고 보시면 됩니다.
물론 고민해주셨던 부분은 제가 의도한 부분이기도 합니다. 좋은 고민을 하신거 같아요.
이렇게 작성하는것은 당연하게도 병목이 발생가능합니다. 이 코드를 작성하면서도 이런 고민을 해보시면 좋지 않을까 싶었고, 딱히 답은 알려드리지 않았어요. 누구든지 고민을 하시는 분들이 많은 것을 알아가실 수 있을거라고 생각하기 때문이죠.
병목은 당연히 발생 가능합니다. 메시지가 몰리게 된다면, Forward
채널에 큐가 처리가 뒤늦게 된다거나 문제가 생길 수 있어요.
그러기 떄문에, 실제로는 이런 메시지 처리를 하는데에 있어서는 단순한 Clinet의 메모리로는 해결이 불가능하다고 생각합니다.
정말 간단하게 필요한 경우가 아니라면,,
그래서 계속해서 말씀드리는 Kafka가 보통은 Forward
역할을 수행하게 됩니다.
결론을 말씀드리자면, 추가적인 조치를 위해 단일지점이 필요하기 떄문에 Forward
가 필요하다고 생각하시면 단순하게 이해가 되실 꺼 같네요.
물론 Read
에서 처리해도 무방하다고 생각하지만, 개인적으로는 중간 지점에서 데이터를 정리하거나 재가공해주는 부분이 필요하다는 의견이 있어서요.. Read
는 정말 단순히 데이터를 읽어주는 역할만 수행하는것이 맞다고 생각합니다.
답변 감사합니다. 나중에 병목이 생긴다면 채널을 관리하는 워커 고루틴을 포문으로 여러개 발생시켜서 병목을 해결하면 될까요? 우선 read함수에서는 read만 수행하고 채널로 보내는 부분 대신 아래와 같은 방법으로도 만들어 보았는데, 이 방법보다 추후, kafka로 확장 또는 메시지들을 한 곳에서 관리할 수 있는 유지보수성을 고려하여 강사님의 방법대로 수정해보려합니다! 감사합니다~
음 네 해당방식도 문제는 없겠지만, 사실 고루틴이 많이 생성이 된다면 유지보수가 어려우실 수 있다는 단점은 존재합니다.
해당 부분도 함게 고려하셔도 괜찮을꺼 같아요.
참고로 저는 실무에서 채팅 데이터는 아니지만, 이벤트성 데이터를 하나의 채널에서 받아서 처리를 하고는 있는데.. 큰 병목은 없었기는 합니다.
좀 더 질문이 있으시다면 추가로 질문 부탁드려요!!
0
안녕하세요, 인프런 AI 인턴입니다.
김영민님의 질문에 대해 유사한 기존의 답변을 찾아보니, 웹소켓 서버 설계와 관련하여 고루틴, 메시지 채널 관리의 효율적인 사용에 대한 논의가 존재합니다. 이에 대한 구체적인 답변을 드리겠습니다.
웹소켓 서버에서 대량의 트래픽을 처리하기 위해서는 효율적인 메시지 채널 관리가 중요합니다. RunInit()
같은 고루틴을 통해 메시지 채널을 관리하는 것은 일반적인 패턴인데, 이는 병목 현상을 완화하고 코드 구조를 명료하게 만들어 줍니다. 그러나, 트래픽의 양에 따라 고루틴 수를 조절하여 병목을 줄일 필요가 있습니다. 병목이 우려되는 상황이라면, RunInit()
을 여러 고루틴으로 분산시키는 방법을 고려해 볼 수 있습니다.
관련하여 더 나은 이해를 돕기 위해 참고할 만한 답변 URL이 있습니다:
이 자료들을 통해 김영민님의 질문에 대한 해답을 얻을 수 있을 것입니다. 추가적으로 궁금한 점이 있으시면, 이어서 질문을 주시면 강사님께서도 구체적인 내용을 답변해 주실 것입니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
카프카를 사용하자는 의미는, 서버에서 관리하고 있는 goroutine들을 모두 카프카로 변형하는것이 좋다는 의미 입니다.
중간에 카프카라는 메시징 툴을 두어서 메시징 처리에 대해서 서버의 메모리 관리가 아닌 카프카를 통해서 관리를 하는것이죠. 해당 방식을 통하면, 일시적인 휘발성 데이터가 아닌 어느정도 메시지가 보장되고 고가용성도 보장이 되며 추가적으로 하나의 입구와 출구가 생기기 떄문에 다양한 모듈에서 적용하여 데이터 처리가 가능하다는 장점이 있습니다.
서버 사이에 존재하는 브로커라는게 어떤 의미로 말씀하신지 모르겠는데, 추가적인 궁금한 부분이 있다면 남겨주세요!!