protobuf、LRU、sigleflight
今天咱一次讲3个吧,赶一下进度,好早点开始聊kubernetes!
从groupcache的项目目录结构看,我们今天要学习groupcachepb、lru、singleflight这3个package:
一、protobuf
这个目录咋一看有2个文件:go和proto后缀的。proto后缀的文件和protocol buffers有关,所以先看看protocol buffers是什么吧。
在github上可以看到这个项目:https://github.com/google/protobuf
google的,是不是瞬间来了兴趣?
官方介绍是:Protocol Buffers (a.k.a., protobuf) are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data.简单说就是跨语言跨平台的可拓展的结构数据序列化用的。翻译着有点别扭,还是直接看英文好理解。。。行,现在大家知道这个是用来做数据序列化的了,大家是否记得Golang自带的一个数据结构序列化编码/解码工具gob?之前我们有专门介绍过:《golang - gob与rpc》。
ok,看过gob这篇文章,大家就知道protobuf需要解决的基本问题了,下面我们结合源码来看protobuf的知识点。
$GOPATH\src\github.com\golang\groupcache\groupcachepb\groupcache.proto内容如下:
1syntax = "proto2";
2
3package groupcachepb;
4
5message GetRequest {
6 required string group = 1;
7 required string key = 2; // not actually required/guaranteed to be UTF-8
8}
9
10message GetResponse {
11 optional bytes value = 1;
12 optional double minute_qps = 2;
13}
14
15service GroupCache {
16 rpc Get(GetRequest) returns (GetResponse) {
17 };
18}
可以看到这是某种语法的数据定义格式,我们先介绍一下这里涉及的概念:
protobuf中主要数据类型有:
标准数据类型:整型,浮点,字符串等
复合数据类型:枚举和message类型
看message部分:
message GetResponse {
optional bytes value = 1;
optional double minute_qps = 2;
}
每个字段末尾有一个tag,这个tag要求不重复,如这里的1、2;
每个字段有一个类型,如这里的bytes、double;
每个字段开头的optional含义为:
required: 必须赋值,不能为空
optional:可以赋值,也可以不赋值
repeated: 该字段可以重复任意次数,包括0次
现在我们可以看懂这个message的名字是GetResponse,有2个可选字段value和minute_qps,两个字段的类型分别为bytes和double,2个字段都是optional的。
protobuf也提供了包的定义,只要在文件开头定义package关键字即可,所以这里的package groupcachepb;这行也好理解;第一行syntax = "proto2";明显是声明版本的,除了proto2外还有proto3版本,类似与py2后有了py3。
到这里就剩下最后几行有点疑惑了:
service GroupCache {
rpc Get(GetRequest) returns (GetResponse) {
};
}
这里可以看到打头的是service,中间的字段是一个rpc相关的类似函数的东西,参数和返回值都是上面定义的message:GetRequest和GetResponse,明显这里和rpc要有关系了,细节我们先不讲,到后面调用到的地方咱再结合业务代码来理解这里的细节。
二、LRU
查一下百度百科,可以得到LRU的解释如下:
内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。
什么是LRU算法? LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的。
所以这里的lru包也就是用来实现lru算法的,详细的解释我放在注释中:$GOPATH\src\github.com\golang\groupcache\lru\lru.go:
1// Package lru implements an LRU cache.
2//【lru包用于实现LRU cache】
3package lru
4
5import "container/list"
6
7// Cache is an LRU cache. It is not safe for concurrent access.
8//【Cache结构用于实现LRU cache算法;并发访问不安全】
9type Cache struct {
10 // MaxEntries is the maximum number of cache entries before
11 // an item is evicted. Zero means no limit.
12 //【最大入口数,也就是缓存中最多存几条数据,超过了就触发数据淘汰;0表示没有限制】
13 MaxEntries int
14
15 // OnEvicted optionally specificies a callback function to be
16 // executed when an entry is purged from the cache.
17 //【销毁前回调】
18 OnEvicted func(key Key, value interface{})
19
20 //【链表】
21 ll *list.List
22 //【key为任意类型,值为指向链表一个结点的指针】
23 cache map[interface{}]*list.Element
24}
25
26// A Key may be any value that is comparable.
27// See http://golang.org/ref/spec#Comparison_operators
28//【任意可比较类型】
29type Key interface{}
30
31//【访问入口结构,包装键值】
32type entry struct {
33 key Key
34 value interface{}
35}
36
37// New creates a new Cache.
38// If maxEntries is zero, the cache has no limit and it's assumed
39// that eviction is done by the caller.
40//【初始化一个Cache类型实例】
41func New(maxEntries int) *Cache {
42 return &Cache{
43 MaxEntries: maxEntries,
44 ll: list.New(),
45 cache: make(map[interface{}]*list.Element),
46 }
47}
48
49// Add adds a value to the cache.
50//【往缓存中增加一个值】
51func (c *Cache) Add(key Key, value interface{}) {
52 //【如果Cache还没有初始化,先初始化,创建cache和l1】
53 if c.cache == nil {
54 c.cache = make(map[interface{}]*list.Element)
55 c.ll = list.New()
56 }
57 //【如果key已经存在,则将记录前移到头部,然后设置value】
58 if ee, ok := c.cache[key]; ok {
59 c.ll.MoveToFront(ee)
60 ee.Value.(*entry).value = value
61 return
62 }
63 //【key不存在时,创建一条记录,插入链表头部,ele是这个Element的指针】
64 //【这里的Element是一个*entry类型,ele是*list.Element类型】
65 ele := c.ll.PushFront(&entry{key, value})
66 //cache这个map设置key为Key类型的key,value为*list.Element类型的ele
67 c.cache[key] = ele
68 //【链表长度超过最大入口值,触发清理操作】
69 if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
70 c.RemoveOldest()
71 }
72}
73
74// Get looks up a key's value from the cache.
75//【根据key查找value】
76func (c *Cache) Get(key Key) (value interface{}, ok bool) {
77 if c.cache == nil {
78 return
79 }
80 //【如果存在】
81 if ele, hit := c.cache[key]; hit {
82 //【将这个Element移动到链表头部】
83 c.ll.MoveToFront(ele)
84 //【返回entry的值】
85 return ele.Value.(*entry).value, true
86 }
87 return
88}
89
90// Remove removes the provided key from the cache.
91//【如果key存在,调用removeElement删除链表and缓存中的元素】
92func (c *Cache) Remove(key Key) {
93 if c.cache == nil {
94 return
95 }
96 if ele, hit := c.cache[key]; hit {
97 c.removeElement(ele)
98 }
99}
100
101// RemoveOldest removes the oldest item from the cache.
102//【删除最旧的元素】
103func (c *Cache) RemoveOldest() {
104 if c.cache == nil {
105 return
106 }
107 //【ele为*list.Element类型,指向链表的尾结点】
108 ele := c.ll.Back()
109 if ele != nil {
110 c.removeElement(ele)
111 }
112}
113
114func (c *Cache) removeElement(e *list.Element) {
115 //【链表中删除一个element】
116 c.ll.Remove(e)
117 //【e.Value本质是*entry类型,entry结构体就包含了key和value2个属性】
118 //【Value本身是interface{}类型,通过类型断言转成*entry类型】
119 kv := e.Value.(*entry)
120 //【删除cache这个map中key为kv.key这个元素;也就是链表中删了之后缓存中也得删】
121 delete(c.cache, kv.key)
122 if c.OnEvicted != nil {
123 c.OnEvicted(kv.key, kv.value)
124 }
125}
126
127// Len returns the number of items in the cache.
128//【返回缓存中的item数,通过链表的Len()方法获取】
129func (c *Cache) Len() int {
130 if c.cache == nil {
131 return 0
132 }
133 return c.ll.Len()
134}
135
136// Clear purges all stored items from the cache.
137//【删除缓存中所有条目,如果有回调函数OnEvicted(),则先调用所有回调函数,然后置空】
138func (c *Cache) Clear() {
139 if c.OnEvicted != nil {
140 for _, e := range c.cache {
141 kv := e.Value.(*entry)
142 c.OnEvicted(kv.key, kv.value)
143 }
144 }
145 c.ll = nil
146 c.cache = nil
147}
三、singleflight
这个package主要实现了这样一个功能:抑制同一个函数调用重复执行。举个例子:给一个常规程序输入一个函数调用A()需要10s返回结果,这时候有10个客户端都调用了这个A(),可能就需要100s才能完成所有的计算结果,但是这个计算是重复的,结果也是一样的。所以可以想个办法,判断是同一个计算过程的情况,不需要重复执行,直接等待上一次计算完成,然后一下子返回结果就行了。下面看一下groupcache中是如何实现这个算法的吧:
1// Package singleflight provides a duplicate function call suppression
2// mechanism.
3//【“单航班”提供重复调用函数的抑制机制】
4package singleflight
5
6import "sync"
7
8// call is an in-flight or completed Do call
9//【在执行的或者已经完成的Do过程】
10type call struct {
11 wg sync.WaitGroup
12 val interface{}
13 err error
14}
15
16// Group represents a class of work and forms a namespace in which
17// units of work can be executed with duplicate suppression.
18//【表示一类工作,组成一个命名空间的概念,一个group的调用会有“重复抑制”】
19type Group struct {
20 mu sync.Mutex // protects m
21 //【懒惰地初始化;这个map的value是*call,call是上面那个struct】
22 m map[string]*call // lazily initialized
23}
24
25// Do executes and returns the results of the given function, making
26// sure that only one execution is in-flight for a given key at a
27// time. If a duplicate comes in, the duplicate caller waits for the
28// original to complete and receives the same results.
29
30//【Do接收一个函数,执行并返回结果,
31// 这个过程中确保同一个key在同一时间只有一个执行过程;
32// 重复的调用会等待最原始的调用过程完成,然后接收到相同的结果】
33func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
34 g.mu.Lock()
35 if g.m == nil {
36 g.m = make(map[string]*call)
37 }
38 //【如果这个call存在同名过程,等待初始调用完成,然后返回val和err】
39 if c, ok := g.m[key]; ok {
40 g.mu.Unlock()
41 c.wg.Wait()
42 //【当所有goroutine执行完毕,call中就存储了执行结果val和err,然后这里返回】
43 return c.val, c.err
44 }
45 //【拿到call结构体类型的指针】
46 c := new(call)
47 //【一个goroutine开始,Add(1),这里最多只会执行到一次,也就是不会并发调用下面的fn()】
48 c.wg.Add(1)
49 //【类似设置一个函数调用的名字“key”对应调用过程c】
50 g.m[key] = c
51 g.mu.Unlock()
52
53 //【函数调用过程】
54 c.val, c.err = fn()
55 //【这里的Done对应上面if里面的Wait】
56 c.wg.Done()
57
58 g.mu.Lock()
59 //【执行完成,删除这个key】
60 delete(g.m, key)
61 g.mu.Unlock()
62
63 return c.val, c.err
64}
今天讲的可能有点多,其中设计到的List之类的没有细讲,希望大家通过互联网掌握这类我没有仔细提到的小知识点,彻底吃透这几个package中的源码。
回过头看一下项目结果,除了testpb包外其他包我们都讲完了,testpb是groupcachepb对应的测试程序,下一讲我们就可以把这几个包外的所有程序分析完,包括对protobuf部分的调用逻辑。
今天就到这里,groupcache源码解析还剩最后一讲!
protobuf、LRU、sigleflight的更多相关文章
- 页置换算法FIFO、LRU、OPT
页置换算法FIFO.LRU.OPT 为什么需要页置换 在地址映射过程中,若在页面中发现所要访问的页面不再内存中,则产生缺页中断.当发生缺页中断时操作系统必须在内存选择一个页面将其移出内存,以便为即将调 ...
- cache4j轻量级java内存缓存框架,实现FIFO、LRU、TwoQueues缓存模型
简介 cache4j是一款轻量级java内存缓存框架,实现FIFO.LRU.TwoQueues缓存模型,使用非常方便. cache4j为java开发者提供一种更加轻便的内存缓存方案,杀鸡焉用EhCac ...
- FIFO、LRU、OPT这三种置换算法的缺页次数
考虑下述页面走向: 1,2,3,4,2,1,5,6,2,1,2,3,7,6,3,2,1,2,3,6 当内存块数量分别为3时,试问FIFO.LRU.OPT这三种置换算法的缺页次数各是多少? 答:缺页定义 ...
- redis-缓存失效三种策略(FIFO 、LRU、LFU)
缓存失效策略(FIFO .LRU.LFU三种算法的区别) 当缓存需要被清理时(比如空间占用已经接近临界值了),需要使用某种淘汰算法来决定清理掉哪些数据.常用的淘汰算法有下面几种: FIFO:First ...
- 缓存淘汰算法(LFU、LRU、ARC、FIFO、MRU)分析
缓存算法是指令序列,用于决定缓存系统中哪些数据应该被删去. 常见类型包括LFU.LRU.ARC.FIFO.MRU. 一.最不经常使用算法(Least Frequently Used-LFU): 它是基 ...
- 网络中,FIFO、LRU、OPT这三种置换算法的缺页次数
FIFO.LRU.OPT这三种置换算法的缺页次数 转载 由于要考计算机四级网络,这里遇到了问题,就搜了一些资料来解疑. 考虑下述页面走向: 1,2,3,4,2,1,5,6,2,1,2,3,7,6,3 ...
- FIFO、LRU、LFU的含义和原理(转)
题目:请简要介绍FIFO.LRU.LFU的含义和原理 含义: FIFO:First In First Out,先进先出LRU:Least Recently Used,最近最少使用 LFU:Leas ...
- 常用缓存淘汰算法(LFU、LRU、ARC、FIFO、MRU)
缓存算法是指令的一个明细表,用于决定缓存系统中哪些数据应该被删去. 常见类型包括LFU.LRU.ARC.FIFO.MRU. 最不经常使用算法(LFU): 这个缓存算法使用一个计数器来记录条目被访问的频 ...
- 昨天面试被问到的 缓存淘汰算法FIFO、LRU、LFU及Java实现
缓存淘汰算法 在高并发.高性能的质量要求不断提高时,我们首先会想到的就是利用缓存予以应对. 第一次请求时把计算好的结果存放在缓存中,下次遇到同样的请求时,把之前保存在缓存中的数据直接拿来使用. 但是, ...
随机推荐
- PHP序列号生成函数和字符串替换函数代码
/** * 序列号生成器 */ function snMaker($pre = '') { $date = date('Ymd'); $rand = rand(1000000,9999999); $t ...
- 二维数组的最大子数组和 时间复杂度:O(n的四次方)
先上代码 小组成员:高达,李奔 package 三月二十一号; import java.io.BufferedReader; import java.io.FileReader; import jav ...
- vue-These relative modules were not found
今天在做vue2.0+webpack的项目的时候,本来一切正常,整理了一下文件夹分类,就是把一些基础的组件新建了一个文件夹移进去,然后就报了以下的错误,其他东西都没改 最后网上找了很多资料,有说配置文 ...
- 创建一个git仓库
1.git init 使用git init命令初始化一个git仓库,git仓库会生成一个.git目录 git init 1.使用指定的目录作为我们的git仓库 git init newrepo 2.初 ...
- 查找更改的PeopleCode
当我们做工程包迁移时,经过会遗漏部分更改过的定义.我们可以用下面的SQL来查找变更项 变量 &OPRID =代码变更者 变量 &PROJECT 项目工程名 SELECT * FROM ...
- LeetCode 15 3Sum [sort] <c++>
LeetCode 15 3Sum [sort] <c++> 给出一个一维数组,找出其中所有和为零的三元组(元素集相同的视作同一个三元组)的集合. C++ 先自己写了一发,虽然过了,但跑了3 ...
- dedecms 后台可以上传mp4,但无法选择
原文链接 找到 /include/dialog/select_media.php 找到rmvb,在其后面加 “|mp4” 即可. 1
- (BUG记录)记一次与其他系统交互协作时造成的锁表问题
最近两日做公司电信某计费项目时,接收一个银行对账的任务,在完成对账后.电信和银行两方金额一致时需要进行充值.冲正操作保持金额一致.冲正服务是JAVA统一调用Tuxedo服务,这个服务已经是一个稳定可用 ...
- pytorch可视化工具visdom
visdom的github repo: https://github.com/facebookresearch/visdom 知乎一个教程:https://zhuanlan.zhihu.com/p/3 ...
- activiti数据库表结构剖析
1.结构设计 1.1. 逻辑结构设计 Activiti使用到的表都是ACT_开头的. ACT_RE_*: ’RE’表示repository(存储),RepositoryService接口所操作的 ...