피드 스트림 제품은 모바일 앱의 거의 모든 곳에 있습니다. 일반적인 피드 스트림에는 WeChat Moments, Sina Weibo, Toutiao 등이 포함됩니다. 피드 스트림의 정의는 엄지 손가락으로 휴대폰 화면을 계속 아래로 스와이프하는 한 정보가 계속해서 나타난다는 것으로 간단히 이해될 수 있습니다. 가축에게 먹이를 주는 것과 마찬가지로, 먹으면 더 많은 양이 추가되므로 Feed라는 이름이 붙었습니다.
대부분의 피드 스트림 제품에는 두 개의 피드 스트림이 포함되어 있습니다. 하나는 알고리즘 추천을 기반으로 하고 다른 하나는 팔로우(우정)를 기반으로 합니다. 예를 들어 아래 사진의 Weibo와 Zhihu에서는 상단 열의 페이지 카드에 '팔로우'와 '추천'이 포함되어 있습니다. 두 가지 공급 흐름 뒤에 사용되는 기술은 상당히 다릅니다. 이 문서에서는 '팔로우' 페이지 카드의 백그라운드 구현을 살펴보는 데 중점을 둡니다.
이미지 출처: Zhihu
수천 명이 추천하는 '추천' 페이지 카드와 달리 '팔로우' 페이지 카드에 표시되는 콘텐츠는 일반적으로 순서대로 표시됩니다. 고정된 규칙 중 가장 일반적인 규칙은 타임라인을 기준으로 정렬하는 것입니다. 즉, "내가 팔로우하는 사람들이 게시한 게시물을 게시 시간에 따라 늦은 것부터 빠른 것까지 정렬"하여 표시하는 것입니다.
피드 스트림 구현 솔루션 소개
읽기 확산은 풀 모드라고도 하는데 가장 직관적인 구현 방법이어야 합니다. 아래와 같이:
각 콘텐츠 게시자는 자신의 보낼 편지함("내가 게시한 콘텐츠")을 가지고 있습니다. 우리가 새 게시물을 보낼 때마다 해당 게시물은 자신의 보낼 편지함에 저장됩니다. 팬이 책을 읽으러 오면 시스템은 먼저 팬이 모든 사람을 팔로우하게 한 다음 모든 게시자의 보낸 편지함을 순회하여 게시한 게시물을 꺼낸 다음 게시 시간에 따라 정렬하여 독자에게 표시해야 합니다.
이 설계에서는 리더가 피드 스트림을 한 번 읽으면 N개의 읽기 작업(N은 팔로우하는 사람 수)과 백그라운드에서 집계 작업으로 확산되므로 읽기라고 합니다. 확산. 피드 스트림을 읽을 때마다 팔로어의 받은 편지함으로 이동하여 적극적으로 게시물을 가져오는 것과 같으므로 이름은 가져오기 모드입니다.
이 모델의 장점은 기본 스토리지가 단순하고 공간 낭비가 없다는 것입니다. 단점은 각 읽기 작업이 매우 무겁고 작업이 많다는 것입니다. 내가 매우 많은 수의 사람을 팔로우하고 내가 팔로우하는 모든 사람을 살펴보고 집계한다면 시스템 오버헤드가 매우 커지고 지연이 견딜 수 없는 수준에 도달할 수 있다고 상상해 보십시오. 따라서 읽기 확산은 시스템의 독자가 많은 사람을 따르지 않고 피드 스트림이 자주 새로 고쳐지지 않는 시나리오에 주로 적합합니다.
풀 모드의 또 다른 주요 단점은 페이징이 불편하다는 점입니다. 웨이보나 모멘트를 탐색할 때 화면을 계속 엄지손가락으로 스와이프해야 하고, 배경에서 페이지별로 콘텐츠를 가져와야 합니다. . 다른 최적화를 하지 않고 실시간 집계만 사용한다면 이후 페이지 번호로 스크롤할 때 매우 번거로울 것입니다.
통계에 따르면 대부분의 피드 스트림 제품의 읽기-쓰기 비율은 약 100:1입니다. 이는 대부분의 경우 다른 사람의 모멘트와 Weibo를 읽기 위해 피드 스트림을 탐색하고, 드문 경우지만, 다른 사람들이 볼 수 있도록 Moments나 Weibo에 개인적으로 메시지를 보낼 수도 있습니다. 따라서 읽기 확산의 무거운 읽기 논리는 대부분의 시나리오에 적합하지 않습니다. 우리는 피드 스트림을 읽는 사용자의 경험에 영향을 주기보다 게시 프로세스를 더 복잡하게 만들기 위해 이전 솔루션을 약간 수정하여 쓰기 확산을 만들었습니다. 쓰기 확산은 푸시 모드라고도 합니다. 이 모드는 풀 모드의 일부 단점을 개선합니다. 아래와 같이:
보낼 편지함 외에도 시스템의 각 사용자에게는 자신의 받은 편지함이 있습니다. 게시자가 게시물을 게시하면 자신의 보낼 편지함에 이를 기록하는 것 외에도 게시자의 모든 팬을 순회하여 해당 팬의 받은 편지함에 동일한 콘텐츠의 복사본을 넣습니다. 이러한 방식으로 독자가 피드 스트림을 읽으러 오면 자신의 받은 편지함에서 직접 읽을 수 있습니다.
이 설계에서는 게시물이 게시될 때마다 M개의 쓰기 작업(M은 팬 수와 동일)으로 확산되므로 쓰기 확산이 됩니다. 각 게시물은 모든 팬의 받은 편지함에 적극적으로 푸시되므로 푸시 모드라는 이름이 붙었습니다.
이 모델은 게시물을 게시하는 데 많은 쓰기 작업이 필요하다고 상상할 수 있습니다. 일반적으로 포스터의 사용자 경험을 위해 게시된 게시물이 보낸 편지함에 기록되면 게시 성공이 반환될 수 있습니다. 백그라운드에서 또 다른 비동기 작업을 시작하고 서두르지 않고 팬의 받은 편지함에 게시물을 전달하기만 하면 됩니다.
쓰기 확산의 이점은 데이터 중복성을 통해 독자의 사용자 경험을 향상시킨다는 것입니다(게시물은 M개의 사본에 저장됩니다). 일반적으로 적절한 데이터 중복은 문제가 되지 않지만 웨이보 스타의 경우 전혀 작동하지 않습니다. 예를 들어, 현재 웨이보 팔로어 상위 2명을 보유하고 있는 Xie Na와 He Jiong은 1억 명이 넘는 웨이보 팔로어를 보유하고 있습니다.
단순히 푸시 모드를 사용하면 Xie Na와 He Jiong이 Weibo를 올릴 때마다 Weibo 백엔드에 지진이 일어날 것이라고 상상해보십시오. Weibo 게시물은 백그라운드에서 수억 건의 쓰기 작업을 수행하는데 이는 분명히 불가능합니다. 게다가 쓰기 확산은 비동기식 작업이기 때문에 쓰기 속도가 너무 느리면 게시물이 오랫동안 전송될 수 있고, 여전히 일부 팬이 볼 수 없는 경우도 있어 좋은 경험은 아닙니다.
일반적으로 쓰기 확산 모드는 친구 수가 많지 않은 상황에 적합합니다. WeChat Moments는 바로 쓰기 확산 모드로 알려져 있습니다. 각 WeChat 사용자의 친구 상한은 5,000명입니다. 즉, 친구 서클을 게시하면 최대 5,000개의 쓰기 작업으로 확산된다는 의미입니다. 비동기 작업 성능이 더 좋으면 전혀 문제가 없습니다.
읽기와 쓰기를 혼합하는 것은 푸시풀 조합이라고도 할 수 있습니다. 이 방법은 읽기 확산과 쓰기 확산의 장점을 결합할 수 있습니다. 먼저 읽기 확산과 쓰기 확산의 장단점을 정리해 보겠습니다.
읽기 확산과 쓰기 확산의 장단점을 꼼꼼히 비교해보면 두 가지에 적용 가능한 시나리오를 찾는 것은 어렵지 않습니다. 보완적인. 따라서 백엔드 스토리지를 설계할 때 시나리오를 구별하고 다양한 시나리오에 가장 적합한 솔루션을 선택하고 전략을 동적으로 조정할 수 있다면 혼합 읽기-쓰기 모드를 달성할 수 있습니다. 아래와 같이:
활성 사용자가 피드 스트림을 탐색하기 위해 로그인하면 받은 편지함에서 직접 게시물을 읽을 수 있으므로 활성 사용자의 경험을 보장할 수 있습니다. 비활성 사용자가 피드 스트림을 탐색하기 위해 갑자기 로그인하면, 한편으로는 그의 받은 편지함을 읽어야 하고, 다른 한편으로는 그가 팔로우하는 Big V 사용자의 보낸 편지함을 탐색하여 게시물을 추출하고 집계된 표시를 수행해야 합니다. . 표시 후에 시스템은 사용자를 활성 사용자로 업그레이드해야 하는지 여부를 결정하는 작업도 필요합니다. 읽기 확산 시나리오가 있기 때문에 혼합 모드에서도 각 독자가 팔로우할 수 있는 사람 수에 상한이 있어야 합니다. 예를 들어 Sina Weibo는 각 계정을 최대 2,000명으로 제한합니다. 상한선이 없다면 사용자가 모든 Weibo 계정을 팔로우한다고 상상해 보십시오. 그러면 그가 팔로우 목록을 열면 Weibo의 모든 게시물을 읽을 것입니다. 일단 읽기 확산이 발생하면 시스템은 필연적으로 쓰기 확산이 될 것입니다. , 그는 내 받은 편지함에 너무 많은 Weibo 게시물을 수용할 수 없습니다.
읽기 및 쓰기 혼합 모드에서는 시스템이 두 가지 판단을 내려야 합니다. 하나는 어떤 사용자가 큰지 vs. 팬 수를 판단 지표로 사용할 수 있습니다. 다른 하나는 어떤 사용자가 활동적인 팬인지입니다. 이 기준은 마지막 로그인 시간 등이 될 수 있습니다. 이 두 가지 판단 기준은 시스템 개발 과정에서 동적으로 식별되고 조정되어야 합니다. 고정된 공식은 없습니다.
읽기와 쓰기 결합 모드가 두 모드의 장점을 결합한 최상의 솔루션임을 알 수 있습니다. 그러나 단점은 시스템 메커니즘이 매우 복잡하여 프로그래머에게 수많은 문제를 안겨준다는 것입니다. 일반적으로 프로젝트 초기 단계에서는 개발자가 한두 명이고 사용자 기반이 작은 경우에도 이 하이브리드 모델을 한 단계로 채택하는 데는 버그가 발생하기 쉬우므로 주의해야 합니다. 프로젝트 규모가 점차 Sina Weibo 수준으로 발전하고 피드 흐름을 전담하는 대규모 팀이 있는 경우 읽기 및 쓰기 혼합 모드가 필요합니다.
이전 기사에서는 타임라인을 기반으로 피드 스트림에 대한 일반적인 설계 솔루션을 설명했지만 이론보다 실습이 훨씬 더 까다롭습니다. 다음으로, 피드 스트림의 페이지 매김이라는 어려운 점을 구체적으로 논의하겠습니다. 읽기 확산이든 쓰기 확산이든 피드 스트림은 본질적으로 동적 목록이며 목록의 내용은 시간이 지남에 따라 계속 변경됩니다. 기존 프런트 엔드 페이징 매개변수는 page_size 및 page_num을 사용합니다. 하위 테이블은 각 페이지에 있는 항목 수와 현재 페이지가 어떤 페이지인지 나타냅니다. 동적 목록의 경우 다음과 같은 문제가 있습니다.
T1 시간에 첫 번째 페이지를 읽고 T2 시간에 누군가 "Content 11"을 새로 게시하면 T3 시간에 두 번째 페이지를 가져옵니다. 이로 인해 정렬 불량이 발생하고 첫 번째 페이지와 두 번째 페이지 모두에 "내용 6"이 반환됩니다. 실제로 두 페이지 사이에 콘텐츠를 추가하거나 삭제하면 정렬 문제가 발생할 수 있습니다.
이 문제를 해결하기 위해 일반적으로 피드 스트림의 페이징 입력 매개변수는 page_size 및 page_num을 사용하지 않고 last_id를 사용하여 이전 페이지의 마지막 콘텐츠 ID를 기록합니다.
프런트엔드가 다음 페이지를 읽을 때 입력 매개변수로 last_id를 사용해야 합니다. 백그라운드는 last_id에 해당하는 데이터를 직접 찾은 다음, page_size 데이터 조각을 오프셋하여 프런트엔드에 반환하므로 정렬 문제가 발생하지 않습니다. 아래와 같이:
last_id를 사용하기 위한 중요한 조건은 last_id 자체의 데이터를 영구 삭제할 수 없다는 것입니다. 위 그림에서 T1 시간에 5개의 데이터가 반환되고 last_id는 콘텐츠 6입니다. T2 시간에 게시자가 콘텐츠 6을 삭제한 다음 T3 시간에 두 번째 페이지를 요청하면 데이터를 찾을 수 없습니다. last_id에 전혀 해당하지 않으며 Paging 오프셋을 확인할 수 없습니다. 일반적으로 삭제 시나리오가 발생하면 콘텐츠가 삭제되었음을 나타내기 위해 콘텐츠에 플래그를 표시하는 소프트 삭제를 사용합니다. 삭제된 콘텐츠는 프런트 엔드로 반환되지 않아야 하므로 소프트 삭제 모드에서는 last_id를 찾아 page_size 막대로 오프셋하면 프런트 엔드에 충분한 데이터가 확보됩니다. 여기서 한 가지 해결 방법은 충분히 찾을 수 없는 경우 검색을 계속하는 것입니다. 또 다른 해결 방법은 반환된 항목 수가 page_size보다 작도록 프런트 엔드와 협상하는 것입니다. 모두가 동의한 후에도 page_size 매개변수는 생략할 수 있습니다.
실제 비즈니스 적용
기사 마지막 부분에서는 자체 비즈니스와 결합하여 실제 비즈니스 시나리오에서 접할 수 있는 매우 특별한 피드 흐름 설계 솔루션을 소개합니다. 라이브 방송은 진행자가 실시간으로 라이브 방송을 생성하고, 라이브 방송이 끝난 후 진행자의 팬들이 라이브 방송 다시보기를 시청할 수 있는 도구입니다. 이러한 방식으로 각 라이브 방송에는 미리 보기(라이브 방송이 생성되었지만 아직 시작되지 않음), 라이브 방송 및 다시 보기의 세 가지 상태가 있습니다. 시청자로서 여러 앵커를 팔로우할 수 있으므로 팬 입장에서는 라이브 방송을 위한 피드 스트림 페이지도 있을 것입니다. 이 피드 스트림의 가장 특별한 점은 피드 스트림 정렬 규칙입니다.
피드 스트림 정렬 규칙:
1. 내가 팔로우하는 모든 앵커의 경우 라이브 방송이 가장 먼저 순위가 매겨지며 미리보기는 중간에 순위가 매겨집니다.
p >
2. 생방송 중인 프로그램이 여러 개라면 늦은 시간부터 이른 방송 시간 순으로 정렬
3. 미리보기에 여러 프로그램이 있는 경우 이른 아침부터 시작 예정 시간 순으로 정렬 늦게
p>
4. 여러 프로그램을 재생 중인 경우 생방송 종료 시간을 기준으로 늦은 것부터 빠른 것으로 정렬
문제 분석
이 요구 사항의 가장 복잡한 점은 피드 스트림 콘텐츠의 통합입니다. "상태" 요소인 상태 변경으로 인해 피드 흐름 순서가 달라지게 됩니다. 정렬에 미치는 영향을 보다 명확하게 설명하기 위해 다음 그림을 사용하여 자세히 설명할 수 있습니다.
이 그림은 시청자로서 T1에서 페이지를 열면 4명의 앵커가 진행하는 5개의 생방송을 보여줍니다. , 그러고 보니 도착 순서는 3회가 맨 위에 있고 나머지 방송은 예고편 상태로 아침부터 밤까지 방송 예정 시간에 맞춰 표시되는 것 같아요. T2에서 페이지를 열면 상단에 5회, 중간에 나머지 3회 공연이 있고, 3회가 종료되어 하단에 있습니다. 비유하자면, 모든 생방송이 종료될 때까지 모든 세션의 최종 상태는 재생으로 변경됩니다.
여기서 주목해야 할 점은 T1에서 첫 번째 페이지를 연 다음 T4까지 움직이지 않고 페이지를 응시한 다음 두 번째 페이지로 스크롤한 다음 이전 페이지의 last_id, 즉, 라이브 방송 상태 변경으로 인해 페이징 오프셋이 해당 위치로 날아갈 가능성이 매우 높으며, 이로 인해 심각한 정렬 문제가 발생하고 라이브 방송 상태가 일관되지 않게 표시됩니다(첫 번째 페이지에는 T1의 라이브 방송 상태가 표시됨). , 첫 번째 페이지에는 T1의 생방송 상태가 표시되고, 두 번째 페이지에는 T4)의 생방송 상태가 표시됩니다.
라이브 방송 시스템은 웨이보와 다소 유사한 일방적 관계 체인입니다. 각 시청자는 소수의 앵커를 팔로우하며 각 앵커는 매우 많은 수의 팔로어를 보유할 수 있습니다. 상태 변화가 존재하기 때문에 쓰기 확산을 달성하는 것은 거의 불가능합니다. 쓰기 확산 방식을 사용하면 라이브 방송 생성, 라이브 방송 시작, 라이브 방송 종료의 세 가지 이벤트로 인해 발생하는 이벤트 상태의 변화가 여러 쓰기 작업으로 확산되기 때문입니다. 작업이 복잡하지만 지연도 불가능합니다. 웨이보가 퍼질 수 있는 이유는 게시물이 게시된 후에는 게시물 정렬에 영향을 미치는 상태 변경이 더 이상 발생하지 않기 때문입니다.
시나리오에서 "미리 보기"와 "라이브 방송"은 두 가지 중간 상태이며 "재생" 상태는 모든 라이브 방송의 최종 대상입니다. 재생이 시작되면 이 라이브 방송에 대한 상태가 더 이상 변경되지 않습니다. 따라서 '생방송' 및 '미리보기' 상태에서는 읽기 확산을 사용할 수 있고, '재생' 상태에서는 쓰기 확산을 사용할 수 있습니다.
최종 해결 방법은 아래 그림에 나와 있습니다.
라이브 방송 상태에 영향을 미치는 세 가지 이벤트(생방송 생성, 방송 시작, 라이브 방송 종료)는 모두 청취 대기열을 사용하여 비동기적으로 처리됩니다. 앵커별로 생방송 + 미리보기 상태 우선순위를 유지하고 있습니다. 라이브 방송을 생성하기 위해 앵커를 모니터링할 때마다 라이브 방송 세션이 대기열에 추가되고 점수는 방송이 시작된 타임스탬프의 반대(음수)가 됩니다. 앵커가 방송을 시작하는 것이 모니터링될 때마다 대기열에 있는 이 라이브 방송의 점수가 시작 시간(양수)으로 수정됩니다. 라이브 방송을 종료하기 위해 호스트를 모니터링할 때마다 재생 정보는 각 시청자의 재생 대기열에 비동기적으로 전달됩니다.
여기에 약간의 트릭이 있습니다. 앞서 언급한 것처럼 라이브 방송에서는 상태가 시작 시간에 따라 큰 것에서 작은 것으로 정렬되고, 예고편의 상태는 시작 시간에 따라 작은 것에서 큰 것으로 정렬됩니다. 따라서, 미리보기 상태의 점수가 이면 전체 방송 시간의 역수를 취하여 역시 큰 것부터 작은 것 순으로 정렬됩니다. 이러한 종류의 변환을 통해 라이브 방송과 미리 보기가 동일한 대기열에 있도록 할 수 있습니다. 미리보기의 점수는 모두 부정적이며, 생방송의 점수는 모두 긍정적입니다. 최종 집계에서는 모든 라이브 방송이 미리보기의 점수보다 자연스럽게 높은 순위를 확보할 수 있습니다.
또한 위에서 언급한 또 다른 문제는 첫 번째 페이지를 T1에서 풀하고 두 번째 페이지를 T4에서 풀링하여 첫 번째 페이지와 두 번째 페이지의 생방송실 상태가 일치하지 않는 문제입니다. 이 문제에 대한 해결책은 스냅샷을 통해서입니다. 청중이 첫 번째 페이지에서 피드 스트림을 가져오면 session_id 식별자를 사용하여 현재 시간을 기준으로 모든 라이브 방송 및 미리보기의 스냅샷을 생성합니다. 그냥 읽어보세요. 스냅샷의 읽기가 완료되면 시청자의 생방송 및 미리보기 시간을 읽었음을 증명하고, 나머지는 다시보기 대기열을 통해 보충됩니다. 이에 따르면, 우리 피드 스트림 시스템에는 프런트엔드 페이징으로 가져온 4개의 매개변수가 있습니다:
session_id와 last_id가 비어 있을 때마다 이는 사용자가 첫 번째 페이지에서 스냅샷을 읽고 싶어한다는 것을 증명합니다. 재건축이 필요합니다. 여기에 또 다른 파생 질문이 있습니다. session_id 값을 얻는 방법은 무엇입니까? 동일한 뷰어가 여러 터미널에서 로그인하는 상황을 고려하지 않으면 실제로 각 뷰어는 스냅샷 ID를 유지할 수 있습니다. 즉, 다중 터미널 로그인 상황을 고려하면 시스템 사용자 ID를 session_id로 직접 설정할 수 있습니다. , session_id에는 멀티 엔드 스냅샷의 상호 영향을 피하기 위해 각 터미널의 ID 정보가 포함되어야 합니다. 메모리에 대해 걱정하지 않으면 매번 session_id로 문자열을 무작위로 지정하고 만료 시간을 길게 설정할 수도 있습니다. 스냅샷이 자연스럽게 만료될 만큼 충분합니다.
사실 위 디자인에서 시스템이 가장 많은 계산량을 갖게 되는 순간이 바로 첫 페이지를 뽑아내고 스냅샷을 구축하는 데 드는 비용이다. 현재 온라인 데이터에 따르면 10개 미만의 앵커만 따르는 시청자의 경우(대부분의 시나리오이기도 함) 첫 페이지를 당기는 QPS는 15,000에 도달할 수 있습니다. 두 번째 페이지 이후의 요청도 포함되면 피드 스트림의 포괄적인 QPS가 더 높은 수준에 도달할 수 있으며 이는 현재 사용자 규모를 지원하기에 충분합니다. 첫 번째 페이지를 가져올 때 처음 10개 항목만 가져오고 직접 반환하고 스냅샷 작성 작업을 비동기식으로 변경하면 QPS가 더 높아질 수 있으며 이는 후속 최적화 지점이 될 수 있습니다.
읽기 확산, 쓰기 확산, 읽기와 쓰기 혼합 타임라인과 주의 관계를 기반으로 하는 거의 모든 피드 스트림은 이 세 가지 기본 디자인 패턴을 벗어날 수 없습니다. 실제 비즈니스에서는 더 복잡한 시나리오가 있을 수 있습니다. 예를 들어, 이 기사에서 언급한 상태 이전은 정렬에 영향을 미칠 수 있습니다. Weibo Moments 시나리오에서는 광고 액세스, 특별한 관심, 인기 주제 등도 영향을 미칠 수 있습니다. 피드 흐름 정렬 요소. 이러한 시나리오는 비즈니스 요구에 따라서만 수정할 수 있습니다.
재인쇄: /developer/article/1744756