Hybrid system for a generic Key-Value Store.
CLIQUE NA IMAGEM OU NO LINK:
- Compilador do Go (
go) - Versão >= 1.22.2 - Compilador do Proto (
protoc) - Gerador de código para go (
protoc-gen-go-grpc) - Docker instalado (
docker) (usamos docker para subir o broker mqtt) - Se quiser testar o broker mqtt tenha instalado o
mosquitto_subemosquitto_publocalmente
-
Entre no diretório raíz e execute:
go mod tidyAssim o compilador do go irá baixar as dependências necessárias para execução dos módulos do programa. -
Após isso, certifique-se de subir o broker mosquitto. Para isso, dirija-se ao diretório
Brokere execute o seguinte comando:docker compose up -d
Para testar se o broker está funcionando, execute o script de teste do broker ./Broker/test.sh
Ele deve devolver a seguinte mensagem Hello World!.
- Agora que o broker mqtt está up, podemos subir nossos servidores kvs, para isto veja a seção Instruções de compilação
-
Para compilar o servidor, você deve executar o seguinte comando na raiz do projeto:
go build -o <NOME_PRO_BINARIO> SuperServer/SuperServer.go -
Para executar a build gerada, só executar o seguinte comando:
./<NOME_PRO_BINARIO> --port=:<PORTA> -
Se quiser fazer tudo isso em um único comando: ((é mais prático))
go run SuperServer/SuperServer.go --port=:<PORTA>
O uso do servidor é super tranquilo, uma vez que tudo foi configurado devidamente (descrito nos tópicos anteriores), basta:
-
Ou compilar o programa e executar o binario gerado com a tag
--port=:<PORTA>(certifique-se que a porta está disponível para uso) -
Ou executar
go run SuperServer/SuperServer.go --port=:<PORTA>.
OBS : certifique-se que a porta que se deseja usar está disponível para uso.
Nosso TAD de KVS está disponível no diretório SuperServer/KVS, vamos investigar a estrutura dos principais modelos utilizados:
Esta é a estrutura da nossa KVS, ela possui um map que mapeia CHAVES(string) para um slice (lista) de Tupla. O RWMutex ali serve para evitar escritas simultâneas, uma vez que os maps do go permitem leitura concorrente por padrão.
type KVS struct {
hash map[string][]*Tupla
mu sync.RWMutex
}
Esta é a estrutura da nossa Tupla, ela foi gerada automaticamente pelo protoc e encontra-se no arquivo SuperServer/KVS/GRPCServerInterface.pb.go
// Retorno de consultas
type Tupla struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Chave string `protobuf:"bytes,1,opt,name=chave,proto3" json:"chave,omitempty"` // a chave passada na consulta
Valor string `protobuf:"bytes,2,opt,name=valor,proto3" json:"valor,omitempty"` // valor encontrado
Versao int32 `protobuf:"varint,3,opt,name=versao,proto3" json:"versao,omitempty"` // versão do valor para a chave
}
Esta é a estrutura do nosso ChaveValor, ela foi gerada automaticamente pelo protoc e encontra-se no arquivo SuperServer/KVS/GRPCServerInterface.pb.go
// Parâmetro de entrada para inserções
type ChaveValor struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Chave string `protobuf:"bytes,1,opt,name=chave,proto3" json:"chave,omitempty"` // a chave
Valor string `protobuf:"bytes,2,opt,name=valor,proto3" json:"valor,omitempty"` // o valor
}
Esta é a estrutura do nosso ChaveVersao, ela foi gerada automaticamente pelo protoc e encontra-se no arquivo SuperServer/KVS/GRPCServerInterface.pb.go
// Parâmetro de entrada para consultas
type ChaveVersao struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Chave string `protobuf:"bytes,1,opt,name=chave,proto3" json:"chave,omitempty"` // a chave pesquisada
Versao *int32 `protobuf:"varint,2,opt,name=versao,proto3,oneof" json:"versao,omitempty"` // a versão pesquisada (opcional)
}
Esta é a estrutura da nossa Versao, ela foi gerada automaticamente pelo protoc e encontra-se no arquivo SuperServer/KVS/GRPCServerInterface.pb.go
// Versão para snapshot
type Versao struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Versao int32 `protobuf:"varint,1,opt,name=versao,proto3" json:"versao,omitempty"` // a versão pesquisada
}
Temos neste arquivo SuperServer/KVS/GRPCServerInterface_gprc.pb.go a interface do KVSServer, a qual implementamos em SuperServer/KVSServer/KVSServer.go
Interface do KVSServer prevista pelo proto:
// KVSServer is the server API for KVS service.
// All implementations must embed UnimplementedKVSServer
// for forward compatibility.
type KVSServer interface {
Insere(context.Context, *ChaveValor) (*Versao, error)
Consulta(context.Context, *ChaveVersao) (*Tupla, error)
Remove(context.Context, *ChaveVersao) (*Versao, error)
InsereVarias(grpc.BidiStreamingServer[ChaveValor, Versao]) error
ConsultaVarias(grpc.BidiStreamingServer[ChaveVersao, Tupla]) error
RemoveVarias(grpc.BidiStreamingServer[ChaveVersao, Versao]) error
Snapshot(*Versao, grpc.ServerStreamingServer[Tupla]) error
mustEmbedUnimplementedKVSServer()
}
Nossa struct que implementa os métodos da interface KVSServer e integra o MQTT para comunicação entre os servidores
// KVSServer implements the KVSServer interface
type KVSServer struct {
kvs.UnimplementedKVSServer
kvs *kvs.KVS
mqtt *mqtt.Client
}
O cliente rust fornecido não sabia lidar direito com as mensagens retornadas pelos nossos servidores nos casos de erro e acabava soltando mensagens de panic que atrapalhava a visualização do resto do teste. Por isso acabamos tirando algumas mensagens de erro. Além disso, sofremos um pouco para evitar duplicações de mensagem entre os servidores via mqtt, o jeito que encontramos foi adicionar um identificador do originador da mensagem, evitando duplicação de mensagens entre os servidores e a reaplicação de métodos nos KVS. Sofremos um pouco pra entender onde seria o melhor lugar de deixar nossa integração com o mqtt também, acabamos optando por deixar no KVSServer, achamos que ficava melhor modularizado assim.
Implementamos tudo que foi requisitado, única coisa que acabamos mudando, foi tirar alguns retornos de mensagem de erro. Fizemos isso pois o cliente rust fornecido para testes não sabia lidar com as mensagens de teste direito, então ele exibia a mensagem, mas logo em seguido dava panic, isso não afetava em nada os nossos servidores, mas os prints do client ficavam bem feios.
Este cara existe na root do projeto, só baixar as seguintes dependências:
godockerprotoc
Ao executar ./compile.sh <NUMERO_DE_SERVIDORES>. O script irá gerar o número de binarios indicado no comando.
Arquivo server.sh para executar o servidor, recebendo como parâmetro único a porta em que o servidor deve aguardar conexões.
Este cara existe na root do projeto, só baixar as seguintes dependências:
godockerprotoc
e executar ./server.sh <PORTA>

