前一篇文章实现了客户端/服务端的交互。这一篇,主要介绍get/set命令的实现。
命令自己比较简单,支撑命令的整个系统基础比较麻烦。本文会介绍get/set操做涉及的组件和模块,并适当简化,最后实现功能。github
Redis用C语言写成,C语言自身不支持复杂数据结构,因此Redis中的string、list、set等结构,均是Redis自身实现;而Go版本的Godis,会尽可能使用原生数据结构。数据库
set命令和get命令是Redis中使用频率最高的命令,以set为例,命令“set key value”,将键值对存储到Redis服务端,能够简化为“操做一个远程关联数组”。
固然,相比关联数组,Redis多了以下特性:segmentfault
本文重点实现数据在内存中的存储及查询,交互协议和持久化会在后续短文实现。数组
从服务端初始化、客户端输入“set alpha 123”命令,到接收到返回结果,经历以下步骤:安全
执行流程大体以下:数据结构
接下来分开说明主要步骤。函数
前篇实现的客户端/服务端交互使用的协议是textproto,没有使用Redis自身的统一协议。这一篇,客户端对服务端执行的get、set命令,均以原生文本方式发送给服务端执行。在读者阅读实现代码时,也能够看到最新release版本,与v0.0.1有一处diff是在godis-cli.go文件:测试
//清除掉回车换行符 //text = strings.Replace(text, "\n", "", -1)
该行被暂时注释掉,也就是在v0.0.2版本,使用“n”做为文本协议分隔符,肯定命令的结尾。ui
如客户端发送"set alpha 123",服务端接收到的就是以下字节数据:
分别对应ASCII码为:
第一篇(https://segmentfault.com/a/11...)提到过服务端须要一个server结构体存储相关信息,在服务端准备好处理请求前,对该结构进行实例化并进行一系列初始化操做:初始化基本配置、分配多db资源、加载磁盘持久化数据、信号监听处理等。
初始化server的代码主要是一些赋值操做和相应结构体初始化:
// 初始化服务端实例 func initServer() { godis.Pid = os.Getpid() godis.DbNum = 16 initDb() godis.Start = time.Now().UnixNano() / 1000000 //var getf server.CmdFun getCommand := &core.GodisCommand{Name: "get", Proc: core.GetCommand} setCommand := &core.GodisCommand{Name: "set", Proc: core.SetCommand} godis.Commands = map[string]*core.GodisCommand{ "get": getCommand, "set": setCommand, } } // 初始化db func initDb() { godis.Db = make([]*core.GodisDb, godis.DbNum) for i := 0; i < godis.DbNum; i++ { godis.Db[i] = new(core.GodisDb) godis.Db[i].Dict = make(map[string]*core.GodisObject, 100) } }
这里简单解释下core.GodisCommand结构。该结构很简单,记录了命令的名字、函数指针和参数校验相关的信息。在执行命令前,校验命令是否存在的过程,须要查找支持的”命令表“。该命令表就是commands,commands由一组core.GodisCommand构成。commands中查不到的命令,则为不支持的命令;而命令参数须要知足哪些条件,由core.GodisCommand结构的其余字段记录。
当服务端准备就绪,开始接受请求。
请求到来,server会实例化一个client结构体,保存当前链接。该client结构体也在前篇有介绍,主要用来存储当前链接的db等信息。
// CreateClient 链接创建 建立client记录当前链接 func (s *Server) CreateClient(conn net.Conn) (c *Client) { c = new(Client) c.Db = s.Db[0] c.Argv = make([]*GodisObject, 5) c.QueryBuf = "" return c }
将请求的命令分解,校验无误后,调用响应函数执行。注意,只在当前client结构指向的db中执行插入、查询、更新等操做。若是须要操做其余db,执行"select"命令便将当前client指向的db指针指向select后的位置。
执行完成后,将结果写入到client结构的Buf字段。
下面的handle函数包括了client的建立、数据接收、执行和返回。
// 处理请求 func handle(conn net.Conn) { c := godis.CreateClient(conn) for { err := c.ReadQueryFromClient(conn) if err != nil { log.Println("readQueryFromClient err", err) return } c.ProcessInputBuffer() godis.ProcessCommand(c) responseConn(conn, c) } } // ProcessCommand 执行命令 func (s *Server) ProcessCommand(c *Client) { v := c.Argv[0].Ptr name, ok := v.(string) if !ok { log.Println("error cmd") os.Exit(1) } cmd := lookupCommand(name, s) if cmd != nil { c.Cmd = cmd call(c, s) } else { addReply(c, CreateObject(ObjectTypeString, fmt.Sprintf("(error) ERR unknown command '%s'", name))) } }
ProcessCommand函数先从命令表中查找命令,若是存在,调用该命令的实现,并将结果写入client.Buf字段。
将client.Buf内容,返回给请求方,完成。
最后将执行结果返回给请求方。
// 响应返回给客户端 func responseConn(conn net.Conn, c *core.Client) { conn.Write([]byte(c.Buf)) }
分别编译服务端和命令行客户端:
go build godis-server.go
go build godis-server.go
启动 ./godis-server

服务端启动:
cli请求: