Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Satu artikel untuk mengetahui tentang pustaka cache freecache di Golang

Satu artikel untuk mengetahui tentang pustaka cache freecache di Golang

青灯夜游
青灯夜游ke hadapan
2022-02-21 10:43:425721semak imbas

Artikel ini akan membawa anda mempelajari tentang Golang cache, dan memperkenalkan pustaka cache freecache dalam Golang dengan cara yang mudah.

Satu artikel untuk mengetahui tentang pustaka cache freecache di Golang

Senario cache pembangunan Go biasanya menggunakan rangka kerja peta atau cache Untuk keselamatan rangkaian, sync.Map atau rangka kerja cache selamat benang akan digunakan.

Jika jumlah data dalam senario cache lebih besar daripada satu juta tahap, pertimbangan khusus perlu diberikan kepada kesan jenis data pada gc (perhatikan bahawa lapisan bawah jenis rentetan ialah penunjuk Len Cap, jadi ia juga jenis penunjuk). Jika kunci cache dan nilai kedua-duanya bukan- Untuk jenis penunjuk, tidak perlu risau tentangnya. [Cadangan berkaitan: Tutorial Video Go]

Walau bagaimanapun, dalam senario aplikasi sebenar, kunci dan nilai adalah sangat biasa (mengandungi) data jenis penunjuk, jadi apabila menggunakan rangka kerja cache , perhatian khusus mesti diberikan kepada kesan GC penggunaannya, dari perspektif sama ada ia mempengaruhi GC, rangka kerja caching secara kasar dibahagikan kepada dua kategori:

  • Sifar GC overhed: seperti freecache atau bigcache, lapisan bawah adalah berdasarkan ringbuf, mengurangkan bilangan penunjuk;
Untuk peta, semua pasangan kunci/nilai akan diimbas semasa gc Jika kesemuanya adalah jenis asas, maka gc tidak akan mengimbasnya lagi.

Yang berikut menggunakan freecache sebagai contoh untuk menganalisis prinsip pelaksanaannya Contoh kod adalah seperti berikut:

1 Initialization
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))
}

freecache.NewCache akan. mulakan cache setempat, saiz Menunjukkan saiz ruang storan Freecache akan memulakan 256 segmen Setiap segmen adalah unit storan bebas Dimensi penguncian freecache juga berdasarkan segmen ialah saiz/256. Sebab mengapa freecache mendakwa sifar GC ialah penunjuknya tetap, dengan hanya 512, dan setiap segmen mempunyai 2, iaitu rb dan slotData (

).

注意切片为指针类型

2 Proses membaca dan menulis
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个单位大小
}

Kunci dan nilai freecache adalah kedua-dua tatasusunan

Anda perlu membuat bersiri dan menyahsiri diri anda apabila menggunakannya. anda tidak boleh mengabaikan jujukan. Untuk memahami kesan penyirian dan penyahserilan, mula-mula lihat proses

: []byteSet

Set proses terlebih dahulu mencincang kekunci Jenis hashVal ialah uint64, dan rendah 8 -bit segID sepadan dengan tatasusunan segmen 8 Bit -15 yang rendah menunjukkan bahawa slotId sepadan dengan subskrip slotsData, dan 16 bit tinggi menunjukkan data
_ = cache.Set([]byte(str), []byte(str), 1)
yang sepadan dengan subskrip slotsData diperlukan di sini. Ambil perhatian bahawa saiz tatasusunan

ialah slotCap (pada mulanya 1), dan slotCap akan digandakan apabila dikembangkan. []entryPtr[]entryPtr

Setiap segmen sepadan dengan kunci (sync.Mutex), jadi ia boleh menyokong sejumlah besar konkurensi, tidak seperti sync.Map yang hanya mempunyai satu kunci.

seg.evacuate akan menilai sama ada ringbuf mempunyai ruang yang mencukupi untuk menyimpan kunci/nilai Jika ruang tidak mencukupi, ia akan menjadi satu digit dari hujung ruang kosong (iaitu , kedudukan permulaan data yang akan dihapuskan) Mula mengimbas (
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)
   hash16 := uint16(hashVal >> 16)
   slot := seg.getSlot(slotId)
   idx, match := seg.lookup(slot, hash16, key)

   var hdrBuf [ENTRY_HDR_SIZE]byte
   hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0]))
   if match { // 有数据更新操作
      matchedPtr := &slot[idx]
      seg.rb.ReadAt(hdrBuf[:], matchedPtr.offset)
      hdr.slotId = slotId
      hdr.hash16 = hash16
      hdr.keyLen = uint16(len(key))
      originAccessTime := hdr.accessTime
      hdr.accessTime = now
      hdr.expireAt = expireAt
      hdr.valLen = uint32(len(value))
      if hdr.valCap >= hdr.valLen {
         // 已存在数据value空间能存下此次value大小
         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
      }
      // 删除对应entryPtr,涉及到slotsData内存copy,ringbug中只是标记删除
      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 { // 无数据
      hdr.slotId = slotId
      hdr.hash16 = hash16
      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
      }
   }
   
   // 数据实际长度为 ENTRY_HDR_SIZE=24 + key和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 &#39;idx&#39; again.
      // otherwise there would be index out of bound error.
      slot = seg.getSlot(slotId)
      idx, match = seg.lookup(slot, hash16, key)
      // assert(match == false)
   }
   newOff := seg.rb.End()
   seg.insertEntryPtr(slotId, hash16, 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
}
). Jika data yang sepadan telah dipadamkan atau tamat tempoh, maka blok memori boleh dikitar semula secara langsung Jika syarat kitar semula tidak dipenuhi, entri ditukar dari kepala cincin ke ekor cincin, dan kemudian indeks kemasukan dikemas kini Jika gelung 5 kali ini masih tidak berfungsi, makaHdrBuf lama semasa perlu dikitar semula untuk memenuhi keperluan memori.

oldOff := seg.rb.End() seg.vacuumLen - seg.rb.Size() Ruang yang diperlukan selepas melaksanakan seg.evacuate pasti akan berpuas hati, dan kemudian tiba masanya untuk menulis indeks dan data insertEntryPtr ialah operasi indeks penulisan Apabila bilangan elemen dalam

lebih besar daripada seg.slotCap (awal 1), operasi pengembangan diperlukan Untuk kaedah yang sepadan, lihat

dan tidak akan diterangkan lagi di sini. []entryPtrseg.expandUntuk menulis kepada ringbuf, hanya laksanakan rb.Tulis.

Proses Dapatkan freecache agak mudah. ​​Cari segmen yang sepadan melalui cincang, cari slot indeks yang sepadan melalui slotId, dan kemudian cari data melalui traversal binari terus kembalikan ErrNotFound, jika tidak, kemas kini beberapa penunjuk masa. Proses Dapatkan juga akan mengemas kini penunjuk berkaitan kadar hit cache.
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) <= atomic.LoadInt64(&seg.totalTime)
      if expired || leastRecentUsed || consecutiveEvacuate > 5 {
      // 可以回收
         seg.delEntryPtrByOffset(oldHdr.slotId, oldHdr.hash16, 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.hash16, oldOff, newOff)
         consecutiveEvacuate++
         atomic.AddInt64(&seg.totalEvacuate, 1)
      }
   }
}

Selepas mencari data, cuma baca ringbuf Ambil perhatian bahawa secara amnya nilai yang dibaca ialah ruang memori yang baru dibuat, jadi ia melibatkan operasi penyalinan
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))
}
data.

[]byte3 Ringkasan

Berdasarkan prestasi ujian tekanan beberapa rangka kerja cache biasa, perbezaan prestasi Set adalah besar tetapi bukan pada tahap kuantitatif perbezaan prestasi The Get tidaklah besar, jadi bagi kebanyakan Dalam kebanyakan senario, anda tidak perlu memberi terlalu banyak perhatian kepada Tetapkan/Dapatkan prestasi Tumpuan harus diberikan pada sama ada fungsi memenuhi keperluan perniagaan dan kesan gc Untuk perbandingan ujian tekanan prestasi, lihat: https://golang2 .eddycjy.com/posts/ch5/04-performance/

Terdapat senario khas untuk caching, iaitu untuk cache semua data dalam memori Apabila ia datang kepada kemas kini, ia dikemas kini sepenuhnya (digantikan). . Dalam senario ini, freecache digunakan. Untuk menggunakan freecache untuk mengelakkan masalah ini, anda perlu menetapkan saiz kepada "cukup besar", tetapi anda juga mesti memberi perhatian kepada penggunaan ruang memorinya.

Untuk lebih banyak pengetahuan berkaitan pengaturcaraan, sila lawati:

Pengajaran Pengaturcaraan

! !

Atas ialah kandungan terperinci Satu artikel untuk mengetahui tentang pustaka cache freecache di Golang. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam