Saya mengembangkan server database yang mirip dengan Cassandra.
Pengembangan dimulai pada C, tetapi berbagai hal menjadi sangat rumit tanpa kelas.
Saat ini saya porting semuanya dalam C ++ 11, tapi saya masih belajar "modern" C ++ dan ragu tentang banyak hal.
Database akan bekerja dengan pasangan Key / Value. Setiap pasangan memiliki beberapa informasi lagi - kapan dibuat juga kapan akan kedaluwarsa (0 jika tidak kedaluwarsa). Setiap pasangan tidak berubah.
Kuncinya adalah string C, Nilai tidak berlaku *, tetapi setidaknya untuk saat ini saya beroperasi dengan nilai sebagai string C juga.
Ada IList
kelas abstrak . Itu diwarisi dari tiga kelas
VectorList
- C dynamic array - mirip dengan std :: vector, tetapi menggunakanrealloc
LinkList
- dibuat untuk pemeriksaan dan perbandingan kinerjaSkipList
- kelas yang akhirnya akan digunakan.
Di masa depan saya mungkin melakukan Red Black
pohon juga.
Masing IList
- masing berisi nol atau lebih pointer untuk dipasangkan, disortir berdasarkan kunci.
Jika IList
terlalu lama, dapat disimpan di disk dalam file khusus. File khusus ini semacam read only list
.
Jika Anda perlu mencari kunci,
- pertama dalam memori
IList
dicari (SkipList
,SkipList
atauLinkList
). - Kemudian pencarian dikirim ke file yang diurutkan berdasarkan tanggal
(file terbaru pertama, file terlama - terakhir).
Semua file ini dalam memori mmap-ed. - Jika tidak ada yang ditemukan, maka kunci tidak ditemukan.
Saya tidak ragu tentang implementasi IList
hal - hal tersebut.
Yang membingungkan saya adalah sebagai berikut:
Pasangan dengan ukuran yang berbeda , mereka dialokasikan oleh new()
dan mereka std::shared_ptr
menunjuk ke mereka.
class Pair{
public:
// several methods...
private:
struct Blob;
std::shared_ptr<const Blob> _blob;
};
struct Pair::Blob{
uint64_t created;
uint32_t expires;
uint32_t vallen;
uint16_t keylen;
uint8_t checksum;
char buffer[2];
};
variabel "buffer" adalah variabel dengan ukuran berbeda. Ini menyimpan nilai kunci +.
Misalnya, jika kunci adalah 10 karakter, dan nilainya 10 byte lain, seluruh objek akan menjadi sizeof(Pair::Blob) + 20
(buffer memiliki ukuran awal 2, karena dua byte null terminating)
Layout yang sama ini digunakan pada disk juga, jadi saya bisa melakukan sesuatu seperti ini:
// get the blob
Pair::Blob *blob = (Pair::Blob *) & mmaped_array[pos];
// create the pair, true makes std::shared_ptr not to delete the memory,
// since it does not own it.
Pair p = Pair(blob, true);
// however if I want the Pair to own the memory,
// I can copy it, but this is slower operation.
Pair p2 = Pair(blob);
Namun ukuran yang berbeda ini merupakan masalah di banyak tempat dengan kode C ++.
Misalnya saya tidak bisa menggunakan std::make_shared()
. Ini penting bagi saya, karena jika saya memiliki 1 juta pasang, saya akan memiliki alokasi 2 juta.
Dari sisi lain, Jika saya melakukan "buffer" ke array dinamis (mis. Char baru [123]), saya akan kehilangan mmap "trik", saya akan melakukan dua dereferensi jika saya ingin memeriksa kunci dan saya akan menambahkan pointer tunggal - 8 byte ke kelas.
Saya juga mencoba untuk "menarik" semua anggota dari Pair::Blob
dalam Pair
, sehingga Pair::Blob
menjadi hanya buffer, tapi ketika saya diuji, itu cukup lambat, mungkin karena menyalin data objek di sekitar.
Perubahan lain yang saya pikirkan adalah untuk menghapus Pair
kelas dan menggantinya dengan std::shared_ptr
dan "mendorong" semua metode kembali ke Pair::Blob
, tetapi ini tidak akan membantu saya dengan Pair::Blob
kelas ukuran variabel .
Saya bertanya-tanya bagaimana saya bisa memperbaiki desain objek agar lebih ramah C ++.
Kode sumber lengkap ada di sini:
https://github.com/nmmmnu/HM3
IList::remove
atau ketika IList dihancurkan. Butuh banyak waktu, tetapi saya akan melakukannya di utas terpisah. Ini akan mudah karena IList akan std::unique_ptr<IList>
tetap. jadi saya akan dapat "beralih" dengan daftar baru dan menyimpan objek lama di suatu tempat di mana saya dapat memanggil d-tor.
C string
dan data selalu buffer void *
atau char *
, sehingga Anda dapat melewati array char. Anda dapat menemukan yang serupa di redis
atau memcached
. Pada titik tertentu saya bisa memutuskan untuk menggunakan std::string
atau memperbaiki array char untuk kunci, tetapi menggarisbawahi itu akan tetap string C.
std::map
ataustd::unordered_map
? Mengapa beberapa nilai (terkait dengan kunci)void*
? Anda mungkin perlu menghancurkan mereka di beberapa titik; bagaimana & kapan? Mengapa Anda tidak menggunakan templat?