前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >​缓存的设计思想

​缓存的设计思想

作者头像
暮雨
发布2019-08-21 10:19:44
5420
发布2019-08-21 10:19:44
举报
文章被收录于专栏:云端漫步

使用缓存

缓存在系统设计中不可缺少,实现了以空间换时间,提高了系统的性能和减少了系统的处理时间。

例如这样的一个简单问题:我们要计算1到n(n>1)自然数的和? 这个可以很简单的通过编写程序实现

代码语言:javascript
复制
package main

import (
    "fmt"
    "strconv"
    "time"
)

func main() {
    var input string
    var output int
    var num int

    for {
        output = 0

        fmt.Println("Please enter calc num: ")
        fmt.Scanln(&input)

        startTime := time.Now()
        num, _ = strconv.Atoi(input)
        output = sum(num)
        timeCost := time.Since(startTime)
        fmt.Println(output)
        fmt.Println("run cost time", timeCost)
        fmt.Println("\n")
    }
}

func sum(num int) (sum int) {
    for i := 1; i <= num; i++ {
        sum += i
    }
    return
}

这里不做性能测试了,简单的通过一定的数据执行,观察下结果

怎么使用缓存来优化这个程序,提高程序的处理性能?借用缓存,把每次计算的结果都记录下来,再次计算该数时,从缓存中获取结果。使用缓存优化后的代码

代码语言:javascript
复制
package main

import (
    "fmt"
    "strconv"
    "time"
)

func main() {
    var input string
    var output int
    var num int
    var cache = make(map[int]int)

    for {
        output = 0

        fmt.Println("Please enter calc num: ")
        fmt.Scanln(&input)

        startTime := time.Now()
        num, _ = strconv.Atoi(input)
        if data, ok := cache[num]; ok {
            output = data
        } else {
            output = sum(num)
            cache[num] = output
        }

        timeCost := time.Since(startTime)
        fmt.Println(output)
        fmt.Println("run cost time", timeCost)
        fmt.Println("\n")
    }
}

func sum(num int) (sum int) {
    for i := 1; i <= num; i++ {
        sum += i
    }
    return
}

添加缓存后的执行结果

通过对比以上的执行结果,发现程序的执行效率有了很大的提升,这就是缓存的存在的理由。以存储的方式,减少cpu的运算。

程序存在的问题

如果每次都输入不同的值,这样缓存就无法命中,会导致cache变得越来越大,最终会内存不足。

怎么优化这个问题呢?

  1. 让缓存中的数据自动失效
  2. 设计淘汰算法

缓存自动过期处理

首先通过让cache中的数据失效, 基于以上的示例,做简单的优化,我的处理思路是设置一个定时器,到期后,map中的key全部失效。

代码语言:javascript
复制
package main

import (
    "fmt"
    "strconv"
    "time"
)

func main() {
    var input string
    var output int
    var num int
    var cache = make(map[int]int)

    go gc(cache)

    for {
        output = 0

        fmt.Println("start cache", cache)
        fmt.Println("Please enter calc num: ")
        fmt.Scanln(&input)

        startTime := time.Now()
        num, _ = strconv.Atoi(input)
        if data, ok := cache[num]; ok {
            output = data
        } else {
            output = sum(num)
            cache[num] = output
        }

        timeCost := time.Since(startTime)
        fmt.Println(output)
        fmt.Println("run cost time", timeCost)
        fmt.Println("\n")
        fmt.Println("end cache", cache)
    }
}

func sum(num int) (sum int) {
    for i := 1; i <= num; i++ {
        sum += i
    }
    return
}

func gc(dic map[int]int) {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        <-ticker.C
        fmt.Println("cache 失效")
        for key, _ := range dic {
            delete(dic, key)
        }
    }
}

使用goroutine运行一个定时任务,5秒清理一次map,这个cache数据过期处理的粒度有些过大,在做cache设计时,不能这样处理,需要将数据过期的粒度控制在存储的数据这一层。这里不做展开,在后边实现cache时,在做实现说明。这里只是简单的展示了缓存数据过期处理。看一下执行的结果

缓存的淘汰算法

为了防止缓存过大,可以使用通过限制一个阀值和制定一个淘汰规则,这样就可以保证缓存不会过大

  1. FIFO先进先出队列规则 这种淘汰的方式很简单,就是排队,队满了就淘汰在队首的数据
  2. LFU最少访问淘汰规则 最少访问淘汰,需要维护一个访问的频次,当阀值触发时,淘汰频次最小的那个数据,并替换为新的数据
  3. LRU最近最久未使用淘汰规则 LRU这个算法经常被提及,也是使用的最普遍,每次set和get时,都将第一次该数据插入链表的头部

再次对这三种缓存的淘汰算法做了简单的说明,其中会设计到一些数据结构的使用,在这里先不做展开,后边再开一个专题来介绍。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云端漫记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 程序存在的问题
  • 缓存自动过期处理
  • 缓存的淘汰算法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档