当前仓库属于关闭状态,部分功能使用受限,详情请查阅 仓库状态说明
20 Star 56 Fork 12

Mr.li / Xedis
关闭

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

#Xedis


Xedis是一个内存kv数据库

你可曾觉得:

- redis的Keys命令性能太低?

- DEL命令不够好用、不支持pattern?

- 支持的数据结构太少,不能满足业务需求?

等等各种不尽如人意、不够契合业务场景的问题。是组合多种不同数据结构最后勉强满足业务需求,还是变更业务形态使之契合redis?都不如定制化来的靠谱。

  • 核心功能

Xedis就是用来解决这种不够契合的问题的,Xedis在数据结构(项目中称为Structures)、命令(Cmd)、Key的组织方式(项目中称为View)三个维度上提供了非常简单的扩展方法,只要用户懂一点点golang的语法即可(从零学golang语法通常只需要花费1-3个小时的学习时间)。

  • 默认兼容redis协议,便于使用。

在通信协议(默认使用redis协议)、通信方式(默认使用TCP)上都保留了扩展性。

目前,实现了两种通信协议:simple协议(一个简单的字符协议, 可以使用telnet交互)和redis协议(提供二进制读写功能)

获取Xedis包

go get git.oschina.net/yyzybb537/Xedis

编译&安装

cd $GOPATH/src/https://git.oschina.net/yyzybb537/Xedis.git

./build.sh

启动Xedis服务端

  // 启动Xedis监听8030端口
  $ ./Xedis
  $ ./Xedis -L ":8030"

  // 启动Xedis监听回环网卡的8030端口
  $ ./Xedis -L "127.0.0.1:8030"

  // 更多启动参数请看help手册
  $ ./Xedis -h

  // 命令帮助手册, 可以显示所有当前支持的命令、数据结构、协议、通信方式、View
  $ ./Xedis -D

使用Xedis客户端

Xedis有3种client:Xedis自带的golang客户端、 任一语言的redis客户端、 redis-cli/telnet命令行工具,后续还会提供xedis专属的命令行工具。

推荐使用Xedis自带的golang客户端,因为每一个自定义的命令在build之后都会生成相应格式的接口,并且可以使用代码补全工具来给出补全提示。

  • Example1:单链接的使用方式
package main

import (
        xedis "git.oschina.net/yyzybb537/Xedis/client"
        "fmt"
)

func main() {
        // 连接Xedis
        c, err := xedis.Dial("127.0.0.1:8030")
        if err != nil {
                fmt.Printf("Dial error:%s", err.Error())
                return 
        }
        defer c.Close()

        // 执行命令: SET A str
        err = c.Set("A", "str")
        if err != nil {
                fmt.Printf("Set error:%s", err.Error())
                return 
        }

        // 执行命令: GET A
        // * 将刚刚写入的内容读取出来
        s, err := c.Get("A")
        if err != nil {
                fmt.Printf("Get error:%s", err.Error())
                return 
        }

        fmt.Printf("A: %s", s)
}
  • Example2: 使用连接池
package main

import (
        xedis "git.oschina.net/yyzybb537/Xedis/client"
        "fmt"
)

func main() {
        // 使用DialPool接口创建连接池
        // * 连接成功后, 连接池的使用方式和单链接的方式完全一样!
        nRepeated := 10 // 连接池中链接的数量
        c, err := xedis.DialPool(nRepeated, "127.0.0.1:8030")
        if err != nil {
                fmt.Printf("Dial error:%s", err.Error())
                return 
        }
        defer c.Close()

        // 执行命令: SET A str
        err = c.Set("A", "str")
        if err != nil {
                fmt.Printf("Set error:%s", err.Error())
                return 
        }

        // 执行命令: GET A
        s, err := c.Get("A")
        if err != nil {
                fmt.Printf("Get error:%s", err.Error())
                return 
        }

        fmt.Printf("A: %s", s)
}

定制Xedis

对xedis的扩展需要使用golang语言,数据结构的扩展代码置于extend/structures目录下,View的扩展代码置于extend/view目录下。

扩展一个数据结构和相应的Cmd

扩展新的数据结构需要自定义一个struct,并符合如下interface:

type Structures interface {                                                        
    Name() string                                                                  
}

这个interface定义在interfaces/structures.go文件中,Name接口返回小写的结构名。

下面的代码定制了一个String结构, 其中的13-15行代码是将String结构体的工厂函数注册到系统中, 注意在14行代码的地方完成相关的初始化代码。

这个String结构定义了两个命令:Get Set,命令名大小写不敏感,且命令不可重复定义。 定义命令的方式是给String结构提供一个接口,接口名以Cmd开头,紧接着的下一个字符(Tag)必须是C、W、R中的一个,然后下一个字符必须是一个下划线,最后是命令名。

Tag: C表示这个命令可以触发创建一个新的String对象; W表示会改变String的内容; R表示对String是只读操作。 生成后的代码会给每个命令接口加上读写锁,CW的接口会加写锁,只读接口会加上读锁;因此,命令接口之间不要互相调用,以免死锁。 如果不希望代码生成阶段被加锁, 给String定义一个名为NoMutex的接口即可.

命令接口的输入参数只支持有限的内置类型,list如下: bool int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 string 以及这些类型的切片(Slice), 比如:[]string, []int等等. 在一个参数表中,切片类型只能有一个。

命令接口的输出参数除了上面的类型以外,还可以在末尾增加一个error类型的参数,用于输出命令执行过程中的错误信息。

接口的帮助信息要以注释的形式写在接口定义的前一行,只能以"//"开头,这部分信息可以在执行./Xedis -D时看到,也可以在客户端调用xedis.HelpInfo()接口来获取。

     1  package structures
     2
     3  import (
     4          "fmt"
     5          I "git.oschina.net/yyzybb537/Xedis/interfaces"
     6          "strconv"
     7  )
     8
     9  type String struct {
    10          Value string
    11  }
    12
    13  var stringRegister interface{} = I.RegisterStructures("string", func() I.Structures {
    14          return &String{}
    15  })
    16
    17  func (this *String) Name() string {
    18          return "string"
    19  }
    20
    21  // Set string value or create
    22  func (this *String) CmdC_Set(v string) {
    23          this.Value = v
    24  }
    25
    26  // Get value of string
    27  func (this *String) CmdR_Get() string {
    28          return this.Value
    29  }

让定制生效

将上文定制的代码保存到extend/structures/string.go文件中,重新执行./build.sh进行编译,即可完成定制。 注意保存的文件名需要与结构体同名,而且是全小写。

此时执行./Xedis -D可以看到增加了一个Structure和两个命令:

$ ./Xedis -D
----- Commands -----
[Structure] string:
  SET func(v string) ()
  summary: Set string value or create

  GET func() (string)
  summary: Get value of string

--------------------

扩展一个View

View是Xedis中所有Key-Value的组织结构,默认使用的是hash表。 当我们有高性能的前缀搜索、后缀搜索、前缀批量操作等需求时,hash表就不再适用,此时需要自定义全新的View来替换掉默认的View。

为了讲解简单,我们扩展一个用slice管理key-value的View。 View需要符合以下interface:

type View interface {
        Name() string
        GetKey(key string) Structures
        SetKey(key string, s Structures) bool
        DelKey(key string) bool
}

这个interface定义在interfaces/view.go文件中,Name接口返回小写的view名。 View的扩展与structures的扩展基本相同,代码如下:

package view

import (
        I "git.oschina.net/yyzybb537/Xedis/interfaces"
)

type structWrap struct {
        key string
        s I.Structures
}

type ListView struct {
        list    []*structWrap
}

var listViewRegister interface{} = I.RegisterView("list", func() I.View {
        return &ListView{ list : make([]*structWrap, 0) }
})

func (this *ListView) Name() string {
        return "list"
}

func (this *ListView) GetKey(key string) I.Structures {
        for _, s := range this.list {
                if s.key == key {
                        return s.s
        }
    }

        return nil
}

func (this *ListView) SetKey(key string, s I.Structures) bool {
        for _, s := range this.list {
                if s.key == key {
                        return false
        }
    }

        this.list = append(this.list, &structWrap{
                key : key,
                s : s,
        })
    return true
}

func (this *ListView) DelKey(key string) bool {
        for i, s := range this.list {
                if s.key == key {
                        this.list[i] = this.list[len(this.list) - 1]
                        this.list = this.list[:len(this.list) - 1]
                        return true
        }
    }

        return false
}

func patternMatch(s, pattern string) bool {
        return true
}

// Get some key name by pattern
func (this *ListView) CmdR_Keys(pattern string) (keys []string) {
        keys = make([]string, 0)
        for _, s := range this.list {
                if patternMatch(s.key, pattern) {
                        keys = append(keys, s.key)
                }
        }

        return
}

可以看到,这个View定义了CmdW_Keys接口,与数据结构的扩展一样的是,这个接口会生成一个名为KEYS的命令。但是不同之处在于,这个KEYS命令是专属于list这个View的,也就是说Xedis选用list来启动时这个命令才会生效。

客户端的调用View专属命令时要用c.ViewList().Keys接口来调用。 不同View之间,View专属命令是可以重名的。

GetKey/SetKey/DelKey以及自定义的命令接口都会被加上读写锁,因此不能相互调用,不然会死锁!

扩展一个全局命令

有时候我们需要一些操作多个key或不操作任何key的命令,我们称之为全局命令。 redis中有个MGET命令,用于一次性获取多个String的值。 下面以此为例讲解一下全局命令的扩展。

全局命令的扩展和普通命令基本一样,唯一的差别在于全局命令定义的是一个全局的函数,不再是类成员函数。

// Multiply GET command
func CmdR_MGet(keys []string) []string {
        r := make([]string, 0)
        for _, key := range keys {
				// 通过View找到key对应的数据结构
                s := I.GetView().GetKey(key)
                if s == nil {
                        r = append(r, "")
                        continue
                }

				// 判断数据结构是否是string
                if s.Name() != "string" {
                        r = append(r, "")
                        continue
                }

                r = append(r, s.(*String).CmdR_Get())
        }
        return r
}

代码中的I.GetView()是View的全局访问点,在这里可以调用当前View的接口找到key对应的数据结构.

将这段代码追加到string.go的文件尾部,重新执行./build.sh,一个全局命令就创建好了。

$ ./Xedis -D
----- Commands -----
[Structure] string:
  SET func(v string) ()
  summary: Set string value or create

  GET func() (string)
  summary: Get value of string

--------------------
----- Commands -----
[View] Global:
  MGET func(keys []string) ([]string)
  summary: Multiply GET command

--------------------
The MIT License (MIT) Copyright (c) 2017 Mr.li Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

一个支持定制化的内存kv数据库 展开 收起
Go
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Go
1
https://gitee.com/yyzybb537/Xedis.git
git@gitee.com:yyzybb537/Xedis.git
yyzybb537
Xedis
Xedis
master

搜索帮助