Git Product home page Git Product logo

godis's Introduction

Godis

license Build Status Coverage Status Go Report Card Go Reference
Mentioned in Awesome Go

中文版

Godis is a golang implementation of Redis Server, which intents to provide an example of writing a high concurrent middleware using golang.

Key Features:

  • Support string, list, hash, set, sorted set, bitmap
  • Multi Database and SELECT command
  • TTL
  • Publish/Subscribe
  • GEO
  • AOF and AOF Rewrite
  • RDB read and write
  • MULTI Commands Transaction is Atomic and Isolated. If any errors are encountered during execution, godis will rollback the executed commands
  • Replication (experimental)
  • Server-side Cluster which is transparent to client. You can connect to any node in the cluster to access all data in the cluster.
    • Use the raft algorithm to maintain cluster metadata. (experimental)
    • MSET, MSETNX, DEL, Rename, RenameNX command is supported and atomically executed in cluster mode, allow over multi node
    • MULTI Commands Transaction is supported within slot in cluster mode
  • Concurrent Core, so you don't have to worry about your commands blocking the server too much.

If you could read Chinese, you can find more details in My Blog.

Get Started

You can get runnable program in the releases of this repository, which supports Linux and Darwin system.

./godis-darwin
./godis-linux

You could use redis-cli or other redis client to connect godis server, which listens on 0.0.0.0:6399 on default mode.

The program will try to read config file path from environment variable CONFIG.

If environment variable is not set, then the program try to read redis.conf in the working directory.

If there is no such file, then the program will run with default config.

cluster mode

Godis can work in cluster mode, please append following lines to redis.conf file

peers localhost:7379,localhost:7389 // other node in cluster
self  localhost:6399 // self address

We provide node1.conf and node2.conf for demonstration. use following command line to start a two-node-cluster:

CONFIG=node1.conf ./godis-darwin &
CONFIG=node2.conf ./godis-darwin &

Connect to a node in the cluster to access all data in the cluster:

redis-cli -p 6399

Supported Commands

See: commands.md

Benchmark

Environment:

Go version:1.17

System: macOS Catalina 10.15.7

CPU: 2.6GHz 6-Core Intel Core i7

Memory: 16 GB 2667 MHz DDR4

Performance report by redis-benchmark:

PING_INLINE: 87260.03 requests per second
PING_BULK: 89206.06 requests per second
SET: 85034.02 requests per second
GET: 87565.68 requests per second
INCR: 91157.70 requests per second
LPUSH: 90334.23 requests per second
RPUSH: 90334.23 requests per second
LPOP: 90334.23 requests per second
RPOP: 90415.91 requests per second
SADD: 90909.09 requests per second
HSET: 84104.29 requests per second
SPOP: 82918.74 requests per second
LPUSH (needed to benchmark LRANGE): 78247.26 requests per second
LRANGE_100 (first 100 elements): 26406.13 requests per second
LRANGE_300 (first 300 elements): 11307.10 requests per second
LRANGE_500 (first 450 elements): 7968.13 requests per second
LRANGE_600 (first 600 elements): 6092.73 requests per second
MSET (10 keys): 65487.89 requests per second

Todo List

  • Multi Command
  • Watch Command and CAS support
  • Stream support
  • RDB file loader
  • Master-Slave mode
  • Sentinel

Read My Code

If you want to read my code in this repository, here is a simple guidance.

  • project root: only the entry point
  • config: config parser
  • interface: some interface definitions
  • lib: some utils, such as logger, sync utils and wildcard

I suggest focusing on the following directories:

  • tcp: the tcp server
  • redis: the redis protocol parser
  • datastruct: the implements of data structures
    • dict: a concurrent hash map
    • list: a linked list
    • lock: it is used to lock keys to ensure thread safety
    • set: a hash set based on map
    • sortedset: a sorted set implements based on skiplist
  • database: the core of storage engine
    • server.go: a standalone redis server, with multiple database
    • database.go: data structure and base functions of single database
    • exec.go: the gateway of database
    • router.go: the command table
    • keys.go: handlers for keys commands
    • string.go: handlers for string commands
    • list.go: handlers for list commands
    • hash.go: handlers for hash commands
    • set.go: handlers for set commands
    • sortedset.go: handlers for sorted set commands
    • pubsub.go: implements of publish / subscribe
    • aof.go: implements of AOF persistence and rewrite
    • geo.go: implements of geography features
    • sys.go: authentication and other system function
    • transaction.go: local transaction
  • cluster:
    • cluster.go: entrance of cluster mode
    • com.go: communication within nodes
    • del.go: atomic implementation of delete command in cluster
    • keys.go: keys command
    • mset.go: atomic implementation of mset command in cluster
    • multi.go: entrance of distributed transaction
    • pubsub.go: pub/sub in cluster
    • rename.go: rename command in cluster
    • tcc.go: try-commit-catch distributed transaction implementation
  • aof: AOF persistence

License

This project is licensed under the GPL license.

godis's People

Contributors

226charles avatar allen9012 avatar blinkbean avatar coderi421 avatar damonslh avatar gleiphir2769 avatar gou-jjjj avatar gu18168 avatar hdt3213 avatar hk40404 avatar kaniubillows avatar kun98-liu avatar luqinwen avatar nanshaner avatar ncghost1 avatar orlion avatar pengyejun avatar qing-turnaround avatar sheldonlyr avatar shining-brain avatar singularity0909 avatar ssnago avatar suger-no avatar testwill avatar xuning888 avatar xxd9980 avatar yikuaibro avatar youguanxinqing avatar youngchiyu avatar yuanshuli11 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

godis's Issues

关于 lib 中 timewheel 时间轮的问题

大佬,时间轮是不并没有真正的运行起来,而是靠 IsExpired 方法实现过期 key 的删除,我没有看到在哪里调用了 timewheel.init 方法。

utils.LimitedReader读取疑问

描述:
lib/utils/limitd_reader.go中的LimitedReader 是有什么特殊考虑吗? (没有使用标准库)
标准库: io.LimitedReader 提供了类似的limit功能, 但是和当前仓库的utils.LimitedReader 有使用的区别

测试如下:
utils.LimitedReader 在limited触发时,会多读取超过buffer (读取缓存)的额外内容

代码如下:

func TestNewLimitedReader(t *testing.T) {
	resourceStr := "0123456789"

	readFunc := func(r io.Reader) (string, error) {
		full := ""
		i := 0
		for i < 16 {
			buffer := make([]byte, 3) // 每次读3个byte
			n, err := r.Read(buffer)
			if err != nil {
				if err == io.EOF {
					// ignore eof err in this test
					return string(full), nil
				}
				return "", nil
			}
			if n != 0 {
				full += string(buffer[0:n])
			}
			i++
		}

		return full, nil
	}

	// test
	readerList := []struct {
		reader io.Reader
		name   string
	}{
		{NewLimitedReader(strings.NewReader(resourceStr), 5), "utils/LimitedReader"},
		{&io.LimitedReader{
			R: strings.NewReader(resourceStr),
			N: 5,
		}, "io.LimitReader"},
	}

	for _, r := range readerList {
		result, err := readFunc(r.reader)
		if err != nil {
			log.Print(r.name + " read err:" + err.Error())
		}
		log.Printf("%s:%s\n", r.name, result)
	}
}

测试结果如下:

image
utils.LimitedReader 额外读取了字符“5” 读取了6个字符

因为limit=5, readFunc中的的buffer设置为3(小于5且不能被5整除),导致utils.LimitedReader 额外多的读出来一个字符“5“

请教一下: 输入命令时和真正redis的区别

用telnet连接真正的reids时 直接输入 ping 就会返回+pong
但是我按照您的第二章(Golang 实现 Redis(2): 实现 Redis 协议解析器)
实现的demo中
必须用先输入一个 *1 回车

`
➜ src telnet 127.0.0.1 8000

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
*1
ping
+PONG
`

连接真实的redis服务:
`
Trying 127.0.0.1...

Connected to localhost.
Escape character is '^]'.
ping
+PONG
`

请问产生这个区别的原因是 redis用的空格来截取cmd 而您的例子是用的\n 吗?
为什么您的例子不也使用空格呢?

rewriteaof 存在死锁的可能

aof重写步骤为:

  1. 开始rewrite,对应startRewrite方法

    1. 获取写锁,暂停aof写入

    2. 获取aof文件大小

    3. 创建rewriteBuffer channel(带缓冲channel)

    4. 生成临时文件

    5. 释放写锁,恢复aof写入

  2. 读取aof文件内容,加载内容到临时DB对象

  3. 根据临时DB对象的数据,生成命令写入临时文件

  4. 结束rewrite

    1. 获取写锁,暂停aof写入

    2. 读取rewriteBuffer,写入aof临时文件

    3. 关闭rewriteBuffer,并设置为nil

    4. 重命名临时文件为aof文件

    5. open 新的aof文件,并设置为db.aofFile

    6. 释放写锁,恢复aof写入

而主程序,在第一步与第四步之间,一直可以写入aof chan,在处理aof chan中的数据时,同步写入一份到rewriteBuffer chan中,此时会存在一个问题: 程序写入aof时命令较多,超过了rewriteBuffer的缓冲大小,此时会出现 handleAof方法获取到了读锁,但是在写入rewriteBuffer时,阻塞住了,无法释放读锁
image

而 finishRewrite 方法,在结束rewrite时,需要先获取到写锁,才会接收 rewriteBuffer chan的数据,就会出现锁已被 handleAof占用,finishRewrite方法获取不到锁的情况,从而导致死锁

image

不会自动生成持久化存储文件吗?

版本:发布版本1.2.8
redis.conf配置如下
`
bind 0.0.0.0
port 6399
maxclients 128

appendonly no
appendfilename appendonly.aof
dbfilename test.rdb
`
当前目录没有生成
appendonly.aof 和 test.rdb 文件

tx_utils.go rollbackGivenKeys 函数逻辑怎么理解

func rollbackGivenKeys(db *DB, keys ...string) []CmdLine {
	var undoCmdLines [][][]byte
	for _, key := range keys {
		entity, ok := db.GetEntity(key)
		if !ok {
			undoCmdLines = append(undoCmdLines,
				utils.ToCmdLine("DEL", key),
			)
		} else {
			undoCmdLines = append(undoCmdLines,
				utils.ToCmdLine("DEL", key), // clean existed first
				aof.EntityToCmd(key, entity).Args,
				toTTLCmd(db, key).Args,
			)
		}
	}
	return undoCmdLines
}
为什么这里key不存在 也是执行的DEL命令进行回滚

sync./wait.go里的WaitWithTimeout

// WaitWithTimeout blocks until the WaitGroup counter is zero or timeout
// returns true if timeout
func (w *Wait) WaitWithTimeout(timeout time.Duration) bool {
	c := make(chan bool, 1)
	go func() {
		defer close(c)
		w.wg.Wait()
		c <- true
	}()
	select {
	case <-c:
		return false // completed normally
	case <-time.After(timeout):
		return true // timed out
	}
}

w.wg.Wait() 换成w.Wait() 会不会更配套点hh

WaitWithTimeout方法会导致goroutine泄漏

image
此处创建了一个无缓冲channel,并创建了一个goroutine,在done时,往该channel写入数据,但是如果在超时场景下,WaitWithTimeout方法退出了,那就没有对该channel的接收方,那这个函数创建的goroutine,就会阻塞在往channel写数据那一步,将一直无法退出该goroutine

ConcurrentDict.ForEach方法,会报读写冲突

这里的读锁加在循环里面肯定拦不住对shard的写请求,会报读写冲突的吧

func (dict *ConcurrentDict) ForEach(consumer Consumer) {
	if dict == nil {
		panic("dict is nil")
	}

	for _, shard := range dict.table {
		for key, value := range shard.m {
			shard.mutex.RLock()
			continues := consumer(key, value)
			shard.mutex.RUnlock()
			if !continues {
				return
			}
		}
	}
}

关于插件系统

pie 是一个用于Go程序插件扩展的库,可以以独立进程插件的方式来扩展Go程序,稳定性和安全性可以有保证。
如果godis能引入pie作为插件系统,那就很方便了,可以形成一个社区。
例如用插件扩展godis实现业务逻辑等。

Create ''good first issue"

I want to help with this project, but don't know what to start with. It will be cool to have some easy tasks to start with, to understand how things works.

问题

  1. 请问集群模式下,是否有高可用特性,key是否自动复制到多台服务上?
  2. 集群模式下加新节点能否自动扩容 ?
  3. 集群模式下数据访问是强一致还是最终一致?

谢谢

关于redis/parser/parser.go里parseBulkHeader方法逻辑

func parseBulkHeader(msg []byte, state *readState) error {
	var err error
	state.bulkLen, err = strconv.ParseInt(string(msg[1:len(msg)-2]), 10, 64)
	if err != nil {
		return errors.New("protocol error: " + string(msg))
	}
	if state.bulkLen == -1 { // null bulk
		return nil
	} else if state.bulkLen > 0 {
		state.msgType = msg[0]
		state.readingMultiLine = true
		state.expectedArgsCount = 1
		state.args = make([][]byte, 0, 1)
		return nil
	} else {
		return errors.New("protocol error: " + string(msg))
	}
}

想问下什么情况下会是 state.bulkLen == -1

sortedset Add()有bug

func(sortedSet *SortedSet) Add(member string, score float64) bool {
	element, ok := sortedSet.dict[member]
	sortedSet.dict[member] = &Element{
		Member: member,
		Score:  score,
	}
	if ok {
		if score != element.Score {
			sortedSet.skiplist.remove(member, score)
			sortedSet.skiplist.insert(member, score)
		}
		return false
	}
	sortedSet.skiplist.insert(member, score)
	return true
}

应改为

func (sortedSet *SortedSet) Add(member string, score float64) bool {
	element, ok := sortedSet.dict[member]
	sortedSet.dict[member] = &Element{
		Member: member,
		Score:  score,
	}
	if ok {
		if score != element.Score {
			sortedSet.skiplist.remove(member, element.Score)
			sortedSet.skiplist.insert(member, score)
		}
		return false
	}
	sortedSet.skiplist.insert(member, score)
	return true
}

提pull有点麻烦,就在这里说下了

Client.Start()方法中的client.handleRead()不需要错误处理

redis/client/client.go

// Start starts asynchronous goroutines
func (client *Client) Start() {
	client.ticker = time.NewTicker(10 * time.Second)
	go client.handleWrite()
	go func() {
		err := client.handleRead() // 不需要处理err
		if err != nil {
			logger.Error(err)
		}
	}()
	go client.heartbeat()
}
// 实际函数中没有返回error
func (client *Client) handleRead() error {
	ch := parser.ParseStream(client.conn)
	for payload := range ch {
		if payload.Err != nil {
			client.finishRequest(reply.MakeErrReply(payload.Err.Error()))
			continue
		}
		client.finishRequest(payload.Data)
	}
	return nil
}

是否可以写成

// Start starts asynchronous goroutines
func (client *Client) Start() {
	client.ticker = time.NewTicker(10 * time.Second)
	go client.handleWrite()
	go  client.handleRead() 
	go client.heartbeat()
}
// 实际函数中没有返回error
func (client *Client) handleRead() {
	ch := parser.ParseStream(client.conn)
	for payload := range ch {
		if payload.Err != nil {
			client.finishRequest(reply.MakeErrReply(payload.Err.Error()))
			continue
		}
		client.finishRequest(payload.Data)
	}
}

WaitWithTimeout方法看起来会造成goroutine泄露

基本说明
当 WaitWithTimeout方法由于超时退出后,如果没有调用Done(),那么内部的goroutine中的Wait()方法还在阻塞,导致该goroutine泄露。
代码位置
lib/sync/wait/wait.go
测试代码

func main() {
	w := &Wait{}
	w.Add(1)
	fmt.Printf("goroutines: %d\n", runtime.NumGoroutine())
	b := w.WaitWithTimeout(5 * time.Second)
	fmt.Println(b)
	//w.Done()
	time.Sleep(10 * time.Second)
	fmt.Printf("goroutines: %d\n", runtime.NumGoroutine())
}

// Wait is similar with sync.WaitGroup which can wait with timeout
type Wait struct {
	wg sync.WaitGroup
}

// Add adds delta, which may be negative, to the WaitGroup counter.
func (w *Wait) Add(delta int) {
	w.wg.Add(delta)
}

// Done decrements the WaitGroup counter by one
func (w *Wait) Done() {
	w.wg.Done()
}

// Wait blocks until the WaitGroup counter is zero.
func (w *Wait) Wait() {
	w.wg.Wait()
}

// WaitWithTimeout blocks until the WaitGroup counter is zero or timeout
// returns true if timeout
func (w *Wait) WaitWithTimeout(timeout time.Duration) bool {
	c := make(chan bool)
	go func() {
		defer close(c)
		fmt.Println("wait...")
		w.wg.Wait()
		fmt.Println("done...")
		c <- true
	}()
	select {
	case <-c:
		return false // completed normally
	case <-time.After(timeout):
		return true // timed out
	}
}

结果

goroutines: 1
wait...
true
goroutines: 2

项目启动错误,报常数int类型溢出

启动项目的电脑环境

启动项目电脑系统:windows10 / 64位
go版本:go version go1.17.7 windows/386

执行的操作

在clone项目之后,进入到目录cmd命令行,执行以下命令

go mod tidy

安装依赖之后在目录下执行:

go run main.go

报以下错误:

# github.com/hdt3213/rdb/core
D:\Code\golang\go\pkg\mod\github.com\hdt3213\[email protected]\core\list.go:283:21: constant 4294967295 overflows int

这个问题只在 Windows10 操作系统出现,我另外一台 Ubuntu 20.04 操作系统的电脑项目正常运行。
那请问如何在 Windows10 操作系统电脑上运行项目代码呢?

终止连接

if h.closing.Get() {
// closing handler refuse new connection
_ = conn.Close()
}
这段代码应该写到for循环里面,如果客户端一直不关闭,handler协程永远无法关闭。而当客户端再次尝试命令时,服务端主动断开连接,一般情况下我们遇到的是客户端不主动关闭连接,继续发送命令这种情况

logger文件名错误

logger.Setup函数里面,
fileName := fmt.Sprintf("%s-%s.%s",
settings.Name,
time.Now().Format(settings.TimeFormat),
settings.Ext)
因为拓展名加了.,格式化字符串也有.,导致最后文件名有两个.

关于博客的问题

大佬,您博客中h.activeConn.Delete(conn)应该改为h.activeConn.Delete(client)才对吧

并发打印日志时,日志级别错乱问题

描述:
并发打印日志时,会出现日志级别与设置不符合情况

详情:
查看源码时, 发现 lib/logger.go 文件中对新增日志是使用 Info(v interface{}) Debug(v interface{})等函数
但是日志级别是在每个函数中调用全局的setPrefix(level logLevel) , 该函数修改全局变量logger的prefix配置;
log.Logger.SetPrefix函数每次修改都会加锁(超大并发性能是否会影响) :
image

这里的log.Logger.SetPrefix加了锁,但是锁的范围是setPrefix函数
Info日志函数后面的 logger.Println(v...) 并没有在锁中:
image

测试代码如下:
image

测试结果如下:
image

简单思考
是否可以每个日志级别单独使用一个 log.Logger
是否可以将prefix与全局的logger对象解绑
Settings对象中是否增加CallerDepth可配置项(如上结果图,我这里文件显示层级有问题)

SimpleDict中的Keys()实现有点问题

func (dict *SimpleDict) Keys() []string {
result := make([]string, len(dict.m))
i := 0
for k := range dict.m {
result[i] = k
}
return result
}

i的值一直不变

ps:虽然除了测试并没有发现这个方法在其它地方用到过

关于redis/client/client.go逻辑的一点疑问,希望能得到解答

1、start中会开启一个协程来handleRead
2、在dorequest的时候如果出现handleConnectionError的情况,那么很显然是网络出现了错误,故1中的handleRead肯定也会从ch中收到网络err,并执行finishRequest,此时执行finishRequest会阻塞在request := <-client.waitingReqs这一步(因为waitingReqs ch中没有值)
3、如果2中出现handleConnectionError后,会重试,重试如果成功后将request往client.waitingReqs中送,那么这个request最后会和2中err Reply绑定,这好像会导致重试成功后的reply无法绑定?

可能我的理解有所偏差,麻烦大佬帮我解答,感谢!

能不能把redis的geo功能也实现一下

最近都在看你的golang实现redis系列文章,受益匪浅。
目前还差redis的geo功能,对redis中georadius的功能比较感兴趣,作者不能把geo的实现也出个文章

可否实现 info

部分GUI客户端 会请求这个命令 如RDM
ERR unknown command 'info'

DISCARD命令返回值的问题

当前DISCARD命令返回值为QUEUED,而Redis协议DISCARD的返回值为OK。
此处代码的返回值应该改为reply.MakeOkReply()
image

skiplist的insert方法好像有bug

Uploading image.png…
skiplist.go中的insert方法好像有bug(90-103行), 如果随机出来的level < skiplist.level 那么后续代码用的就是随机出来的level

connection/conn.go 这里Write加锁是什么原因呢

// Write sends response to client over tcp connection
func (c *Connection) Write(b []byte) error {
	if len(b) == 0 {
		return nil
	}
	c.mu.Lock()
	c.waitingReply.Add(1)
	defer func() {
		c.waitingReply.Done()
		c.mu.Unlock()
	}()

	_, err := c.conn.Write(b)
	return err
}

提一个string.go中execSet函数的重大Bug

第一次执行set key value ex 10000,那么key设置成功,并且ttl为10000秒,
过几秒接下来执行set key value nx ex 20000,会发现虽然命令返回nil,但是该key的ttl已经被改成了20000。

bug的问题就出现在如下代码(见注释)

	if ttl != unlimitedTTL {
		expireTime := time.Now().Add(time.Duration(ttl) * time.Millisecond)
		db.Expire(key, expireTime) // 可以看到不管怎么样,只要有ex,这里key的ttl就会被更新
		db.addAof(CmdLine{
			[]byte("SET"),
			args[0],
			args[1],
		})
		db.addAof(aof.MakeExpireCmd(key, expireTime).Args)
	} else if result > 0 {
		db.Persist(key) // override ttl
		db.addAof(utils.ToCmdLine3("set", args...))
	} else {
		db.addAof(utils.ToCmdLine3("set", args...))
	}

建议改成如下

	if ttl != unlimitedTTL {
                // 如果有nx命令,并且set设置失败,则不更新ttl,直接返回nil了
                if policy == insertPolicy && result == 0 {
			                return reply.MakeNullBulkReply()
                
		}
                db.Persist(key) 
		expireTime := time.Now().Add(time.Duration(ttl) * time.Millisecond)
		db.Expire(key, expireTime) // 可以看到不管怎么样,只要有ex,这里key的ttl就会被更新
		db.addAof(CmdLine{
			[]byte("SET"),
			args[0],
			args[1],
		})
		db.addAof(aof.MakeExpireCmd(key, expireTime).Args)
	} else if result > 0 {
		db.Persist(key) // override ttl
		db.addAof(utils.ToCmdLine3("set", args...))
	} else {
                db.Persist(key) 
		db.addAof(utils.ToCmdLine3("set", args...))
	}

一个基础的关于client.go的问题

在测试client_test.go时,发现发送Ping消息能够收到Pong。但是似乎并没有看到启动server的代码。我在sys.go的Ping方法打了个断点,发现并没有进入,看了下代码似乎只有调用sys.go的Ping方法才会返回&reply.PongReply{}。所以我有点疑惑这个Pong是什么时候写到client.conn里的呢?
(菜鸡顺便求个godis交流群)

插个眼

打不过就加入,跟着大佬学了,感谢分享,感谢开源。

Handle函数中EOF异常为什么只是把client从map中移除呢?

for {
        // may occurs: client EOF, client timeout, server early close
        msg, err := reader.ReadString('\n')
        if err != nil {
	        if err == io.EOF {
		        logger.Info("connection close")
		        h.activeConn.Delete(client)
	        } else {
		        logger.Warn(err)
	        }
	        return
        }
        client.Waiting.Add(1)
        //logger.Info("sleeping")
        //time.Sleep(10 * time.Second)
        b := []byte(msg)
        _, _ = conn.Write(b)
        client.Waiting.Done()
}

在echo_test中,客户端调用了close(),会走到EOF分支,但是这个时候服务端好像没有调用Conn.Close(),仅仅是从Map中移除
这样貌似导致被动关闭方卡在Close wait状态了?
image

dict.computeCapacity 函数的作用没看明白

如题,调用 dict.MakeConcurrent 创建 ConcurrentDict 时,会先用该函数计算shardCount,但是这个函数的算法没明白时啥意思,作者能辛苦解惑一下吗?谢谢

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.