+14

Nhật ký công việc: Ngày 1, tôi đã khắc phục tình trạng khách hàng bị treo email như thế nào?

Ngày 01 tháng x năm 2xxx Thời tiết ngày sáng đêm đen, nhiệt độ 8 độ thích hợp ngủ, ăn chơi. Không hợp làm việc fix bug.

Buổi sáng vừa tới công ty, vừa chào em lễ tân xinh gái, vươn vai ngáp vặt đi vào chỗ làm thì bị báo ngay một sự cố. Một số khách hàng báo gửi email từ hôm qua tới bây giờ vẫn không được. Cái gì vậy đúng là hôm nay nên nghỉ ở nhà. Sau một hồi điều tra thì phát hiện ra có một ông khách hàng gửi dưới 1 tỷ cái email vẫn chưa gửi xong nên các ông khách hàng khác phải chờ. Đại ý cái hệ thống email nó như thế này.

image.png

Trong hệ thống trên có một queue để xếp hàng các yêu cầu gửi email. Thông thường các yêu cầu gửi là ít và cứ chạy tuần tự sẽ hết, đằng này ông khách hàng kia gửi quá nhiều email. Ông ấy gửi một mình thì chậm không sao nhưng những khách hàng khác gửi 1,2 cái email mà mãi không nhận được đang gọi cho các em hỗ trợ khách hàng toát hết mồ hôi (trời đang 8 độ). Tôi thì hay thương các em xinh đẹp nên phải làm thôi.

Hiện chỉ đang có một queue nghẽn thì ta tăng queue lên thôi. Lúc này thì hệ thống sẽ như thế này.

image.png

Cứ tưởng mọi chuyện đều ổn, thế nhưng mà vẫn không ổn. Tại sao scale lên rồi mà vẫn chậm thì ra là producer đang truyền message sang queue theo kiểu round robin. Nghĩa là cứ nhận email mới thì tuần tự đẩy vào các queue kia. Dẫn tới ông dưới 1 tỷ email kia được chia đều mỗi queue dưới 100 triệu vẫn nghẽn các ông khác. Lúc này mình cũng hơi cáu đã thế thì cho ông kia vào 1 queue duy nhất để tránh ảnh hưởng. Tuy nhiên vấn đề là làm sao để biết được email tới từ ông ấy mà nhét vào queue kia và làm sao để nhiều ông sau này nữa thì cũng sẽ chia mỗi công ty chỉ vào một queue thôi tránh trường hợp 1 công ty lại vào 2 queue ảnh hưởng tới người khác. Đương nhiên sẽ có trường hợp có ông nào đen vẫn vào chung queue ông kia nhưng sẽ ít bị kêu hơn.

Đầu tiên giải pháp đơn giản nhất là chia lấy phần dư. Ví dụ có 5 server thì lấy ID ông khách hàng đó chia cho 5 ông dư 0 vào queue1 ông dư 1 vào queue số 2 cứ như thế! Cách này đảm bảo mỗi ông vào đúng 1 queue. Tuy nhiên điều gì sẽ xảy ra nếu tôi cần thêm queue vào hoặc bỏ bớt queue ra? Lúc này tất cả message sẽ xáo trộn hết dẫn tới việc các cache ở các hệ thống consumer sẽ loạn hết cả lên. Ngoài ra còn phải tính tới việc chia đều nữa, nếu có mấy ông chia cho 5 dư 0 hết thì sao?

Lúc này cần một thuật toán để xử lý vấn đề trên sao cho khi thêm node hay bớt node không làm loạn hết cả message với queue lên. Lúc này có hai cái hay được dùng là consistent hashing và Rendezvous Hashing. Hai thằng này thường được dùng để chia server khi load-balancing để làm cái việc giống cái queue kia. Và quan trọng là nó phải chia đều. Cái consistent hashing hơi phức tạp, mà tôi thì lười nên kiếm cái Rendezvous Hashing để xử lý. Nhưng hơi buồn là tìm mãi chả thấy cái thư viện nào nên đành tự code một cái vậy. Source code tôi để ở đây . Lúc này mô hình sẽ là như này

image.png

Ok vậy là từ giờ mỗi khách hàng sẽ vào một queue duy nhất nếu treo thì treo queue đó thôi. Ít ra không ảnh hưởng toàn bộ hệ thống như trước nữa. Còn trường hợp vẫn có ông khác chui vào queue đó thì sao? Thì chịu chứ sao 😂. Thực ra trường hợp một queue bị tồn quá nhiều thì cần có monitor cảnh bảo để chúng ta biết được mà khắc phục. Việc chia tách này sẽ đảm bảo tới lúc ta khắc phục thì có một phần nhỏ bị ảnh hưởng mà không ảnh hưởng toàn bộ hệ thống như cũ.

PS dưới đây là mô tả về sự chia đều của thuật toán Rendezvous Hashing nhé!

Giả sử có 5 node với 10000 công ty khác nhau với đoạn code C# như sau

using Rendezvous;

namespace Demo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            
            List<Node> nodes = new List<Node>();
            for (int i = 0; i < 5; i++)
                {
                    nodes.Add(new Node("node" + i, 1));
                }
            Rendezvous.RendezvousHash rendezvous = new Rendezvous.RendezvousHash(nodes);

            Dictionary<string, int> countItemInNode = new Dictionary<string, int>();

            for (int i = 0; i < 10000; i++)
            {
                var item = Guid.NewGuid().ToString();
                var nodeName=rendezvous.GetNode(item).Name;
                if (countItemInNode.ContainsKey(nodeName))
                {
                    countItemInNode[nodeName]++;
                }
                else
                {
                    countItemInNode[nodeName] = 1;
                }
            }

            foreach (var item in countItemInNode)
            {
                Console.WriteLine(item.Key + ":" + item.Value);
            }
        }
    }
}

Dưới đây là kết quả phân phối node với thuật toán này. Các bạn có thể thấy nó rất đều không bị lệch bao nhiêu cả mỗi queue sẽ đều được nhận gần 2000 message.

image.png

Nếu các bạn thấy hay thì upvote, thấy thích thì chia sẻ giúp mình để mình có động lực viết tiếp nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí