Skip to content

Commit

Permalink
MF-382 - Improve performance by adding Redis cache for message auth (#…
Browse files Browse the repository at this point in the history
…383)

* Add redis caching for thing and channel connections

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix authorization caching flow

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update things documentation

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
  • Loading branch information
anovakovic01 authored and drasko committed Sep 4, 2018
1 parent 970c1c8 commit 0c77d84
Show file tree
Hide file tree
Showing 55 changed files with 11,366 additions and 54 deletions.
17 changes: 16 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 71 additions & 38 deletions cmd/things/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,64 +14,78 @@ import (
"net/http"
"os"
"os/signal"
"strconv"
"syscall"

kitprometheus "github.com/go-kit/kit/metrics/prometheus"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux"
log "github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/things"
"github.com/mainflux/mainflux/things/api"
grpcapi "github.com/mainflux/mainflux/things/api/grpc"
httpapi "github.com/mainflux/mainflux/things/api/http"
"github.com/mainflux/mainflux/things/postgres"
rediscache "github.com/mainflux/mainflux/things/redis"
"github.com/mainflux/mainflux/things/uuid"
usersapi "github.com/mainflux/mainflux/users/api/grpc"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc"
)

const (
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "things"
defHTTPPort = "8180"
defGRPCPort = "8181"
defUsersURL = "localhost:8181"
envDBHost = "MF_THINGS_DB_HOST"
envDBPort = "MF_THINGS_DB_PORT"
envDBUser = "MF_THINGS_DB_USER"
envDBPass = "MF_THINGS_DB_PASS"
envDBName = "MF_THINGS_DB"
envHTTPPort = "MF_THINGS_HTTP_PORT"
envGRPCPort = "MF_THINGS_GRPC_PORT"
envUsersURL = "MF_USERS_URL"
defDBHost = "localhost"
defDBPort = "5432"
defDBUser = "mainflux"
defDBPass = "mainflux"
defDBName = "things"
defCacheURL = "localhost:6379"
defCachePass = ""
defCacheDB = "0"
defHTTPPort = "8180"
defGRPCPort = "8181"
defUsersURL = "localhost:8181"
envDBHost = "MF_THINGS_DB_HOST"
envDBPort = "MF_THINGS_DB_PORT"
envDBUser = "MF_THINGS_DB_USER"
envDBPass = "MF_THINGS_DB_PASS"
envDBName = "MF_THINGS_DB"
envCacheURL = "MF_THINGS_CACHE_URL"
envCachePass = "MF_THINGS_CACHE_PASS"
envCacheDB = "MF_THINGS_CACHE_DB"
envHTTPPort = "MF_THINGS_HTTP_PORT"
envGRPCPort = "MF_THINGS_GRPC_PORT"
envUsersURL = "MF_USERS_URL"
)

type config struct {
DBHost string
DBPort string
DBUser string
DBPass string
DBName string
HTTPPort string
GRPCPort string
UsersURL string
DBHost string
DBPort string
DBUser string
DBPass string
DBName string
CacheURL string
CachePass string
CacheDB int
HTTPPort string
GRPCPort string
UsersURL string
}

func main() {
cfg := loadConfig()

logger := log.New(os.Stdout)

cfg := loadConfig(logger)

cache := connectToCache(cfg.CacheURL, cfg.CachePass, cfg.CacheDB)

db := connectToDB(cfg, logger)
defer db.Close()

conn := connectToUsersService(cfg.UsersURL, logger)
defer conn.Close()

svc := newService(conn, db, logger)
svc := newService(conn, db, cache, logger)
errs := make(chan error, 2)

go startHTTPServer(svc, cfg.HTTPPort, logger, errs)
Expand All @@ -87,19 +101,36 @@ func main() {
logger.Error(fmt.Sprintf("Things service terminated: %s", err))
}

func loadConfig() config {
func loadConfig(logger log.Logger) config {
db, err := strconv.Atoi(mainflux.Env(envCacheDB, defCacheDB))
if err != nil {
logger.Error(fmt.Sprintf("Failed to connect to cache: %s", err))
os.Exit(1)
}

return config{
DBHost: mainflux.Env(envDBHost, defDBHost),
DBPort: mainflux.Env(envDBPort, defDBPort),
DBUser: mainflux.Env(envDBUser, defDBUser),
DBPass: mainflux.Env(envDBPass, defDBPass),
DBName: mainflux.Env(envDBName, defDBName),
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
GRPCPort: mainflux.Env(envGRPCPort, defGRPCPort),
UsersURL: mainflux.Env(envUsersURL, defUsersURL),
DBHost: mainflux.Env(envDBHost, defDBHost),
DBPort: mainflux.Env(envDBPort, defDBPort),
DBUser: mainflux.Env(envDBUser, defDBUser),
DBPass: mainflux.Env(envDBPass, defDBPass),
DBName: mainflux.Env(envDBName, defDBName),
CacheURL: mainflux.Env(envCacheURL, defCacheURL),
CachePass: mainflux.Env(envCachePass, defCachePass),
CacheDB: db,
HTTPPort: mainflux.Env(envHTTPPort, defHTTPPort),
GRPCPort: mainflux.Env(envGRPCPort, defGRPCPort),
UsersURL: mainflux.Env(envUsersURL, defUsersURL),
}
}

func connectToCache(cacheURL, cachePass string, cacheDB int) *redis.Client {
return redis.NewClient(&redis.Options{
Addr: cacheURL,
Password: cachePass,
DB: cacheDB,
})
}

func connectToDB(cfg config, logger log.Logger) *sql.DB {
db, err := postgres.Connect(cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBUser, cfg.DBPass)
if err != nil {
Expand All @@ -118,13 +149,15 @@ func connectToUsersService(usersAddr string, logger log.Logger) *grpc.ClientConn
return conn
}

func newService(conn *grpc.ClientConn, db *sql.DB, logger log.Logger) things.Service {
func newService(conn *grpc.ClientConn, db *sql.DB, client *redis.Client, logger log.Logger) things.Service {
users := usersapi.NewClient(conn)
thingsRepo := postgres.NewThingRepository(db, logger)
channelsRepo := postgres.NewChannelRepository(db, logger)
chanCache := rediscache.NewChannelCache(client)
thingCache := rediscache.NewThingCache(client)
idp := uuid.New()

svc := things.New(users, thingsRepo, channelsRepo, idp)
svc := things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
svc = api.LoggingMiddleware(svc, logger)
svc = api.MetricsMiddleware(
svc,
Expand Down
8 changes: 8 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ services:
networks:
- mainflux-base-net

things-cache:
image: redis:4.0.9-alpine
container_name: mainflux-things-cache
restart: on-failure
networks:
- mainflux-base-net

things:
image: mainflux/things:latest
container_name: mainflux-things
Expand All @@ -97,6 +104,7 @@ services:
MF_THINGS_DB_USER: mainflux
MF_THINGS_DB_PASS: mainflux
MF_THINGS_DB: things
MF_THINGS_CACHE_URL: things-cache:6379
MF_THINGS_HTTP_PORT: 8182
MF_THINGS_GRPC_PORT: 8183
MF_USERS_URL: users:8181
Expand Down
8 changes: 7 additions & 1 deletion things/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ default values.
| MF_THINGS_DB_USER | Database user | mainflux |
| MF_THINGS_DB_PASS | Database password | mainflux |
| MF_THINGS_DB | Name of the database used by the service | things |
| MF_THINGS_CACHE_URL | Cache database URL | localhost:6379 |
| MF_THINGS_CACHE_PASS | Cache database password | |
| MF_THINGS_CACHE_DB | Cache instance that should be used | 0 |
| MF_THINGS_HTTP_PORT | Things service HTTP port | 8180 |
| MF_THINGS_GRPC_PORT | Things service gRPC port | 8181 |
| MF_USERS_URL | Users service URL | localhost:8181 |
Expand All @@ -48,6 +51,9 @@ services:
MF_THINGS_DB_USER: [Database user]
MF_THINGS_DB_PASS: [Database password]
MF_THINGS_DB: [Name of the database used by the service]
MF_THINGS_CACHE_URL: [Cache database URL]
MF_THINGS_CACHE_PASS: [Cache database password]
MF_THINGS_CACHE_DB: [Cache instance that should be used]
MF_THINGS_HTTP_PORT: [Service HTTP port]
MF_THINGS_GRPC_PORT: [Service gRPC port]
MF_USERS_URL: [Users service URL]
Expand All @@ -69,7 +75,7 @@ make things
make install

# set the environment variables and run the service
MF_THINGS_DB_HOST=[Database host address] MF_THINGS_DB_PORT=[Database host port] MF_THINGS_DB_USER=[Database user] MF_THINGS_DB_PASS=[Database password] MF_THINGS_DB=[Name of the database used by the service] MF_THINGS_HTTP_PORT=[Service HTTP port] MF_THINGS_GRPC_PORT=[Service gRPC port] MF_USERS_URL=[Users service URL] $GOBIN/mainflux-things
MF_THINGS_DB_HOST=[Database host address] MF_THINGS_DB_PORT=[Database host port] MF_THINGS_DB_USER=[Database user] MF_THINGS_DB_PASS=[Database password] MF_THINGS_DB=[Name of the database used by the service] MF_THINGS_CACHE_URL=[Cache database URL] MF_THINGS_CACHE_PASS=[Cache database password] MF_THINGS_CACHE_DB=[Cache instance that should be used] MF_THINGS_HTTP_PORT=[Service HTTP port] MF_THINGS_GRPC_PORT=[Service gRPC port] MF_USERS_URL=[Users service URL] $GOBIN/mainflux-things
```

## Usage
Expand Down
5 changes: 4 additions & 1 deletion things/api/grpc/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ func newService(tokens map[string]string) things.Service {
users := mocks.NewUsersService(tokens)
thingsRepo := mocks.NewThingRepository()
channelsRepo := mocks.NewChannelRepository(thingsRepo)
chanCache := mocks.NewChannelCache()
thingCache := mocks.NewThingCache()
idp := mocks.NewIdentityProvider()
return things.New(users, thingsRepo, channelsRepo, idp)

return things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
}
5 changes: 4 additions & 1 deletion things/api/http/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,11 @@ func newService(tokens map[string]string) things.Service {
users := mocks.NewUsersService(tokens)
thingsRepo := mocks.NewThingRepository()
channelsRepo := mocks.NewChannelRepository(thingsRepo)
chanCache := mocks.NewChannelCache()
thingCache := mocks.NewThingCache()
idp := mocks.NewIdentityProvider()
return things.New(users, thingsRepo, channelsRepo, idp)

return things.New(users, thingsRepo, channelsRepo, chanCache, thingCache, idp)
}

func newServer(svc things.Service) *httptest.Server {
Expand Down
15 changes: 15 additions & 0 deletions things/channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,18 @@ type ChannelRepository interface {
// thing's ID.
HasThing(uint64, string) (uint64, error)
}

// ChannelCache contains channel-thing connection caching interface.
type ChannelCache interface {
// Connect channel thing connection.
Connect(uint64, uint64) error

// HasThing checks if thing is connected to channel.
HasThing(uint64, uint64) bool

// Disconnects thing from channel.
Disconnect(uint64, uint64) error

// Removes channel from cache.
Remove(uint64) error
}
43 changes: 43 additions & 0 deletions things/mocks/channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,46 @@ func (crm *channelRepositoryMock) HasThing(chanID uint64, key string) (uint64, e

return 0, things.ErrNotFound
}

type channelCacheMock struct {
mu sync.Mutex
channels map[uint64]uint64
}

// NewChannelCache returns mock cache instance.
func NewChannelCache() things.ChannelCache {
return &channelCacheMock{
channels: make(map[uint64]uint64),
}
}

func (ccm *channelCacheMock) Connect(chanID uint64, thingID uint64) error {
ccm.mu.Lock()
defer ccm.mu.Unlock()

ccm.channels[chanID] = thingID
return nil
}

func (ccm *channelCacheMock) HasThing(chanID uint64, thingID uint64) bool {
ccm.mu.Lock()
defer ccm.mu.Unlock()

return ccm.channels[chanID] == thingID
}

func (ccm *channelCacheMock) Disconnect(chanID uint64, thingID uint64) error {
ccm.mu.Lock()
defer ccm.mu.Unlock()

delete(ccm.channels, chanID)
return nil
}

func (ccm *channelCacheMock) Remove(chanID uint64) error {
ccm.mu.Lock()
defer ccm.mu.Unlock()

delete(ccm.channels, chanID)
return nil
}
46 changes: 46 additions & 0 deletions things/mocks/things.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,49 @@ func (trm *thingRepositoryMock) RetrieveByKey(key string) (uint64, error) {
}
return 0, things.ErrNotFound
}

type thingCacheMock struct {
mu sync.Mutex
things map[string]uint64
}

// NewThingCache returns mock cache instance.
func NewThingCache() things.ThingCache {
return &thingCacheMock{
things: make(map[string]uint64),
}
}

func (tcm *thingCacheMock) Save(key string, id uint64) error {
tcm.mu.Lock()
defer tcm.mu.Unlock()

tcm.things[key] = id
return nil
}

func (tcm *thingCacheMock) ID(key string) (uint64, error) {
tcm.mu.Lock()
defer tcm.mu.Unlock()

id, ok := tcm.things[key]
if !ok {
return 0, things.ErrNotFound
}

return id, nil
}

func (tcm *thingCacheMock) Remove(id uint64) error {
tcm.mu.Lock()
defer tcm.mu.Unlock()

for key, val := range tcm.things {
if val == id {
delete(tcm.things, key)
return nil
}
}

return things.ErrNotFound
}
Loading

0 comments on commit 0c77d84

Please sign in to comment.