Serviço de rastreamento com Go e Redis

Douglas Mendez Segue 09 de ago de 2018 · 6 min ler

Parte 2: Acompanhando o serviço com o Go e o Redis V2

Imagine que trabalhamos em uma startup como a Uber, e precisamos criar um novo serviço que salve os locais dos drivers a cada momento e os processe. Dessa forma, quando alguém solicita um driver, podemos descobrir quais drivers estão mais próximos do nosso ponto de captação.

Este é o núcleo do nosso serviço. Salve os locais e pesquise os drivers próximos. Para este serviço, estamos usando o Go e o Redis.

Redis

O Redis é um armazenamento de estrutura de dados na memória de código-fonte aberto (BSD), usado como banco de dados, cache e intermediário de mensagens. Ele suporta estruturas de dados como strings, hashes, listas, conjuntos, conjuntos classificados com consultas de intervalo, bitmaps, hiperloglogs e índices geoespaciais com consultas radius.

O Redis tem múltiplas funções, mas para o propósito deste serviço, vamos nos concentrar em suas funções geoespaciais.

Primeiro precisamos instalar o Redis. Eu recomendo usar o Docker rodando um container com Redis. Simplesmente seguindo este comando, teremos um container rodando Redis em nossa máquina.

 docker run -d -p 6379: 6379 redis 

Vamos começar a codificar

Vamos escrever uma implementação básica para esse serviço, pois quero escrever outros artigos sobre como melhorar esse serviço. Eu usarei este código como base nos meus próximos artigos.

Para este serviço, precisamos usar o pacote “github.com/go-redis/redis” que fornece um cliente Redis para o Golang.

Crie um novo projeto (pasta) no seu workdir. No meu caso, vou chamar de "rastreamento". Primeiro precisamos instalar o pacote.

 vai buscar -u github.com/go-redis/redis 

Em seguida, criamos o arquivo 'storages / redis.go' que contém a implementação que nos ajudará a obter um cliente Redis e algumas funções para trabalhar com geoespacial.

Nós agora criamos uma estrutura que contém um ponteiro para o cliente redis. Este ponteiro terá as funções que nos ajudam com este serviço, nós também criamos uma constante com o nome da chave para o nosso conjunto em redis.

 tipo RedisClient struct {* redis.Client} 
chave const = "drivers"

Para obter a função para obter o cliente Redis, vamos usar o padrão singleton com a ajuda do pacote de sincronização e sua funcionalidade Once.Do.

Na engenharia de software, o padrão singleton é um padrão de design de software que restringe a instanciação de uma classe a um objeto. Isso é útil quando exatamente um objeto é necessário para coordenar ações no sistema. Se você quiser ler mais sobre o Padrão Singleton .

Mas como faz uma once.Do . O struct sync.Once tem um contador atômico e usa atomic.StoreUint32 para definir um valor como 1, quando a função foi chamada, e depois atomic.LoadUint32 para ver se ele precisa ser chamado novamente. Para esta implementação básica, GetRedisClient será chamado a partir de dois pontos de extremidade, mas nós só queremos obter uma instância.

 var once sync.Once 
var redisClient * RedisClient
func GetRedisClient () * RedisClient {
once.Do (func () {
cliente: = redis.NewClient (& redis.Options {
Addr: "localhost: 6379",
Senha: "", // sem senha definida
DB: 0, // usar DB padrão
})
redisClient = & RedisClient {client}
})
_, err: = redisClient.Ping (). Result ()
if err! = nil {
log.Fatalf ("Não foi possível conectar-se ao redis% v", err)
}
return redisClient
}

Então criamos três funções para o RedisClient.

AddDriverLocation: Adicione o item geoespacial especificado (latitude, longitude, nome “neste caso, o nome do driver”) para a chave especificada. Você se lembra da chave que definimos no início do Set in Redis? É isso.

 func (c * RedisClient) AddDriverLocation (lng, lat e float64, id string) { 
c.GeoAdd (
chave,
& redis.GeoLocation {Longitude: lng, Latitude: lat, nome: id},
)
}

RemoveDriverLocation: O cliente redis não possui a função GeoDel porque o comando GEODEL não existe, portanto podemos usar o ZREM para remover elementos. A estrutura do índice Geo é apenas um conjunto ordenado.

 func (c * RedisClient) RemoveDriverLocation (id string) { 
c.ZRem (chave, id)
}

SearchDrivers: a função GeoRadius implementa o comando GEORADIUS que retorna os membros de um conjunto classificado preenchido com informações geoespaciais usando GEOADD, que estão dentro das bordas da área especificada com a localização central e a distância máxima do centro (o raio). Se você quiser saber mais sobre isso, vá para GEORADIUS

 func (c * RedisClient) SearchDrivers (limite int, lat, lng, r float64) [] redis.GeoLocation { 
/ *
WITHDIST: Também retorna a distância dos itens retornados do centro especificado. A distância é retornada na mesma unidade que a unidade especificada como o raio do argumento do comando.

WITHCOORD: Também retorna as coordenadas de latitude e longitude dos itens correspondentes.

WITHHASH: Também retorna a pontuação do conjunto classificado, codificado por geohash, do item, na forma de um inteiro não assinado de 52 bits. Isso é útil apenas para hacks de baixo nível ou depuração e, de outra forma, é de pouco interesse para o usuário geral.
* / res, _: = c.GeoRadius (chave, lng, lat e redis.GeoRadiusQuery {
Raio: r
Unidade: "km"
Com o GeoHash: true
Com a Coorde: true
WithDist: true
Contagem: limite
Ordenar: "ASC",
}). Result ()
return res
}

Em seguida, crie um main.go

 importação principal do pacote ( 
"net / http"
"fmt"
"registro"
)
func main () {
// Criamos um simples httpserver
servidor: = http.Server {
Addr: fmt.Sprint (": 8000"),
Manipulador: NewHandler (),
}
// Executa o servidor
log.Printf ("Iniciando o servidor HTTP. Escutando em% q", server.Addr)
if err: = server.ListenAndServe (); err! = nil {
log.Printf ("% v", err)
} outro {
log.Println ("Servidor fechado!")
}
}

Criamos um servidor simples usando http.Server.

Em seguida, criamos o arquivo 'handler / handler.go' que contém os pontos de extremidade do nosso aplicativo.

 func NewHandler () * http.ServeMux { 
mux: = http.NewServeMux ()
mux.HandleFunc ("tracking", tracking)
mux.HandleFunc ("pesquisa", pesquisa)
retorno mux
}

Usamos o http.ServeMux para lidar com nossos endpoints, criamos dois endpoints para nosso serviço.

O primeiro endpoint 'tracking' nos permite salvar o último local enviado de um driver, neste caso, queremos apenas salvar o último local. Poderíamos modificar esse endpoint para que os locais anteriores sejam salvos em outro banco de dados

 func tracking (w http.ResponseWriter, r * http.Request) { 
// cria uma estrutura anônima para os dados do driver.
var driver = struct {
ID string `json:" id "`
Lat float64 `json:" lat "`
Lng float64 `json:" lng "`
} {}
rClient: = storages.GetRedisClient () se err: = json.NewDecoder (r.Body) .Decode (& driver); err! = nil {
log.Printf ("não pôde decodificar a solicitação:% v", err)
http.Error (w, "não pôde decodificar solicitação", http.StatusInternalServerError)
Retorna
}
// Adicionar novo local
// Você pode salvar locais em outro banco de dados
rClient.AddDriverLocation (driver.Lng, driver.Lat, driver.ID)
w.WriteHeader (http.StatusOK)
Retorna
}

O segundo endpoint é 'search' com este endpoint, podemos encontrar todos os drivers perto de um determinado ponto,

 // search recebe lat e lng do ponto de picking e pesquisa os drivers sobre esse ponto. 
func search (w http.ResponseWriter, r * http.Request) {
rClient: = storages.GetRedisClient ()
corpo: = struct {
Lat float64 `json:" lat "`
Lng float64 `json:" lng "`
Limite int `json:" limite "`
} {}
se err: = json.NewDecoder (r.Body) .Decode (& body); err! = nil {
log.Printf ("não pôde decodificar a solicitação:% v", err)
http.Error (w, "não pôde decodificar solicitação", http.StatusInternalServerError)
Retorna
}
drivers: = rClient.SearchDrivers (body.Limit, body.Lat, body.Lng, 15)
dados, err: = json.Marshal (drivers)
if err! = nil {
http.Error (w, err.Error (), http.StatusInternalServerError)
Retorna
}
w.Header (). Set ("Content-Type", "aplicativo / json")
w.WriteHeader (http.StatusOK)
w.Write (dados)
Retorna
}

Vamos testar o serviço

Primeiro, execute o servidor.

 vai correr main.go 

Em seguida, precisamos adicionar quatro locais de drivers.

Adicionamos quatro drivers como acima no mapa, as linhas verdes mostram a distância entre o ponto de picking e os drivers.

 curl -i --header "Tipo de conteúdo: aplicativo / json" --data '{"id": "1", "lat": -33.44091, "lng": -70.6301}' http: // localhost: 8000 / tracking curl -i --header "Tipo de conteúdo: aplicativo / json" --data '{"id": "2", "lat": -33.44005, "lng": -70.63279}' http: // localhost : 8000 / tracking curl -i --header "Tipo de conteúdo: aplicativo / json" --data '{"id": "3", "lat": -33.44338, "lng": -70.63335}' http: / / localhost: 8000 / tracking curl -i --header "Tipo de conteúdo: aplicativo / json" --data '{"id": "4", "lat": -33.44186, "lng": -70.62653}' http : // localhost: 8000 / acompanhamento 

Como agora temos os locais dos drivers, podemos fazer uma pesquisa espacial.

vamos procurar por 4 motoristas próximos

 curl -i --header "Tipo de conteúdo: aplicativo / json" --data '{"lat": -33.44262, "lng": -70.63054, "limite": 5}' http: // localhost: 8000 / search 

Como você verá o resultado corresponde ao mapa, veja as linhas verdes no mapa.

 HTTP / 1.1 200 OK 
Tipo de Conteúdo: application / json
Data: quarta, 08 ago 2018 05:07:57 GMT
Content-Length: 456
[
{
"Nome": "1",
"Longitude": -70.63009768724442,
"Latitude": -33.44090957099124,
"Dist": 0,1946,
"GeoHash": 861185092131738
}
{
"Nome": "3",
"Longitude": -70.63334852457047,
"Latitude": -33.44338092412159,
"Dist": 0,2741,
"GeoHash": 861185074815667
}
{
"Nome": "2",
"Longitude": -70.63279062509537,
"Latitude": -33.44005030051822,
"Dist": 0,354,
"GeoHash": 861185086448695
}
{
"Nome": "4",
"Longitude": -70.62653034925461,
"Latitude": -33.44186009142599,
"Dist": 0,3816,
"GeoHash": 861185081504625
}
]

Procure o motorista mais próximo

 curl -i --header "Tipo de conteúdo: aplicativo / json" --data '{"lat": -33.44262, "lng": -70.63054, "limite": 1}' http: // localhost: 8000 / search 

Resultado

 HTTP / 1.1 200 OK 
Tipo de Conteúdo: application / json
Data: quarta, 08 ago 2018 05:12:24 GMT
Content-Length: 115
[{"Name": "1", "Longitude": - 70.63009768724442, "Latitude": - 33.44090957099124, "Dist": 0.1946, "GeoHash": 861185092131738}]

Github Repo