Sto sviluppando un server di database simile a Cassandra.
Lo sviluppo iniziò in C, ma le cose diventarono molto complicate senza lezioni.
Attualmente ho portato tutto in C ++ 11, ma sto ancora imparando il C ++ "moderno" e ho dubbi su molte cose.
Il database funzionerà con coppie chiave / valore. Ogni coppia ha qualche informazione in più - quando viene creata anche quando scadrà (0 se non scade). Ogni coppia è immutabile.
La chiave è stringa C, il valore è nullo *, ma almeno per il momento sto operando con il valore anche come stringa C.
Ci sono IList
classi astratte . È ereditato da tre classi
VectorList
- C array dinamico - simile a std :: vector, ma utilizzarealloc
LinkList
- fatto per i controlli e il confronto delle prestazioniSkipList
- la classe che verrà infine utilizzata.
In futuro potrei fare anche l' Red Black
albero.
Ciascuno IList
contiene zero o più puntatori a coppie, ordinati per chiave.
Se è IList
diventato troppo lungo, può essere salvato sul disco in un file speciale. Questo file speciale è una specie di read only list
.
Se devi cercare una chiave,
- viene prima
IList
cercata in memoria (SkipList
,SkipList
oLinkList
). - Quindi la ricerca viene inviata ai file ordinati per data
(prima il file più recente, il file più vecchio - ultimo).
Tutti questi file sono mmap-ed in memoria. - Se non viene trovato nulla, la chiave non viene trovata.
Non ho dubbi sull'attuazione delle IList
cose.
Quello che attualmente mi sta sconcertando è il seguente:
Le coppie hanno dimensioni diverse , sono assegnate da new()
e le hanno std::shared_ptr
indicate.
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];
};
La variabile membro "buffer" è quella con dimensioni diverse. Memorizza la chiave + valore.
Ad esempio, se la chiave è di 10 caratteri e il valore è di altri 10 byte, l'intero oggetto sarà sizeof(Pair::Blob) + 20
(il buffer ha una dimensione iniziale di 2, a causa di due byte finali nulli)
Lo stesso layout viene utilizzato anche sul disco, quindi posso fare qualcosa del genere:
// 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);
Tuttavia, questa diversa dimensione è un problema in molti luoghi con codice C ++.
Ad esempio non posso usare std::make_shared()
. Questo è importante per me, perché se avessi coppie 1M, avrei allocazioni 2M.
Dall'altro lato, se faccio "buffer" su un array dinamico (es. Nuovo carattere [123]), perderò il "trucco" di mmap, avrò due dereferenze se voglio controllare la chiave e aggiungerò un singolo puntatore - 8 byte per la classe.
Ho anche cercato di "estrarre" tutti i membri da Pair::Blob
dentro Pair
, quindi Pair::Blob
per essere solo il buffer, ma quando l'ho provato, è stato piuttosto lento, probabilmente a causa della copia dei dati dell'oggetto.
Un'altra modifica che sto pensando è anche quella di rimuovere la Pair
classe e sostituirla con std::shared_ptr
e di "rimandare" tutti i metodi a Pair::Blob
, ma questo non mi aiuterà con la Pair::Blob
classe di dimensioni variabili .
Mi chiedo come posso migliorare la progettazione degli oggetti per essere più amichevole con il C ++.
Il codice sorgente completo è qui:
https://github.com/nmmmnu/HM3
IList::remove
o quando IList viene distrutto. Ci vuole molto tempo, ma lo farò in thread separati. Sarà facile perché IList lo sarà std::unique_ptr<IList>
comunque. così sarò in grado di "cambiarlo" con un nuovo elenco e mantenere il vecchio oggetto da qualche parte dove posso chiamare d-tor.
C string
e i dati sono sempre dei buffer void *
o char *
, quindi è possibile passare array di caratteri. Puoi trovare simili in redis
o memcached
. Ad un certo punto potrei decidere di usare std::string
o correggere un array di caratteri per la chiave, ma sottolineo che sarà ancora la stringa C.
std::map
ostd::unordered_map
? Perché i valori (associati alle chiavi) sono alcunivoid*
? Probabilmente avresti bisogno di distruggerli ad un certo punto; come quando? Perché non usi i template?