Golang中如何用EchoVault替代Redis实现内存数据存储

Golang中如何用EchoVault替代Redis实现内存数据存储 大家好,

在过去的一年里,我一直在致力于构建 EchoVault。EchoVault 是一个内存数据存储,既可以通过 TCP 进行客户端-服务器通信,也可以完全嵌入到 Go 应用程序中:

当前功能:

  1. 支持 TLS 和 mTLS,适用于多个服务器和客户端的 RootCA。
  2. 使用 RAFT 算法支持复制集群。
  3. 用于用户身份验证和授权的 ACL 层。
  4. 具有消费者组的分布式发布/订阅功能。
  5. 集合、有序集合、哈希、列表等数据结构。
  6. 具有快照和仅追加文件功能的持久化层。
  7. 多种键淘汰策略。
  8. 通过共享对象文件扩展命令。
  9. 通过嵌入式 API 扩展命令。

还有许多功能正在开发中,例如:

  1. 分片
  2. 位图
  3. HyperLogLog
  4. Lua 模块

EchoVault 通过 TCP 完全兼容 RESP 协议,因此您可以使用现有的 Redis 客户端与 EchoVault 服务器进行通信。

为什么需要它?

以下是我决定构建 EchoVault 的一些原因:

  1. Redis 虽然轻量,但仍然是一个必须运行的外部服务。EchoVault 允许您嵌入一个内存存储,提供 Redis 功能的一个子集,直接集成到您的应用程序中,这样您就无需担心管理单独的服务。

  2. EchoVault 消除了处理 Redis 复制集群的需要。EchoVault 的嵌入式实例可以通过简单的配置连接起来形成一个集群。因此,您的应用程序的每个实例都可以使用嵌入式 API,而 EchoVault 会为您处理复制。

  3. 鉴于今年早些时候 Redis 的许可证风波,我相信 EchoVault 将能够填补一个角色,特别是在 Go 生态系统中。EchoVault 采用 Apache 2.0 许可证。

EchoVault 的目标是在许多通常适合使用 Redis 的 Go 应用程序或生态系统场景中,使 Redis 变得不再必要。随着时间的推移,我们旨在让这个适用场景列表越来越长。

EchoVault 可能的用例

您可以在许多与 Redis 相同的场景中使用 EchoVault,包括但不限于:

  1. 服务发现。
  2. 内存数据缓存。
  3. 会话管理(跨应用程序的多个实例)。
  4. 发布/订阅工作流。
  5. 无序的键/值存储。

请查看 GitHub 仓库,如果您喜欢这个项目,欢迎给它点个星。

也欢迎贡献代码!


更多关于Golang中如何用EchoVault替代Redis实现内存数据存储的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何用EchoVault替代Redis实现内存数据存储的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


EchoVault作为Go原生的内存数据存储方案,确实为Go开发者提供了Redis的替代选择。以下是几种使用EchoVault的方式:

1. 嵌入式使用示例

package main

import (
    "context"
    "fmt"
    "github.com/echovault/echovault"
)

func main() {
    // 创建嵌入式实例
    ev, err := echovault.NewEchoVault(
        echovault.WithConfig(echovault.Config{
            DataDir:          "./data",
            EvictionPolicy:   "allkeys-lru",
            BindAddr:         "localhost",
            Port:             9851,
            EnablePersistence: true,
        }),
    )
    if err != nil {
        panic(err)
    }
    defer ev.Shutdown()

    ctx := context.Background()
    
    // 设置键值
    err = ev.Set(ctx, "user:1001", "John Doe")
    if err != nil {
        panic(err)
    }
    
    // 获取值
    value, err := ev.Get(ctx, "user:1001")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Value: %s\n", value)
    
    // 使用哈希
    err = ev.HSet(ctx, "user:profile:1001", map[string]interface{}{
        "name":  "John",
        "email": "john@example.com",
        "age":   30,
    })
    if err != nil {
        panic(err)
    }
    
    // 获取哈希字段
    email, err := ev.HGet(ctx, "user:profile:1001", "email")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Email: %s\n", email)
}

2. 集群配置示例

package main

import (
    "context"
    "fmt"
    "github.com/echovault/echovault"
)

func main() {
    // 配置集群节点
    config := echovault.Config{
        DataDir:          "./node1_data",
        BindAddr:         "localhost",
        Port:             9851,
        ClusterConfig: echovault.ClusterConfig{
            Enabled: true,
            Nodes: []string{
                "localhost:9851",
                "localhost:9852",
                "localhost:9853",
            },
            NodeID: "node1",
        },
    }
    
    ev, err := echovault.NewEchoVault(
        echovault.WithConfig(config),
    )
    if err != nil {
        panic(err)
    }
    defer ev.Shutdown()
    
    // 集群会自动处理数据复制
    ctx := context.Background()
    err = ev.Set(ctx, "cluster_key", "replicated_value")
    if err != nil {
        panic(err)
    }
}

3. 使用现有Redis客户端连接

package main

import (
    "context"
    "fmt"
    "github.com/redis/go-redis/v9"
)

func main() {
    // 使用标准的Redis客户端连接EchoVault
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:9851",
        Password: "", // 如果配置了ACL
        DB:       0,
    })
    
    ctx := context.Background()
    
    // 所有Redis命令都可以正常使用
    err := rdb.Set(ctx, "key", "value", 0).Err()
    if err != nil {
        panic(err)
    }
    
    val, err := rdb.Get(ctx, "key").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println("key", val)
    
    // 发布订阅示例
    pubsub := rdb.Subscribe(ctx, "mychannel")
    defer pubsub.Close()
    
    // 发布消息
    err = rdb.Publish(ctx, "mychannel", "hello").Err()
    if err != nil {
        panic(err)
    }
    
    // 接收消息
    msg, err := pubsub.ReceiveMessage(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Received: %s from channel %s\n", msg.Payload, msg.Channel)
}

4. 自定义命令扩展

package main

import (
    "context"
    "fmt"
    "github.com/echovault/echovault"
    "github.com/echovault/echovault/internal/commands"
)

// 自定义命令
func customIncrByFloat(ctx context.Context, ev *echovault.EchoVault, params []string) (interface{}, error) {
    if len(params) != 2 {
        return nil, fmt.Errorf("wrong number of arguments")
    }
    
    key := params[0]
    increment := params[1]
    
    // 实现自定义逻辑
    current, err := ev.Get(ctx, key)
    if err != nil {
        // 键不存在,从0开始
        current = "0"
    }
    
    // 转换为浮点数并增加
    // ... 实现浮点数增加逻辑
    
    return result, nil
}

func main() {
    ev, err := echovault.NewEchoVault(
        echovault.WithConfig(echovault.Config{
            BindAddr: "localhost",
            Port:     9851,
        }),
    )
    if err != nil {
        panic(err)
    }
    
    // 注册自定义命令
    ev.RegisterCommand("CUSTOMINCRBYFLOAT", customIncrByFloat)
    
    // 现在可以通过客户端调用CUSTOMINCRBYFLOAT命令
}

5. 会话管理示例

package main

import (
    "context"
    "fmt"
    "github.com/echovault/echovault"
    "time"
)

type SessionManager struct {
    ev *echovault.EchoVault
}

func NewSessionManager(ev *echovault.EchoVault) *SessionManager {
    return &SessionManager{ev: ev}
}

func (sm *SessionManager) CreateSession(userID string, data map[string]interface{}) (string, error) {
    ctx := context.Background()
    sessionID := generateSessionID()
    
    // 存储会话数据
    err := sm.ev.HSet(ctx, "session:"+sessionID, data)
    if err != nil {
        return "", err
    }
    
    // 设置过期时间
    err = sm.ev.Expire(ctx, "session:"+sessionID, 24*time.Hour)
    if err != nil {
        return "", err
    }
    
    // 关联用户ID和会话ID
    err = sm.ev.Set(ctx, "user:session:"+userID, sessionID)
    if err != nil {
        return "", err
    }
    
    return sessionID, nil
}

func (sm *SessionManager) GetSession(sessionID string) (map[string]interface{}, error) {
    ctx := context.Background()
    return sm.ev.HGetAll(ctx, "session:"+sessionID)
}

func generateSessionID() string {
    // 生成唯一会话ID
    return fmt.Sprintf("session_%d", time.Now().UnixNano())
}

func main() {
    ev, _ := echovault.NewEchoVault(
        echovault.WithConfig(echovault.Config{
            BindAddr: "localhost",
            Port:     9851,
        }),
    )
    
    sm := NewSessionManager(ev)
    
    // 创建会话
    sessionID, err := sm.CreateSession("user123", map[string]interface{}{
        "username": "john_doe",
        "role":     "admin",
        "last_login": time.Now().Unix(),
    })
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Session created: %s\n", sessionID)
}

EchoVault的RESP协议兼容性意味着现有的Redis工具和客户端库可以直接使用,同时嵌入式API为Go应用程序提供了更紧密的集成方式。对于需要内存数据存储且希望避免外部依赖的Go项目,EchoVault确实是一个值得考虑的选项。

回到顶部