支持分布式节点间使用 HTTP
或 gRPC
协议通信、
支持高可用的服务注册发现、
支持动态节点管理和网络拓扑快速收敛的分布式键值缓存系统;
-
v1 version,实现了
-
并发访问控制(singleFlight)
-
负载均衡(consistenthash 算法)
-
多种缓存淘汰策略(lru、lfu、fifo,策略类模式)
-
分布式缓存节点间基于 http 协议的通信
-
分布式缓存节点间基于 gRPC 协议的通信
-
简单的服务注册发现(需要手动导入)
-
高可用 etcd 集群(使用 goreman 进行配置)
-
简单测试用例
-
-
v2 version,实现了
-
改进 singleFlight,增加结果缓存逻辑提升性能
-
增加 TTL 机制( 单独一个 goroutine),自动清理过期缓存
-
实现了简单的业务逻辑,在数据库和缓存之间进行交互测试(两种测试用例)
-
改进服务注册发现,使用 endpoint manager 对节点进行管理
-
提供自动化测试脚本和环境搭建介绍
-
-
v3 version,实现了
-
服务注册发现最终版(使用 endpoint manager 和 watch channel 实现类似于服务订阅发布的能力)
-
使用类似于事件回调的处理机制,根据节点的 PUT、DEL 事件更新节点状态
-
实现秒级节点之间拓扑结构的快速收敛(动态节点管理)
-
增加 grpc client 测试重试逻辑
-
failover 机制,节点失效后请求将转发到其他节点处理;即使所有节点下线,只要其中一个节点完成重启仍可继续提供服务(需要重新缓存预热)
-
不同节点预热完成后,节点之间的 rpc 调用时延仅为 1ms(但是如果查询的是不存在的数据,延迟最高达到 999ms)
-
即使其中一个节点崩溃或者宕机,也不会大范围影响其他已经预热好的节点(只会影响虚拟节点逆时针方向的一小段范围),而且该节点恢复后可以直接提供服务
-
增加缓存穿透的防御策略(将不存在的 key 的空值存到缓存中,设置合理过期时间,防止不存在的 key 的大量并发请求打穿数据库)
-
Rob Pike:"Dont't communicate by sharing memory, share memory by communicating". 不要通过共享内存来通信,应该通过通信来共享内存。
这句话奠定了 Go 应用并发设计的主流风格:使用 channel 进行不同 goroutine 之间的通信。在动态节点管理实现时,使用 channel 实现了负责哈希环视图重建的 goroutine(g1)和负责监听 endpoint 事件变更 goroutine(g2)之间的通信。一旦系统新增或者移除了节点,g2 监听到了变更事件,通过 g1 和 g2 共享的信号 channel 告知 g1,g1 收到通知后上锁重建哈希环视图,从而实现并发安全的动态节点管理。
.
├── README.md
├── api
│ ├── groupcachepb // grpc server idl.
│ ├── studentpb // business idl.
│ └── website
├── cmd
│ ├── grpc
│ │ └── main.go // grpc server latest version implement
│ └── http // http server implement
├── config // global config manage
│ ├── config.go
│ └── config.yml
├── go.mod
├── go.sum
├── internal
│ ├── middleware
│ │ └── etcd
│ │ ├── cluster // goerman etcd cluster manage
│ │ ├── discovery // service registration discovery (latest version use discovery3 impl.)
│ ├── pkg
│ │ ├── student // business logic
│ │ │ ├── dao
│ │ │ ├── ecode
│ │ │ ├── model
│ │ │ └── service
│ │ └── website
│ └── service // grpc group cache service imp.
│ ├── byteview.go // read-only
│ ├── cache.go // concurrency-safe caching imp.
│ ├── cachepurge // cache eviction algorithm implemented in strategy mode
│ │ ├── fifo
│ │ ├── interfaces // cache eviction algorithm interface abstract
│ │ ├── lfu
│ │ ├── lru
│ │ └── purge.go // export distributed kv object
│ ├── consistenthash // consistent hash algorithm for load balance
│ ├── group.go
│ ├── groupcache.go // group cache imp.
│ ├── grpc_fetcher.go // grpc proxy
│ ├── grpc_picker.go // grpc peer selector
│ ├── http_fetcher.go // http proxy
│ ├── http_helper.go // http api server and http server start helper
│ ├── http_picker.go // http peer selector
│ ├── interface.go // grpc peer selector and grpc proxy abstract
│ └── singleflight // single flight concurrent access control
├── main.go // grpc server default imp. equal to cmd/grpc/main.go
├── script // grpc and http service test
│ ├── test
│ │ ├── grpc
│ │ └── http
│ ├── test.md // test step
│ ├── test0.sh
│ ├── test1.sh
│ ├── test2.sh
│ └── test3.sh
└── utils
├── logger
├── shutdown // goroutine gracefully_shutdown
├── trace
└── validate // ip address validation
-
etcd 服务("127.0.0.1:2379")
-
mysql 服务("127.0.0.1:3306")
-
goreman (etcd 集群)
internal/middleware/etcd/cluster
中有使用说明- 本项目默认依赖了 etcd 集群,需要先关闭本地的 etcd 服务,然后使用 goreman 同时启动三个 etcd 服务进行统一管理
- 如果不想依赖 etcd 集群,那么在 config/config.yml 中将 22379 和 32379 删除然后启动本地 etcd 服务即可
-
gorm(数据库)
-
grpc(rpc 通信协议)
-
logrus(日志管理)
-
viper(配置管理)
-
protobuf(序列化)
default.mp4
-
使用 goreman 启动 etcd 集群(3 个 etcd 节点),实现高可用的、强一致性的外部存储中心
-
启动多个 grpc service 服务节点,可以在秒级获取到节点信息变化,快速收敛
-
启动多个 grpc client 客户端,不停地向 grpc 服务集群发送查询请求(其中特意添加了不存在的 key)
-
每个节点刚启动时并没有缓存数据,都需要跑到后端数据库进行查询,实际上是对缓存进行预热
default.mp4
-
某些节点的缓存预热完毕后,可以直接从缓存中返回结果,因此处理速度肉眼可见的变快了
-
发送终止信号给其中两个服务节点,正常工作节点不受影响,重新启动节点在加入后继续提供服务(后台会自动重建网络拓扑)
-
所有节点全部关闭,客户端重试机制生效,最大重试次数为 3 次(可配置),分别退避 1s、2s、4s,在重试期间,只有有一个节点完成重启可以立即返回结果
-
节点重启后,重新回到之前正常状态,节点间负责的 key 关系不会发生变化(hash 环上虚拟节点固定),因此不会导致大量缓存失效
default.mp4
动态获取速度可自定义配置,因为一般情况下网络拓扑相对比较稳定,没有必要后台启动一个长轮询任务一直监听不太可能发生的事件,这对于系统性能是一种浪费。
- 自动检测服务节点信息变化,动态增删节点(节点变化时,动态重构哈希环,对于系统的请求分发十分重要)✅
-
实现思路一:监听 server 启停信号,使用
endpoint manager
管理 ✅ -
实现思路二:使用 etcd 官方 WatchChan() 提供的服务订阅发布机制)✅
-
添加缓存命中率指标(动态调整缓存容量)todo
-
负载均衡策略优化 ✅
-
添加
arc
算法 todo -
LRU2
算法升级(高低水位) todo
-
增加请求限流(令牌桶算法) todo
-
增加对不存在 key 的特殊处理 ✅
-
实现缓存和数据库的一致性(增加消息队列异步处理)(也可以通过缓存淘汰时的回调函数实现) todo(重点)
...