建站知识上海网站建设技术指导公司

当前位置: 首页 > news >正文

建站知识,上海网站建设技术指导公司,网络推广运营优化,wordpress 信息分类模板经过上个章节的学习#xff0c;我们已经实现了一致性哈希算法#xff0c;这个算法保证我们可以在节点发生变动时#xff0c;最少的key请求受到影响#xff0c;并返回这个节点的名称#xff1b;这很大程度上避免了哈希雪崩和哈希穿透的问题。这个章节我们要基于此实现完整的…  经过上个章节的学习我们已经实现了一致性哈希算法这个算法保证我们可以在节点发生变动时最少的key请求受到影响并返回这个节点的名称这很大程度上避免了哈希雪崩和哈希穿透的问题。这个章节我们要基于此实现完整的服务器端在处理客户端请求时内部如何进行选择节点并从此节点中找到key-value。 前文链接 手撕分布式缓存之一 | 定义缓存结构体与实现底层功能函数 手撕分布式缓存之二 | 互斥锁的优化 手撕分布式缓存之三 | HTTP Server搭建 手撕分布式缓存之四 | 多节点的调取策略 由于战线拉的太长了导致后面几个章节有点失去了热情因此就不复现代码了采用人工理解AI注释的方式记录 系列目录 1多节点情况下的交互1.2原理讲解1.3代码注释 2防止缓存击穿2.1缓存常有的三种问题2.2缓存击穿的解决方案2.3代码注释 3引入Protobuf优化服务性能 1多节点情况下的交互 1.2原理讲解 当我们有多个缓存节点时请求key发送时应该发送给谁例如Redis这种分布式缓存采用的方法均是客户端发送请求时是随机发送的接收的服务端也不一定就存有这个key-value但他会先检查本地的缓存是否存有这个key-value如果没有服务器端会通过一致性hash算法计算应该去哪个节点上去查询并去调用对应服务器端的查询接口获取到数据后统一返回给客户端不再让客户端去调取。 当新的节点加入后需要广播通知其他节点自己的存在如果是非主从复制节点关系的情况下由于一致性hash算法原本需要查看节点B才可以获取到的key-value现在需要通过新增的节点A去获取如果当时节点B的压力过大那么可以在请求查询B查询不到时通过节点B获取的key-value逐步的存储在节点A以实现符合一致性hash的预期如果当时节点B的压力并不大那么可以直接通过计算查询出节点B的哪些key现在会被定位到节点A由节点B主动的将数据同步给节点A。如果是删除节点也是同理。 1.3代码注释 package geecacheimport (errorsfmtnet/httpstringsgithub.com/gorilla/mux )// httpGetter 结构体实现了 PeerGetter 接口并使用 HTTP GET 方法从指定 URL 检索数据。 //如果没有错误则返回字节数组否则返回错误。 type httpGetter struct {baseURL string // baseURL 是该对等机的基本 URL协议 IP地址 端口 }func (h *httpGetter) Get(key string) ([]byte, error) {resp, err : http.Get(h.baseURL /cache/ key) // 向给定的 URL 发出 GET 请求if err ! nil {return nil, fmt.Errorf(HTTPPool: Error fetching %s from peer: %v, key, err)}defer resp.Body.Close()if resp.StatusCode http.StatusNotFound {return nil, errors.New(HTTPPool: Key not found)} else if resp.StatusCode ! http.StatusOK {return nil, fmt.Errorf(HTTPPool: Peer returned HTTP status code %d, resp.StatusCode)}body, err : io.ReadAll(resp.Body) // 从响应中读取数据if err ! nil {return nil, fmt.Errorf(HTTPPool: Error reading response body: %v, err)}return body, nil }// 这是一个简单的 Getter用于检索来自对等机通过 HTTP的键值。如果未找到该键或发生错误则返回错误。 // func (h *httpGetter) Get(key string) ([]byte, error) { // 向给定 URL 发出 GET 请求 resp, err : http.Get(h.baseURL /cache/ key) 2防止缓存击穿 2.1缓存常有的三种问题 缓存雪崩缓存在同一时刻全部失效造成瞬时DB请求量大、压力骤增引起雪崩。缓存雪崩通常因为缓存服务器宕机、缓存的 key 设置了相同的过期时间等引起。缓存击穿一个存在的key在缓存过期的一刻同时有大量的请求这些请求都会击穿到 DB 造成瞬时DB请求量大、压力骤增。缓存穿透查询一个不存在的数据因为不存在则不会写到缓存中所以每次都会去请求 DB如果瞬间流量过大穿透到 DB导致宕机。 三者的直接原因均是缓存中的key失效请求只能通过查询DB才能够获取key-value但是它们出现的场景和解决方式不同这才是我们区分三者的方式。 2.2缓存击穿的解决方案 缓存击穿的本质问题是当存在一个时刻存在key失效后有大量的key请求同时发送且都落在了DB上。 针对这一问题我们无法限制客户端同时发送大量key这一问题但是我们可以限制当请求没有在缓存阶段找到key-value时只有一个请求可以落到DB上。例如当有大量的请求进行访问我们可以通过互斥锁的方式进行限制比如我们先判断如果缓存中存在key-value那么可以直接返回结果也不会造成缓存击穿的影响但如果在缓存中找不到对应的key-value那么我们可以允许第一个请求此不存在的key进行接下来的操作DB操作其他的请求则需要进行等待等到唯一的一个请求处理结束之后该对应key的互斥锁会打开之后等待的请求直接返回结果即可。也就是说我们可以声明一个map这个map的key是缓存中的keymap的value是一个对象这个对象不仅可以表示当前的key是否已经有请求进行访问是不是已经被锁定也可以存储获取此key的第一个请求获取到的value值或异常信息。 2.3代码注释 type call struct {wg sync.WaitGroup // WaitGroup用于同步等待所有goroutine完成任务后再返回结果val interface{} // 保存函数fn()的返回值err error // 保存函数fn()的错误信息 }type Group struct {mu sync.Mutex // 互斥锁保证map操作的原子性m map[string]*call
}func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { // 传入一个字符串类型的key和接收两个参数的函数指针fn返回一个interface{}类型的值和error类型的错误信息g.mu.Lock()if g.m nil { // 如果map为空则初始化mapg.m make(map[string]*call)}if c, ok : g.m[key]; ok { // 判断是否已经有正在运行或者等待中的goroutine处理该任务g.mu.Unlock() // 解锁以防止死锁c.wg.Wait() // 等待任务完成后获取结果return c.val, c.err // 返回结果}c : new(call) c.wg.Add(1) // WaitGroup加1表示新增一个需要等待的goroutineg.m[key] c // 将当前任务添加到map中用于标记正在执行或者等待中的任务g.mu.Unlock()go func() {defer c.wg.Done() // 函数执行完成后WaitGroup减1c.val, c.err fn() // 执行传入的fn函数并保存值和错误信息}()g.mu.Lock()delete(g.m, key) // 从map中删除已经完成的任务g.mu.Unlock()return c.val, c.err // 返回结果 }3引入Protobuf优化服务性能 简单看了下介绍个人理解Protobuf是非常值得学习的一门技术对于服务性能的优化有很大的作用准备深入了解一下然后再完善这一部分感兴趣的同学可以留个眼更新之后一一通知。