造轮子系列-GO实现分布式缓存

1.淘汰算法模块

GOCache(随便取的名字)主要参考GeeCache实现https://geektutu.com/post/geecache.html,同时借鉴了
Redis的部分特性,力求以最简单的方式实现最关键的功能,正在完善中。
|--Cache
  |----CacheAlgorithm
   	|----DoubleList.go   //lru算法使用的双向链表
    |----ICacheAlgorithm.go   //淘汰算法抽象层 后期可实现多个淘汰算法
    |----lru.go   //淘汰算法实现类

首先,实现最底层的淘汰算法抽象层:
实现ICacheAlgorithm接口需要实现Put、Get、Remove、RemoveOldest(移除最‘老’的元素)四个方法

GOCache\Cache\CacheAlgorithm\ICacheAlgorithm.go

package CacheAlgorithm

type ICacheAlgorithm interface { //淘汰算法抽象层 后期可实现多个淘汰算法
	Put(key string, val Value)
	Get(key string) (Value, bool)
	Remove(key string) error
	RemoveOldest() //淘汰
}

//包装string类型实现LEN()便可以作为缓存值value
type String string 

func (d String) Len() int { //实现了LEN方法
	return len(d)
}

//缓存值value可以是所有实现了Len()方法的type
type Value interface {
	Len() int
}

接下来完成一个淘汰算法实现类:LRU算法,如果此处无法理解可以找力扣上的LRU算法题解看看,也可使用其他算法,如LFU、FIFO等。
GOCache\Cache\CacheAlgorithm\lru.go

package CacheAlgorithm

import (
	"errors"
)

type LRUCache struct {//去掉了钩子函数,我觉得放到业务层解决比较好
	maxBytes   uint64//最大可存储的总比特大小
	nBytes     uint64//现在存储的总比特大小
	doubleList *DoubleList//单独实现的双向链表
	cacheMap   map[string]*ListNode
}

type Entry struct {//包装键值对放到链表节点上
	Key string
	val Value
}

func NewEntry(key string, val Value) *Entry {
	return &Entry{Key: key, val: val}
}

func NewLRUCache(maxBytes uint64) ICacheAlgorithm {
	var a ICacheAlgorithm = &LRUCache{
		maxBytes:   maxBytes,
		doubleList: NewDoubleList(),
		cacheMap:   make(map[string]*ListNode),
	}
	return a
}

//1.检查是否存在key
func (c *LRUCache) Put(key string, val Value) {
	if oldValListNode, ok := c.cacheMap[key]; ok { //插入过(更新)
		oldValListNode.Entry.val = val
		c.doubleList.MoveToLast(oldValListNode)
	} else { //未插入过
		dataLen := uint64(len(key) + val.Len())
		for c.nBytes+dataLen > c.maxBytes {
			c.RemoveOldest()
		}
		c.nBytes += dataLen

		NewNode := NewListNodeWithEntry(NewEntry(key, val))
		c.cacheMap[key] = NewNode
		c.doubleList.AddToLast(NewNode)

	}
}

func (c *LRUCache) Get(key string) (Value, bool) {
	if listNode, ok := c.cacheMap[key]; ok { //插入过
		c.doubleList.MoveToLast(listNode)
		return listNode.Entry.val, true
	} else { //未插入过
		return nil, false
	}
}
func (c *LRUCache) Remove(key string) error { //没有该元素怎么办
	if delListNode, ok := c.cacheMap[key]; ok { //插入过
		delKey, delLen := c.doubleList.DeleteNode(delListNode)
		delete(c.cacheMap, delKey)
		c.nBytes -= uint64(len(delKey) + delLen)
		return nil
	} else { //未插入过
		return errors.New("KEY[" + key + "]NOT FOUND!")
	}
}

func (c *LRUCache) RemoveOldest() {
	delKey, delLen := c.doubleList.DeleteOldest()
	delete(c.cacheMap, delKey)
	c.nBytes -= uint64(len(delKey) + delLen)
}

DoubleList为单独实现的双向链表,支持LRU算法的使用,也可使用原生库的双向链表。
GOCache\Cache\CacheAlgorithm\DoubleList.go

package CacheAlgorithm

type DoubleList struct {
	head *ListNode
	tail *ListNode
}
type IDoubleList interface {
	DeleteOldest() (string, int) //删除最久未使用
	MoveToLast(node *ListNode)   //移动到链表尾部  最近使用
	AddToLast(node *ListNode)
	DeleteNode(node *ListNode) (string, int)
}
type ListNode struct {
	next  *ListNode
	pre   *ListNode
	Entry *Entry
}

func NewListNode() *ListNode {
	return &ListNode{
		next:  nil,
		pre:   nil,
		Entry: nil,
	}
}
func NewListNodeWithEntry(entry *Entry) *ListNode {
	return &ListNode{
		next:  nil,
		pre:   nil,
		Entry: entry,
	}
}

func (d *DoubleList) DeleteOldest() (string, int) {
	DelNode := d.head.next
	d.head.next = d.head.next.next
	d.head.next.pre = d.head //取消对头部元素的引用
	return DelNode.Entry.Key, DelNode.Entry.val.Len()
}

func (d *DoubleList) DeleteNode(node *ListNode) (string, int) {
	node.pre.next = node.next
	node.next.pre = node.pre
	return node.Entry.Key, node.Entry.val.Len()
}

func (d *DoubleList) MoveToLast(node *ListNode) {
	node.pre.next = node.next
	node.next.pre = node.pre
	d.AddToLast(node)
}

func (d *DoubleList) AddToLast(node *ListNode) {
	d.tail.pre.next = node
	node.pre = d.tail.pre
	node.next = d.tail
	d.tail.pre = node
}

func NewDoubleList() *DoubleList {
	d := &DoubleList{
		head: NewListNode(),
		tail: NewListNode(),
	}
	d.head.next = d.tail
	d.tail.pre = d.head
	return d
}

测试类(待完善)

func TestLRU(t *testing.T) {
	cache := CacheAlgorithm.NewLRUCache(5)
	cache.Put("1", CacheAlgorithm.String("a"))
	cache.Put("2", CacheAlgorithm.String("b"))
	cache.Put("3", CacheAlgorithm.String("c"))
	cache.Put("4", CacheAlgorithm.String("d"))
	cache.Put("5", CacheAlgorithm.String("e"))
	fmt.Println(cache.Get("4"))
	fmt.Println(cache.Get("2"))
	fmt.Println(cache.Get("3"))
	fmt.Println(cache.Get("4"))
}

结果:

d true
<nil> false
<nil> false
d true

2.缓存主模块

上一节中实现了LRU淘汰算法,这里完成淘汰算法的抽象层:
GOCache\Cache\Cache.go

package Cache

import (
	"GOCache/Cache/CacheAlgorithm"
	"sync"
)

//cache.go的实现非常简单,实例化 lru(或者其他算法),封装 get 和 put 方法,并添加互斥锁 mu。
type Cache struct {
	lock       sync.Mutex                     //互斥锁
	CacheImpl  CacheAlgorithm.ICacheAlgorithm //实现了ICacheAlgorithm的算法,现在只有LRU
	algorithm  string                         //使用的底层算法 例如"LRU"
	cacheBytes uint64                         //最大可存储的比特大小
}

func (c *Cache) newCache(algorithm string) CacheAlgorithm.ICacheAlgorithm {
	switch c.algorithm {
	case "lru": //执行实例化  可添加其他算法
		return CacheAlgorithm.NewLRUCache(c.cacheBytes)
	default:
		return CacheAlgorithm.NewLRUCache(c.cacheBytes)
	}
}

func (c *Cache) put(key string, value ByteView) {
	c.lock.Lock()
	defer c.lock.Unlock()
	if c.CacheImpl == nil { //延迟初始化
		c.CacheImpl = c.newCache(c.algorithm)
	}
	c.CacheImpl.Put(key, value)
}

func (c *Cache) get(key string) (value ByteView, ok bool) {
	c.lock.Lock()
	defer c.lock.Unlock()
	if c.CacheImpl == nil { //延迟初始化
		c.CacheImpl = c.newCache(c.algorithm)
	}
	if v, ok := c.CacheImpl.Get(key); ok {
		return v.(ByteView), ok
	}

	return
}

抽象了一个只读数据结构 ByteView 用来表示缓存值,ByteView是byte数组的包装类,可以存储真实字节,图片等
GOCache\Cache\byteview.go

package Cache

//抽象了一个只读数据结构 ByteView 用来表示缓存值
type ByteView struct {
	Bytes []byte //存储真实字节 可存图片等
}

// 实现了Len方法,ByteView是value的实现类,可以作为缓存的val
func (v ByteView) Len() int {
	return len(v.Bytes)
}

// ByteSlice 返回一个比特切片的拷贝
func (v ByteView) ByteSlice() []byte {
	return cloneBytes(v.Bytes)
}

// byte[]转为string的方法
func (v ByteView) String() string {
	return string(v.Bytes)
}

func cloneBytes(b []byte) []byte {
	c := make([]byte, len(b))
	copy(c, b)
	return c
}

接下来完成Group结构,类似于一个数据库,目前是一个数据库对应一个cache结构,每个数据库有唯一id,默认实例化了一个id为1的group。
GOCache\Cache\group.go

package Cache

import (
	"GOCache/conf"
	"fmt"
	"log"
	"sync"
)

type Group struct { //一个group可类似于一个数据库
	GroupID int
	mainCache Cache //实现的并发缓存
}

var ( //全局变量
	mu     sync.RWMutex
	groups = make(map[int]*Group)
	group1 = NewGroup(0) //实例化一个group全局可用
)


func NewGroup(id int) *Group {
	mu.Lock()
	defer mu.Unlock()
	g := &Group{
		GroupID: id,
		//getter:    getter,
		mainCache: Cache{cacheBytes: conf.Config.MaxBytes, algorithm: conf.Config.CacheAlgorithm},
	}
	groups[id] = g
	return g
}
//根据id返回实例指针
func GetGroup(groupID int) *Group {
	mu.RLock()
	g := groups[groupID]
	mu.RUnlock()
	return g
}
//包装get方法
func (g *Group) Get(key string) (ByteView, error) {
	if key == "" {
		return ByteView{}, fmt.Errorf("key is required")
	}

	if v, ok := g.mainCache.get(key); ok {
		log.Println("[GOCache] hit")
		return v, nil
	}
	return ByteView{}, fmt.Errorf("[" + key + "]key Not Found!")
}
//包装put方法
func (g *Group) Put(key string, value ByteView) {
	g.mainCache.put(key, value)
}

至此,GoCache的最核心部分已完成,接下来完成客户端和服务端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值