极客兔兔Gee-Cache Day5

  • HTTPPool 既可以是服务端,也可以是客户端,这取决于特定的使用场景和上下文:

    • 作为客户端 :当本地缓存没有找到需要的数据时,HTTPPool 需要作为客户端,通过 httpGetter (实现了 PeerGetter 接口)去其他的远程节点(服务端)请求数据。
    • 作为服务端 :当其他节点的 HTTPPool 实例(作为客户端)请求当前节点存储的数据时,当前节点的 HTTPPool 实例则作为服务端响应这些请求。
  • HTTPPool实现了PeerPicker接口,httpGetter实现了PeerGetter接口,前者用来找到对应的远程节点(由于每个远程节点对应一个httpGetter,因此实际找到的是httpGetter),后者用来向远处节点请求数据

  • Peer.go

    复制代码
      package gee
    
      type PeerPicker interface {
      	PickPeer(key string) (peer PeerGetter, ok bool)
      }
    
      type PeerGetter interface {
      	Get(group string, key string) ([]byte, error)
      }
    • PickerPeer用来找到key对应的远程节点,远程节点和PeerGetter一一对应,找到PeerGetter就找到对应的远程节点

    • PeerGetter根据groupkey向远程节点获取数据

  • url.QueryEscape()net/url包,用于将url中的特殊字符用asll码进行替换,防止丢失

  • var _ PeerGetter = (*httpGetter)(nil) :检验接口实现是否正确

  • day5需要实现http的客户端,对于缓存未命中时,需要向远程节点请求数据,此时被作为客户端,http.go中已经在day2实现了服务端的代码,需要再加上客户端

    • HTTPPool实现了上面的PeerPicker接口,实现了其需要实现的PickPeer()函数,该函数返回远程节点对应的PeerGetter,找到PeerGetter就能去访问远程节点的数据

      go 复制代码
      // 查找PeerGetter
      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 {
      		return p.httpGetters[peer], true
      	}
      	return nil, false
      }
      var _ PeerPicker = (*HTTPPool)(nil) // 检验接口实现是否正确
    • ``httpGetter实现了PeerGetter接口,实现了其需要实现的Get()函数,Get()函数主要是吊桶net/http中的Get()方法,向远程节点请求数据,并使用ioutilReadAll`获取

      go 复制代码
      func (h *httpGetter) Get(group string, key string) ([]byte, error) {
      	u := fmt.Sprintf(
      		"%s%s%s",
      		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("error response body: %v", err)
      	}
      
      	bytes, err := ioutil.ReadAll(res.Body)
      	if err != nil {
      		return nil, fmt.Errorf("error response body: %v", err)
      	}
      	return bytes, nil
      }
      var _ PeerGetter = (*httpGetter)(nil) // 检验接口实现是否正确
    • HTTPPool管理客户端的所有过程,因此需要加入httpGetter属性,其是一个map[string]*httpGetter类型,通过key映射httpGetter,同时加入day4中实现的一致性哈希过程,由于这些需要实现互斥访问哈希表,因此还需要一个mutex

      go 复制代码
      type HTTPPool struct {
      	self     string
      	basePath string
      
      	mu          sync.Mutex
      	peers       *consistenthash.Map    // 缓存值
      	httpGetters map[string]*httpGetter // 每个httpGetter对应一个远程节点,在缓存未命中时向远程节点请求数据
      }
    • 最后,将HTTPPool集成在主流程中,即group中,group需要实现RegisterPeer()函数,将对应的HTTPPool注入,同时封装getFromPeer()方法,其中调用了之前的Get()方法,用来请求远程节点的数据,重写一下load()方法,在不存在HTTPPool时才调用getLocally(),否则调用getFromPeer

      go 复制代码
      type Group struct {
      	name      string // 缓存名称
      	getter    Getter // 回调函数
      	mainCache cache  // 缓存
      
      	peers PeerPicker // 集成HTTPPool
      }
      // 将HTTPPool注入到group中
      func (g *Group) RegisterPeers(peer PeerPicker) {
      	if g.peers != nil {
      		panic("最多一个peerPicker")
      	}
      	g.peers = peer
      }
      
      // 缓存未命中 向远程节点请求
      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("获取Peer失败")
      		}
      	}
      	// 不存在远程节点 调用回调函数
      	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
      
      }
    • 因此,流程如下

      复制代码
                                   是
        接收 key --> 检查是否被缓存 -----> 返回缓存值 ⑴
                        |  否                         是
                        |-----> 是否应当从远程节点获取 -----> 与远程节点交互 --> 返回缓存值 ⑵
                                    |  否
                                    |-----> 调用`回调函数`,获取值并添加到缓存 --> 返回缓存值 ⑶
相关推荐
一个热爱生活的普通人6 小时前
如何开发一个可以在命令行执行的Coding Agent
人工智能·go·aigc
凉、介6 小时前
CPU Cache 的映射与寻址
linux·arm开发·数据库·redis·缓存·嵌入式
丘山子8 小时前
如何确保 Go 系统在面临超时或客户端主动取消时,能够优雅地释放资源?
后端·面试·go
喵手10 小时前
使用 Java 集合进行缓存系统设计的实践分享!
java·开发语言·缓存
岁忧10 小时前
(LeetCode 面试经典 150 题) 169. 多数元素(哈希表 || 二分查找)
java·c++·算法·leetcode·go·散列表
丘山子11 小时前
了解 Go Channel
后端·面试·go
mCell11 小时前
密码校验与攻击面:不再“裸奔”的防线
后端·安全·go
白一梓12 小时前
Cursor 下配置 golang
go
萌新小码农‍13 小时前
Redis后端的简单了解与使用(项目搭建前置)
数据库·redis·缓存
DemonAvenger13 小时前
Go高并发场景下内存管理最佳实践
性能优化·架构·go