|
|
|
package memcache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
|
|
|
"github.com/go-kratos/kratos/pkg/container/pool"
|
|
|
|
xtime "github.com/go-kratos/kratos/pkg/time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Flag, 15(encoding) bit+ 17(compress) bit
|
|
|
|
|
|
|
|
// FlagRAW default flag.
|
|
|
|
FlagRAW = uint32(0)
|
|
|
|
// FlagGOB gob encoding.
|
|
|
|
FlagGOB = uint32(1) << 0
|
|
|
|
// FlagJSON json encoding.
|
|
|
|
FlagJSON = uint32(1) << 1
|
|
|
|
// FlagProtobuf protobuf
|
|
|
|
FlagProtobuf = uint32(1) << 2
|
|
|
|
|
|
|
|
_flagEncoding = uint32(0xFFFF8000)
|
|
|
|
|
|
|
|
// FlagGzip gzip compress.
|
|
|
|
FlagGzip = uint32(1) << 15
|
|
|
|
|
|
|
|
// left mv 31??? not work!!!
|
|
|
|
flagLargeValue = uint32(1) << 30
|
|
|
|
)
|
|
|
|
|
|
|
|
// Item is an reply to be got or stored in a memcached server.
|
|
|
|
type Item struct {
|
|
|
|
// Key is the Item's key (250 bytes maximum).
|
|
|
|
Key string
|
|
|
|
|
|
|
|
// Value is the Item's value.
|
|
|
|
Value []byte
|
|
|
|
|
|
|
|
// Object is the Item's object for use codec.
|
|
|
|
Object interface{}
|
|
|
|
|
|
|
|
// Flags are server-opaque flags whose semantics are entirely
|
|
|
|
// up to the app.
|
|
|
|
Flags uint32
|
|
|
|
|
|
|
|
// Expiration is the cache expiration time, in seconds: either a relative
|
|
|
|
// time from now (up to 1 month), or an absolute Unix epoch time.
|
|
|
|
// Zero means the Item has no expiration time.
|
|
|
|
Expiration int32
|
|
|
|
|
|
|
|
// Compare and swap ID.
|
|
|
|
cas uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
// Conn represents a connection to a Memcache server.
|
|
|
|
// Command Reference: https://github.com/memcached/memcached/wiki/Commands
|
|
|
|
type Conn interface {
|
|
|
|
// Close closes the connection.
|
|
|
|
Close() error
|
|
|
|
|
|
|
|
// Err returns a non-nil value if the connection is broken. The returned
|
|
|
|
// value is either the first non-nil value returned from the underlying
|
|
|
|
// network connection or a protocol parsing error. Applications should
|
|
|
|
// close broken connections.
|
|
|
|
Err() error
|
|
|
|
|
|
|
|
// Add writes the given item, if no value already exists for its key.
|
|
|
|
// ErrNotStored is returned if that condition is not met.
|
|
|
|
Add(item *Item) error
|
|
|
|
|
|
|
|
// Set writes the given item, unconditionally.
|
|
|
|
Set(item *Item) error
|
|
|
|
|
|
|
|
// Replace writes the given item, but only if the server *does* already
|
|
|
|
// hold data for this key.
|
|
|
|
Replace(item *Item) error
|
|
|
|
|
|
|
|
// Get sends a command to the server for gets data.
|
|
|
|
Get(key string) (*Item, error)
|
|
|
|
|
|
|
|
// GetMulti is a batch version of Get. The returned map from keys to items
|
|
|
|
// may have fewer elements than the input slice, due to memcache cache
|
|
|
|
// misses. Each key must be at most 250 bytes in length.
|
|
|
|
// If no error is returned, the returned map will also be non-nil.
|
|
|
|
GetMulti(keys []string) (map[string]*Item, error)
|
|
|
|
|
|
|
|
// Delete deletes the item with the provided key.
|
|
|
|
// The error ErrNotFound is returned if the item didn't already exist in
|
|
|
|
// the cache.
|
|
|
|
Delete(key string) error
|
|
|
|
|
|
|
|
// Increment atomically increments key by delta. The return value is the
|
|
|
|
// new value after being incremented or an error. If the value didn't exist
|
|
|
|
// in memcached the error is ErrNotFound. The value in memcached must be
|
|
|
|
// an decimal number, or an error will be returned.
|
|
|
|
// On 64-bit overflow, the new value wraps around.
|
|
|
|
Increment(key string, delta uint64) (newValue uint64, err error)
|
|
|
|
|
|
|
|
// Decrement atomically decrements key by delta. The return value is the
|
|
|
|
// new value after being decremented or an error. If the value didn't exist
|
|
|
|
// in memcached the error is ErrNotFound. The value in memcached must be
|
|
|
|
// an decimal number, or an error will be returned. On underflow, the new
|
|
|
|
// value is capped at zero and does not wrap around.
|
|
|
|
Decrement(key string, delta uint64) (newValue uint64, err error)
|
|
|
|
|
|
|
|
// CompareAndSwap writes the given item that was previously returned by
|
|
|
|
// Get, if the value was neither modified or evicted between the Get and
|
|
|
|
// the CompareAndSwap calls. The item's Key should not change between calls
|
|
|
|
// but all other item fields may differ. ErrCASConflict is returned if the
|
|
|
|
// value was modified in between the calls.
|
|
|
|
// ErrNotStored is returned if the value was evicted in between the calls.
|
|
|
|
CompareAndSwap(item *Item) error
|
|
|
|
|
|
|
|
// Touch updates the expiry for the given key. The seconds parameter is
|
|
|
|
// either a Unix timestamp or, if seconds is less than 1 month, the number
|
|
|
|
// of seconds into the future at which time the item will expire.
|
|
|
|
// ErrNotFound is returned if the key is not in the cache. The key must be
|
|
|
|
// at most 250 bytes in length.
|
|
|
|
Touch(key string, seconds int32) (err error)
|
|
|
|
|
|
|
|
// Scan converts value read from the memcache into the following
|
|
|
|
// common Go types and special types:
|
|
|
|
//
|
|
|
|
// *string
|
|
|
|
// *[]byte
|
|
|
|
// *interface{}
|
|
|
|
//
|
|
|
|
Scan(item *Item, v interface{}) (err error)
|
|
|
|
|
|
|
|
// Add writes the given item, if no value already exists for its key.
|
|
|
|
// ErrNotStored is returned if that condition is not met.
|
|
|
|
AddContext(ctx context.Context, item *Item) error
|
|
|
|
|
|
|
|
// Set writes the given item, unconditionally.
|
|
|
|
SetContext(ctx context.Context, item *Item) error
|
|
|
|
|
|
|
|
// Replace writes the given item, but only if the server *does* already
|
|
|
|
// hold data for this key.
|
|
|
|
ReplaceContext(ctx context.Context, item *Item) error
|
|
|
|
|
|
|
|
// Get sends a command to the server for gets data.
|
|
|
|
GetContext(ctx context.Context, key string) (*Item, error)
|
|
|
|
|
|
|
|
// GetMulti is a batch version of Get. The returned map from keys to items
|
|
|
|
// may have fewer elements than the input slice, due to memcache cache
|
|
|
|
// misses. Each key must be at most 250 bytes in length.
|
|
|
|
// If no error is returned, the returned map will also be non-nil.
|
|
|
|
GetMultiContext(ctx context.Context, keys []string) (map[string]*Item, error)
|
|
|
|
|
|
|
|
// Delete deletes the item with the provided key.
|
|
|
|
// The error ErrNotFound is returned if the item didn't already exist in
|
|
|
|
// the cache.
|
|
|
|
DeleteContext(ctx context.Context, key string) error
|
|
|
|
|
|
|
|
// Increment atomically increments key by delta. The return value is the
|
|
|
|
// new value after being incremented or an error. If the value didn't exist
|
|
|
|
// in memcached the error is ErrNotFound. The value in memcached must be
|
|
|
|
// an decimal number, or an error will be returned.
|
|
|
|
// On 64-bit overflow, the new value wraps around.
|
|
|
|
IncrementContext(ctx context.Context, key string, delta uint64) (newValue uint64, err error)
|
|
|
|
|
|
|
|
// Decrement atomically decrements key by delta. The return value is the
|
|
|
|
// new value after being decremented or an error. If the value didn't exist
|
|
|
|
// in memcached the error is ErrNotFound. The value in memcached must be
|
|
|
|
// an decimal number, or an error will be returned. On underflow, the new
|
|
|
|
// value is capped at zero and does not wrap around.
|
|
|
|
DecrementContext(ctx context.Context, key string, delta uint64) (newValue uint64, err error)
|
|
|
|
|
|
|
|
// CompareAndSwap writes the given item that was previously returned by
|
|
|
|
// Get, if the value was neither modified or evicted between the Get and
|
|
|
|
// the CompareAndSwap calls. The item's Key should not change between calls
|
|
|
|
// but all other item fields may differ. ErrCASConflict is returned if the
|
|
|
|
// value was modified in between the calls.
|
|
|
|
// ErrNotStored is returned if the value was evicted in between the calls.
|
|
|
|
CompareAndSwapContext(ctx context.Context, item *Item) error
|
|
|
|
|
|
|
|
// Touch updates the expiry for the given key. The seconds parameter is
|
|
|
|
// either a Unix timestamp or, if seconds is less than 1 month, the number
|
|
|
|
// of seconds into the future at which time the item will expire.
|
|
|
|
// ErrNotFound is returned if the key is not in the cache. The key must be
|
|
|
|
// at most 250 bytes in length.
|
|
|
|
TouchContext(ctx context.Context, key string, seconds int32) (err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Config memcache config.
|
|
|
|
type Config struct {
|
|
|
|
*pool.Config
|
|
|
|
|
|
|
|
Name string // memcache name, for trace
|
|
|
|
Proto string
|
|
|
|
Addr string
|
|
|
|
DialTimeout xtime.Duration
|
|
|
|
ReadTimeout xtime.Duration
|
|
|
|
WriteTimeout xtime.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
// Memcache memcache client
|
|
|
|
type Memcache struct {
|
|
|
|
pool *Pool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reply is the result of Get
|
|
|
|
type Reply struct {
|
|
|
|
err error
|
|
|
|
item *Item
|
|
|
|
conn Conn
|
|
|
|
closed bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replies is the result of GetMulti
|
|
|
|
type Replies struct {
|
|
|
|
err error
|
|
|
|
items map[string]*Item
|
|
|
|
usedItems map[string]struct{}
|
|
|
|
conn Conn
|
|
|
|
closed bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// New get a memcache client
|
|
|
|
func New(cfg *Config) *Memcache {
|
|
|
|
return &Memcache{pool: NewPool(cfg)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close close connection pool
|
|
|
|
func (mc *Memcache) Close() error {
|
|
|
|
return mc.pool.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Conn direct get a connection
|
|
|
|
func (mc *Memcache) Conn(ctx context.Context) Conn {
|
|
|
|
return mc.pool.Get(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set writes the given item, unconditionally.
|
|
|
|
func (mc *Memcache) Set(ctx context.Context, item *Item) (err error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
err = conn.SetContext(ctx, item)
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add writes the given item, if no value already exists for its key.
|
|
|
|
// ErrNotStored is returned if that condition is not met.
|
|
|
|
func (mc *Memcache) Add(ctx context.Context, item *Item) (err error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
err = conn.AddContext(ctx, item)
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace writes the given item, but only if the server *does* already hold data for this key.
|
|
|
|
func (mc *Memcache) Replace(ctx context.Context, item *Item) (err error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
err = conn.ReplaceContext(ctx, item)
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// CompareAndSwap writes the given item that was previously returned by Get
|
|
|
|
func (mc *Memcache) CompareAndSwap(ctx context.Context, item *Item) (err error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
err = conn.CompareAndSwapContext(ctx, item)
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get sends a command to the server for gets data.
|
|
|
|
func (mc *Memcache) Get(ctx context.Context, key string) *Reply {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
item, err := conn.GetContext(ctx, key)
|
|
|
|
if err != nil {
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
return &Reply{err: err, item: item, conn: conn}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Item get raw Item
|
|
|
|
func (r *Reply) Item() *Item {
|
|
|
|
return r.item
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scan converts value, read from the memcache
|
|
|
|
func (r *Reply) Scan(v interface{}) (err error) {
|
|
|
|
if r.err != nil {
|
|
|
|
return r.err
|
|
|
|
}
|
|
|
|
err = r.conn.Scan(r.item, v)
|
|
|
|
if !r.closed {
|
|
|
|
r.conn.Close()
|
|
|
|
r.closed = true
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMulti is a batch version of Get
|
|
|
|
func (mc *Memcache) GetMulti(ctx context.Context, keys []string) (*Replies, error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
items, err := conn.GetMultiContext(ctx, keys)
|
|
|
|
rs := &Replies{err: err, items: items, conn: conn, usedItems: make(map[string]struct{}, len(keys))}
|
|
|
|
if (err != nil) || (len(items) == 0) {
|
|
|
|
rs.Close()
|
|
|
|
}
|
|
|
|
return rs, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close close rows.
|
|
|
|
func (rs *Replies) Close() (err error) {
|
|
|
|
if !rs.closed {
|
|
|
|
err = rs.conn.Close()
|
|
|
|
rs.closed = true
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Item get Item from rows
|
|
|
|
func (rs *Replies) Item(key string) *Item {
|
|
|
|
return rs.items[key]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scan converts value, read from key in rows
|
|
|
|
func (rs *Replies) Scan(key string, v interface{}) (err error) {
|
|
|
|
if rs.err != nil {
|
|
|
|
return rs.err
|
|
|
|
}
|
|
|
|
item, ok := rs.items[key]
|
|
|
|
if !ok {
|
|
|
|
rs.Close()
|
|
|
|
return ErrNotFound
|
|
|
|
}
|
|
|
|
rs.usedItems[key] = struct{}{}
|
|
|
|
err = rs.conn.Scan(item, v)
|
|
|
|
if (err != nil) || (len(rs.items) == len(rs.usedItems)) {
|
|
|
|
rs.Close()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keys keys of result
|
|
|
|
func (rs *Replies) Keys() (keys []string) {
|
|
|
|
keys = make([]string, 0, len(rs.items))
|
|
|
|
for key := range rs.items {
|
|
|
|
keys = append(keys, key)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Touch updates the expiry for the given key.
|
|
|
|
func (mc *Memcache) Touch(ctx context.Context, key string, timeout int32) (err error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
err = conn.TouchContext(ctx, key, timeout)
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete deletes the item with the provided key.
|
|
|
|
func (mc *Memcache) Delete(ctx context.Context, key string) (err error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
err = conn.DeleteContext(ctx, key)
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Increment atomically increments key by delta.
|
|
|
|
func (mc *Memcache) Increment(ctx context.Context, key string, delta uint64) (newValue uint64, err error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
newValue, err = conn.IncrementContext(ctx, key, delta)
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decrement atomically decrements key by delta.
|
|
|
|
func (mc *Memcache) Decrement(ctx context.Context, key string, delta uint64) (newValue uint64, err error) {
|
|
|
|
conn := mc.pool.Get(ctx)
|
|
|
|
newValue, err = conn.DecrementContext(ctx, key, delta)
|
|
|
|
conn.Close()
|
|
|
|
return
|
|
|
|
}
|