golang学习之ngrok源码分析

从去年开始就对go语言产生一点兴趣,总感觉java有时太过臃肿,是时候尝试一种新语言了。我看了几本关于go的书,不过看完就忘了,最近开发一个微信公众号项目,使用ngrok做内网穿透,顺道研究一下ngrok源码,巩固一下go语言。

网上有两篇文章已对ngrok原理讲得很清晰了:

https://blog.messyidea.com/archives/41/

https://tonybai.com/2015/05/14/ngrok-source-intro/

我试图记录一些细节,然后实现在个简单的ngrok。

  1. 源码/ngrok/cache/lru.go,这是随手点开的一个文件,从名字可以看出它的用处:实现一个lru(Least Recently Used)缓存,在以往的项目中,我使用的本地缓存是guava提供的工具,很久前我还用过WeakReferenceMap作为一个简单缓存。以下是ngrok中的源码

type LRUCache struct {
   mu sync.Mutex

   // list & table of *entry objects
   list  *list.List
   table map[string]*list.Element

   // Our current size, in bytes. Obviously a gross simplification and low-grade
   // approximation.
   size uint64

   // How many bytes we are limiting the cache to.
   capacity uint64
}

type Item struct {
   Key   string
   Value Value
}

虽然算法很简单,但有几个地方还是值得把玩的。list是一个链表,存储的是item,它可以看成是一个具有优先级的队列,每次访问一个元素时,都会对将相应元素移至队首。这样当list长度超过capacity时,就可以移除队尾的元素。而table是用作索引,通过一个键查询缓存时,通过table得到list中一个element的引用。而list保存的item有一个key属性对应用table中的key,这样很方便对table做移除操作。

以前我想在java中用PriorityBolckingQueue,和ConcurrentHashMap实现一个类似的缓存(不仅是LRU缓存,它们组合实现多种缓存算法),但只想到用map保存键值对,而queue用来做优先队列(这种双向索引的方式我没想到)。说不定用JDK中的HashLinkedMap更容易实现LRU,因为他已同时具备链表和索引的功能。

完整代码如下:

// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The implementation borrows heavily from SmallLRUCache (originally by Nathan
// Schrenk). The object maintains a doubly-linked list of elements in the
// When an element is accessed it is promoted to the head of the list, and when
// space is needed the element at the tail of the list (the least recently used
// element) is evicted.
package cache

import (
 "container/list"
 "encoding/gob"
 "fmt"
 "io"
 "os"
 "sync"
 "time"
)

type LRUCache struct {
 mu sync.Mutex

 // list & table of *entry objects
 list  *list.List
 table map[string]*list.Element

 // Our current size, in bytes. Obviously a gross simplification and low-grade
 // approximation.
 size uint64

 // How many bytes we are limiting the cache to.
 capacity uint64
}

// Values that go into LRUCache need to satisfy this interface.
type Value interface {
 Size() int
}

type Item struct {
 Key   string
 Value Value
}

type entry struct {
 key           string
 value         Value
 size          int
 time_accessed time.Time
}

func NewLRUCache(capacity uint64) *LRUCache {
 return &LRUCache{
     list:     list.New(),
     table:    make(map[string]*list.Element),
     capacity: capacity,
 }
}

func (lru *LRUCache) Get(key string) (v Value, ok bool) {
 lru.mu.Lock()
 defer lru.mu.Unlock()

 element := lru.table[key]
 if element == nil {
     return nil, false
 }
 lru.moveToFront(element)
 return element.Value.(*entry).value, true
}

func (lru *LRUCache) Set(key string, value Value) {
 lru.mu.Lock()
 defer lru.mu.Unlock()

 if element := lru.table[key]; element != nil {
     lru.updateInplace(element, value)
 } else {
     lru.addNew(key, value)
 }
}

func (lru *LRUCache) SetIfAbsent(key string, value Value) {
 lru.mu.Lock()
 defer lru.mu.Unlock()

 if element := lru.table[key]; element != nil {
     lru.moveToFront(element)
 } else {
     lru.addNew(key, value)
 }
}

func (lru *LRUCache) Delete(key string) bool {
 lru.mu.Lock()
 defer lru.mu.Unlock()

 element := lru.table[key]
 if element == nil {
     return false
 }

 lru.list.Remove(element)
 delete(lru.table, key)
 lru.size -= uint64(element.Value.(*entry).size)
 return true
}

func (lru *LRUCache) Clear() {
 lru.mu.Lock()
 defer lru.mu.Unlock()

 lru.list.Init()
 lru.table = make(map[string]*list.Element)
 lru.size = 0
}

func (lru *LRUCache) SetCapacity(capacity uint64) {
 lru.mu.Lock()
 defer lru.mu.Unlock()

 lru.capacity = capacity
 lru.checkCapacity()
}

func (lru *LRUCache) Stats() (length, size, capacity uint64, oldest time.Time) {
 lru.mu.Lock()
 defer lru.mu.Unlock()
 if lastElem := lru.list.Back(); lastElem != nil {
     oldest = lastElem.Value.(*entry).time_accessed
 }
 return uint64(lru.list.Len()), lru.size, lru.capacity, oldest
}

func (lru *LRUCache) StatsJSON() string {
 if lru == nil {
     return "{}"
 }
 l, s, c, o := lru.Stats()
 return fmt.Sprintf("{\"Length\": %v, \"Size\": %v, \"Capacity\": %v, \"OldestAccess\": \"%v\"}", l, s, c, o)
}

func (lru *LRUCache) Keys() []string {
 lru.mu.Lock()
 defer lru.mu.Unlock()

 keys := make([]string, 0, lru.list.Len())
 for e := lru.list.Front(); e != nil; e = e.Next() {
     keys = append(keys, e.Value.(*entry).key)
 }
 return keys
}

func (lru *LRUCache) Items() []Item {
 lru.mu.Lock()
 defer lru.mu.Unlock()

 items := make([]Item, 0, lru.list.Len())
 for e := lru.list.Front(); e != nil; e = e.Next() {
     v := e.Value.(*entry)
     items = append(items, Item{Key: v.key, Value: v.value})
 }
 return items
}

func (lru *LRUCache) SaveItems(w io.Writer) error {
 items := lru.Items()
 encoder := gob.NewEncoder(w)
 return encoder.Encode(items)
}

func (lru *LRUCache) SaveItemsToFile(path string) error {
 if wr, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
     return err
 } else {
     defer wr.Close()
     return lru.SaveItems(wr)
 }
}

func (lru *LRUCache) LoadItems(r io.Reader) error {
 items := make([]Item, 0)
 decoder := gob.NewDecoder(r)
 if err := decoder.Decode(&items); err != nil {
     return err
 }

 lru.mu.Lock()
 defer lru.mu.Unlock()
 for _, item := range items {
     // XXX: copied from Set()
     if element := lru.table[item.Key]; element != nil {
         lru.updateInplace(element, item.Value)
     } else {
         lru.addNew(item.Key, item.Value)
     }
 }

 return nil
}

func (lru *LRUCache) LoadItemsFromFile(path string) error {
 if rd, err := os.Open(path); err != nil {
     return err
 } else {
     defer rd.Close()
     return lru.LoadItems(rd)
 }
}

func (lru *LRUCache) updateInplace(element *list.Element, value Value) {
 valueSize := value.Size()
 sizeDiff := valueSize - element.Value.(*entry).size
 element.Value.(*entry).value = value
 element.Value.(*entry).size = valueSize
 lru.size += uint64(sizeDiff)
 lru.moveToFront(element)
 lru.checkCapacity()
}

func (lru *LRUCache) moveToFront(element *list.Element) {
 lru.list.MoveToFront(element)
 element.Value.(*entry).time_accessed = time.Now()
}

func (lru *LRUCache) addNew(key string, value Value) {
 newEntry := &entry{key, value, value.Size(), time.Now()}
 element := lru.list.PushFront(newEntry)
 lru.table[key] = element
 lru.size += uint64(newEntry.size)
 lru.checkCapacity()
}

func (lru *LRUCache) checkCapacity() {
 // Partially duplicated from Delete
 for lru.size > lru.capacity {
     delElem := lru.list.Back()
     delValue := delElem.Value.(*entry)
     lru.list.Remove(delElem)
     delete(lru.table, delValue.key)
     lru.size -= uint64(delValue.size)
 }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容