Các phương pháp hiệu quả để lưu trữ hàng chục triệu đối tượng để truy vấn, với số lượng chèn nhiều trong một giây?


15

Về cơ bản, đây là một ứng dụng ghi / đếm đang đếm số lượng gói và đếm loại gói, v.v. trên mạng trò chuyện p2p. Điều này tương đương với khoảng 4 - 6 triệu gói trong khoảng thời gian 5 phút. Và bởi vì tôi chỉ chụp "ảnh chụp" thông tin này, nên tôi chỉ xóa các gói cũ hơn 5 phút mỗi năm phút. Vì vậy, tối đa về các mặt hàng sẽ có trong bộ sưu tập này là 10 đến 12 triệu.

Vì tôi cần thực hiện 300 kết nối với các siêu nhân khác nhau, nên có khả năng mỗi gói đang cố gắng chèn ít nhất 300 lần (có lẽ đó là lý do tại sao giữ dữ liệu này trong bộ nhớ là lựa chọn hợp lý duy nhất).

Hiện tại, tôi đang sử dụng một Từ điển để lưu trữ thông tin này. Nhưng vì số lượng lớn các mặt hàng tôi đang cố gắng lưu trữ, tôi gặp phải các vấn đề với đống đối tượng lớn và lượng sử dụng bộ nhớ liên tục tăng theo thời gian.

Dictionary<ulong, Packet>

public class Packet
{
    public ushort RequesterPort;
    public bool IsSearch;
    public string SearchText;
    public bool Flagged;
    public byte PacketType;
    public DateTime TimeStamp;
}

Tôi đã thử sử dụng mysql, nhưng nó không thể theo kịp lượng dữ liệu tôi cần chèn (trong khi kiểm tra để đảm bảo rằng nó không phải là một bản sao) và đó là trong khi sử dụng các giao dịch.

Tôi đã thử mongodb, nhưng việc sử dụng cpu cho điều đó là điên rồ và cũng không giữ được.

Vấn đề chính của tôi phát sinh cứ sau 5 phút, bởi vì tôi xóa tất cả các gói cũ hơn 5 phút và chụp "ảnh chụp" dữ liệu này. Vì tôi đang sử dụng các truy vấn LINQ để đếm số lượng gói chứa một loại gói nhất định. Tôi cũng đang gọi một truy vấn () riêng biệt trên dữ liệu, trong đó tôi tách 4 byte (địa chỉ IP) ra khỏi khóa của keyvaluepair và kết hợp nó với giá trị requestport trong Giá trị của keyvalupair và sử dụng số đó để lấy số khác biệt đồng nghiệp từ tất cả các gói.

Ứng dụng hiện đang dao động khoảng 1,1 GB sử dụng bộ nhớ và khi một ảnh chụp nhanh được gọi, nó có thể tăng gấp đôi mức sử dụng.

Bây giờ điều này sẽ không thành vấn đề nếu tôi có một lượng ram điên rồ, nhưng vm tôi có chạy này bị giới hạn ở mức 2GB ram vào lúc này.

Có giải pháp nào dễ không?


Kịch bản rất tốn bộ nhớ của nó và trên hết là bạn đang sử dụng một vm để chạy ứng dụng, wow. Dù sao, bạn đã khám phá memcached để lưu trữ các gói. Về cơ bản, bạn có thể chạy memcached trên một máy riêng biệt và ứng dụng có thể tiếp tục chạy trên chính vm.

Khi bạn đã thử cả MySQL và MongoDB, có vẻ như các yêu cầu của ứng dụng của bạn (nếu bạn muốn làm đúng) cho rằng bạn chỉ cần nhiều mã lực hơn. Nếu ứng dụng của bạn quan trọng với bạn, hãy tăng cường máy chủ. Bạn cũng có thể muốn xem lại mã "thanh trừng" của mình. Tôi chắc chắn rằng bạn có thể tìm thấy một cách xử lý tối ưu hơn, trong trường hợp nó không làm cho ứng dụng của bạn không thể sử dụng được.
Matt Beckman

4
Hồ sơ của bạn nói gì với bạn?
jasonk

Bạn sẽ không nhận được bất cứ điều gì nhanh hơn đống địa phương. Đề nghị của tôi sẽ là gọi thủ công thu gom rác sau khi thanh trừng.
vartec

@vartec - như một vấn đề thực tế, trái với niềm tin phổ biến, việc gọi thủ công thu gom rác không thực sự đảm bảo ngay lập tức, cũng ... thu gom rác. GC có thể trì hoãn hành động đến một giai đoạn sau theo thuật toán gc riêng. Gọi nó cứ sau 5 phút thậm chí có thể thêm căng thẳng, thay vì giải tỏa nó. Chỉ cần nói;)
Jas

Câu trả lời:


12

Thay vì có một từ điển và tìm kiếm từ điển đó cho các mục quá cũ; có 10 từ điển. Cứ sau 30 giây, hãy tạo một từ điển "hiện tại" mới và loại bỏ từ điển cũ nhất mà không cần tìm kiếm gì cả.

Tiếp theo, khi bạn loại bỏ từ điển cũ nhất, hãy đặt tất cả các đối tượng cũ vào hàng đợi FILO sau đó và thay vì sử dụng "mới" để tạo các đối tượng mới, kéo một đối tượng cũ ra khỏi hàng đợi FILO và sử dụng phương thức để xây dựng lại đối tượng cũ đối tượng (trừ khi hàng đợi của các đối tượng cũ trống). Điều này có thể tránh được rất nhiều phân bổ và rất nhiều chi phí thu gom rác.


1
Phân vùng theo lát cắt thời gian! Chỉ là những gì tôi sẽ đề nghị.
James Anderson

Vấn đề với điều này là, tôi sẽ phải truy vấn tất cả những từ điển được tạo ra trong vòng năm phút cuối cùng. Vì có 300 kết nối, cùng một gói sẽ đến từng ít nhất một lần. Vì vậy, để không xử lý cùng một gói nhiều lần, tôi phải giữ chúng trong ít nhất khoảng thời gian 5 phút.
Josh

1
Một phần của vấn đề với các cấu trúc chung là chúng không được tùy chỉnh cho một mục đích cụ thể. Có lẽ bạn nên thêm trường "nextItemForHash" và trường "nextItemForTimeBucket" vào cấu trúc Gói của bạn và triển khai bảng băm của riêng bạn và ngừng sử dụng Từ điển. Bằng cách đó, bạn có thể nhanh chóng tìm thấy tất cả các gói quá cũ và chỉ tìm kiếm một lần khi gói được chèn (ví dụ: có bánh của bạn và ăn nó). Nó cũng giúp ích cho việc quản lý bộ nhớ (vì "Từ điển" sẽ không phân bổ / giải phóng các cấu trúc dữ liệu bổ sung cho quản lý Từ điển).
Brendan

@Josh cách nhanh nhất để xác định xem bạn đã thấy thứ gì trước đó là hashset chưa . Các bộ băm cắt thời gian sẽ nhanh và bạn vẫn không cần phải tìm kiếm để đuổi các mục cũ. Nếu bạn chưa từng nhìn thấy nó trước đó, thì bạn có thể lưu trữ nó trong từ điển của bạn (y / ies).
Cơ bản


3

Ý nghĩ đầu tiên nảy ra trong đầu là tại sao bạn đợi 5 phút. Bạn có thể chụp ảnh nhanh thường xuyên hơn và do đó giảm được tình trạng quá tải lớn mà bạn thấy ở ranh giới 5 phút không?

Thứ hai, LINQ rất tốt cho mã ngắn gọn, nhưng trong thực tế, LINQ là cú pháp cú pháp trên C # "thông thường" và không có gì đảm bảo rằng nó sẽ tạo ra mã tối ưu nhất. Là một bài tập bạn có thể thử và viết lại các điểm nóng với LINQ, bạn có thể không cải thiện hiệu suất nhưng bạn sẽ có một ý tưởng rõ ràng hơn về những gì bạn đang làm và nó sẽ giúp công việc định hình dễ dàng hơn.

Một điều khác để xem xét là cấu trúc dữ liệu. Tôi không biết bạn làm gì với dữ liệu của mình, nhưng bạn có thể đơn giản hóa dữ liệu bạn lưu trữ theo bất kỳ cách nào không? Bạn có thể sử dụng một chuỗi hoặc mảng byte và sau đó trích xuất các phần có liên quan từ các mục đó khi bạn cần chúng không? Bạn có thể sử dụng một cấu trúc thay vì một lớp và thậm chí làm điều gì đó xấu xa với stackalloc để đặt bộ nhớ sang một bên và tránh chạy GC không?


1
Không sử dụng một chuỗi chuỗi / byte, sử dụng một cái gì đó như BitArray: msdn.microsoft.com/en-us/l Library / ám để tránh phải quay bit bằng tay. Mặt khác, đây là một câu trả lời tốt, thực sự không phải là một lựa chọn dễ dàng ngoài các thuật toán tốt hơn, phần cứng nhiều hơn hoặc phần cứng tốt hơn.
Ed James

1
Điều năm phút là do thực tế là 300 kết nối này có thể nhận được cùng một gói. Vì vậy, tôi phải theo dõi những gì tôi đã xử lý và 5 phút là khoảng thời gian cần thiết để các gói truyền đến toàn bộ các nút trên mạng cụ thể này.
Josh

3

Cách tiếp cận đơn giản: thử memcached .

  • Nó được tối ưu hóa để chạy các nhiệm vụ như thế này.
  • Nó có thể tái sử dụng bộ nhớ dự phòng trên các hộp ít bận rộn hơn, không chỉ trên hộp chuyên dụng của bạn.
  • Nó có cơ chế hết hạn bộ nhớ cache tích hợp, lười biếng nên không có trục trặc.

Nhược điểm là nó dựa trên bộ nhớ và không có bất kỳ sự kiên trì nào. Nếu một trường hợp không hoạt động, dữ liệu sẽ biến mất. Nếu bạn cần kiên trì, hãy tuần tự hóa dữ liệu.

Cách tiếp cận phức tạp hơn: thử Redis .

Nhược điểm là nó phức tạp hơn một chút.


1
Memcached có thể được chia thành các máy để tăng lượng ram có sẵn. Bạn có thể có một máy chủ thứ hai nối tiếp dữ liệu vào hệ thống tập tin để bạn không bị mất đồ nếu hộp memcache bị hỏng. API Memcache rất đơn giản để sử dụng và hoạt động từ bất kỳ ngôn ngữ nào cho phép bạn sử dụng các ngăn xếp khác nhau ở những nơi khác nhau.
Michael Storesin

1

Bạn không phải lưu trữ tất cả các gói cho các truy vấn bạn đã đề cập. Ví dụ: bộ đếm loại gói:

Bạn cần hai mảng:

int[] packageCounters = new int[NumberOfTotalTypes];
int[,] counterDifferencePerMinute = new int[6, NumberOfTotalTypes];

Mảng đầu tiên theo dõi có bao nhiêu gói trong các loại khác nhau. Mảng thứ hai theo dõi số lượng gói được thêm vào mỗi phút để bạn biết có bao nhiêu gói cần được loại bỏ trong mỗi khoảng thời gian. Tôi hy vọng bạn có thể nói rằng mảng thứ hai được sử dụng như một hàng đợi FIFO tròn.

Vì vậy, đối với mỗi gói, các thao tác sau được thực hiện:

packageCounters[packageType] += 1;
counterDifferencePerMinute[current, packageType] += 1;
if (oneMinutePassed) {
  current = (current + 1) % 6;
  for (int i = 0; i < NumberOfTotalTypes; i++) {
    packageCounters[i] -= counterDifferencePerMinute[current, i];
    counterDifferencePerMinute[current, i] = 0;
}

Bất cứ lúc nào, bộ đếm gói có thể được lấy chỉ mục ngay lập tức và chúng tôi không lưu trữ tất cả các gói.


Lý do chính cho việc phải lưu trữ dữ liệu mà tôi làm, là thực tế là 300 kết nối này có thể nhận được cùng một gói chính xác. Vì vậy, tôi cần giữ mọi gói tin nhìn thấy trong ít nhất năm phút để đảm bảo rằng tôi không xử lý / đếm chúng nhiều lần. Đó là những gì ulong cho khóa từ điển là cho.
Josh

1

(Tôi biết đây là một câu hỏi cũ, nhưng tôi đã chạy qua nó trong khi tìm kiếm một giải pháp cho một vấn đề tương tự trong đó bộ sưu tập rác thế hệ thứ hai đã tạm dừng ứng dụng trong vài giây, vì vậy ghi lại cho người khác trong tình huống tương tự).

Sử dụng một cấu trúc chứ không phải là một lớp cho dữ liệu của bạn (nhưng hãy nhớ rằng nó được coi là một giá trị với ngữ nghĩa truyền qua bản sao). Điều này đưa ra một cấp độ tìm kiếm gc phải thực hiện mỗi lần vượt qua điểm.

Sử dụng mảng (nếu bạn biết kích thước của dữ liệu bạn đang lưu trữ) hoặc Danh sách - sử dụng mảng bên trong. Nếu bạn thực sự cần truy cập ngẫu nhiên nhanh, hãy sử dụng từ điển các chỉ mục mảng. Điều này sẽ đưa ra một vài cấp độ khác (hoặc một tá hoặc nhiều hơn nếu bạn đang sử dụng SortedDipedia) để gc phải tìm kiếm.

Tùy thuộc vào những gì bạn đang làm, tìm kiếm danh sách các cấu trúc có thể nhanh hơn tra cứu từ điển (do nội địa hóa bộ nhớ) - hồ sơ cho ứng dụng cụ thể của bạn.

Sự kết hợp của struct & list làm giảm đáng kể cả việc sử dụng bộ nhớ và kích thước của trình thu gom rác quét đáng kể.


Tôi có một thử nghiệm gần đây, tạo ra các bộ sưu tập & từ điển trong đĩa nhanh nhất, sử dụng sqlite github.com/modma/PersistenceCollections
ModMa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.