mirror of
https://github.com/mailgun/groupcache.git
synced 2024-11-16 14:10:04 +00:00
153 lines
5.3 KiB
Markdown
153 lines
5.3 KiB
Markdown
# groupcache
|
|
|
|
A modified version of [group cache](https://github.com/golang/groupcache) with
|
|
support for `context.Context`, [go modules](https://github.com/golang/go/wiki/Modules),
|
|
and explicit key removal and expiration. See the `CHANGELOG` for a complete list of
|
|
modifications.
|
|
|
|
## Summary
|
|
|
|
groupcache is a caching and cache-filling library, intended as a
|
|
replacement for memcached in many cases.
|
|
|
|
For API docs and examples, see http://godoc.org/github.com/mailgun/groupcache
|
|
|
|
|
|
### Modifications from original library
|
|
|
|
* Support for explicit key removal from a group. `Remove()` requests are
|
|
first sent to the peer who owns the key, then the remove request is
|
|
forwarded to every peer in the groupcache. NOTE: This is a best case design
|
|
since it is possible a temporary network disruption could occur resulting
|
|
in remove requests never making it their peers. In practice this scenario
|
|
is very rare and the system remains very consistent. In case of an
|
|
inconsistency placing a expiration time on your values will ensure the
|
|
cluster eventually becomes consistent again.
|
|
|
|
* Support for expired values. `SetBytes()`, `SetProto()` and `SetString()` now
|
|
accept an optional `time.Time{}` which represents a time in the future when the
|
|
value will expire. Expiration is handled by the LRU Cache when a `Get()` on a
|
|
key is requested. This means no network coordination of expired values is needed.
|
|
However this does require that time on all nodes in the cluster is synchronized
|
|
for consistent expiration of values.
|
|
|
|
* Now always populating the hotcache. A more complex algorithm is unnecessary
|
|
when the LRU cache will ensure the most used values remain in the cache. The
|
|
evict code ensures the hotcache never overcrowds the maincache.
|
|
|
|
## Comparing Groupcache to memcached
|
|
|
|
### **Like memcached**, groupcache:
|
|
|
|
* shards by key to select which peer is responsible for that key
|
|
|
|
### **Unlike memcached**, groupcache:
|
|
|
|
* does not require running a separate set of servers, thus massively
|
|
reducing deployment/configuration pain. groupcache is a client
|
|
library as well as a server. It connects to its own peers.
|
|
|
|
* comes with a cache filling mechanism. Whereas memcached just says
|
|
"Sorry, cache miss", often resulting in a thundering herd of
|
|
database (or whatever) loads from an unbounded number of clients
|
|
(which has resulted in several fun outages), groupcache coordinates
|
|
cache fills such that only one load in one process of an entire
|
|
replicated set of processes populates the cache, then multiplexes
|
|
the loaded value to all callers.
|
|
|
|
* does not support versioned values. If key "foo" is value "bar",
|
|
key "foo" must always be "bar".
|
|
|
|
## Loading process
|
|
|
|
In a nutshell, a groupcache lookup of **Get("foo")** looks like:
|
|
|
|
(On machine #5 of a set of N machines running the same code)
|
|
|
|
1. Is the value of "foo" in local memory because it's super hot? If so, use it.
|
|
|
|
2. Is the value of "foo" in local memory because peer #5 (the current
|
|
peer) is the owner of it? If so, use it.
|
|
|
|
3. Amongst all the peers in my set of N, am I the owner of the key
|
|
"foo"? (e.g. does it consistent hash to 5?) If so, load it. If
|
|
other callers come in, via the same process or via RPC requests
|
|
from peers, they block waiting for the load to finish and get the
|
|
same answer. If not, RPC to the peer that's the owner and get
|
|
the answer. If the RPC fails, just load it locally (still with
|
|
local dup suppression).
|
|
|
|
## Example
|
|
|
|
```go
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/mailgun/groupcache/v2"
|
|
)
|
|
|
|
func ExampleUsage() {
|
|
// Keep track of peers in our cluster and add our instance to the pool `http://localhost:8080`
|
|
pool := groupcache.NewHTTPPoolOpts("http://localhost:8080", &groupcache.HTTPPoolOptions{})
|
|
|
|
// Add more peers to the cluster
|
|
pool.Set("http://peer1:8080", "http://peer2:8080")
|
|
|
|
server := http.Server{
|
|
Addr: "localhost:8080",
|
|
Handler: pool,
|
|
}
|
|
|
|
// Start a HTTP server to listen for peer requests from the groupcache
|
|
go func() {
|
|
log.Printf("Serving....\n")
|
|
if err := server.ListenAndServe(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}()
|
|
defer server.Shutdown(context.Background())
|
|
|
|
// Create a new group cache with a max cache size of 3MB
|
|
group := groupcache.NewGroup("users", 3000000, groupcache.GetterFunc(
|
|
func(ctx context.Context, id string, dest groupcache.Sink) error {
|
|
|
|
// Returns a protobuf struct `User`
|
|
if user, err := fetchUserFromMongo(ctx, id); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the user in the groupcache to expire after 5 minutes
|
|
if err := dest.SetProto(&user, time.Now().Add(time.Minute*5)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
},
|
|
))
|
|
|
|
var user User
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
|
|
defer cancel()
|
|
|
|
if err := group.Get(ctx, "12345", groupcache.ProtoSink(&user)); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
fmt.Printf("-- User --\n")
|
|
fmt.Printf("Id: %s\n", user.Id)
|
|
fmt.Printf("Name: %s\n", user.Name)
|
|
fmt.Printf("Age: %d\n", user.Age)
|
|
fmt.Printf("IsSuper: %t\n", user.IsSuper)
|
|
|
|
// Remove the key from the groupcache
|
|
if err := group.Remove(ctx, "12345"); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
```
|
|
|