├── .idea ├── .gitignore ├── Rcache.iml ├── modules.xml └── vcs.xml ├── README.md ├── Rcache.dump ├── caches ├── cache.go ├── cache_test.go ├── dump.go ├── options.go ├── segment.go ├── status.go └── value.go ├── client ├── async_client.go ├── async_client_test.go └── models.go ├── go.mod ├── go.sum ├── gossip-demo └── server.go ├── helpers └── byte.go ├── main.go ├── redis_test.go ├── servers ├── http.go ├── node.go ├── options.go ├── server.go ├── tcp.go └── tcp_client.go ├── test └── performance_test.go └── vex ├── client.go ├── protocol.go ├── request.go ├── response.go └── server.go /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/Rcache.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 基于自定义网络协议实现分布式kv存储 2 | 3 | ### 功能特征 4 | 5 | - 加入一致性哈希,集群每个节点负责独立的数据 6 | 7 | - 使用分段锁机制提高并发速度 8 | 9 | - 提供 Get/Set/Delete/Status 几种调用接口 10 | 11 | - 提供 HTTP / TCP 两种调用服务 12 | 13 | - 使用httprouter提供HTTP的调用服务 14 | 15 | - 使用自定义网络协议提供TCP的调用服务 16 | 17 | - 支持获取缓存信息,比如 key 和 value 的占用空间 18 | 19 | - 引入内存写满保护,使用 TTL 和 LRU 两种算法进行过期 20 | 21 | - 引入 GC 机制,随机淘汰过期数据 22 | 23 | - 基于内存快照实现持久化功能 24 | 25 | - 使用基于Gossip协议的开源项目memberlist进行分布式通信 26 | 27 | 28 | 29 | ### 性能测试 30 | 31 | `测试环境` **阿里云ESC服务器2核心2GB** 32 | 33 | `测试数据量`**10000数据** 34 | 35 | | 服务类型 | 写入速度 | 读取速度 | 36 | | -------- | -------- | --------- | 37 | | `tcp` | `1313ms` | `923ms` | 38 | | `http` | `3952ms` | `10950ms` | 39 | 40 | ### 自定义协议 41 | 42 | - 请求:版本 命令 参数个数 参数长度 参数内容 43 | - 响应:版本 答复含义 数据长度 数据内容 44 | 45 | ``` 46 | 请求: 47 | version command argsLength {argLength arg} 48 | 1byte 1byte 4byte 4byte unknown 49 | 50 | 响应: 51 | version reply bodyLength {body} 52 | 1byte 1byte 4byte unknown 53 | ``` 54 | 55 | 56 | 57 | ### 使用服务 58 | 59 | `HTTP` 60 | 61 | ```go 62 | go run main.go -serverType http -address 127.0.0.1 63 | ``` 64 | 65 | `HTTP集群中加入机器` 66 | 67 | ```go 68 | go run main.go -serverType http -address 127.0.0.2 -cluster 127.0.0.1 69 | ``` 70 | 71 | `TCP` 72 | 73 | ```go 74 | go run main.go -address 127.0.0.1 75 | ``` 76 | 77 | `TCP集群中加入机器` 78 | 79 | ```go 80 | go run main.go -address 127.0.0.2 -cluster 127.0.0.1 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /Rcache.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoraocoding/Rcache/e38e07f918a083b7a81c85cced3412af94aec070/Rcache.dump -------------------------------------------------------------------------------- /caches/cache.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | // Cache 是代表缓存的结构体。 10 | type Cache struct { 11 | 12 | // segmentSize 是 segment 的数量,这个数量越多,理论上并发的性能就越好。 13 | segmentSize int 14 | 15 | // segments 存储着所有的 segment 实例。 16 | segments []*segment 17 | 18 | // options 是缓存配置。 19 | options *Options 20 | 21 | // dumping 标识当前缓存是否处于持久化状态。1 表示处于持久化状态。 22 | // 因为现在的 cache 是没有全局锁的,而持久化需要记录下当前的状态,不允许有更新,所以使用一个变量记录着, 23 | // 如果处于持久化状态,就让所有更新操作进入自旋状态,等待持久化完成再进行。 24 | dumping int32 25 | } 26 | 27 | // NewCache 返回一个默认配置的缓存实例。 28 | func NewCache() *Cache { 29 | return NewCacheWith(DefaultOptions()) 30 | } 31 | 32 | // NewCacheWith 返回一个使用 options 初始化过的缓存实例 33 | func NewCacheWith(options Options) *Cache { 34 | // 尝试从持久化文件中恢复 35 | if cache, ok := recoverFromDumpFile(options.DumpFile); ok { 36 | return cache 37 | } 38 | return &Cache{ 39 | segmentSize: options.SegmentSize, 40 | 41 | // 初始化所有的 segment 42 | segments: newSegments(&options), 43 | options: &options, 44 | dumping: 0, 45 | } 46 | } 47 | 48 | // recoverFromDumpFile 从持久化文件中恢复缓存。 49 | func recoverFromDumpFile(dumpFile string) (*Cache, bool) { 50 | cache, err := newEmptyDump().from(dumpFile) 51 | if err != nil { 52 | return nil, false 53 | } 54 | return cache, true 55 | } 56 | 57 | // newSegments 返回初始化好的 segment 实例列表。 58 | func newSegments(options *Options) []*segment { 59 | // 根据配置的数量生成 segment 60 | segments := make([]*segment, options.SegmentSize) 61 | for i := 0; i < options.SegmentSize; i++ { 62 | segments[i] = newSegment(options) 63 | } 64 | return segments 65 | } 66 | 67 | // index 是选择 segment 的“特殊算法”。 68 | // 这里参考了 Java 中的哈希生成逻辑,尽可能避免重复。不用去纠结为什么这么写,因为没有唯一的写法。 69 | // 为了能使用到哈希值的全部数据,这里使用高位和低位进行异或操作。 70 | func index(key string) int { 71 | index := 0 72 | keyBytes := []byte(key) 73 | for _, b := range keyBytes { 74 | index = 31*index + int(b&0xff) 75 | } 76 | return index ^ (index >> 16) 77 | } 78 | 79 | // segmentOf 返回 key 对应的 segment。 80 | // 使用 index 生成的哈希值去获取 segment,这里使用 & 运算也是 Java 中的奇淫技巧。 81 | func (c *Cache) segmentOf(key string) *segment { 82 | return c.segments[index(key)&(c.segmentSize-1)] 83 | } 84 | 85 | // Get 返回指定 key 的数据。 86 | func (c *Cache) Get(key string) ([]byte, bool) { 87 | // 这边会等待持久化完成 88 | c.waitForDumping() 89 | return c.segmentOf(key).get(key) 90 | } 91 | 92 | // Set 添加指定的数据到缓存中。 93 | func (c *Cache) Set(key string, value []byte) error { 94 | return c.SetWithTTL(key, value, NeverDie) 95 | } 96 | 97 | // SetWithTTL 添加指定的数据到缓存中,并设置相应的有效期。 98 | func (c *Cache) SetWithTTL(key string, value []byte, ttl int64) error { 99 | // 这边会等待持久化完成 100 | c.waitForDumping() 101 | return c.segmentOf(key).set(key, value, ttl) 102 | } 103 | 104 | // Delete 从缓存中删除指定 key 的数据。 105 | func (c *Cache) Delete(key string) error { 106 | // 这边会等待持久化完成 107 | c.waitForDumping() 108 | c.segmentOf(key).delete(key) 109 | return nil 110 | } 111 | 112 | // Status 返回缓存当前的情况。 113 | func (c *Cache) Status() Status { 114 | result := NewStatus() 115 | for _, segment := range c.segments { 116 | status := segment.status() 117 | result.Count += status.Count 118 | result.KeySize += status.KeySize 119 | result.ValueSize += status.ValueSize 120 | } 121 | return *result 122 | } 123 | 124 | // gc 会清理缓存中过期的数据。 125 | func (c *Cache) gc() { 126 | // 这边会等待持久化完成 127 | c.waitForDumping() 128 | wg := &sync.WaitGroup{} 129 | for _, seg := range c.segments { 130 | wg.Add(1) 131 | go func(s *segment) { 132 | defer wg.Done() 133 | s.gc() 134 | }(seg) 135 | } 136 | wg.Wait() 137 | } 138 | 139 | // AutoGc 会开启一个异步任务去定时清理过期的数据。 140 | func (c *Cache) AutoGc() { 141 | go func() { 142 | ticker := time.NewTicker(time.Duration(c.options.GcDuration) * time.Minute) 143 | for { 144 | select { 145 | case <-ticker.C: 146 | c.gc() 147 | } 148 | } 149 | }() 150 | } 151 | 152 | // dump 会将缓存数据持久化到文件中。 153 | func (c *Cache) dump() error { 154 | // 这边使用 atomic 包中的原子操作完成状态的切换 155 | atomic.StoreInt32(&c.dumping, 1) 156 | defer atomic.StoreInt32(&c.dumping, 0) 157 | return newDump(c).to(c.options.DumpFile) 158 | } 159 | 160 | // AutoDump 会开启一个异步任务去定时持久化缓存数据。 161 | func (c *Cache) AutoDump() { 162 | go func() { 163 | ticker := time.NewTicker(time.Duration(c.options.DumpDuration) * time.Minute) 164 | for { 165 | select { 166 | case <-ticker.C: 167 | c.dump() 168 | } 169 | } 170 | }() 171 | } 172 | 173 | // waitForDumping 会等待持久化完成才返回。 174 | func (c *Cache) waitForDumping() { 175 | for atomic.LoadInt32(&c.dumping) != 0 { 176 | // 每次循环都会等待一定的时间,如果不睡眠,会导致 CPU 空转消耗资源 177 | time.Sleep(time.Duration(c.options.CasSleepTime) * time.Microsecond) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /caches/cache_test.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | const ( 11 | // concurrency 是测试的并发度。 12 | concurrency = 100000 13 | ) 14 | 15 | // testTask 是一个包装器,把 task 包装成 testTask. 16 | func testTask(task func(no int)) string { 17 | 18 | beginTime := time.Now() 19 | wg := &sync.WaitGroup{} 20 | for i := 0; i < concurrency; i++ { 21 | wg.Add(1) 22 | go func(no int) { 23 | defer wg.Done() 24 | task(no) 25 | }(i) 26 | } 27 | wg.Wait() 28 | return time.Now().Sub(beginTime).String() 29 | } 30 | 31 | // go test -v -run=^TestCacheSetGet$ 32 | func TestCacheSetGet(t *testing.T) { 33 | 34 | cache := NewCache() 35 | 36 | writeTime := testTask(func(no int) { 37 | data := strconv.Itoa(no) 38 | cache.Set(data, []byte(data)) 39 | }) 40 | 41 | t.Logf("写入消耗时间为 %s。", writeTime) 42 | 43 | time.Sleep(3 * time.Second) 44 | 45 | readTime := testTask(func(no int) { 46 | data := strconv.Itoa(no) 47 | cache.Get(data) 48 | }) 49 | 50 | t.Logf("读取消耗时间为 %s。", readTime) 51 | } 52 | -------------------------------------------------------------------------------- /caches/dump.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | import ( 4 | "encoding/gob" 5 | "os" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type dump struct { 11 | // SegmentSize 是 segment 的数量。 12 | SegmentSize int 13 | 14 | // Segments 存储着所有的 segment 实例。 15 | Segments []*segment 16 | 17 | // Options 是缓存的选项配置。 18 | Options *Options 19 | } 20 | 21 | // newEmptyDump 返回一个空的持久化实例。 22 | func newEmptyDump() *dump { 23 | return &dump{} 24 | } 25 | 26 | // newDump 返回一个从缓存实例初始化过来的持久化实例。 27 | func newDump(c *Cache) *dump { 28 | return &dump{ 29 | SegmentSize: c.segmentSize, 30 | Segments: c.segments, 31 | Options: c.options, 32 | } 33 | } 34 | 35 | // nowSuffix 返回当前时间,类似于 20060102150405。 36 | func nowSuffix() string { 37 | return "." + time.Now().Format("20060102150405") 38 | } 39 | 40 | // to 会将 dump 实例持久化到文件中。 41 | func (d *dump) to(dumpFile string) error { 42 | 43 | newDumpFile := dumpFile + nowSuffix() 44 | file, err := os.OpenFile(newDumpFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) 45 | if err != nil { 46 | return err 47 | } 48 | defer file.Close() 49 | 50 | err = gob.NewEncoder(file).Encode(d) 51 | if err != nil { 52 | file.Close() 53 | os.Remove(newDumpFile) 54 | return err 55 | } 56 | 57 | os.Remove(dumpFile) 58 | file.Close() 59 | return os.Rename(newDumpFile, dumpFile) 60 | } 61 | 62 | // from 返回一个从持久化文件中恢复的缓存实例。 63 | func (d *dump) from(dumpFile string) (*Cache, error) { 64 | 65 | file, err := os.Open(dumpFile) 66 | if err != nil { 67 | return nil, err 68 | } 69 | defer file.Close() 70 | 71 | if err = gob.NewDecoder(file).Decode(d); err != nil { 72 | return nil, err 73 | } 74 | 75 | // 恢复出 segment 之后需要为每一个 segment 的未导出字段进行初始化 76 | for _, segment := range d.Segments { 77 | segment.options = d.Options 78 | segment.lock = &sync.RWMutex{} 79 | } 80 | 81 | return &Cache{ 82 | segmentSize: d.SegmentSize, 83 | segments: d.Segments, 84 | options: d.Options, 85 | dumping: 0, 86 | }, nil 87 | } 88 | -------------------------------------------------------------------------------- /caches/options.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | // Options 是选项配置结构体。 4 | type Options struct { 5 | 6 | // MaxEntrySize 指键值对最大容量。 7 | MaxEntrySize int 8 | 9 | // MaxGcCount 指每个 segment 要清理的过期数据个数。 10 | MaxGcCount int 11 | 12 | // GcDuration 指多久执行一次 Gc 工作。 13 | // 单位是分钟。 14 | GcDuration int 15 | 16 | // DumpFile 指持久化文件的路径。 17 | DumpFile string 18 | 19 | // DumpDuration 指多久执行一次持久化。 20 | // 单位是分钟。 21 | DumpDuration int 22 | 23 | // MapSizeOfSegment 指 segment 中 map 的初始化大小。 24 | MapSizeOfSegment int 25 | 26 | // SegmentSize 指缓存中有多少个 segment。 27 | SegmentSize int 28 | 29 | // CasSleepTime 指每一次 CAS 自旋需要等待的时间。 30 | // 单位是微秒。 31 | CasSleepTime int 32 | } 33 | 34 | // DefaultOptions 返回默认的选项配置。 35 | func DefaultOptions() Options { 36 | return Options{ 37 | MaxEntrySize: 4, // 4 GB 38 | MaxGcCount: 10, 39 | GcDuration: 60, // 1 hour 40 | DumpFile: "kafo.dump", 41 | DumpDuration: 30, // 30 minutes 42 | MapSizeOfSegment: 256, 43 | SegmentSize: 1024, 44 | CasSleepTime: 1000, // 1 ms 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /caches/segment.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | type segment struct { 9 | 10 | // 存储这个数据块的数据。 11 | Data map[string]*value 12 | 13 | // 记录着这个数据块的情况。 14 | Status *Status 15 | 16 | // 是缓存的选项设置。 17 | options *Options 18 | 19 | lock *sync.RWMutex 20 | } 21 | 22 | func newSegment(options *Options) *segment { 23 | return &segment{ 24 | // 初始化 map 的时候给出初始大小,可以避免大量扩容带来的性能损耗 25 | Data: make(map[string]*value, options.MapSizeOfSegment), 26 | Status: NewStatus(), 27 | options: options, 28 | lock: &sync.RWMutex{}, 29 | } 30 | } 31 | 32 | // get 返回指定 key 的数据。 33 | // 这个方法和原来 cache 的方法一样,只是移动到 segment 这里。 34 | func (s *segment) get(key string) ([]byte, bool) { 35 | s.lock.RLock() 36 | defer s.lock.RUnlock() 37 | value, ok := s.Data[key] 38 | if !ok { 39 | return nil, false 40 | } 41 | 42 | if !value.alive() { 43 | s.lock.RUnlock() 44 | s.delete(key) 45 | s.lock.RLock() 46 | return nil, false 47 | } 48 | return value.visit(), true 49 | } 50 | 51 | // set 添加一个数据进 segment。 52 | func (s *segment) set(key string, value []byte, ttl int64) error { 53 | s.lock.Lock() 54 | defer s.lock.Unlock() 55 | if oldValue, ok := s.Data[key]; ok { 56 | s.Status.subEntry(key, oldValue.Data) 57 | } 58 | 59 | if !s.checkEntrySize(key, value) { 60 | if oldValue, ok := s.Data[key]; ok { 61 | s.Status.addEntry(key, oldValue.Data) 62 | } 63 | return errors.New("the entry size will exceed if you set this entry") 64 | } 65 | 66 | s.Status.addEntry(key, value) 67 | s.Data[key] = newValue(value, ttl) 68 | return nil 69 | } 70 | 71 | // delete 从 segment 中删除指定 key 的数据。 72 | func (s *segment) delete(key string) { 73 | s.lock.Lock() 74 | defer s.lock.Unlock() 75 | if oldValue, ok := s.Data[key]; ok { 76 | s.Status.subEntry(key, oldValue.Data) 77 | delete(s.Data, key) 78 | } 79 | } 80 | 81 | // Status 返回这个 segment 的情况。 82 | func (s *segment) status() Status { 83 | s.lock.RLock() 84 | defer s.lock.RUnlock() 85 | return *s.Status 86 | } 87 | 88 | // checkEntrySize 会判断数据容量是否已经达到了设定的上限。 89 | // 因为这个配置是针对整个缓存的,而这边判断大小是针对单个 segment 的,所以需要算出单个 segment 的上限来判断。 90 | func (s *segment) checkEntrySize(newKey string, newValue []byte) bool { 91 | return s.Status.entrySize()+int64(len(newKey))+int64(len(newValue)) <= int64((s.options.MaxEntrySize*1024*1024)/s.options.SegmentSize) 92 | } 93 | 94 | func (s *segment) gc() { 95 | s.lock.Lock() 96 | defer s.lock.Unlock() 97 | count := 0 98 | for key, value := range s.Data { 99 | if !value.alive() { 100 | s.Status.subEntry(key, value.Data) 101 | delete(s.Data, key) 102 | count++ 103 | if count >= s.options.MaxGcCount { 104 | break 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /caches/status.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | type Status struct { 4 | 5 | // Count 记录着缓存中的数据个数。 6 | Count int `json:"count"` 7 | 8 | // KeySize 记录着 key 占用的空间大小。 9 | KeySize int64 `json:"keySize"` 10 | 11 | // ValueSize 记录着 value 占用的空间大小。 12 | ValueSize int64 `json:"valueSize"` 13 | } 14 | 15 | // newStatus 返回一个缓存信息对象指针。 16 | func NewStatus() *Status { 17 | return &Status{ 18 | Count: 0, 19 | KeySize: 0, 20 | ValueSize: 0, 21 | } 22 | } 23 | 24 | // addEntry 可以将 key 和 value 的信息记录起来。 25 | func (s *Status) addEntry(key string, value []byte) { 26 | 27 | s.Count++ 28 | s.KeySize += int64(len(key)) 29 | s.ValueSize += int64(len(value)) 30 | } 31 | 32 | // subEntry 可以将 key 和 value 的信息从 Status 中减去。 33 | func (s *Status) subEntry(key string, value []byte) { 34 | // 每减少一个键值对,count 就需要减 1,key 和 value 占用的空间也需要减去相应的大小。 35 | s.Count-- 36 | s.KeySize -= int64(len(key)) 37 | s.ValueSize -= int64(len(value)) 38 | } 39 | 40 | // entrySize 返回键值对占用的总大小。 41 | func (s *Status) entrySize() int64 { 42 | return s.KeySize + s.ValueSize 43 | } 44 | -------------------------------------------------------------------------------- /caches/value.go: -------------------------------------------------------------------------------- 1 | package caches 2 | 3 | import ( 4 | "Rcache/helpers" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | const ( 10 | // NeverDie 是一个常量,我们设计的时候规定如果 ttl 为 0,那就是永不过期,相当于灵丹妙药。 11 | NeverDie = 0 12 | ) 13 | 14 | // value 是一个包装了数据的结构体。 15 | type value struct { 16 | 17 | // data 存储着真正的数据。 18 | Data []byte 19 | 20 | // ttl 代表这个数据的寿命。 21 | // 这个值的单位是秒。 22 | Ttl int64 23 | 24 | // ctime 代表这个数据的创建时间。 25 | Ctime int64 26 | } 27 | 28 | func newValue(data []byte, ttl int64) *value { 29 | return &value{ 30 | // 注意修改字段为大写开头 31 | Data: helpers.Copy(data), 32 | Ttl: ttl, 33 | Ctime: time.Now().Unix(), 34 | } 35 | } 36 | 37 | func (v *value) alive() bool { 38 | return v.Ttl == NeverDie || time.Now().Unix()-v.Ctime < v.Ttl 39 | } 40 | 41 | func (v *value) visit() []byte { 42 | // 注意修改字段为大写开头 43 | atomic.SwapInt64(&v.Ctime, time.Now().Unix()) 44 | return v.Data 45 | } -------------------------------------------------------------------------------- /client/async_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "Rcache/vex" 5 | "encoding/binary" 6 | ) 7 | 8 | const ( 9 | // getCommand 是 get 的命令。 10 | getCommand = byte(1) 11 | 12 | // setCommand 是 set 的命令。 13 | setCommand = byte(2) 14 | 15 | // deleteCommand 是 delete 的命令。 16 | deleteCommand = byte(3) 17 | 18 | // statusCommand 是 status 的命令。 19 | statusCommand = byte(4) 20 | ) 21 | 22 | // AsyncClient 是异步客户端。 23 | type AsyncClient struct { 24 | 25 | // client 用于内部执行命令。 26 | client *vex.Client 27 | 28 | // requestChan 用于接收请求。 29 | requestChan chan *request 30 | } 31 | 32 | // NewAsyncClient 会创建一个异步客户端并返回。 33 | func NewAsyncClient(address string) (*AsyncClient, error) { 34 | 35 | client, err := vex.NewClient("tcp", address) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | c := &AsyncClient{ 41 | client: client, 42 | requestChan: make(chan *request, 163840), 43 | } 44 | c.handleRequests() 45 | return c, nil 46 | } 47 | 48 | // handleRequests 会开启一个 goroutine 去处理请求。 49 | func (ac *AsyncClient) handleRequests() { 50 | 51 | go func() { 52 | for request := range ac.requestChan { 53 | body, err := ac.client.Do(request.command, request.args) 54 | request.resultChan <- &Response{ 55 | Body: body, 56 | Err: err, 57 | } 58 | } 59 | }() 60 | } 61 | 62 | // do 使用异步的方式执行命令。 63 | func (ac *AsyncClient) do(command byte, args [][]byte) <-chan *Response { 64 | 65 | // 设置一个缓冲位置放响应 66 | resultChan := make(chan *Response, 1) 67 | ac.requestChan <- &request{ 68 | command: command, 69 | args: args, 70 | resultChan: resultChan, 71 | } 72 | return resultChan 73 | } 74 | 75 | // Get 用于执行 get 命令。 76 | func (ac *AsyncClient) Get(key string) <-chan *Response { 77 | return ac.do(getCommand, [][]byte{[]byte(key)}) 78 | } 79 | 80 | // Set 用于执行 set 命令。 81 | func (ac *AsyncClient) Set(key string, value []byte, ttl int64) <-chan *Response { 82 | ttlBytes := make([]byte, 8) 83 | binary.BigEndian.PutUint64(ttlBytes, uint64(ttl)) 84 | return ac.do(setCommand, [][]byte{ 85 | ttlBytes, []byte(key), value, 86 | }) 87 | } 88 | 89 | // Delete 用于执行 delete 命令。 90 | func (ac *AsyncClient) Delete(key string) <-chan *Response { 91 | return ac.do(deleteCommand, [][]byte{[]byte(key)}) 92 | } 93 | 94 | // Status 用于执行 status 命令。 95 | func (ac *AsyncClient) Status() <-chan *Response { 96 | return ac.do(statusCommand, nil) 97 | } 98 | 99 | // Close 关闭客户端并释放资源。 100 | func (ac *AsyncClient) Close() error { 101 | close(ac.requestChan) 102 | return ac.client.Close() 103 | } 104 | -------------------------------------------------------------------------------- /client/async_client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | const ( 10 | // keySize is the key size of test. 11 | keySize = 10000 12 | ) 13 | 14 | // testTask is a wrapper wraps task to testTask. 15 | func testTask(task func(no int)) string { 16 | beginTime := time.Now() 17 | for i := 0; i < keySize; i++ { 18 | task(i) 19 | } 20 | return time.Now().Sub(beginTime).String() 21 | } 22 | 23 | // go test -v -count=1 -run=^TestAsyncClientPerformance$ 24 | func TestAsyncClientPerformance(t *testing.T) { 25 | 26 | client, err := NewAsyncClient(":5837") 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | defer client.Close() 31 | 32 | writeTime := testTask(func(no int) { 33 | data := strconv.Itoa(no) 34 | client.Set(data, []byte(data), 0) 35 | }) 36 | 37 | t.Logf("写入消耗时间为 %s!", writeTime) 38 | 39 | time.Sleep(3 * time.Second) 40 | 41 | readTime := testTask(func(no int) { 42 | data := strconv.Itoa(no) 43 | client.Get(data) 44 | }) 45 | 46 | t.Logf("读取消耗时间为 %s!", readTime) 47 | 48 | time.Sleep(time.Second) 49 | } 50 | -------------------------------------------------------------------------------- /client/models.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "encoding/json" 4 | 5 | // Status 是缓存状态结构体。 6 | type Status struct { 7 | 8 | // Count 是键值对的数量。 9 | Count int `json:"count"` 10 | 11 | // KeySize 是 key 占用的大小 12 | KeySize int64 `json:"keySize"` 13 | 14 | // ValueSize 是 value 占用的大小。 15 | ValueSize int64 `json:"valueSize"` 16 | } 17 | 18 | // request 是请求结构体。 19 | type request struct { 20 | 21 | // command 是执行的命令。 22 | command byte 23 | 24 | // args 是执行的参数。 25 | args [][]byte 26 | 27 | // resultChan 是用于接收结果的管道。 28 | resultChan chan *Response 29 | } 30 | 31 | // Response 是响应结构体。 32 | type Response struct { 33 | 34 | // Body 是响应体。 35 | Body []byte 36 | 37 | // Err 是响应的错误。 38 | Err error 39 | } 40 | 41 | // ToStatus 会返回一个状态实例和错误。 42 | func (r *Response) ToStatus() (*Status, error) { 43 | if r.Err != nil { 44 | return nil, r.Err 45 | } 46 | status := &Status{} 47 | return status, json.Unmarshal(r.Body, status) 48 | } 49 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module Rcache 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gomodule/redigo v1.8.8 7 | github.com/hashicorp/memberlist v0.3.1 8 | github.com/julienschmidt/httprouter v1.3.0 9 | stathat.com/c/consistent v1.0.0 10 | 11 | ) 12 | 13 | require ( 14 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 15 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect 16 | github.com/hashicorp/errwrap v1.0.0 // indirect 17 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 18 | github.com/hashicorp/go-msgpack v0.5.3 // indirect 19 | github.com/hashicorp/go-multierror v1.0.0 // indirect 20 | github.com/hashicorp/go-sockaddr v1.0.0 // indirect 21 | github.com/hashicorp/golang-lru v0.5.0 // indirect 22 | github.com/miekg/dns v1.1.26 // indirect 23 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect 24 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 // indirect 25 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect 26 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= 2 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= 7 | github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= 8 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= 9 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 10 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 11 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 12 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= 13 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 14 | github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= 15 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 16 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 17 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 18 | github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= 19 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 20 | github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= 21 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 22 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 23 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 24 | github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM= 25 | github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= 26 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 27 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 28 | github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= 29 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= 30 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= 31 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 32 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 34 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= 35 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 36 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 37 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 38 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 39 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 40 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 41 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= 42 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= 43 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 44 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 45 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= 46 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 47 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 48 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 49 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 50 | golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= 52 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 54 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 55 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 56 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 57 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 60 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 61 | stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c= 62 | stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0= 63 | -------------------------------------------------------------------------------- /gossip-demo/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "net/http" 7 | "github.com/hashicorp/memberlist" 8 | ) 9 | 10 | // address 是当前服务器绑定的地址 11 | // existedCluster 是一个现有的机器群 12 | func RunServer(address string, existedCluster string) { 13 | 14 | // 创建一个配置,并将绑定的地址都配置上 15 | conf := memberlist.DefaultLANConfig() 16 | conf.Name = address 17 | conf.BindAddr = address 18 | 19 | // 创建一个成员实例 20 | cluster, err := memberlist.Create(conf) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | // 如果没有指定机器群,当前服务器就是为新的机器群 26 | if existedCluster == "" { 27 | existedCluster = address 28 | } 29 | 30 | // 加入指定的机器群 31 | _, err = cluster.Join([]string{existedCluster}) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | // 提供一个 http 服务,用于查询当前服务器的机器群有哪些成员 37 | http.HandleFunc("/servers", func(writer http.ResponseWriter, request *http.Request) { 38 | 39 | // 查询成员,并取出地址存到一个切片中,响应 Json 编码的数据 40 | members := cluster.Members() 41 | hosts := make([]string, len(members)) 42 | for i, node := range members { 43 | hosts[i] = node.Addr.String() 44 | } 45 | membersJson, _ := json.Marshal(hosts) 46 | writer.Write(membersJson) 47 | }) 48 | http.ListenAndServe(address+":8080", nil) 49 | } 50 | 51 | func main() { 52 | 53 | // 指定当前服务器的地址,默认是 127.0.0.1 54 | ip := flag.String("ip", "127.0.0.1", "The ip of this server") 55 | 56 | // 指定要加入的机器群 57 | cluser := flag.String("cluster", "", "The existed server of a cluster") 58 | flag.Parse() 59 | 60 | // 启动服务器 61 | RunServer(*ip, *cluser) 62 | } 63 | -------------------------------------------------------------------------------- /helpers/byte.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import "strconv" 4 | 5 | func Copy(src []byte) []byte { 6 | dst := make([]byte, len(src)) 7 | copy(dst, src) 8 | return dst 9 | } 10 | 11 | func JoinAddressAndPort(address string, port int) string { 12 | return address + ":" + strconv.Itoa(port) 13 | } 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "Rcache/caches" 5 | "Rcache/servers" 6 | "flag" 7 | "log" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | 13 | // 准备服务器的选项配置 14 | serverOptions := servers.DefaultOptions() 15 | flag.StringVar(&serverOptions.Address, "address", serverOptions.Address, "The address used to listen, such as 127.0.0.1.") 16 | flag.IntVar(&serverOptions.Port, "port", serverOptions.Port, "The port used to listen, such as 5837.") 17 | flag.StringVar(&serverOptions.ServerType, "serverType", serverOptions.ServerType, "The type of server (http, tcp).") 18 | flag.IntVar(&serverOptions.VirtualNodeCount, "virtualNodeCount", serverOptions.VirtualNodeCount, "The number of virtual nodes in consistent hash.") 19 | flag.IntVar(&serverOptions.UpdateCircleDuration, "updateCircleDuration", serverOptions.UpdateCircleDuration, "The duration between two circle updating operations. The unit is second.") 20 | cluster := flag.String("cluster", "", "The cluster of servers. One node in cluster will be ok.") 21 | 22 | // 准备缓存的选项配置 23 | cacheOptions := caches.DefaultOptions() 24 | flag.IntVar(&cacheOptions.MaxEntrySize, "maxEntrySize", cacheOptions.MaxEntrySize, "The max memory size that entries can use. The unit is GB.") 25 | flag.IntVar(&cacheOptions.MaxGcCount, "maxGcCount", cacheOptions.MaxGcCount, "The max count of entries that gc will clean.") 26 | flag.IntVar(&cacheOptions.GcDuration, "gcDuration", cacheOptions.GcDuration, "The duration between two gc tasks. The unit is Minute.") 27 | flag.StringVar(&cacheOptions.DumpFile, "dumpFile", cacheOptions.DumpFile, "The file used to dump the cache.") 28 | flag.IntVar(&cacheOptions.DumpDuration, "dumpDuration", cacheOptions.DumpDuration, "The duration between two dump tasks. The unit is Minute.") 29 | flag.IntVar(&cacheOptions.MapSizeOfSegment, "mapSizeOfSegment", cacheOptions.MapSizeOfSegment, "The map size of segment.") 30 | flag.IntVar(&cacheOptions.SegmentSize, "segmentSize", cacheOptions.SegmentSize, "The number of segment in a cache. This value should be the pow of 2 for precision.") 31 | flag.IntVar(&cacheOptions.CasSleepTime, "casSleepTime", cacheOptions.CasSleepTime, "The time of sleep in one cas step. The unit is Microsecond.") 32 | flag.Parse() 33 | 34 | // 从 flag 中解析出集群信息 35 | serverOptions.Cluster = nodesInCluster(*cluster) 36 | 37 | // 使用选项配置初始化缓存 38 | cache := caches.NewCacheWith(cacheOptions) 39 | cache.AutoGc() 40 | cache.AutoDump() 41 | 42 | // 使用选项配置初始化服务器 43 | server, err := servers.NewServer(cache, serverOptions) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | log.Printf("Using server options %+v\n", serverOptions) 49 | log.Printf("Using cache options %+v\n", cacheOptions) 50 | log.Printf("Rcache is running on %s at %s:%d.", serverOptions.ServerType, serverOptions.Address, serverOptions.Port) 51 | err = server.Run() 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | 57 | // nodesInCluster 使用 "," 分割 cluster 并解析出集群信息。 58 | func nodesInCluster(cluster string) []string { 59 | if cluster == "" { 60 | return nil 61 | } 62 | return strings.Split(cluster, ",") 63 | } 64 | -------------------------------------------------------------------------------- /redis_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | "time" 7 | 8 | "github.com/gomodule/redigo/redis" 9 | ) 10 | 11 | const ( 12 | // keySize 是测试的键值对数量。 13 | keySize = 10000 14 | ) 15 | 16 | // testTask 是一个包装器,包装一个任务为测试任务。 17 | func testTask(task func(no int)) string { 18 | beginTime := time.Now() 19 | for i := 0; i < keySize; i++ { 20 | task(i) 21 | } 22 | return time.Now().Sub(beginTime).String() 23 | } 24 | 25 | // go test -v -count=1 redis_test.go -run=^TestRedis$ 26 | func TestRedis(t *testing.T) { 27 | 28 | conn, err := redis.DialURL("redis://127.0.0.1:6379") 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | defer conn.Close() 33 | 34 | writeTime := testTask(func(no int) { 35 | data := strconv.Itoa(no) 36 | conn.Do("set", data, data) 37 | }) 38 | 39 | t.Logf("写入消耗时间为 %s。", writeTime) 40 | 41 | time.Sleep(3 * time.Second) 42 | 43 | readTime := testTask(func(no int) { 44 | data := strconv.Itoa(no) 45 | conn.Do("get", data) 46 | }) 47 | 48 | t.Logf("读取消耗时间为 %s。", readTime) 49 | } 50 | -------------------------------------------------------------------------------- /servers/http.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | import ( 4 | "Rcache/caches" 5 | "Rcache/helpers" 6 | "encoding/json" 7 | "github.com/julienschmidt/httprouter" 8 | "io/ioutil" 9 | "net/http" 10 | "path" 11 | "strconv" 12 | ) 13 | 14 | // HTTPServer 是提供 http 服务的服务器。 15 | type HTTPServer struct { 16 | // cache 是内部存储用的缓存实例。 17 | cache *caches.Cache 18 | // node 是内部用于记录集群信息的实例。 19 | *node 20 | // options 存储着这个服务器的选项配置。 21 | options *Options 22 | } 23 | 24 | //返回一个HTTP实例 25 | func NewHTTPServer(cache *caches.Cache, options *Options) (*HTTPServer, error) { 26 | 27 | // 创建 node 实例 28 | n, err := newNode(options) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return &HTTPServer{ 34 | node: n, 35 | cache: cache, 36 | options: options, 37 | }, nil 38 | } 39 | 40 | // Run 启动这个 http 服务器。 41 | func (hs *HTTPServer) Run() error { 42 | return http.ListenAndServe(helpers.JoinAddressAndPort(hs.options.Address, hs.options.Port), hs.routerHandler()) 43 | } 44 | 45 | 46 | 47 | // wrapUriWithVersion 会用 API 版本去包装 uri,比如 "v1" 版本的 API 包装 "/cache" 就会变成 "/v1/cache"。 48 | func wrapUriWithVersion(uri string) string { 49 | return path.Join("/", APIVersion, uri) 50 | } 51 | 52 | // routerHandler 返回注册的路由处理器。 53 | func (hs *HTTPServer) routerHandler() http.Handler { 54 | router := httprouter.New() 55 | router.GET(wrapUriWithVersion("/cache/:key"), hs.getHandler) 56 | router.PUT(wrapUriWithVersion("/cache/:key"), hs.setHandler) 57 | router.DELETE(wrapUriWithVersion("/cache/:key"), hs.deleteHandler) 58 | router.GET(wrapUriWithVersion("/status"), hs.statusHandler) 59 | router.GET(wrapUriWithVersion("/nodes"), hs.nodesHandler) 60 | return router 61 | } 62 | 63 | 64 | // getHandler 获取缓存中的数据并返回。 65 | func (hs *HTTPServer) getHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { 66 | 67 | // 使用一致性哈希选择出这个 key 所属的物理节点 68 | key := params.ByName("key") 69 | node, err := hs.selectNode(key) 70 | if err != nil { 71 | writer.WriteHeader(http.StatusInternalServerError) 72 | return 73 | } 74 | 75 | // 判断这个 key 所属的物理节点是否是当前节点,如果不是,需要响应重定向信息给客户端,并告知正确的节点地址 76 | if !hs.isCurrentNode(node) { 77 | writer.Header().Set("Location", node + request.RequestURI) 78 | writer.WriteHeader(http.StatusTemporaryRedirect) 79 | return 80 | } 81 | 82 | // 当前节点处理 83 | value, ok := hs.cache.Get(key) 84 | if !ok { 85 | writer.WriteHeader(http.StatusNotFound) 86 | return 87 | } 88 | writer.Write(value) 89 | } 90 | 91 | // setHandler 添加数据到缓存中。 92 | func (hs *HTTPServer) setHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { 93 | 94 | // 使用一致性哈希选择出这个 key 所属的物理节点 95 | key := params.ByName("key") 96 | node, err := hs.selectNode(key) 97 | if err != nil { 98 | writer.WriteHeader(http.StatusInternalServerError) 99 | return 100 | } 101 | 102 | // 判断这个 key 所属的物理节点是否是当前节点,如果不是,需要响应重定向信息给客户端,并告知正确的节点地址 103 | if !hs.isCurrentNode(node) { 104 | writer.Header().Set("Location", node+request.RequestURI) 105 | writer.WriteHeader(http.StatusTemporaryRedirect) 106 | return 107 | } 108 | 109 | // 当前节点处理 110 | value, err := ioutil.ReadAll(request.Body) 111 | if err != nil { 112 | writer.WriteHeader(http.StatusInternalServerError) 113 | return 114 | } 115 | 116 | // 从请求中获取 ttl 117 | ttl, err := ttlOf(request) 118 | if err != nil { 119 | writer.WriteHeader(http.StatusInternalServerError) 120 | return 121 | } 122 | 123 | // 添加数据,并设置为指定的 ttl 124 | err = hs.cache.SetWithTTL(key, value, ttl) 125 | if err != nil { 126 | // 如果返回了错误,说明触发了写满保护机制,返回 413 错误码,这个错误码表示请求体中的数据太大了 127 | // 同时返回错误信息,加上一个 "Error: " 的前缀,方便识别为错误码 128 | writer.WriteHeader(http.StatusRequestEntityTooLarge) 129 | writer.Write([]byte("Error: " + err.Error())) 130 | return 131 | } 132 | // 成功添加就返回 201 的状态码,其实 200 的状态码也可以,不过 201 的语义更符合,所以就选了这个状态码 133 | writer.WriteHeader(http.StatusCreated) 134 | } 135 | 136 | // ttlOf 从请求中解析 ttl 并返回,如果 error 不为空,说明 ttl 解析出错。 137 | func ttlOf(request *http.Request) (int64, error) { 138 | 139 | // 从请求头中获取 ttl 头部,如果没有设置或者 ttl 为空均按不设置 ttl 处理,也就是不会过期 140 | ttls, ok := request.Header["Ttl"] 141 | if !ok || len(ttls) < 1 { 142 | return caches.NeverDie, nil 143 | } 144 | return strconv.ParseInt(ttls[0], 10, 64) 145 | } 146 | 147 | // deleteHandler 从缓存中删除指定数据。 148 | func (hs *HTTPServer) deleteHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { 149 | 150 | // 使用一致性哈希选择出这个 key 所属的物理节点 151 | key := params.ByName("key") 152 | node, err := hs.selectNode(key) 153 | if err != nil { 154 | writer.WriteHeader(http.StatusInternalServerError) 155 | return 156 | } 157 | 158 | // 判断这个 key 所属的物理节点是否是当前节点,如果不是,需要响应重定向信息给客户端,并告知正确的节点地址 159 | if !hs.isCurrentNode(node) { 160 | writer.Header().Set("Location", node+request.RequestURI) 161 | writer.WriteHeader(http.StatusTemporaryRedirect) 162 | return 163 | } 164 | 165 | // 当前节点处理 166 | err = hs.cache.Delete(key) 167 | if err != nil { 168 | writer.WriteHeader(http.StatusInternalServerError) 169 | return 170 | } 171 | } 172 | 173 | // statusHandler 返回缓存信息。 174 | func (hs *HTTPServer) statusHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { 175 | status, err := json.Marshal(hs.cache.Status()) 176 | if err != nil { 177 | writer.WriteHeader(http.StatusInternalServerError) 178 | return 179 | } 180 | writer.Write(status) 181 | } 182 | 183 | // nodesHandler is handler for fetching the nodes of cluster. 184 | func (hs *HTTPServer) nodesHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) { 185 | nodes, err := json.Marshal(hs.nodes()) 186 | if err != nil { 187 | writer.WriteHeader(http.StatusInternalServerError) 188 | return 189 | } 190 | writer.Write(nodes) 191 | } -------------------------------------------------------------------------------- /servers/node.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | import ( 4 | "Rcache/helpers" 5 | "github.com/hashicorp/memberlist" 6 | "io/ioutil" 7 | "stathat.com/c/consistent" 8 | "time" 9 | ) 10 | 11 | 12 | // node 代表集群中的一个节点,会保存一些和集群相关的数据。 13 | type node struct { 14 | 15 | // options 存储着一些服务器相关的选项。 16 | options *Options 17 | 18 | // address 记录的是当前节点的访问地址,包含 ip 或者主机、端口等信息。 19 | address string 20 | 21 | // circle 是一致性哈希的实例。 22 | circle *consistent.Consistent 23 | 24 | // nodeManager 是节点管理器,用于管理节点。 25 | nodeManager *memberlist.Memberlist 26 | } 27 | 28 | // newNode 创建一个节点实例,并使用 options 去初始化。 29 | func newNode(options *Options) (*node, error) { 30 | 31 | // 如果没有需要加入的集群,则把当前节点当成新集群 32 | if options.Cluster == nil || len(options.Cluster) == 0 { 33 | options.Cluster = []string{options.Address} 34 | } 35 | 36 | // 创建节点管理器,后续所有和集群相关的操作都需要通过这个节点管理器 37 | nodeManager, err := createNodeManager(options) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | // 创建节点 43 | node := &node{ 44 | options: options, 45 | address: helpers.JoinAddressAndPort(options.Address, options.Port), 46 | circle: consistent.New(), 47 | nodeManager: nodeManager, 48 | } 49 | 50 | // 注意这里设置了一致性哈希的虚拟节点数,并开启了自动更新一致性哈希内的物理节点信息 51 | node.circle.NumberOfReplicas = options.VirtualNodeCount 52 | node.autoUpdateCircle() 53 | return node, nil 54 | } 55 | 56 | func createNodeManager(options *Options) (*memberlist.Memberlist, error) { 57 | 58 | // 在默认的 LAN 配置上进行设置 59 | config := memberlist.DefaultLANConfig() 60 | config.Name = helpers.JoinAddressAndPort(options.Address, options.Port) 61 | config.BindAddr = options.Address 62 | config.LogOutput = ioutil.Discard // 禁用日志输出 63 | 64 | // 创建 memberlist 实例 65 | nodeManager, err := memberlist.Create(config) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | // 加入到指定的集群 71 | _, err = nodeManager.Join(options.Cluster) 72 | return nodeManager, err 73 | } 74 | 75 | func (n *node) nodes() []string { 76 | members := n.nodeManager.Members() 77 | nodes := make([]string, len(members)) 78 | for i, member := range members { 79 | nodes[i] = member.Name 80 | } 81 | return nodes 82 | } 83 | 84 | // 根据 name 选择出一个适合的 node。 85 | func (n *node) selectNode(name string) (string, error) { 86 | return n.circle.Get(name) 87 | } 88 | 89 | // 判断 address 是否指当前节点。 90 | func (n *node) isCurrentNode(address string) bool { 91 | return n.address == address 92 | } 93 | 94 | // 更新一致性哈希的信息。 95 | // 一致性哈希的信息来源就是 memberlist 实例。 96 | func (n *node) updateCircle() { 97 | n.circle.Set(n.nodes()) 98 | } 99 | 100 | // autoUpdateCircle 开启一个定时任务去定期更新一致性哈希的信息。 101 | func (n *node) autoUpdateCircle() { 102 | n.updateCircle() 103 | go func() { 104 | ticker := time.NewTicker(time.Duration(n.options.UpdateCircleDuration) * time.Second) 105 | for { 106 | select { 107 | case <-ticker.C: 108 | n.updateCircle() 109 | } 110 | } 111 | }() 112 | } 113 | -------------------------------------------------------------------------------- /servers/options.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | // Options 是服务器的选项配置。 4 | type Options struct { 5 | 6 | // Address 是服务器监听使用的地址。 7 | Address string 8 | 9 | // Port 是服务器监听使用的端口。 10 | Port int 11 | 12 | // ServerType 是服务器的类型。 13 | ServerType string 14 | 15 | // VirtualNodeCount 是指一致性哈希的虚拟节点个数。 16 | VirtualNodeCount int 17 | 18 | // UpdateCircleDuration 是指更新一致性哈希信息的时间间隔。 19 | // 单位是秒。 20 | UpdateCircleDuration int 21 | 22 | // cluster 是指需要加入的集群,只需要集群中一个节点的地址即可。 23 | Cluster []string 24 | } 25 | 26 | func DefaultOptions() Options { 27 | return Options{ 28 | Address: "127.0.0.1", 29 | Port: 5837, 30 | ServerType: "tcp", 31 | VirtualNodeCount: 1024, 32 | UpdateCircleDuration: 3, //这里的单位是秒 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /servers/server.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | import "Rcache/caches" 4 | 5 | const ( 6 | // APIVersion 代表当前服务的版本。 7 | // 因为我们做的服务是提供给外部调用的,而版本的升级可能会带来 API 的改动。 8 | // 我们需要标记当前服务能提供 API 的版本,这样即使后面升级了 API 也不用担心,只要用户调用的版本是正确的,调用就不会出错 9 | APIVersion = "v1" 10 | ) 11 | 12 | // Server 是服务器的抽象接口。 13 | type Server interface { 14 | 15 | // Run 方法会启动这个服务器。 16 | Run() error 17 | } 18 | 19 | 20 | 21 | 22 | func NewServer(cache *caches.Cache, options Options) (Server, error) { 23 | if options.ServerType == "tcp" { 24 | return NewTCPServer(cache, &options) 25 | } 26 | return NewHTTPServer(cache, &options) 27 | } 28 | -------------------------------------------------------------------------------- /servers/tcp.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | import ( 4 | "Rcache/caches" 5 | "Rcache/helpers" 6 | "Rcache/vex" 7 | "encoding/binary" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | ) 12 | 13 | const ( 14 | // getCommand 是 get 命令。 15 | getCommand = byte(1) 16 | 17 | // setCommand 是 set 命令。 18 | setCommand = byte(2) 19 | 20 | // deleteCommand 是 delete 命令。 21 | deleteCommand = byte(3) 22 | 23 | // statusCommand 是 status 命令。 24 | statusCommand = byte(4) 25 | 26 | // nodesCommand 是 nodes 命令。 27 | nodesCommand = byte(5) 28 | ) 29 | 30 | var ( 31 | // commandNeedsMoreArgumentsErr 是命令需要更多参数的错误。 32 | commandNeedsMoreArgumentsErr = errors.New("command needs more arguments") 33 | 34 | // notFoundErr 是找不到的错误。 35 | notFoundErr = errors.New("not found") 36 | ) 37 | 38 | // TCPServer 是 TCP 类型的服务器。 39 | type TCPServer struct { 40 | // cache 是内部用于存储数据的缓存组件。 41 | cache *caches.Cache 42 | 43 | *node 44 | 45 | // server 是内部真正用于服务的服务器。 46 | server *vex.Server 47 | // options 存储着这个服务器的选项配置。 48 | options *Options 49 | } 50 | 51 | // NewTCPServer 返回新的 TCP 服务器。 52 | func NewTCPServer(cache *caches.Cache, options *Options) (*TCPServer, error) { 53 | 54 | n, err := newNode(options) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return &TCPServer{ 60 | node: n, 61 | cache: cache, 62 | server: vex.NewServer(), 63 | options: options, 64 | }, nil 65 | } 66 | 67 | // Run 运行这个 TCP 服务器。 68 | func (ts *TCPServer) Run() error { 69 | // 注册几种命令的处理器 70 | ts.server.RegisterHandler(getCommand, ts.getHandler) 71 | ts.server.RegisterHandler(setCommand, ts.setHandler) 72 | ts.server.RegisterHandler(deleteCommand, ts.deleteHandler) 73 | ts.server.RegisterHandler(statusCommand, ts.statusHandler) 74 | ts.server.RegisterHandler(nodesCommand, ts.nodesHandler) 75 | return ts.server.ListenAndServe("tcp", helpers.JoinAddressAndPort(ts.options.Address, ts.options.Port)) 76 | } 77 | 78 | // Close 用于关闭服务器。 79 | func (ts *TCPServer) Close() error { 80 | return ts.server.Close() 81 | } 82 | 83 | 84 | 85 | // getHandler 是处理 get 命令的的处理器。 86 | func (ts *TCPServer) getHandler(args [][]byte) (body []byte, err error) { 87 | 88 | // 检查参数个数是否足够 89 | if len(args) < 1 { 90 | return nil, commandNeedsMoreArgumentsErr 91 | } 92 | 93 | // 使用一致性哈希选择出这个 key 所属的物理节点 94 | key := string(args[0]) 95 | node, err := ts.selectNode(key) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | // 判断这个 key 所属的物理节点是否是当前节点,如果不是,需要响应重定向信息给客户端,并告知正确的节点地址 101 | if !ts.isCurrentNode(node) { 102 | return nil, fmt.Errorf("redirect to node %s", node) 103 | } 104 | 105 | // 调用缓存的 Get 方法,如果不存在就返回 notFoundErr 错误 106 | value, ok := ts.cache.Get(key) 107 | if !ok { 108 | return value, notFoundErr 109 | } 110 | return value, nil 111 | } 112 | 113 | // setHandler 是处理 set 命令的处理器。 114 | func (ts *TCPServer) setHandler(args [][]byte) (body []byte, err error) { 115 | 116 | // 检查参数个数是否足够 117 | if len(args) < 3 { 118 | return nil, commandNeedsMoreArgumentsErr 119 | } 120 | 121 | // 使用一致性哈希选择出这个 key 所属的物理节点 122 | key := string(args[1]) 123 | node, err := ts.selectNode(key) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | // 判断这个 key 所属的物理节点是否是当前节点,如果不是,需要响应重定向信息给客户端,并告知正确的节点地址 129 | if !ts.isCurrentNode(node) { 130 | return nil, fmt.Errorf("redirect to node %s", node) 131 | } 132 | 133 | // 读取 ttl,注意这里使用大端的方式读取,所以要求客户端也以大端的方式进行存储 134 | ttl := int64(binary.BigEndian.Uint64(args[0])) 135 | err = ts.cache.SetWithTTL(key, args[2], ttl) 136 | if err != nil { 137 | return nil, err 138 | } 139 | return nil, nil 140 | } 141 | 142 | // deleteHandler 是处理 delete 命令的处理器。 143 | func (ts *TCPServer) deleteHandler(args [][]byte) (body []byte, err error) { 144 | 145 | // 检查参数个数是否足够 146 | if len(args) < 1 { 147 | return nil, commandNeedsMoreArgumentsErr 148 | } 149 | 150 | // 使用一致性哈希选择出这个 key 所属的物理节点 151 | key := string(args[0]) 152 | node, err := ts.selectNode(key) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | // 判断这个 key 所属的物理节点是否是当前节点,如果不是,需要响应重定向信息给客户端,并告知正确的节点地址 158 | if !ts.isCurrentNode(node) { 159 | return nil, fmt.Errorf("redirect to node %s", node) 160 | } 161 | 162 | // 删除指定的数据 163 | err = ts.cache.Delete(key) 164 | if err != nil { 165 | return nil, err 166 | } 167 | return nil, nil 168 | } 169 | 170 | // statusHandler 是返回缓存状态的处理器。 171 | func (ts *TCPServer) statusHandler(args [][]byte) (body []byte, err error) { 172 | return json.Marshal(ts.cache.Status()) 173 | } 174 | 175 | // nodesHandler 是返回集群所有节点名称的处理器。 176 | func (ts *TCPServer) nodesHandler(args [][]byte) (body []byte, err error) { 177 | return json.Marshal(ts.nodes()) 178 | } -------------------------------------------------------------------------------- /servers/tcp_client.go: -------------------------------------------------------------------------------- 1 | package servers 2 | 3 | import ( 4 | "Rcache/caches" 5 | "Rcache/vex" 6 | "encoding/binary" 7 | "encoding/json" 8 | ) 9 | 10 | // TCPClient 是 TCP 客户端结构。 11 | type TCPClient struct { 12 | // client 是内部使用的真正的 TCP 客户端。 13 | client *vex.Client 14 | } 15 | 16 | // NewTCPClient 返回一个新的 TCP 客户端。 17 | func NewTCPClient(address string) (*TCPClient, error) { 18 | 19 | // 连接指定的地址 20 | client, err := vex.NewClient("tcp", address) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &TCPClient{ 25 | client: client, 26 | }, nil 27 | } 28 | 29 | // Get 获取指定 key 的 value。 30 | func (tc *TCPClient) Get(key string) ([]byte, error) { 31 | return tc.client.Do(getCommand, [][]byte{[]byte(key)}) 32 | } 33 | 34 | // Set 添加一个键值对到缓存中。 35 | func (tc *TCPClient) Set(key string, value []byte, ttl int64) error { 36 | 37 | // 注意使用大端的形式存储数字 38 | ttlBytes := make([]byte, 8) 39 | binary.BigEndian.PutUint64(ttlBytes, uint64(ttl)) 40 | _, err := tc.client.Do(setCommand, [][]byte{ 41 | ttlBytes, []byte(key), value, 42 | }) 43 | return err 44 | } 45 | 46 | // Delete 删除指定 key 的 value。 47 | func (tc *TCPClient) Delete(key string) error { 48 | _, err := tc.client.Do(deleteCommand, [][]byte{[]byte(key)}) 49 | return err 50 | } 51 | 52 | // Status 返回缓存的状态。 53 | func (tc *TCPClient) Status() (*caches.Status, error) { 54 | body, err := tc.client.Do(statusCommand, nil) 55 | if err != nil { 56 | return nil, err 57 | } 58 | status := caches.NewStatus() 59 | err = json.Unmarshal(body, status) 60 | return status, err 61 | } 62 | 63 | // Close 关闭这个客户端。 64 | func (tc *TCPClient) Close() error { 65 | return tc.client.Close() 66 | } 67 | 68 | 69 | func (tc *TCPClient) Nodes() ([]string, error) { 70 | body, err := tc.client.Do(nodesCommand, nil) 71 | if err != nil { 72 | return nil, err 73 | } 74 | var nodes []string 75 | err = json.Unmarshal(body, &nodes) 76 | return nodes, err 77 | } 78 | -------------------------------------------------------------------------------- /test/performance_test.go: -------------------------------------------------------------------------------- 1 | 2 | package test 3 | 4 | import ( 5 | "Rcache/servers" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | const ( 14 | // keySize 是测试的键值对数量。 15 | keySize = 10000 16 | ) 17 | 18 | // testTask 将任务包装成测试任务。 19 | func testTask(task func(no int)) string { 20 | beginTime := time.Now() 21 | for i := 0; i < keySize; i++ { 22 | task(i) 23 | } 24 | return time.Now().Sub(beginTime).String() 25 | } 26 | 27 | // go test -v -count=1 performance_test.go -run=^TestHttpServer$ 28 | func TestHttpServer(t *testing.T) { 29 | 30 | writeTime := testTask(func(no int) { 31 | data := strconv.Itoa(no) 32 | request, err := http.NewRequest("PUT", "http://localhost:5837/v1/cache/"+data, strings.NewReader(data)) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | response, err := http.DefaultClient.Do(request) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | response.Body.Close() 42 | }) 43 | 44 | t.Logf("写入消耗时间为 %s。", writeTime) 45 | 46 | time.Sleep(3 * time.Second) 47 | 48 | readTime := testTask(func(no int) { 49 | data := strconv.Itoa(no) 50 | request, err := http.NewRequest("GET", "http://localhost:5837/v1/cache/"+data, nil) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | response, err := http.DefaultClient.Do(request) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | response.Body.Close() 60 | }) 61 | 62 | t.Logf("读取消耗时间为 %s。", readTime) 63 | } 64 | 65 | // go test -v -count=1 performance_test.go -run=^TestTcpServer$ 66 | func TestTcpServer(t *testing.T) { 67 | 68 | client, err := servers.NewTCPClient("127.0.0.1:5837") 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | defer client.Close() 73 | 74 | writeTime := testTask(func(no int) { 75 | data := strconv.Itoa(no) 76 | err := client.Set(data, []byte(data), 0) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | }) 81 | 82 | t.Logf("写入消耗时间为 %s。", writeTime) 83 | 84 | time.Sleep(3 * time.Second) 85 | 86 | readTime := testTask(func(no int) { 87 | data := strconv.Itoa(no) 88 | _, err := client.Get(data) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | }) 93 | 94 | t.Logf("读取消耗时间为 %s。", readTime) 95 | } -------------------------------------------------------------------------------- /vex/client.go: -------------------------------------------------------------------------------- 1 | package vex 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "io" 7 | "net" 8 | ) 9 | 10 | // 客户端结构。 11 | type Client struct { 12 | 13 | // 和服务端建立的连接。 14 | conn net.Conn 15 | 16 | // 通往服务端的读取器。 17 | reader io.Reader 18 | } 19 | 20 | // 创建新的客户端。 21 | func NewClient(network string, address string) (*Client, error) { 22 | 23 | // 和服务端建立连接 24 | conn, err := net.Dial(network, address) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &Client{ 29 | conn: conn, 30 | reader: bufio.NewReader(conn), 31 | }, nil 32 | } 33 | 34 | func (c *Client) Do(command byte, args [][]byte) (body []byte, err error) { 35 | 36 | // 包装请求然后发送给服务端 37 | _, err = writeRequestTo(c.conn, command, args) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | // 读取服务端返回的响应 43 | reply, body, err := readResponseFrom(c.reader) 44 | if err != nil { 45 | return body, err 46 | } 47 | 48 | // 如果是错误答复码,将内容包装成 error 并返回 49 | if reply == ErrorReply { 50 | return body, errors.New(string(body)) 51 | } 52 | return body, nil 53 | } 54 | 55 | // 关闭客户端。 56 | func (c *Client) Close() error { 57 | return c.conn.Close() 58 | } 59 | -------------------------------------------------------------------------------- /vex/protocol.go: -------------------------------------------------------------------------------- 1 | package vex 2 | 3 | import "errors" 4 | 5 | // Request: 6 | // version command argsLength {argLength arg} 7 | // 1byte 1byte 4byte 4byte unknown 8 | 9 | // Response: 10 | // version reply bodyLength {body} 11 | // 1byte 1byte 4byte unknown 12 | 13 | const ( 14 | ProtocolVersion = byte(1) // 协议版本号 15 | headerLengthInProtocol = 6 // 协议中头部占用的字节数 16 | argsLengthInProtocol = 4 // 协议中参数个数占用的字节数 17 | argLengthInProtocol = 4 // 协议中参数长度占用的字节数 18 | bodyLengthInProtocol = 4 // 协议体长度占用的字节数 19 | ) 20 | 21 | var ( 22 | // 协议版本不匹配错误,如果客户端和服务端的版本不一样就会返回这个错误 23 | ProtocolVersionMismatchErr = errors.New("protocol version between client and server doesn't match") 24 | ) 25 | -------------------------------------------------------------------------------- /vex/request.go: -------------------------------------------------------------------------------- 1 | package vex 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | //读取请求,解析出命令 9 | func readRequestFrom(reader io.Reader) (command byte, args [][]byte, err error) { 10 | header := make([]byte, headerLengthInProtocol) 11 | _, err = io.ReadFull(reader, header) 12 | if err != nil { 13 | return 0, nil, err 14 | } 15 | version := header[0] 16 | if version != ProtocolVersion { 17 | return 0, nil, ProtocolVersionMismatchErr 18 | } 19 | command = header[1] 20 | header = header[2:] 21 | //将头部的信息转化为一个数字 22 | argsLength := binary.BigEndian.Uint32(header) //此时的argsLength就是本次请求的个数 23 | 24 | args = make([][]byte, argsLength) 25 | if argsLength > 0 { 26 | argLength := make([]byte, argLengthInProtocol) 27 | for i := uint32(0); i < argsLength; i++ { 28 | _, err = io.ReadFull(reader, argLength) 29 | if err != nil { 30 | return 0, nil, err 31 | } 32 | 33 | arg := make([]byte, binary.BigEndian.Uint32(argLength)) 34 | _, err = io.ReadFull(reader, arg) 35 | if err != nil { 36 | return 0, nil, err 37 | } 38 | args[i] = arg 39 | } 40 | } 41 | return command, args, nil 42 | } 43 | 44 | //将请求的具体内容写入到writer 45 | func writeRequestTo(writer io.Writer, command byte, args [][]byte) (int, error) { 46 | // 创建一个缓存区,并将协议版本号、命令和参数个数等写入缓存区 47 | request := make([]byte, headerLengthInProtocol) 48 | request[0] = ProtocolVersion 49 | request[1] = command 50 | binary.BigEndian.PutUint32(request[2:], uint32(len(args))) 51 | 52 | if len(args) > 0 { 53 | // 将参数都添加到缓存区 54 | argLength := make([]byte, argLengthInProtocol) 55 | for _, arg := range args { 56 | binary.BigEndian.PutUint32(argLength, uint32(len(arg))) 57 | request = append(request, argLength...) 58 | request = append(request, arg...) 59 | } 60 | } 61 | return writer.Write(request) 62 | } 63 | -------------------------------------------------------------------------------- /vex/response.go: -------------------------------------------------------------------------------- 1 | package vex 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | const ( 10 | SuccessReply = 0 // 成功的答复码 11 | ErrorReply = 1 // 发生错误的答复码 12 | ) 13 | 14 | //解析从服务端发送过来的响应 15 | func readResponseFrom(reader io.Reader) (reply byte, body []byte, err error) { 16 | header := make([]byte, headerLengthInProtocol) 17 | _, err = io.ReadFull(reader, header) 18 | if err != nil { 19 | return ErrorReply, nil, err 20 | } 21 | version := header[0] 22 | if version != ProtocolVersion { 23 | return ErrorReply, nil, errors.New("response " + ProtocolVersionMismatchErr.Error()) 24 | } 25 | reply = header[1] 26 | header = header[2:] 27 | body = make([]byte, binary.BigEndian.Uint32(header)) 28 | _, err = io.ReadFull(reader, body) 29 | if err != nil { 30 | return ErrorReply, nil, err 31 | } 32 | return reply, body, nil 33 | } 34 | 35 | //服务端将响应写入到writer 36 | func writeResponseTo(writer io.Writer, reply byte, body []byte) (int, error) { 37 | // 将响应体相关数据写入响应缓存区,并发送 38 | bodyLengthBytes := make([]byte, bodyLengthInProtocol) 39 | binary.BigEndian.PutUint32(bodyLengthBytes, uint32(len(body))) 40 | 41 | response := make([]byte, 2, headerLengthInProtocol+len(body)) 42 | response[0] = ProtocolVersion 43 | response[1] = reply 44 | response = append(response, bodyLengthBytes...) 45 | response = append(response, body...) 46 | return writer.Write(response) 47 | } 48 | 49 | func writeErrorResponseTo(writer io.Writer, msg string) (int, error) { 50 | return writeResponseTo(writer, ErrorReply, []byte(msg)) 51 | } 52 | -------------------------------------------------------------------------------- /vex/server.go: -------------------------------------------------------------------------------- 1 | package vex 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "net" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | var ( 12 | // 找不到对应的命令处理器错误 13 | commandHandlerNotFoundErr = errors.New("failed to find a handler of command") 14 | ) 15 | 16 | // 服务端结构。 17 | type Server struct { 18 | 19 | // 监听器,这个应该大家都很熟悉了吧。 20 | listener net.Listener 21 | 22 | // 命令处理器,通过命令可以找到对应的处理器。 23 | handlers map[byte]func(args [][]byte) (body []byte, err error) 24 | } 25 | 26 | // 创建新的服务端。 27 | func NewServer() *Server { 28 | return &Server{ 29 | handlers: map[byte]func(args [][]byte) (body []byte, err error){}, 30 | } 31 | } 32 | 33 | // 注册命令处理器。 34 | func (s *Server) RegisterHandler(command byte, handler func(args [][]byte) (body []byte, err error)) { 35 | s.handlers[command] = handler 36 | } 37 | 38 | // 监听并服务于 network 和 address。 39 | func (s *Server) ListenAndServe(network string, address string) (err error) { 40 | 41 | // 监听指定地址 42 | s.listener, err = net.Listen(network, address) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | // 使用 WaitGroup 记录连接数,并等待所有连接处理完毕 48 | wg := &sync.WaitGroup{} 49 | for { 50 | // 等待客户端连接 51 | conn, err := s.listener.Accept() 52 | if err != nil { 53 | if strings.Contains(err.Error(), "use of closed network connection") { 54 | break 55 | } 56 | continue 57 | } 58 | 59 | // 记录连接 60 | wg.Add(1) 61 | go func() { 62 | defer wg.Done() 63 | s.handleConn(conn) 64 | }() 65 | } 66 | 67 | // 等待所有连接处理完毕 68 | wg.Wait() 69 | return nil 70 | } 71 | 72 | // 处理连接。 73 | func (s *Server) handleConn(conn net.Conn) { 74 | 75 | // 将连接包装成缓冲读取器,提高读取的性能 76 | reader := bufio.NewReader(conn) 77 | defer conn.Close() 78 | 79 | for { 80 | // 读取并解析请求请求 81 | command, args, err := readRequestFrom(reader) 82 | if err != nil { 83 | if err == ProtocolVersionMismatchErr { 84 | continue 85 | } 86 | return 87 | } 88 | 89 | // 处理请求 90 | reply, body, err := s.handleRequest(command, args) 91 | if err != nil { 92 | writeErrorResponseTo(conn, err.Error()) 93 | continue 94 | } 95 | 96 | // 发送处理结果的响应 97 | _, err = writeResponseTo(conn, reply, body) 98 | if err != nil { 99 | continue 100 | } 101 | } 102 | } 103 | 104 | // 处理请求。 105 | func (s *Server) handleRequest(command byte, args [][]byte) (reply byte, body []byte, err error) { 106 | 107 | // 从命令处理器集合中选出对应的处理器 108 | handle, ok := s.handlers[command] 109 | if !ok { 110 | return ErrorReply, nil, commandHandlerNotFoundErr 111 | } 112 | 113 | // 将处理结果返回 114 | body, err = handle(args) 115 | if err != nil { 116 | return ErrorReply, body, err 117 | } 118 | return SuccessReply, body, err 119 | } 120 | 121 | // 关闭服务端的方法。 122 | func (s *Server) Close() error { 123 | if s.listener == nil { 124 | return nil 125 | } 126 | return s.listener.Close() 127 | } 128 | --------------------------------------------------------------------------------