redis vs http,你选哪一个?

Saner-Lee · 2021-08-20 18:06:23 · 1883 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2021-08-20 18:06:23 的主题,其中的信息可能已经有所发展或是发生改变。

redigosrv

what

redis server侧的golang版本实现。

why

作为后端RD,你开发的,维护的最多的应该就是你的server,作为服务的提供方来说,想要和客户端进行有效的交互,首先要要能理解信息的含义,因此一套通信协议是必须的。

为什么选择redis协议,应用层有很多成熟的协议,比如

  • http
  • http2
  • websockets

当然,上述协议都很强大,但是作为内网的服务来说,需要的协议最重要的一点是可阅读的文本,因为文本协议是好理解,便于观察的,而且内网做好权限收敛即可,不需要做加密的二进制协议。

那么为什么不使用http呢?http很强大,适用范围也比redis更广。但是任何东西都是有限制的,比如:

  • 服务是存储介质/proxy
  • 协议数据包尽可能的小,提高带宽利用率

这时候redis协议更加适合!

很多开发者自己实现redis server的时候,都是自己解析协议,根据参数找到对应的handler。开发者初期通常认为程序很小,没有做抽象,因此最终的实现都耦合在一起,并且一旦规模大起来后很难再维护。

因此你需要一个库帮你屏蔽底层,提高可维护性,它拥有的能力包括:

  • 路由注册/寻址
  • 连接限制策略
  • 支持监控接口
  • 中间件能力
  • 异常捕获
  • 优雅关闭

什么,你担心不安全,放心,这是在我在百度项目中已经使用很久的库了,重构的服务支持了500+微服务,历经一年没出过性能问题和资源问题。并且解决了当时耦合导致的很多性能问题和资源泄露问题。

放心大胆的使用它,如果有什么需求可以直接issue搞起,长期维护。

how

go语言的net/http的基础上修改开发,接口完全兼容,将你的学习成本降到最低。你完全可以在5分钟内学会如何使用!

example中附带了一个小例子,为了方便阅读,内容直接copy到下面了。

package main

import (
    "context"
    "fmt"
    "log"
    "runtime/debug"
    "strings"
    "sync"
    "time"

    "github.com/Saner-Lee/redigosrv/redis"
)

func main() {
    ExampleServer()
}

func ExampleServer() {
    // 限制连接数量为1024
    // 达到上限时,不完全阻塞
    // 阻塞到500ms未等到可用连接返回redis错误
    sema, err := redis.NewSemaphore(1024, redis.WithBlock(false), redis.WithWaitDura(500*time.Millisecond))
    if err != nil {
        panic(err)
    }

    // cmd get -> handler
    redis.HandleFunc("GET", func(_ context.Context, w redis.ResponseWriter, r *redis.Request) {
        fmt.Printf("rece get cmd, req %v\n", r)
        w.Text("OK")
    })

    opts := []redis.Option{
        // 连接限制策略
        redis.WithSemaphore(sema),
        // 异常捕获机制
        redis.WithPanicStack(func(format string, args ...interface{}) {
            fmt.Printf("redis rece a panic, err %s", fmt.Sprintf(format, args...))
            debug.PrintStack()
        }),
        // access日志,为监控提供数据源
        redis.WithLoggerFactory(func() redis.Logger { return &logger{} }),
    }

    // 启动Server
    redis.ListenAndServe("0.0.0.0:6379", nil, opts...)
}

type logger struct {
    mu sync.RWMutex
    m  map[string]string
}

func (l *logger) prefix() string {
    l.mu.RLock()
    defer l.mu.RUnlock()

    if len(l.m) == 0 {
        return ""
    }

    var secs []string
    for k, v := range l.m {
        secs = append(secs, fmt.Sprintf("%s:%s", k, v))
    }
    return strings.Join(secs, " ")
}

func (l *logger) Debug(s string, args ...interface{}) {
    p := l.prefix()
    log.Printf(p+s, args...)
}

func (l *logger) Info(s string, args ...interface{}) {
    p := l.prefix()
    log.Printf(p+s, args...)
}

func (l *logger) Notice(s string, args ...interface{}) {
    p := l.prefix()
    log.Printf(p+s, args...)
}

func (l *logger) Warn(s string, args ...interface{}) {
    p := l.prefix()
    log.Printf(p+s, args...)
}

func (l *logger) Error(s string, args ...interface{}) {
    p := l.prefix()
    log.Printf(p+s, args...)
}

func (l *logger) AddMeta(k, v string) {
    l.mu.Lock()
    defer l.mu.Unlock()

    if l.m != nil {
        l.m = make(map[string]string)
    }
    l.m[k] = v
}

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1883 次点击  ∙  2 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传