day5 分布式节点

news2024/11/28 8:46:51

文章目录

  • 1 流程回顾
  • 2 抽象 PeerPicker
  • 3 节点选择与 HTTP 客户端
  • 4 实现主流程
  • 5 main 函数测试。
  • 6 QA

本文代码地址:

本文是7天用Go从零实现分布式缓存GeeCache的第五篇。

  • 注册节点(Register Peers),借助一致性哈希算法选择节点。
  • 实现 HTTP 客户端,与远程节点的服务端通信,代码约90

1 流程回顾

在这里插入图片描述

我们在GeeCache 第二天中描述了 geecache 的流程。在这之前已经实现了流程,今天实现流程,从远程节点获取缓存值。

我们进一步细化流程

在这里插入图片描述

2 抽象 PeerPicker

day5-multi-nodes/geecache/peers.go

package geecache

// PeerPicker is the interface that must be implemented to locate
// the peer that owns a specific key.
type PeerPicker interface {
	PickPeer(key string) (peer PeerGetter, ok bool)
}

// PeerGetter is the interface that must be implemented by a peer.
type PeerGetter interface {
	Get(group string, key string) ([]byte, error)
}
  • 在这里,抽象出 2 个接口,PeerPicker PickPeer() 方法用于根据传入的 key 选择相应节点 PeerGetter
  • 接口 PeerGetterGet() 方法用于从对应 group 查找缓存值。PeerGetter 就对应于上述流程中的 HTTP 客户端。

3 节点选择与 HTTP 客户端

在 GeeCache 第三天 中我们为 HTTPPool 实现了服务端功能,通信不仅需要服务端还需要客户端,因此,我们接下来要为 HTTPPool 实现客户端的功能。

首先创建具体的HTTP客户端类 httpGetter,实现 PeerGetter 接口。

day5-multi-nodes/geecache/http.go

type httpGetter struct {
	baseURL string
}

func (h *httpGetter) Get(group string, key string) ([]byte, error) {
	u := fmt.Sprintf(
		"%v%v/%v",
		h.baseURL,
		url.QueryEscape(group),
		url.QueryEscape(key),
	)
	res, err := http.Get(u)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("server returned: %v", res.Status)
	}

	bytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("reading response body: %v", err)
	}

	return bytes, nil
}

var _ PeerGetter = (*httpGetter)(nil)
  • baseURL 表示将要访问的远程节点的地址,例如 http://example.com/_geecache/
  • 使用http.Get()方式获取返回值,并转换为[]bytes类型。

第二步,为 HTTPPool 添加节点选择的功能。

const (
	defaultBasePath = "/_geecache/"
	defaultReplicas = 50
)
// HTTPPool implements PeerPicker for a pool of HTTP peers.
type HTTPPool struct {
	// this peer's base URL, e.g. "https://example.net:8000"
	self        string
	basePath    string
	mu          sync.Mutex // guards peers and httpGetters
	peers       *consistenthash.Map
	httpGetters map[string]*httpGetter // keyed by e.g. "http://10.0.0.2:8008"
}
  • 新增成员变量 peers,类型是一致性哈希算法的 Map,用来根据具体的 key 选择节点。
  • 新增成员变量 httpGetters,映射远程节点与对应的 httpGetter。每一个远程节点对应一个 httpGetter,因为 httpGetter 与远程节点的地址 baseURL 有关。

第三步,实现 PeerPicker 接口。

// Set updates the pool's list of peers.
func (p *HTTPPool) Set(peers ...string) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.peers = consistenthash.New(defaultReplicas, nil)
	p.peers.Add(peers...)
	p.httpGetters = make(map[string]*httpGetter, len(peers))
	for _, peer := range peers {
		p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}
	}
}

// PickPeer picks a peer according to key
func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if peer := p.peers.Get(key); peer != "" && peer != p.self {
		p.Log("Pick peer %s", peer)
		return p.httpGetters[peer], true
	}
	return nil, false
}

var _ PeerPicker = (*HTTPPool)(nil)
  • Set() 方法实例化了一致性哈希算法,并且添加了传入的节点。
  • 并为每一个节点创建了一个 HTTP 客户端 httpGetter
  • PickerPeer() 包装了一致性哈希算法的 Get() 方法,根据具体的 key,选择节点,返回节点对应的 HTTP 客户端。

至此,HTTPPool 既具备了提供 HTTP 服务的能力,也具备了根据具体的 key,创建 HTTP 客户端从远程节点获取缓存值的能力。

4 实现主流程

最后,我们需要将上述新增的功能集成在主流程(geecache.go)中。

day5-multi-nodes/geecache/geecache.go

// A Group is a cache namespace and associated data loaded spread over
type Group struct {
	name      string
	getter    Getter
	mainCache cache
	peers     PeerPicker
}

// RegisterPeers registers a PeerPicker for choosing remote peer
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) {
	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)
}

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
}
  • 新增 RegisterPeers() 方法,将 实现了 PeerPicker 接口的 HTTPPool 注入到 Group 中。
  • 新增 getFromPeer() 方法,使用实现了 PeerGetter 接口的 httpGetter 从访问远程节点,获取缓存值。
  • 修改 load 方法,使用 PickPeer() 方法选择节点,若非本机节点,则调用 getFromPeer() 从远程获取。若是本机节点或从远程节点获取失败,则回退到 getLocally()

5 main 函数测试。

day5-multi-nodes/main.go

var db = map[string]string{
	"Tom":  "630",
	"Jack": "589",
	"Sam":  "567",
}

func createGroup() *geecache.Group {
	return geecache.NewGroup("scores", 2<<10, geecache.GetterFunc(
		func(key string) ([]byte, error) {
			log.Println("[SlowDB] search key", key)
			if v, ok := db[key]; ok {
				return []byte(v), nil
			}
			return nil, fmt.Errorf("%s not exist", key)
		}))
}

func startCacheServer(addr string, addrs []string, gee *geecache.Group) {
	peers := geecache.NewHTTPPool(addr)
	peers.Set(addrs...)
	gee.RegisterPeers(peers)
	log.Println("geecache is running at", addr)
	log.Fatal(http.ListenAndServe(addr[7:], peers))
}

func startAPIServer(apiAddr string, gee *geecache.Group) {
	http.Handle("/api", http.HandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			key := r.URL.Query().Get("key")
			view, err := gee.Get(key)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			w.Header().Set("Content-Type", "application/octet-stream")
			w.Write(view.ByteSlice())

		}))
	log.Println("fontend server is running at", apiAddr)
	log.Fatal(http.ListenAndServe(apiAddr[7:], nil))

}

func main() {
	var port int
	var api bool
	flag.IntVar(&port, "port", 8001, "Geecache server port")
	flag.BoolVar(&api, "api", false, "Start a api server?")
	flag.Parse()

	apiAddr := "http://localhost:9999"
	addrMap := map[int]string{
		8001: "http://localhost:8001",
		8002: "http://localhost:8002",
		8003: "http://localhost:8003",
	}

	var addrs []string
	for _, v := range addrMap {
		addrs = append(addrs, v)
	}

	gee := createGroup()
	if api {
		go startAPIServer(apiAddr, gee)
	}
	startCacheServer(addrMap[port], []string(addrs), gee)
}

main 函数的代码比较多,但是逻辑是非常简单的。

  • startCacheServer() 用来启动缓存服务器:创建 HTTPPool,添加节点信息,注册到 gee 中,启动 HTTP 服务(共3个端口,8001/8002/8003),用户不感知。
  • startAPIServer() 用来启动一个 API 服务(端口 9999),与用户进行交互,用户感知。
  • main() 函数需要命令行传入 portapi 2 个参数,用来在指定端口启动 HTTP 服务。

为了方便,我们将启动的命令封装为一个 shell 脚本:

#!/bin/bash
trap "rm server;kill 0" EXIT

go build -o server
./server -port=8001 &
./server -port=8002 &
./server -port=8003 -api=1 &

sleep 2
echo ">>> start test"
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &

wait

trap 命令用于在 shell 脚本退出时,删掉临时文件,结束子进程。

$ ./run.sh
2020/02/16 21:17:43 geecache is running at http://localhost:8001
2020/02/16 21:17:43 geecache is running at http://localhost:8002
2020/02/16 21:17:43 geecache is running at http://localhost:8003
2020/02/16 21:17:43 fontend server is running at http://localhost:9999
>>> start test
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
2020/02/16 21:17:45 [Server http://localhost:8003] Pick peer http://localhost:8001
...
630630630

此时,我们可以打开一个新的 shell,进行测试:

$ curl "http://localhost:9999/api?key=Tom"
630
$ curl "http://localhost:9999/api?key=kkk"
kkk not exist

测试的时候,我们并发了 3 个请求 ?key=Tom,从日志中可以看到,三次均选择了节点 8001,这是一致性哈希算法的功劳。但是有一个问题在于,同时向 8001 发起了 3 次请求。试想,假如有 10 万个在并发请求该数据呢?那就会向 8001 同时发起 10 万次请求,如果 8001 又同时向数据库发起 10 万次查询请求,很容易导致缓存被击穿。

三次请求的结果是一致的,对于相同的 key,能不能只向 8001 发起一次请求?这个问题下一次解决。

6 QA

  1. 这里有一个疑惑想问一下博主,如果要将GeeCache进行横向扩展的话,应该如何部署,可不可以将peer部署到其他机器上?
    答:把 IP 和端口换成部署机器的 IP 和端口就可以了。只是测试用例中,在本机启动了三个实例,基于网络通信,部署在哪里都可以。

A :是不是漏掉了从远程节点拿到缓存后更新本地缓存这一步?
B : groupcache 中缓存值只淘汰不更新,也没有超时淘汰机制,这样取舍简化了设计。
A : 我的意思是当请求当前缓存服务器时 此服务器本地没有缓存 接着由当前服务器去请求其他节点服务器 当拿回来缓存值后 不应该更新此服务器的本地缓存吗 ?
B :分布式缓存的目的是不同key缓存在不同的节点上,增加总的吞吐量。如果大家转发请求后,都再备份一次,每台机器上都缓存了相同的数据,就失去意义了。每个节点缓存1G数据,理论上10个节点总共可以缓存10G不同的数据。当然对于热点数据,每个节点拿到值后,本机备份一次是有价值的,增加热点数据的吞吐量。groupcache 的原生实现中,有1/10的概率会在本机存一次。这样10个节点,理论上可以缓存9G不同的数据,算是一种取舍。

  1. 如何才能缓存多个group?是通过go 开辟新goroutine吗?或者是给多个group注册相同的HTTPPool?
    答:都可以,group 和 HTTPPool 是解耦的,可以复用,也可以各自搭配各自的。

  2. 当前版本应该是不支持动态的横向扩展,现在需要把一致性hash中的物理节点提前写在配置中,或者map中,如果我想要新增一个节点怎么办呢,需要重新启动,然后利用peers.Set(addrs…)把新增后的所有节点加进来吗
    答:当前版本只能这样。后续可以将缓存服务注册到注册中心,通过服务发现获取所有节点IP。可以参看带注册中心的版本:https://github.com/peanutzhen/peanutcache

  3. 用户只知晓API:9999.那么API与分布式缓存是一个1对3的关系. 那么当用户查询的时候,首先查询的get是三个缓存中的哪一个呢? 还是说API本身是一个group 本地有, 没有再去三个缓存中找呢? 这个模型实在是没搞清晰 希望指点
    答: api服务绑定了一个本地的geecache服务,这个服务miss的时候,API用consistent hasher 去算出应该去哪个节点找数据。

  4. 获取缓存数据的流程是:从本地缓存查找->从远程节点查找->回调函数,写到本地。如果是这样的话,远程节点是不是就一直没有缓存到数据。数据要么是本地缓存直接得到,要么是本地和远程都找不到,然后回调,写到本地缓存。不知道我理解的正确吗?
    答:不对。远程节点查找的时候,如果在远程节点的缓存中找不到,是调用远程节点的回调函数,存储到远程节点

原文地址: https://geektutu.com/post/geecache-day5.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1937937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenGL-ES 学习(7) ---- VBO EBO 和 VAO

目录 VBO(Vertex Buffer Object)EBO(Element Buffer Object)VAO(Vertex Array Object) VBO(Vertex Buffer Object) EBO(Element Buffer Object) VBO(Vertex Buffer Object) 实际是指顶点缓冲器对象 在 opengl-es 2.0 的编程中&#xff0c;用于绘制图元的顶点数据是从 CPU 传…

LoadRunner-Vugen脚本使用教程

1 使用VuGen录制脚本 1.1新建脚本和解决方案 &#xff08;1&#xff09;打开VuGen&#xff0c;单击【File】→【New Script and Solution】创建项目&#xff0c;弹出Create a New Script对话框。 左侧栏是协议分类&#xff0c;每项含义如下所示&#xff1a; ● Single Pro…

数字通云平台 智慧政务OA PayslipUser SQL注入漏洞复现

0x01 产品简介 数字通云平台智慧政务OA产品是基于云计算、大数据、人工智能等先进技术,为政府部门量身定制的智能化办公系统。该系统旨在提高政府部门的办公效率、协同能力和信息资源共享水平,推动电子政务向更高层次发展。 0x02 漏洞概述 数字通云平台 智慧政务OA Paysli…

pytorch学习(六)transforms使用

1.Transforms可以对训练图像进行预处理&#xff0c;以提高模型的稳定性&#xff0c;提高泛化能力。其中包含&#xff1a; 中心裁剪、数据标准化、缩放、裁剪、旋转、仿射、反转、填充、噪声添加、灰度变换、线性变换、亮度饱和度以及对比度变换等。 所处理的图像用tensorboard…

【iOS】APP仿写——网易云音乐

网易云音乐 启动页发现定时器控制轮播图UIButtonConfiguration 发现换头像 我的总结 启动页 这里我的启动页是使用Xcode自带的启动功能&#xff0c;将图片放置在LaunchScreen中即可。这里也可以通过定时器控制&#xff0c;来实现启动的效果 效果图&#xff1a; 这里放一篇大…

基于视觉工具箱和背景差法的行人检测,行走轨迹跟踪,人员行走习惯统计matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 在三维图中&#xff0c;幅度越大&#xff0c;则表示人员更习惯的行走路线。 2.算法运行软件版本 matlab2022a 3.部分核…

【Nacos】Nacos服务注册与发现 心跳检测机制源码解析

在前两篇文章&#xff0c;介绍了springboot的自动配置原理&#xff0c;而nacos的服务注册就依赖自动配置原理。 Nacos Nacos核心功能点 服务注册 :Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务&#xff0c;提供自身的元数据&#xff0c;比如ip地址、端…

【C语言】深入解析希尔排序

文章目录 什么是希尔排序&#xff1f;希尔排序的基本实现代码解释希尔排序的优化希尔排序的性能分析希尔排序的实际应用结论 在C语言编程中&#xff0c;希尔排序是一种高效的排序算法&#xff0c;是插入排序的一种更高效的改进版本。它通过比较相距一定间隔的元素来进行排序&am…

JRT报告打印设计

检验报告单打印一直是个难点问题&#xff0c;JRT开发时候重点考虑了简化检验报告打印&#xff0c;首先采用脚本化方便快速修改报告。然后打印基础解决难点问题&#xff0c;基于JRT打印就可以简化到本文代码的水平&#xff0c;维护方便&#xff0c;结构清晰&#xff0c;上线修改…

【1】Spring Cloud 工程搭建

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;Spring学习之路&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1. 父子工程创建 1.1 创建父工程 1.2 创建子项目 重点关注Spring C…

头发稀疏治疗笔记

1. 前言 今天去中南医院看了一下“头发稀疏”的病症&#xff1b; 2. 头皮检测 2.1 毛发光镜检查 2.2 皮肤镜影像

root的安卓12系统上,如何使apk获得root或者高级别的系统权限?

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

JVM--经典的垃圾收集器

1. 垃圾收集器的概念 定义 ; 如果说收集算法是内存回收的方法论&#xff0c;那垃圾收集器就是内存回收的实践者。《Java虚拟机规 范》中对垃圾收集器应该如何实现并没有做出任何规定&#xff0c;因此不同的厂商、不同版本的虚拟机所包含 的垃圾收集器都可能会有很大差别…

Linux journalctl日志太长,如何倒序查看journalctl --reverse,journalctl -xeu

文章目录 需求实验方法一方法二 需求 Linux journalctl日志太长&#xff0c;如何倒序查看 我们通常关心的是最近的日志&#xff0c;但是每次打开日志都是按时间先后顺序显示的&#xff0c;如何倒序查看&#xff0c;请看下面&#xff1a; 实验 方法一 journalctl 命令默认按…

uniapp封装请求拦截器,封装请求拦截和响应拦截的方法

首先我们先看一下uni官方给开发者提供的uni.request用来网络请求的api 1 2 3 4 5 6 7 8 9 uni.request({ url: , method: GET, data: {}, header: {}, success: res > {}, fail: () > {}, complete: () > {} }); 可以看到我们每次请求数据的时候都需…

两种调用方法可以让Contact form 7表单在任意地方显示

Contact form 7是wordpress建站过程中最常用到的插件之一&#xff0c;不过&#xff0c;在Contact form 7调用的时候&#xff0c;有些新手还是搞不太清楚它的调用方法。下面简站wordpress小编&#xff0c;就把常用的两种调用方法&#xff0c;分享给大家&#xff1a; Contact fo…

Open3D 非线性最小二乘法拟合空间球

目录 一、概述 1.1原理 1.2实现步骤 二、代码实现 2.1关键代码 2.1.1定义残差函数 2.1.2拟合球面 2.2完整代码 三、实现效果 3.1原始点云 3.2拟合后点云 3.3结果数据 前期试读&#xff0c;后续会将博客加入下列链接的专栏&#xff0c;欢迎订阅 Open3D点云算法与点…

题解|2023暑期杭电多校02

【原文链接】 &#xff08;补发&#xff09;题解|2023暑期杭电多校02 1002.Binary Number 字符串、贪心 题目大意 给定一段长度为 n n n的01串&#xff0c;首位保证为1 任选定其中任意长的一段并将其反转 必须执行以上操作 k k k次&#xff0c;求操作后得到的01串表示的二进…

JAVA.2.运算符

目录 1.算术运算符 小数有可能会不精确&#xff0c;整数除以整数得整数 例子 package demo1;public class Hello {public static void main(String[] args) {System.out.println(12);System.out.println(1-2);System.out.println(5/2);System.out.println(5.0/2);System.out.…

代码随想录移除元素二刷

代码随想录移除元素二刷 leetcode 27 这道题思路的话可以这样去理解&#xff0c;用两个指针&#xff0c;一个慢指针&#xff0c;一个快指针。先让快指针往前面去探路&#xff0c;也就是去遍历数组&#xff0c;遇到不为val的值再去把该值赋值给nums[slow]&#xff0c;slow指针1…