机器编码访谈:使用基于时间的到期和驱逐的有效缓存实现
#编程 #go #面试 #systemdesign

我在高级Golang开发人员角色的一项编码访谈中被问到了这个问题。


这是一个机器编码,我不得不使用我选择的编程语言设计和实现高效的缓存,这应该非常有效,线程安全,并且不应太多的内存使用。

我选择了Golang,因为..

  • Golang对并发的内置支持,包括轻量级的goroutines和频道,使其成为建立具有大量并发操作的高性能系统的理想选择,例如高速缓存管理。

  • 语言的简单且一致的语法,以及它的重点是最大程度地减少内存开销和最大化吞吐量,这也使其非常适合构建可扩展和高效的缓存系统。

  • 此外,Golang的广泛标准库还包括用于处理基于时间的事件的软件包,例如本示例中使用的时间软件包,这简化了缓存到期策略的实现。

  • 总的来说,Golang的并发支持,性能和简单性的组合使其成为建立高性能缓存系统的绝佳选择。


这是带有评论的代码,以下是

package main

import (
 "fmt"
 "sync"
 "time"
)

type Cache struct {
 data      map[string]any
 expiry    time.Duration
 evictPool chan struct{} // we don't want infinite goroutines
 mutex     sync.RWMutex
}

func NewCache(expiry time.Duration, evictPoolSize int) *Cache {
 return &Cache{
  data:      make(map[string]any),
  expiry:    expiry,
  evictPool: make(chan struct{}, evictPoolSize),
 }
}

func (c *Cache) Set(key string, value any) {
 c.mutex.Lock()
 defer c.mutex.Unlock()

 c.data[key] = value
 go c.scheduleEviction(key) // schedule eviction
}

func (c *Cache) Get(key string) (any, bool) {
 c.mutex.RLock()
 defer c.mutex.RUnlock()

 value, ok := c.data[key]
 return value, ok
}

func (c *Cache) Delete(key string) {
 c.mutex.Lock()
 defer c.mutex.Unlock()

 delete(c.data, key)
}

func (c *Cache) scheduleEviction(key string) {
 select {
 case c.evictPool <- struct{}{}:
  // Goroutine acquired from pool, proceed with eviction
 case <-time.After(c.expiry):
  // Timed out waiting for goroutine from pool, evicting from current goroutine
  c.evict(key)
  return
 }

 // start eviction goroutine
 go func() {
  c.evict(key)
  <-c.evictPool // free the pool, so more eviction can be scheduled
 }()
}

func (c *Cache) evict(key string) {
 c.mutex.Lock()
 defer c.mutex.Unlock()

 if _, ok := c.data[key]; ok {
  delete(c.data, key)
  fmt.Printf("Evicted key %s\n", key)
 }
}

func main() {
 cache := NewCache(2*time.Second, 10)

 cache.Set("key1", "value1")
 cache.Set("key2", "value2")
 cache.Set("key3", "value3")

 time.Sleep(3 * time.Second)

 // Key1 should have been evicted, key2 and key3 should still be present
 if _, ok := cache.Get("key1"); !ok {
  fmt.Println("key1 evicted")
 }
 if _, ok := cache.Get("key2"); ok {
  fmt.Println("key2 present")
 }
 if _, ok := cache.Get("key3"); ok {
  fmt.Println("key3 present")
 }
}

解释

我们还使用缓冲频道驱逐池来限制可驱逐键可产生的goroutines数量。

如果池中有goroutine,我们将其用于驱逐。否则,我们等待按时间指定的超时。从当前的goroutine驱逐后。

驱逐后,goroutine被释放回池。

使用线程/goroutine池是一个更好的选择,而无需检查就产生goroutines,因为这限制了使用中的内存,如果未检查,则可以无限。

一个重要的说明

这个程序仍然远非完美,其想法是向您展示您使用频道来创建一个goroutine池,但是在高吞吐量系统的情况下,我们将在下一个中为此设计一个更好的系统张贴。

请鼓掌!

如果您发现这篇文章很有帮助,我将感谢一些拍手ðððð7,它会激励我将来写更多这样有用的文章。

跟着我获取常规的令人敬畏的内容和见解。
订阅我的时事通讯

如果您喜欢我的内容,请考虑订阅我的免费新闻通讯,以获取直接交付给收件箱的独家,教育,技术,有趣和与职业有关的内容

https://dsysd.beehiiv.com/subscribe

重要链接

感谢您阅读帖子,请务必遵循以下链接以获取将来更多令人敬畏的内容。

Twitter:https://twitter.com/dsysd_dev
YouTube:https://www.youtube.com/@dsysd-dev
github:https://github.com/dsysd-dev
媒介:https://medium.com/@dsysd-dev
电子邮件:dsysd.mail@gmail.com
Linkedin: https://www.linkedin.com/in/dsysd-dev/
新闻通讯:https://dsysd.beehiiv.com/subscribe
Gumroad:https://dsysd.gumroad.com/
Dev.TO:https://dev.to/dsysd_dev/