Estou desenvolvendo um servidor de banco de dados semelhante ao Cassandra.
O desenvolvimento foi iniciado em C, mas as coisas se tornaram muito complicadas sem classes.
Atualmente, eu portado tudo em C ++ 11, mas ainda estou aprendendo C ++ "moderno" e tenho dúvidas sobre muitas coisas.
O banco de dados funcionará com pares de chave / valor. Cada par tem mais informações - quando é criado também quando expira (0 se não expirar). Cada par é imutável.
A chave é a cadeia C, o valor é nulo *, mas pelo menos no momento estou operando com o valor como a cadeia C também.
Há IList
classe abstrata . É herdado de três classes
VectorList
- matriz dinâmica C - semelhante ao std :: vector, mas usarealloc
LinkList
- feito para verificações e comparação de desempenhoSkipList
- a classe que finalmente será usada.
No futuro, eu também poderia fazer Red Black
árvores.
Cada um IList
contém zero ou mais ponteiros para pares, classificados por chave.
Se IList
ficar muito tempo, ele pode ser salvo no disco em um arquivo especial. Esse arquivo especial é meio que read only list
.
Se você precisar procurar uma chave,
- o primeiro na memória
IList
é pesquisado (SkipList
,SkipList
ouLinkList
). - Em seguida, a pesquisa é enviada para os arquivos classificados por data
(primeiro arquivo mais recente, arquivo mais antigo - último).
Todos esses arquivos são mmap-ed na memória. - Se nada for encontrado, a chave não será encontrada.
Não tenho dúvidas sobre a implementação das IList
coisas.
O que está me intrigando atualmente é o seguinte:
Os pares têm tamanhos diferentes , são alocados por new()
e std::shared_ptr
apontaram para eles.
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];
};
A variável de membro "buffer" é aquela com tamanho diferente. Ele armazena a chave + valor.
Por exemplo, se a chave tiver 10 caracteres e o valor tiver outros 10 bytes, o objeto inteiro será sizeof(Pair::Blob) + 20
(o buffer terá tamanho inicial de 2, devido a dois bytes de terminação nulos)
Esse mesmo layout também é usado no disco, para que eu possa fazer algo assim:
// 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);
No entanto, esse tamanho diferente é um problema em muitos lugares com código C ++.
Por exemplo, eu não posso usar std::make_shared()
. Isso é importante para mim, porque se eu tiver pares de 1 milhão, eu teria alocações de 2 milhões.
Por outro lado, se eu fizer "buffer" em um array dinâmico (por exemplo, novo char [123]), perderei o "truque" do mmap, terei duas desreferências se quiser verificar a chave e adicionarei um ponteiro único - 8 bytes para a classe.
Também tentei "puxar" todos os membros para Pair::Blob
dentro Pair
, Pair::Blob
para ser apenas o buffer, mas quando o testei, era bastante lento, provavelmente por causa da cópia dos dados do objeto.
Outra mudança que também estou pensando é remover a Pair
classe e substituí-la por std::shared_ptr
e "empurrar" todos os métodos de volta Pair::Blob
, mas isso não vai me ajudar com a Pair::Blob
classe de tamanho variável .
Eu estou querendo saber como posso melhorar o design do objeto para ser mais amigável ao C ++.
O código fonte completo está aqui:
https://github.com/nmmmnu/HM3
IList::remove
ou quando o IList é destruído. Leva muito tempo, mas vou fazer em um tópico separado. Será fácil porque o IList será std::unique_ptr<IList>
assim mesmo. então poderei "alternar" com a nova lista e manter o objeto antigo em algum lugar onde eu possa chamar d-tor.
C string
e os dados sempre são um buffer void *
ou char *
, portanto, você pode passar o array de caracteres. Você pode encontrar similar em redis
ou memcached
. Em algum momento, eu poderia decidir usar std::string
ou fixar a matriz de caracteres para a chave, mas sublinhe que ainda será uma string C.
std::map
oustd::unordered_map
? Por que os valores (associados às chaves) são algunsvoid*
? Você provavelmente precisaria destruí-los em algum momento; como e quando? Por que você não usa modelos?