+
53
-

go如何写一个类似redis的nosql数据库让python客户端调用?

go如何写一个类似redis的nosql数据库让python客户端调用?


网友回复

+
29
-

我这种写法兼容redis的协议,可以让Python的redis库连接操作。

Go服务器端代码

// main.go
package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "strconv"
    "strings"
    "sync"
    "time"
)

// 数据存储结构
type DataStore struct {
    mu      sync.RWMutex
    data    map[string]string
    expires map[string]time.Time
}

// 创建新的数据存储
func NewDataStore() *DataStore {
    ds := &DataStore{
        data:    make(map[string]string),
        expires: make(map[string]time.Time),
    }
    // 启动过期清理协程
    go ds.cleanupExpired()
    return ds
}

// 定期清理过期的键
func (ds *DataStore) cleanupExpired() {
    ticker := time.NewTicker(1 * time.Second)
    for range ticker.C {
        ds.mu.Lock()
        now := time.Now()
        for key, expireTime := range ds.expires {
            if now.After(expireTime) {
                delete(ds.data, key)
                delete(ds.expires, key)
            }
        }
        ds.mu.Unlock()
    }
}

// SET操作
func (ds *DataStore) Set(key, value string, expire int) {
    ds.mu.Lock()
    defer ds.mu.Unlock()

    ds.data[key] = value
    if expire > 0 {
        ds.expires[key] = time.Now().Add(time.Duration(expire) * time.Second)
    } else {
        delete(ds.expires, key)
    }
}

// GET操作
func (ds *DataStore) Get(key string) (string, bool) {
    ds.mu.RLock()
    defer ds.mu.RUnlock()

    // 检查是否过期
    if expireTime, exists := ds.expires[key]; exists {
        if time.Now().After(expireTime) {
            return "", false
        }
    }

    value, exists := ds.data[key]
    return value, exists
}

// DEL操作
func (ds *DataStore) Del(keys []string) int {
    ds.mu.Lock()
    defer ds.mu.Unlock()

    count := 0
    for _, key := range keys {
        if _, exists := ds.data[key]; exists {
            delete(ds.data, key)
            delete(ds.expires, key)
            count++
        }
    }
    return count
}

// EXISTS操作
func (ds *DataStore) Exists(keys []string) int {
    ds.mu.RLock()
    defer ds.mu.RUnlock()

    count := 0
    for _, key := range keys {
        if _, exists := ds.data[key]; exists {
            // 检查是否过期
            if expireTime, hasExpire := ds.expires[key]; hasExpire {
                if time.Now().Before(expireTime) {
                    count++
                }
            } else {
                count++
            }
        }
    }
    return count
}

// KEYS操作
func (ds *DataStore) Keys(pattern string) []string {
    ds.mu.RLock()
    defer ds.mu.RUnlock()

    var keys []string
    now := time.Now()

    for key := range ds.data {
        // 检查是否过期
        if expireTime, hasExpire := ds.expires[key]; hasExpire {
            if now.After(expireTime) {
                continue
            }
        }

        // 简单的模式匹配(只支持*)
        if pattern == "*" || strings.Contains(key, strings.ReplaceAll(pattern, "*", "")) {
            keys = append(keys, key)
        }
    }
    return keys
}

// EXPIRE操作
func (ds *DataStore) Expire(key string, seconds int) bool {
    ds.mu.Lock()
    defer ds.mu.Unlock()

    if _, exists := ds.data[key]; exists {
        ds.expires[key] = time.Now().Add(time.Duration(seconds) * time.Second)
        return true
    }
    return false
}

// TTL操作
func (ds *DataStore) TTL(key string) int {
    ds.mu.RLock()
    defer ds.mu.RUnlock()

    if _, exists := ds.data[key]; !exists {
        return -2 // 键不存在
    }

    if expireTime, hasExpire := ds.expires[key]; hasExpire {
        ttl := int(time.Until(expireTime).Seconds())
        if ttl < 0 {
            return -2 // 已过期
        }
        return ttl
    }

    return -1 // 没有设置过期时间
}

// Redis服务器
type RedisServer struct {
    store *DataStore
}

// 创建新的Redis服务器
func NewRedisServer() *RedisServer {
    return &RedisServer{
        store: NewDataStore(),
    }
}

// 解析RESP协议
func (s *RedisServer) parseCommand(re...

点击查看剩余70%

+
24
-

这个项目将分为两部分:

Go 服务端 (GoRedis):一个用 Go 语言编写的、并发的、基于 TCP 的键值存储服务器。它会监听一个端口,接收并处理类似 Redis 的命令(如 SET, GET, DEL)。为了简单起见,我们将实现一个简单的文本协议,而不是 Redis 复杂的 RESP 协议。

Python 客户端:一个用 Python 编写的客户端。重要提示:由于我们的 Go 服务器使用自定义的简单文本协议,标准的 redis-py 库将无法直接连接。redis-py 库是为与遵循 RESP 协议的 Redis 服务器通信而设计的。因此,我将提供一个使用 Python socket 库的自定义客户端来与我们的 Go 服务器交互,并解释为什么 redis-py 不能用,以及如果想让它能用需要做什么。

第 1 部分:Go 内存 NoSQL 服务器 (GoRedis)

这个服务器将具备以下特点:

并发处理:为每个客户端连接创建一个新的 Goroutine。

线程安全:使用 sync.RWMutex 来保护对内存中数据的并发读写,允许多个读操作同时进行,但写操作是独占的。

支持的命令:SET key value, GET key, DEL key, PING, QUIT。

网络协议:简单的基于文本行的协议(例如,命令和参数用空格隔开,以 \n 结尾)。

代码 (server.go)
package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net"
    "strings"
    "sync"
)

// dataStore 是我们的内存键值存储
var dataStore = make(map[string]string)

// rwMutex 用于保护对 dataStore 的并发访问
// RWMutex 允许并发读取,但写入是独占的,非常适合我们的场景
var rwMutex = &sync.RWMutex{}

const (
    // 定义服务器监听的端口
    SERVER_PORT = "6380" // 使用一个不同于默认Redis端口的端口
    SERVER_TYPE = "tcp"
)

func main() {
    log.Println("GoRedis Server is starting...")

    // 监听指定的TCP端口
    server, err := net.Listen(SERVER_TYPE, ":"+SERVER_PORT)
    if err != nil {
        log.Fatalf("Error listening: %v", err)
    }
    // 在main函数结束时关闭监听器
    defer server.Close()

    log.Printf("Listening on %s:%s", "localhost", SERVER_PORT)

    // 无限循环,等待并接受新的客户端连接
    for {
        conn, err := server.Accept()
        if err != nil {
            log.Printf("Error accepting connection: %v", err)
            continue
        }
        // 为每个连接创建一个新的Goroutine来处理
        go handleConnection(conn)
    }
}

// handleConnection 处理单个客户端连接
func handleConnection(conn net.Conn) {
    // 在函数结束时确保连接被关闭
    defer conn.Close()
    log.Printf("Client connected: %s", conn.RemoteAddr().String())

    // 使用bufio.Reader来方便地读取一行数据
    reader := bufio.NewReader(conn)

    for {
        // 读取直到遇到换行符 '\n'
        commandLine, err := reader.ReadString('\n')
        if err != nil {
            if err != io.EOF {
                log.Printf("Error reading from client %s: %v", conn.RemoteAddr().String(), err)
            } else {
                log.Printf("Client %s disconnected", conn.RemoteAddr().String())
            }
            return // 发生错误或客户端关闭连接时,退出循环
        }

        // 去除命令行的前后空白字符(包括换行符)
        commandLine = strings.TrimSpace(commandLine)
        if commandLine == "" {
            continue // 忽略空行
        }

        // 解析命令和参数
        parts := strings.Fields(commandLine) // Fields按空白字符分割
        command := strings.ToUpper(parts[0])
        args := parts[1:]

        // 根据命令执行相应的操作
        switch command {
        case "SET":
            handleSet(conn, args)
        case "GET":
            handleGet(conn, args)
        case "DEL":
            handleDel(conn, args)
        case "PING":
         ...

点击查看剩余70%

我知道答案,我要回答