redigo
接口 Conn
type Conn interface {
// Close closes the connection.
Close() error
// Err returns a non-nil value when the connection is not usable.
Err() error
// Do sends a command to the server and returns the received reply.
Do(commandName string, args ...interface{}) (reply interface{}, err error)
// Send writes the command to the client's output buffer.
Send(commandName string, args ...interface{}) error
// Flush flushes the output buffer to the Redis server.
Flush() error
// Receive receives a single reply from the Redis server
Receive() (reply interface{}, err error)
}
获取 Conn
常用 Dial
方法来获取一个 redis
链接
c, err := redis.Dial("tcp", "localhost:6379",
redis.DialUsername("username"),
redis.DialPassword("password"),
)
if err != nil {
// handle error
}
defer c.Close()
// Dial connects to the Redis server at the given network and
// address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error) {
return DialContext(context.Background(), network, address, options...)
}
// DialContext connects to the Redis server at the given network and
// address using the specified options and context.
func DialContext(ctx context.Context, network, address string, options ...DialOption) (Conn, error) {
do := dialOptions{
dialer: &net.Dialer{
KeepAlive: time.Minute * 5,
},
}
for _, option := range options {
option.f(&do) // 简化 赋值
}
if do.dialContext == nil {
do.dialContext = do.dialer.DialContext
}
netConn, err := do.dialContext(ctx, network, address) //使用 net.Dialer dialContext 连接服务器
if err != nil {
return nil, err
}
if do.useTLS {
var tlsConfig *tls.Config
if do.tlsConfig == nil {
tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify}
} else {
tlsConfig = cloneTLSConfig(do.tlsConfig)
}
if tlsConfig.ServerName == "" {
host, _, err := net.SplitHostPort(address)
if err != nil {
netConn.Close()
return nil, err
}
tlsConfig.ServerName = host
}
tlsConn := tls.Client(netConn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
netConn.Close()
return nil, err
}
netConn = tlsConn
}
c := &conn{
conn: netConn, // net.Conn 构造 Conn返回
bw: bufio.NewWriter(netConn),
br: bufio.NewReader(netConn),
readTimeout: do.readTimeout,
writeTimeout: do.writeTimeout,
}
if do.password != "" {
authArgs := make([]interface{}, 0, 2)
if do.username != "" {
authArgs = append(authArgs, do.username)
}
authArgs = append(authArgs, do.password)
if _, err := c.Do("AUTH", authArgs...); err != nil { // auth
netConn.Close()
return nil, err
}
}
if do.clientName != "" {
if _, err := c.Do("CLIENT", "SETNAME", do.clientName); err != nil {
netConn.Close()
return nil, err
}
}
if do.db != 0 {
if _, err := c.Do("SELECT", do.db); err != nil { // db
netConn.Close()
return nil, err
}
}
return c, nil
}
执行 Do
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
return c.DoWithTimeout(c.readTimeout, cmd, args...)
}
func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
c.mu.Lock()
pending := c.pending
c.pending = 0
c.mu.Unlock()
if cmd == "" && pending == 0 {
return nil, nil
}
if c.writeTimeout != 0 {
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) //设置 写超时时间
}
if cmd != "" {
if err := c.writeCommand(cmd, args); err != nil { //写命令
return nil, c.fatal(err)
}
}
if err := c.bw.Flush(); err != nil {
return nil, c.fatal(err)
}
var deadline time.Time
if readTimeout != 0 {
deadline = time.Now().Add(readTimeout)
}
c.conn.SetReadDeadline(deadline) // 设置 读超时时间
if cmd == "" {
reply := make([]interface{}, pending)
for i := range reply {
r, e := c.readReply()
if e != nil {
return nil, c.fatal(e)
}
reply[i] = r
}
return reply, nil
}
var err error
var reply interface{}
for i := 0; i <= pending; i++ {
var e error
if reply, e = c.readReply(); e != nil { //readReply 读取结果
return nil, c.fatal(e)
}
if e, ok := reply.(Error); ok && err == nil {
err = e
}
}
return reply, err
}
发送命令
redis 基本协议
*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF
示例:
*3
$3
SET
$5
mykey
$7
myvalue
func (c *conn) writeCommand(cmd string, args []interface{}) error {
c.writeLen('*', 1+len(args)) // *<参数数量> CR LF
if err := c.writeString(cmd); err != nil { // $<参数 1 的字节数量> CR LF <参数 1 的数据> CR LF
return err
}
for _, arg := range args {
if err := c.writeArg(arg, true); err != nil { // $<参数 N 的字节数量> CR LF <参数 N 的数据> CR LF
return err
}
}
return nil
}
读取结果
func (c *conn) readReply() (interface{}, error) {
line, err := c.readLine()
if err != nil {
return nil, err
}
if len(line) == 0 {
return nil, protocolError("short response line")
}
switch line[0] {
case '+':
switch string(line[1:]) {
case "OK":
// Avoid allocation for frequent "+OK" response.
return okReply, nil
case "PONG":
// Avoid allocation in PING command benchmarks :)
return pongReply, nil
default:
return string(line[1:]), nil
}
case '-':
return Error(string(line[1:])), nil
case ':':
return parseInt(line[1:])
case '$':
n, err := parseLen(line[1:])
if n < 0 || err != nil {
return nil, err
}
p := make([]byte, n)
_, err = io.ReadFull(c.br, p)
if err != nil {
return nil, err
}
if line, err := c.readLine(); err != nil {
return nil, err
} else if len(line) != 0 {
return nil, protocolError("bad bulk string format")
}
return p, nil
case '*':
n, err := parseLen(line[1:])
if n < 0 || err != nil {
return nil, err
}
r := make([]interface{}, n)
for i := range r {
r[i], err = c.readReply()
if err != nil {
return nil, err
}
}
return r, nil
}
return nil, protocolError("unexpected response line")
}
结果转化
reply.go 中提供各种方法格式化返回结果
例如 String 方法
func String(reply interface{}, err error) (string, error) {
if err != nil {
return "", err
}
switch reply := reply.(type) {
case []byte:
return string(reply), nil
case string:
return reply, nil
case nil:
return "", ErrNil
case Error:
return "", reply
}
return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
}