【go项目-geecache】动手写分布式缓存 - day6 - 防止缓存击穿

这篇具有很好参考价值的文章主要介绍了【go项目-geecache】动手写分布式缓存 - day6 - 防止缓存击穿。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

索引

【go项目-geecache】动手写分布式缓存 - day1 - 实现LRU算法
【go项目-geecache】动手写分布式缓存 - day2 - 单机并发缓存
【go项目-geecache】动手写分布式缓存 - day3 - HTTP 服务端
【go项目-geecache】动手写分布式缓存 - day4 - 一致性哈希(hash)
【go项目-geecache】动手写分布式缓存 - day5 - 分布式节点
【go项目-geecache】动手写分布式缓存 - day6 - 防止缓存击穿
【go项目-geecache】动手写分布式缓存 - day7 - 使用 Protobuf 通信

收获

  1. 了解缓存穿透,缓存雪崩, 缓存击穿
  2. 知道怎么防止缓存击穿
  3. 了解匿名函数的使用
  4. 加深了锁的使用
  5. 了解空接口interface{}和模板的区别和作用

介绍缓存穿透,缓存雪崩, 缓存击穿

概念可以看我这篇博客缓存穿透,缓存雪崩,缓存击穿概念及解决方法
简单说一下缓存击穿,就是在缓存中找不到数据,导致直接访问数据库,降低效率

为什么我们要防止缓存击穿

当前我们所做的项目对数据库的访问没有做任何限制的,所以当客户端发起大量请求,很容易导致缓存击穿和穿透,所以我们需要减少重复请求

引入singleflight

singleflight是一种用于减少重复请求的技术,它可以避免在高并发场景下出现重复的请求
singleflight的实现方式是在请求前先检查是否已经有相同的请求正在处理,如果有,则等待该请求的处理结果并直接返回,避免重复发起请求。

实现singleflight数据结构

package singleflight  
  
import "sync"  
  
type call struct {  
	wg  sync.WaitGroup  
	val interface{}  
	err error  
}  
  
type Group struct {  
	mu sync.Mutex       // protects m  
	m  map[string]*call  
}
  • call:表示正在进行中,或已经完成的函数调用,其中wg字段sync.WaitGroup 锁避免冲突,val字段是一个空接口,err字段表示函数调用是否发生了错误。
  • Group:表示一组正在进行中的函数调用,其中mu字段是一个互斥锁,用于保护m字段的读写,m字段是一个map,用于存储正在进行中的函数调用

这里我对sync.WaitGroupinterface{} 空接口解释一下:

sync.WaitGroup

用于等待一组goroutine执行完成。主要用于在主goroutine等待一组子goroutine执行完毕后再继续执行的场景。

interface{} 空接口

我们知道接口时一组方法的集合,接口类型定义了一组方法,但没有实现这些方法。空接口里面没有方法,意味着它可以表示任意类型的值,可以方便地实现通用性较强的函数或数据结构文章来源地址https://www.toymoban.com/news/detail-418845.html

空接口和模板的区别

  • 空接口的类型判断需要通过断言,而模板的数据类型是利用编译器进行类型推导来获得的
  • 模板在使用时需要在编译阶段进行类型检查,因此可以保证类型安全性

实现singleflight的Do方法

func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {  
	g.mu.Lock()  
	if g.m == nil {  
		g.m = make(map[string]*call)  
	}  
	if c, ok := g.m[key]; ok {  
		g.mu.Unlock()  
		c.wg.Wait()  
		return c.val, c.err  
	}  
	c := new(call)  
	c.wg.Add(1)  
	g.m[key] = c  
	g.mu.Unlock()  
  
	c.val, c.err = fn()  
	c.wg.Done()  
  
	g.mu.Lock()  
	delete(g.m, key)  
	g.mu.Unlock()  
  
	return c.val, c.err  
}
  • Do() : 这个函数传入key和函数fn,只要key相同,那么fn函数指挥运行一次
  1. 首先mutex锁,防止g.m并发读写冲突
  2. 检查group的m成员变量是否存在,不存在则初始化
  3. 检查key的请求是否正在执行,如果正在执行,则等待结果并解锁,等待结果后返回结果
  4. 否则如果没有在执行,新建一个call类型表示正在执行
  5. 发起请求前加锁
  6. 将这个call加入到group的m(map)中,表示正在执行
  7. 调用fn
  8. c.wg.Done() 表示请求结束
  9. group的m(map)中从删除call

把singleflight加入主进程group.go

type Group struct {  
	name      string  
	getter    Getter  
	mainCache cache  
	peers     PeerPicker  
	
	loader *singleflight.Group  
}  
  
func NewGroup(name string, cacheBytes int64, getter Getter) *Group {  
	g := &Group{  
		loader:    &singleflight.Group{},  
	}  
	return g  
}  
  
func (g *Group) load(key string) (value ByteView, err error) {  
	viewi, err := g.loader.Do(key, func() (interface{}, error) {  
		if g.peers != nil {  
			if peer, ok := g.peers.PickPeer(key); ok {  
				if value, err = g.getFromPeer(peer, key); err == nil {  
					return value, nil  
				}  
				log.Println("[GeeCache] Failed to get from peer", err)  
			}  
		}  
		return g.getLocally(key)  
	})  
	if err == nil {  
		return viewi.(ByteView), nil  
	}  
	return  
}
  • 添加Group结构体loader,更新NewGroup
  • 修改load函数,使用匿名函数保证对于同一个key只会执行一次

测试

$ ./run.sh  
2020/02/16 22:36:00 [Server http://localhost:8003] Pick peer http://localhost:8001  
2020/02/16 22:36:00 [Server http://localhost:8001] GET /_geecache/scores/Tom  
2020/02/16 22:36:00 [SlowDB] search key Tom  
630630630

更新后的group.go

// 负责与外部交互,控制缓存存储和获取的主流程
package geecache

import (
	"fmt"
	"geecache/singleflight"
	"log"
	"sync"
)

type Group struct {
	name      string
	getter    Getter
	mainCache cache
	peers     PeerPicker

	loader *singleflight.Group
}

type Getter interface {
	Get(key string) ([]byte, error)
}

type GetterFunc func(key string) ([]byte, error)

func (f GetterFunc) Get(key string) ([]byte, error) {
	return f(key)
}

var (
	mu     sync.RWMutex
	groups = make(map[string]*Group)
)

func NewGroup(name string, cacheBytes int64, getter Getter) *Group {
	if getter == nil {
		panic("nil Getter")
	}
	mu.Lock()
	defer mu.Unlock()
	g := &Group{
		name:      name,
		getter:    getter,
		mainCache: cache{cacheBytes: cacheBytes},
		loader:    &singleflight.Group{},
	}
	groups[name] = g
	return g
}

func GetGroup(name string) *Group {
	mu.RLock()
	g := groups[name]
	mu.RUnlock()
	return g
}

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("[GeeCache] hit")
		return v, nil
	}

	return g.load(key)
}

func (g *Group) getLocally(key string) (ByteView, error) {
	bytes, err := g.getter.Get(key)
	if err != nil {
		return ByteView{}, err

	}
	value := ByteView{b: cloneBytes(bytes)}
	g.populateCache(key, value)
	return value, nil
}

func (g *Group) populateCache(key string, value ByteView) {
	g.mainCache.add(key, value)
}
func (g *Group) RegisterPeers(peers PeerPicker) {
	if g.peers != nil {
		panic("RegisterPeerPicker called more than once")
	}
	g.peers = peers
}

func (g *Group) load(key string) (value ByteView, err error) {
	// each key is only fetched once (either locally or remotely)
	// regardless of the number of concurrent callers.
	viewi, err := g.loader.Do(key, func() (interface{}, error) {
		if g.peers != nil {
			if peer, ok := g.peers.PickPeer(key); ok {
				if value, err = g.getFromPeer(peer, key); err == nil {
					return value, nil
				}
				log.Println("[GeeCache] Failed to get from peer", err)
			}
		}

		return g.getLocally(key)
	})

	if err == nil {
		return viewi.(ByteView), nil
	}
	return
}

func (g *Group) getFromPeer(peer PeerGetter, key string) (ByteView, error) {
	bytes, err := peer.Get(g.name, key)
	if err != nil {
		return ByteView{}, err
	}
	return ByteView{b: bytes}, nil
}

到了这里,关于【go项目-geecache】动手写分布式缓存 - day6 - 防止缓存击穿的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一文拿捏分布式、分布式缓存及其问题解决

    1.集中式 传统的计算模型通常是集中式的,所有的计算任务和数据处理都由 单一的计算机或服务器 完成。然而,随着数据量和计算需求的增加,集中式系统可能会面临性能瓶颈和可靠性问题。 故而引出了分布式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

    2024年02月07日
    浏览(43)
  • 分布式系统架构设计之分布式缓存技术选型

    随着互联网业务的快速发展,分布式系统已经成为了解决大规模并发请求、高可用性、可扩展性等问题的重要手段。在分布式系统中,缓存作为提高系统性能的关键技术,能够显著降低数据库负载、减少网络延迟、提高数据访问速度。当面对大量并发请求时,如果每次都直接

    2024年02月03日
    浏览(107)
  • SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】

    上一篇实现了单体应用下如何上锁,这一篇主要说明如何在分布式场景下上锁 上一篇地址:加锁 需要注意的点是: 在上锁和释放锁的过程中要保证 原子性操作 核心是上锁和解锁的过程 关于解锁使用脚本参考:SET key value [EX seconds] [PX milliseconds] [NX|XX] 3.1 一个服务按照多个端口同时

    2023年04月10日
    浏览(47)
  • 分布式缓存

    – 基于Redis集群解决单机Redis存在的问题 Redis有两种持久化方案: RDB持久化 AOF持久化 RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是 把内存中的所有数据 都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快

    2023年04月25日
    浏览(44)
  • Redis分布式缓存

    -- 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题: Redis有两种持久化方案: RDB持久化 AOF持久化        RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做 Redis数据快照 。简单来说就是把 内存中的所有数据都记录到磁盘 中。当Redis实例故障重启后,

    2024年02月12日
    浏览(49)
  • Redis 分布式缓存

    单点 Redis 的问题及解决 数据丢失:实现Redis数据持久化 并发能力:搭建主从集群,实现读写分离 存储能力:搭建分片集群,利用插槽机制实现动态扩容 故障恢复能力:利用哨兵机制,实现健康检测和自动恢复 RDB RDB全称Redis Database Backup file (Redis数据备份文件),也被叫做

    2024年02月10日
    浏览(49)
  • 微服务07-分布式缓存

    前提: 单机的Redis存在四大问题: 解决办法:基于Redis集群解决单机Redis存在的问题 Redis 具有持久化功能,其会按照设置以 快照 或 操作日志 的形式将数据持久化到磁盘。 Redis有两种持久化方案: RDB持久化 AOF持久化 注意: RDB 是默认持久化方式,但 Redis 允许 RDB 与 AOF 两种

    2024年02月12日
    浏览(36)
  • Redis分布式缓存方案

    数据丢失:数据持久化 并发能力弱:搭建主从集群,实现读写分离 故障恢复问题:哨兵实现健康检测,自动恢复 存储能力:搭建分片集群,利用插槽机制实现动态扩容 RDB持久化 数据库备份文件,也叫快照,把内存数据存到磁盘。使用save进行主动RDB,会阻塞所有命令。建议

    2023年04月25日
    浏览(41)
  • Redis(分布式缓存详解)

    Redis:基于内存的键值存储系统,通常用作高性能的数据库、缓存和消息队列代理,是互联网广泛应用的存储中间件 特点 :基于内存存储,读写性能高 Redis与MySQL区别 Redis以键值对形式存储,MySQL以表格形式存储 Redis存储在 内存 ,MySQL存储在 磁盘 Redis存储 高效 ,MySQL存储 安

    2024年02月16日
    浏览(45)
  • Redis高级-分布式缓存

    – 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题: Redis有两种持久化方案: RDB持久化 AOF持久化 RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取

    2024年04月16日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包