스케일링 플레이! 수천 건의 동시 요청

게시 됨: 2022-03-11

Scala 웹 개발자는 수천 명의 사용자가 동시에 우리 애플리케이션에 액세스하는 결과를 고려하지 못하는 경우가 많습니다. 아마도 우리는 빠르게 프로토타입을 만드는 것을 좋아하기 때문일 것입니다. 아마도 그러한 시나리오를 테스트하는 것이 단순히 어렵 기 때문일 것입니다.

그럼에도 불구하고 적절한 도구 세트를 사용하고 우수한 개발 사례를 따른다면 확장성을 무시하는 것이 생각만큼 나쁘지 않다고 주장할 것입니다.

적절한 도구를 사용한다면 확장성을 무시하는 것이 생각만큼 나쁘지는 않습니다.

로진하와 플레이! 뼈대

얼마 전 저는 경매 사이트를 구축하려는 시도인 Lojinha(포르투갈어로 "작은 상점"으로 번역됨)라는 프로젝트를 시작했습니다. (참고로 이 프로젝트는 오픈 소스입니다.) 제 동기는 다음과 같았습니다.

  • 더 이상 사용하지 않는 오래된 물건을 판매하고 싶었습니다.
  • 저는 전통적인 경매 사이트, 특히 여기 브라질에 있는 경매 사이트를 좋아하지 않습니다.
  • 나는 Play! 프레임워크 2(말장난).

그래서 분명히 위에서 언급했듯이 Play!를 사용하기로 결정했습니다! 뼈대. 빌드하는 데 걸린 시간을 정확히 계산할 수는 없지만 http://lojinha.jcranky.com에 배포된 간단한 시스템으로 내 사이트를 시작하고 실행하는 데 오래 걸리지 않았습니다. 사실 저는 개발 시간의 절반 이상을 Twitter Bootstrap을 사용하는 디자인에 투자했습니다(참고: 저는 디자이너가 아닙니다…).

위의 단락은 최소한 한 가지를 분명히 해야 합니다. Lojinha를 만들 때 성능에 대해 너무 걱정하지 않았습니다.

이것이 바로 제 요점입니다. 올바른 도구를 사용하는 것에는 힘이 있습니다. 올바른 방향으로 계속 나아가게 하는 도구, 구축함으로써 최상의 개발 방법을 따르도록 권장하는 도구입니다.

이 경우 해당 도구는 Play! Akka가 "손님 출연"을 하는 프레임워크 및 Scala 언어.

무슨 말인지 보여드리겠습니다.

불변성과 캐싱

일반적으로 변경 가능성을 최소화하는 것이 좋은 방법이라는 데 동의합니다. 간단히 말해서, 가변성은 코드에 대해 추론하기 어렵게 만듭니다. 특히 병렬 처리나 동시성을 도입하려고 할 때 그렇습니다.

플레이! Scala 프레임워크는 많은 시간 동안 불변성을 사용하도록 하며 Scala 언어 자체도 마찬가지입니다. 예를 들어 컨트롤러에 의해 생성된 결과는 변경할 수 없습니다. 때때로 당신은 이 불변성을 "성가시거나" "성가시게" 생각할 수 있지만, 이러한 "좋은 관행"이 "좋은" 이유가 있습니다.

이 경우, 내가 마침내 몇 가지 성능 테스트를 실행하기로 결정했을 때 컨트롤러의 불변성이 절대적으로 중요했습니다. 병목 현상을 발견하고 이를 수정하기 위해 이 불변 응답을 단순히 캐시했습니다.

캐싱 이란 응답 개체를 저장하고 동일한 인스턴스를 있는 그대로 모든 새 클라이언트에 제공하는 것을 의미합니다. 이렇게 하면 서버에서 결과를 다시 계산할 필요가 없습니다. 이 결과가 변경 가능한 경우 여러 클라이언트에 동일한 응답을 제공할 수 없습니다.

단점: 짧은 기간(캐시 만료 시간) 동안 클라이언트는 오래된 정보를 받을 수 있습니다. 이것은 클라이언트가 지연을 허용하지 않고 가장 최근 데이터에 액세스해야 하는 시나리오에서만 문제입니다.

참고로 다음은 캐싱 없이 제품 목록이 있는 시작 페이지를 로드하는 스칼라 코드입니다.

 def index = Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) }

이제 캐시를 추가합니다.

 def index = Cached("index", 5) { Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) } }

아주 간단하지 않습니까? 여기서 "index" 는 캐시 시스템에서 사용할 키이고 5는 만료 시간(초)입니다.

캐싱 후 처리량이 초당 최대 800개 요청으로 증가했습니다. 이는 2줄 미만의 코드에 대해 4배 이상 개선된 것입니다.

이 변경 사항의 효과를 테스트하기 위해 일부 JMeter 테스트(GitHub 저장소에 포함)를 로컬에서 실행했습니다. 캐시를 추가하기 전에 초당 약 180개 요청의 처리량을 달성했습니다. 캐싱 후 처리량이 초당 최대 800개 요청으로 증가했습니다. 이는 2줄 미만의 코드에 대해 4배 이상 개선된 것입니다.

이것이 내가 플레이를 사용하는 방법입니다! 내 Scala 경매 사이트의 성능을 향상시키기 위해 캐시.

메모리 소비

적절한 Scala 도구가 큰 차이를 만들 수 있는 또 다른 영역은 메모리 소비입니다. 자, 또 플레이! 올바른(확장 가능한) 방향으로 밀어줍니다. Java 세계에서 서블릿 API로 작성된 "일반" 웹 응용 프로그램(즉, 거의 모든 Java 또는 Scala 프레임워크)의 경우 API가 사용하기 쉬운 그렇게 할 수 있는 호출 방법:

 session.setAttribute("attrName", attrValue);

사용자 세션에 정보를 추가하는 것이 너무 쉽기 때문에 종종 남용됩니다. 결과적으로 정당한 이유 없이 너무 많은 메모리를 사용할 위험도 똑같이 높습니다.

플레이와 함께! 프레임워크에는 옵션이 없습니다. 프레임워크에는 단순히 서버 측 세션 공간이 없습니다. 플레이! 프레임워크 사용자 세션은 브라우저 쿠키에 보관되며 사용자는 그 쿠키와 함께 살아야 합니다. 이것은 세션 공간이 크기와 유형이 제한되어 있음을 의미합니다. 문자열만 저장할 수 있습니다. 개체를 저장해야 하는 경우 이전에 논의한 캐싱 메커니즘을 사용해야 합니다. 예를 들어 현재 사용자의 이메일 주소나 사용자 이름을 세션에 저장하고 싶지만 도메인 모델의 전체 사용자 개체를 저장해야 하는 경우 캐시를 사용해야 합니다.

놀다! 실제로 클러스터 준비가 된 첫 번째 패스 코드를 생성하는 메모리 사용량을 신중하게 고려해야 합니다.

다시 말하지만 처음에는 고통스러워 보일 수 있지만 실제로는 Play! 메모리 사용량을 주의 깊게 고려하도록 하여 실제로 클러스터 준비가 된 첫 번째 패스 코드를 생성합니다. 특히 클러스터 전체에 전파해야 하는 서버 측 세션이 없다는 점을 감안할 때 수명이 연장됩니다. 무한히 쉽습니다.

비동기 지원

이 플레이의 다음! 프레임워크 검토에서는 Play! async(hronous) 지원에서도 빛을 발합니다. 그리고 기본 기능을 넘어 Play! 비동기 처리를 위한 강력한 도구인 Akka를 포함할 수 있습니다.

하지만 Lojinha는 아직 Play!와의 간단한 통합인 Akka를 최대한 활용하지 못하고 있습니다. 정말 쉽게 만들었습니다:

  1. 비동기식 전자 메일 서비스를 예약합니다.
  2. 다양한 제품에 대한 제안을 동시에 처리합니다.

간단히 말해서, Akka는 Erlang에 의해 유명해진 Actor Model의 구현입니다. Akka Actor Model이 익숙하지 않다면 메시지로만 소통하는 작은 단위라고 상상해 보세요.

이메일을 비동기적으로 보내려면 먼저 적절한 메시지와 액터를 만듭니다. 그런 다음 다음과 같이 하면 됩니다.

 EMail.actor ! BidToppedMessage(item.name, itemUrl, bidderEmail)

이메일 전송 로직은 액터 내부에서 구현되며 메시지는 액터에게 우리가 어떤 이메일을 보내고 싶은지 알려줍니다. 이것은 fire-and-forget 방식으로 수행됩니다. 즉, 위의 줄이 요청을 보낸 다음 그 이후에 우리가 가진 모든 것을 계속 실행합니다(즉, 차단하지 않음).

Play!의 기본 Async에 대한 자세한 내용은 공식 문서를 참조하세요.

결론

요약: 저는 확장 및 확장이 가능한 작은 응용 프로그램인 Lojinha를 빠르게 개발했습니다. 문제가 발생하거나 병목 현상을 발견했을 때 내가 사용한 도구(Play!, Scala, Akka 등) 덕분에 수정이 빠르고 쉬웠고 효율성과 효율성 측면에서 모범 사례를 따르게 되었습니다. 확장성. 성능에 대한 걱정 없이 수천 개의 동시 요청으로 확장할 수 있었습니다.

다음 애플리케이션을 개발할 때 도구를 신중하게 고려하십시오.

관련 항목: Scala 매크로 및 준따옴표로 상용구 코드 줄이기