├── .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 |
--------------------------------------------------------------------------------