DEV Community

Cover image for DREAMEMO: An out-of-the-box, high-scalability, modular-design distributed cache
Lorain
Lorain

Posted on

DREAMEMO: An out-of-the-box, high-scalability, modular-design distributed cache

Introduction

As shown in the title, DREAMEMO is a distributed cache with out-of-the-box, high-scalability, modular-design features.The groupcache implementation is referenced, and re-structured, specific module differentiation is as follows:

arch

The main modules will be introduced in detail in the design module.

Quick Start

Install

Execute following command to install DREAMEMO:

go get github.com/B1NARY-GR0UP/dreamemo
Enter fullscreen mode Exit fullscreen mode

Run with standalone mode

DREAMEMO provides a function dream.StandAlone used default configuration to help user to start in standalone mode swiftly. All you need to do is configure the source.Getter of the corresponding data source.

package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/B1NARY-GR0UP/dreamemo/common/constant"
    "github.com/B1NARY-GR0UP/dreamemo/dream"
    "github.com/B1NARY-GR0UP/dreamemo/guidance"
    "github.com/B1NARY-GR0UP/dreamemo/source"
    log "github.com/B1NARY-GR0UP/inquisitor/core"
    "github.com/B1NARY-GR0UP/piano/core"
    "github.com/B1NARY-GR0UP/piano/core/bin"
)

var db = map[string]string{
    "binary": "dreamemo",
    "hello":  "world",
    "ping":   "pong",
}

func getFromDB(_ context.Context, key string) ([]byte, error) {
    log.Info("Get from DB")
    if v, ok := db[key]; ok {
        return []byte(v), nil
    }
    return nil, fmt.Errorf("key %v is not exist", key)
}

// go run .
// curl localhost:8080/hello?key=ping
func main() {
    dream.StandAlone(source.GetterFunc(getFromDB))
    p := bin.Default(core.WithHostAddr(":8080"))
    p.GET("/hello", func(ctx context.Context, pk *core.PianoKey) {
        key := pk.Query("key")
        g := guidance.GetGroup(constant.DefaultGroupName)
        value, _ := g.Get(ctx, key)
        pk.JSON(http.StatusOK, core.M{
            key: value.String(),
        })
    })
    p.Play()
}
Enter fullscreen mode Exit fullscreen mode

Here in the form of a map simulation data source database, and use PIANO HTTP framework as a front-end server instead of using other HTTP frameworks like Hertz, Gin, etc. You can start the server with go run . and then simply visit the URL to retrieve the key.

go run .
curl localhost:8080/hello?key=ping
Enter fullscreen mode Exit fullscreen mode

Run with cluster mode

DREAMEMO also provides a dream.Cluster function that runs in Cluster mode using the default configuration. All you need to do is configure the address of the corresponding cluster node and the data source.

package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/B1NARY-GR0UP/dreamemo/common/constant"
    "github.com/B1NARY-GR0UP/dreamemo/common/util"
    "github.com/B1NARY-GR0UP/dreamemo/dream"
    "github.com/B1NARY-GR0UP/dreamemo/guidance"
    "github.com/B1NARY-GR0UP/dreamemo/source"
    log "github.com/B1NARY-GR0UP/inquisitor/core"
    "github.com/B1NARY-GR0UP/piano/core"
    "github.com/B1NARY-GR0UP/piano/core/bin"
)

var db = map[string]string{
    "binary": "dreamemo",
    "hello":  "world",
    "ping":   "pong",
}

func getFromDB(_ context.Context, key string) ([]byte, error) {
    log.Info("Get from DB")
    if v, ok := db[key]; ok {
        return []byte(v), nil
    }
    return nil, fmt.Errorf("key %v is not exist", key)
}

// go run . --addrs=http://localhost:7246,http://localhost:7247,http://localhost:7248 --api
// go run . --addrs=http://localhost:7247,http://localhost:7248,http://localhost:7246
// go run . --addrs=http://localhost:7248,http://localhost:7246,http://localhost:7247
// curl localhost:8080/hello?key=ping
func main() {
    addrs, api := util.ParseFlags()
    e := dream.Cluster(source.GetterFunc(getFromDB), addrs...)
    if api {
        go func() {
            p := bin.Default(core.WithHostAddr(":8080"))
            p.GET("/hello", func(ctx context.Context, pk *core.PianoKey) {
                key := pk.Query("key")
                g := guidance.GetGroup(constant.DefaultGroupName)
                value, _ := g.Get(ctx, key)
                pk.JSON(http.StatusOK, core.M{
                    key: value.String(),
                })
            })
            p.Play()
        }()
    }
    e.Run()
}
Enter fullscreen mode Exit fullscreen mode

Again, the database data source is simulated as map, and three cache nodes are configured at localhost:7246, localhost:7247 and localhost:7248, the frontend server is configured on port 8080. To retrieve the value of the key, run the following command and visit the URL:

go run . --addrs=http://localhost:7246,http://localhost:7247,http://localhost:7248 --api
go run . --addrs=http://localhost:7247,http://localhost:7248,http://localhost:7246
go run . --addrs=http://localhost:7248,http://localhost:7246,http://localhost:7247
curl localhost:8080/hello?key=ping
Enter fullscreen mode Exit fullscreen mode

Custom Assemble

dream.StandAlone and dream.Cluster functions configured by DREAMEMO were used earlier, here we'll look at a more customized way of running the assembly.

  • Configure Engine

Here we configure the engine, util.ParseFlags is a utility method provided to parse flag arguments. We use app.WithHostAddr to configure the engine to listen to and app.WithThrift0 to use Thrift as the serialization protocol and register other nodes.

  addrs, api := util.ParseFlags()
  e := server.NewEngine(app.WithHostAddr(addrs[0]), app.WithThrift0())
  e.RegisterInstances(addrs...)
Enter fullscreen mode Exit fullscreen mode
  • Configure cache eliminate strategy

Configure LFU

  l := lfu.NewLFUCore()
  m := memo.NewMemo(l)
Enter fullscreen mode Exit fullscreen mode
  • Configure cache group

We configure the cache group name with guidance.WithGroupName, configure Thrift as the serialization protocol with guidance.WithThrift1, be consistent with the engine, and configure the data source with source.Getter

  guidance.NewGroup(m, e, guidance.WithGroupName("hello"), guidance.WithThrift1(), guidance.WithGetter(source.GetterFunc(getFromDB)))
Enter fullscreen mode Exit fullscreen mode

The complete code is as follows:

package main

import (
    "context"
    "fmt"
    "net/http"

    "github.com/B1NARY-GR0UP/dreamemo/app"
    "github.com/B1NARY-GR0UP/dreamemo/app/server"
    "github.com/B1NARY-GR0UP/dreamemo/common/util"
    "github.com/B1NARY-GR0UP/dreamemo/guidance"
    "github.com/B1NARY-GR0UP/dreamemo/memo"
    "github.com/B1NARY-GR0UP/dreamemo/source"
    "github.com/B1NARY-GR0UP/dreamemo/strategy/eliminate/lfu"
    log "github.com/B1NARY-GR0UP/inquisitor/core"
    "github.com/B1NARY-GR0UP/piano/core"
    "github.com/B1NARY-GR0UP/piano/core/bin"
)

var db = map[string]string{
    "binary": "dreamemo",
    "hello":  "world",
    "ping":   "pong",
}

func getFromDB(_ context.Context, key string) ([]byte, error) {
    log.Info("Get from DB")
    if v, ok := db[key]; ok {
        return []byte(v), nil
    }
    return nil, fmt.Errorf("key %v is not exist", key)
}

// go run . --addrs=http://localhost:7246,http://localhost:7247,http://localhost:7248 --api
// go run . --addrs=http://localhost:7247,http://localhost:7248,http://localhost:7246
// go run . --addrs=http://localhost:7248,http://localhost:7246,http://localhost:7247
// curl localhost:8080/hello?key=ping
func main() {
    addrs, api := util.ParseFlags()
    e := server.NewEngine(app.WithHostAddr(addrs[0]), app.WithThrift0())
    e.RegisterInstances(addrs...)
    l := lfu.NewLFUCore()
    m := memo.NewMemo(l)
    guidance.NewGroup(m, e, guidance.WithGroupName("hello"), guidance.WithThrift1(), guidance.WithGetter(source.GetterFunc(getFromDB)))
    if api {
        go func() {
            p := bin.Default(core.WithHostAddr(":8080"))
            p.GET("/hello", func(ctx context.Context, pk *core.PianoKey) {
                key := pk.Query("key")
                g := guidance.GetGroup("hello")
                value, _ := g.Get(ctx, key)
                pk.JSON(http.StatusOK, core.M{
                    key: value.String(),
                })
            })
            p.Play()
        }()
    }
    e.Run()
}
Enter fullscreen mode Exit fullscreen mode

Run and get the cache value with the following command:

go run . --addrs=http://localhost:7246,http://localhost:7247,http://localhost:7248 --api
go run . --addrs=http://localhost:7247,http://localhost:7248,http://localhost:7246
go run . --addrs=http://localhost:7248,http://localhost:7246,http://localhost:7247
curl localhost:8080/hello?key=ping
Enter fullscreen mode Exit fullscreen mode

Design

Eliminate strategy

In terms of cache elimination strategy, DREAMEMO supports LRU and LFU cache elimination algorithms, and users can choose and assemble them by themselves:

  • LRU
  c := lru.NewLRUCore()
Enter fullscreen mode Exit fullscreen mode
  • LFU
  c := lfu.NewLFUCore()
Enter fullscreen mode Exit fullscreen mode

DREAMEMO also provides an interface for uses to extend other eliminate strategies. Just implement the interface and pass the object to memo.NewMemo as follows:

type ICore interface {
    Add(Key, Value)
    Get(Key) (Value, bool)
    Remove(Key)
    Clear()
    Name() string
}
Enter fullscreen mode Exit fullscreen mode

Serialization

DREAMEMO supports both Thrift and Protobuf serialization protocols.The default is Protobuf, and to use Thrift, you need to enable Thrift in both the configuration engine and the cache group:

e := server.NewEngine(app.WithThrift0()) // engine
guidance.NewGroup(m, e, guidance.WithThrift1()) // group
Enter fullscreen mode Exit fullscreen mode

Data Source

DREAMEMO provides a default configuration to use Redis as a data source:

s := redis.NewSource()
Enter fullscreen mode Exit fullscreen mode

DREAMEMO also provides an interface to configure more data sources of your own:

type Getter interface {
    Get(ctx context.Context, key string) ([]byte, error)
}
Enter fullscreen mode Exit fullscreen mode

Consistent Hash

DREAMEMO, like groupcache, uses consistent hashing for distributed node selection, while also providing an interface to configure more policies.

TODO

DREAMEMO is still very young, and there are many changes that can be made to it. In the future, more node selection algorithms and quick configuration tools will be provided.

Summary

That's all for this article. Hope this can help you. I would be appreciate if you could give DREAMEMO a star.

If you have any questions, please leave them in the comments or as issues. Thanks for reading.

Reference List

Top comments (0)