Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use the cache library freecache in Golang

2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly explains "how to use freecache in Golang". The content in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "how to use freecache in Golang".

Go development caching scenarios generally use map or caching framework, and sync.Map or thread-safe caching framework is used for thread safety.

If the amount of data in the cache scenario is greater than one million, you need to consider the impact of the data type on gc (note that the underlying string type is pointer + Len+Cap, so it is also a pointer type). If both cache key and value are non-pointer types, there is no need to worry about it.

However, in practical application scenarios, it is very common that key and value are pointer-type data, so using caching frameworks requires special attention to their impact on gc. Caching frameworks are roughly divided into two categories from the point of view of whether they affect GC or not:

Zero GC overhead: such as freecache or bigcache. The underlying layer is based on ringbuf to reduce the number of pointers.

There is GC overhead: a caching framework implemented directly based on Map.

For map, all key/value key-value pairs are scanned during gc, and if they are all basic types, then gc will no longer scan.

Take freecache as an example to analyze its implementation principle. The code example is as follows:

Func main () {cacheSize: = 100 * 1024 * 1024 cache: = freecache.NewCache (cacheSize) for I: = 0; I

< N; i++ { str := strconv.Itoa(i) _ = cache.Set([]byte(str), []byte(str), 1) } now := time.Now() runtime.GC() fmt.Printf("freecache, GC took: %s\n", time.Since(now)) _, _ = cache.Get([]byte("aa")) now = time.Now() for i := 0; i < N; i++ { str := strconv.Itoa(i) _, _ = cache.Get([]byte(str)) } fmt.Printf("freecache, Get took: %s\n\n", time.Since(now))}1 初始化 freecache.NewCache会初始化本地缓存,size表示存储空间大小,freecache会初始化256个segment,每个segment是独立的存储单元,freecache加锁维度也是基于segment的,每个segment有一个ringbuf,初始大小为size/256。freecache号称零GC的来源就是其指针是固定的,只有512个,每个segment有2个,分别是rb和slotData(注意切片为指针类型)。 type segment struct { rb RingBuf // ring buffer that stores data segId int _ uint32 // 占位 missCount int64 hitCount int64 entryCount int64 totalCount int64 // number of entries in ring buffer, including deleted entries. totalTime int64 // used to calculate least recent used entry. timer Timer // Timer giving current time totalEvacuate int64 // used for debug totalExpired int64 // used for debug overwrites int64 // used for debug touched int64 // used for debug vacuumLen int64 // up to vacuumLen, new data can be written without overwriting old data. slotLens [256]int32 // The actual length for every slot. slotCap int32 // max number of entry pointers a slot can hold. slotsData []entryPtr // 索引指针}func NewCacheCustomTimer(size int, timer Timer) (cache *Cache) { cache = new(Cache) for i := 0; i < segmentCount; i++ { cache.segments[i] = newSegment(size/segmentCount, i, timer) }}func newSegment(bufSize int, segId int, timer Timer) (seg segment) { seg.rb = NewRingBuf(bufSize, 0) seg.segId = segId seg.timer = timer seg.vacuumLen = int64(bufSize) seg.slotCap = 1 seg.slotsData = make([]entryPtr, 256*seg.slotCap) // 每个slotData初始化256个单位大小}2 读写流程 freecache的key和value都是[]byte数组,使用时需要自行序列化和反序列化,如果缓存复杂对象不可忽略其序列化和反序列化带来的影响,首先看下Set流程: _ = cache.Set([]byte(str), []byte(str), 1) Set流程首先对key进行hash,hashVal类型uint64,其低8位segID对应segment数组,低8-15位表示slotId对应slotsData下标,高16位表示slotsData下标对应的[]entryPtr某个数据,这里需要查找操作。注意[]entryPtr数组大小为slotCap(初始为1),当扩容时会slotCap倍增。 每个segment对应一个lock(sync.Mutex),因此其能够支持较大并发量,而不像sync.Map只有一个锁。 func (cache *Cache) Set(key, value []byte, expireSeconds int) (err error) { hashVal := hashFunc(key) segID := hashVal & segmentAndOpVal // 低8位 cache.locks[segID].Lock() // 加锁 err = cache.segments[segID].set(key, value, hashVal, expireSeconds) cache.locks[segID].Unlock()}func (seg *segment) set(key, value []byte, hashVal uint64, expireSeconds int) (err error) { slotId := uint8(hashVal >

> 8) hash26: = uint16 (hashVal > > 16) slot: = seg.getSlot (slotId) idx, match: = seg.lookup (slot, hash26, key) var hdrBuf [ENTRY_HDR_SIZE] byte hdr: = (* entryHdr) (unsafe.Pointer (& hdrBuf [0]) if match {/ / have data update operation matchedPtr: = & slot[ IDX] seg.rb.ReadAt (hdrBuf [:]) MatchedPtr.offset) hdr.slotId = slotId hdr.hash26 = hash26 hdr.keyLen = uint16 (len (key)) originAccessTime: = hdr.accessTime hdr.accessTime = now hdr.expireAt = expireAt hdr.valLen = uint32 (len (value)) if hdr.valCap > = hdr.valLen {/ / existing data value space can store this value size atomic.AddInt64 (& seg.totalTime) Int64 (hdr.accessTime)-int64 (originAccessTime) seg.rb.WriteAt (hdrBuf [:], matchedPtr.offset) seg.rb.WriteAt (value, matchedPtr.offset+ENTRY_HDR_SIZE+int64 (hdr.keyLen)) atomic.AddInt64 (& seg.overwrites, 1) return} / / Delete the corresponding entryPtr When it comes to slotsData memory copy,ringbug, it's just marked to delete seg.delEntryPtr (slotId, slot, idx) match = false / / increase capacity and limit entry len. For hdr.valCap

< hdr.valLen { hdr.valCap *= 2 } if hdr.valCap >

Uint32 (maxKeyValLen-len (key)) {hdr.valCap = uint32 (maxKeyValLen-len (key))} else {/ / innumerable data hdr.slotId = slotId hdr.hash26 = hash26 hdr.keyLen = uint16 (len (key)) hdr.accessTime = now hdr.expireAt = expireAt hdr.valLen = uint32 (len (value)) hdr.valCap = uint32 (len (value) if hdr. ValCap = = 0 {/ / avoid infinite loop when increasing capacity. Hdr.valCap = 1}} / / the actual length of data is the length of ENTRY_HDR_SIZE=24 + key and value entryLen: = ENTRY_HDR_SIZE + int64 (len (key)) + int64 (hdr.valCap) slotModified: = seg.evacuate (entryLen, slotId, now) if slotModified {/ / the slot has been modified during evacuation, we need to looked up for the 'idx' again. / / otherwise there would be index out of bound error. Slot = seg.getSlot (slotId) idx, match = seg.lookup (slot, hash26, key) / / assert (match = = false)} newOff: = seg.rb.End () seg.insertEntryPtr (slotId, hash26, newOff, idx, hdr.keyLen) seg.rb.Write (hdrBuf [:]) seg.rb.Write (key) seg.rb.Write (value) seg.rb.Skip (int64 (hdr.valCap-hdr.valLen)) atomic.AddInt64 (& seg.totalTime Int64 (now)) atomic.AddInt64 (& seg.totalCount, 1) seg.vacuumLen-= entryLen return}

Seg.evacuate will evaluate whether the ringbuf has enough space to store key/value. If there is not enough space, it will start scanning from the last bit of the free space (that is, the starting position of the data to be eliminated) (oldOff: = seg.rb.End () + seg.vacuumLen-seg.rb.Size ()). If the corresponding data has been logically deleted or expired, the block of memory can be reclaimed directly. Change the entry from the beginning of the ring to the end of the ring, and then update the index of the entry. If this does not work five times, then the current oldHdrBuf needs to be recycled to meet the memory needs.

The space required for the execution of seg.evacuate must be satisfied, and then the index and data are written. InsertEntryPtr is a write index operation. When the number of elements in [] entryPtr is greater than seg.slotCap (initial 1), a capacity expansion operation is required. For the corresponding method, please see seg.expand, which is not discussed here.

To write ringbuf is to execute rb.Write.

Func (seg * segment) evacuate (entryLen int64, slotId uint8, now uint32) (slotModified bool) {var oldHdrBuf [ENTRY_HDR_SIZE] byte consecutiveEvacuate: = 0 for seg.vacuumLen

< entryLen { oldOff := seg.rb.End() + seg.vacuumLen - seg.rb.Size() seg.rb.ReadAt(oldHdrBuf[:], oldOff) oldHdr := (*entryHdr)(unsafe.Pointer(&oldHdrBuf[0])) oldEntryLen := ENTRY_HDR_SIZE + int64(oldHdr.keyLen) + int64(oldHdr.valCap) if oldHdr.deleted { // 已删除 consecutiveEvacuate = 0 atomic.AddInt64(&seg.totalTime, -int64(oldHdr.accessTime)) atomic.AddInt64(&seg.totalCount, -1) seg.vacuumLen += oldEntryLen continue } expired := oldHdr.expireAt != 0 && oldHdr.expireAt < now leastRecentUsed := int64(oldHdr.accessTime)*atomic.LoadInt64(&seg.totalCount) 5 { // 可以回收 seg.delEntryPtrByOffset(oldHdr.slotId, oldHdr.hash26, oldOff) if oldHdr.slotId == slotId { slotModified = true } consecutiveEvacuate = 0 atomic.AddInt64(&seg.totalTime, -int64(oldHdr.accessTime)) atomic.AddInt64(&seg.totalCount, -1) seg.vacuumLen += oldEntryLen if expired { atomic.AddInt64(&seg.totalExpired, 1) } else { atomic.AddInt64(&seg.totalEvacuate, 1) } } else { // evacuate an old entry that has been accessed recently for better cache hit rate. newOff := seg.rb.Evacuate(oldOff, int(oldEntryLen)) seg.updateEntryPtr(oldHdr.slotId, oldHdr.hash26, oldOff, newOff) consecutiveEvacuate++ atomic.AddInt64(&seg.totalEvacuate, 1) } }} freecache的Get流程相对来说简单点,通过hash找到对应segment,通过slotId找到对应索引slot,然后通过二分+遍历寻找数据,如果找不到直接返回ErrNotFound,否则更新一些time指标。Get流程还会更新缓存命中率相关指标。 func (cache *Cache) Get(key []byte) (value []byte, err error) { hashVal := hashFunc(key) segID := hashVal & segmentAndOpVal cache.locks[segID].Lock() value, _, err = cache.segments[segID].get(key, nil, hashVal, false) cache.locks[segID].Unlock() return}func (seg *segment) get(key, buf []byte, hashVal uint64, peek bool) (value []byte, expireAt uint32, err error) { hdr, ptr, err := seg.locate(key, hashVal, peek) // hash+定位查找 if err != nil { return } expireAt = hdr.expireAt if cap(buf) >

= int (hdr.valLen) {value = buf [: hdr.valLen]} else {value = make ([] byte, hdr.valLen)} seg.rb.ReadAt (value, ptr.offset+ENTRY_HDR_SIZE+int64 (hdr.keyLen))}

After locating the data, you can read the ringbuf. Note that generally speaking, the read value is the newly created memory space, so it involves the copy operation of [] byte data.

Thank you for reading, the above is the content of "how to use freecache in Golang". After the study of this article, I believe you have a deeper understanding of how to use freecache in Golang, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report