go如何写一个类似redis的nosql数据库让python客户端调用?
网友回复
我这种写法兼容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%
这个项目将分为两部分:
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%


