挥别低速,拥抱Redis:数据存储的新范式 | 青训营

382 阅读20分钟

Redis 基础概念

什么是Redis

Redis(Remote Dictionary Server) 是一个开源的内存数据存储系统(NoSQL技术),它可以用作缓存、数据库和消息队列。它支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,并且所有数据都存储在内存中,这使得它具有非常高的读写速度。由于数据存储在内存中,Redis常被用作高速缓存,从而显著提高访问数据的速度

同时,Redis数据库是一种非关系型数据库,其存储形式就是key-value格式进行存储,memory,disk,file等持久化存储都可以实现。key-value型数据库的特点就是可以直接根据key能够定位得到对应的值,因此,相较于关系型数据库的表结构查询的方式,非关系型数据库执行效率非常高,能够满足我们高流量,高并发的需要。

为什么要使用Redis?Redis与MySQL的关系?

在我们前面的学习中,我们知道,MySQL对应了我们代码中的持久层,它是一个关系型数据库管理系统,用于持久性存储和管理结构化数据。它适用于存储大量数据,支持复杂的查询操作,以及具备数据持久化的特性。

而在有MySQL和Redis两个数据库可以选择使用时,我们应该从以下几个方面对比评估:

  1. 读写速度和性能: Redis由于数据存储在内存中,读取速度非常快,适合用作缓存来加速读取操作。如果你的项目需要高速读取和查询操作,那么使用Redis作为缓存层可能会对性能有所帮助。
  2. 数据持久性: Redis虽然支持持久化选项,但它的主要优势在于内存中的高速读写,而不是长期数据存储。如果你需要保证数据长期存储并且具备强大的事务支持,MySQL等关系型数据库可能更合适。
  3. 数据一致性: Redis是一个键值存储系统,不同于MySQL的表结构。如果你的数据需要复杂的关系和查询,关系型数据库更适合保持数据的一致性和结构化。
  4. 缓存需求: 如果你的应用需要频繁读取相同的数据,使用Redis作为缓存可以大大减轻数据库的负担,提高整体性能。

综上所述,Redis通常与MySQL一起使用,但用途不同。MySQL用于持久性数据存储和复杂查询,而Redis用于高速缓存、实时计数、消息队列等。在项目中是否需要同时使用它们取决于项目的具体需求和性能优化的目标。

项目中的应用

我们的目标是做一个短视频平台的简易项目,在这里我们可以使用Redis来实现多种功能,以提升性能和用户体验:

  1. 缓存热门视频和用户数据: Redis适合用作缓存层,将热门的视频数据和用户信息存储在内存中,以减少从数据库中的频繁读取。这可以大大提高访问速度,减轻数据库的负载。
  2. 实时计数和排名: 在短视频平台中,你可能需要追踪视频的播放次数、点赞数、评论数等。Redis的高速读写能力使其适合用作实时计数和排名系统,可以实时更新这些数据并展示热门内容。
  3. 会话管理和登录状态: 使用Redis存储用户的会话信息和登录状态,可以方便地管理用户的登录和登出状态,从而实现用户认证和权限管理。
  4. 消息队列: 如果你的平台需要处理后台任务,如视频转码、通知推送等,可以使用Redis作为消息队列,将任务推送到队列中并由后台工作者处理。
  5. 实时评论和聊天: 如果你的平台支持实时评论和聊天功能,可以使用Redis的发布订阅机制来实现实时消息的传递和处理。
  6. 限流和防刷: 在短视频平台中,可能需要限制用户对视频的操作频率,以防止恶意刷点击、评论等行为。Redis可以用来实现限流和防刷机制,控制用户操作的速率。
  7. ~地理位置数据: 如果你的平台支持基于地理位置的功能,比如附近的视频或用户推荐,Redis的地理位置数据结构可以帮助你存储和查询地理位置信息。

那么,我们本篇文章的目标也就出现了,即完成这7个实现要求。

安装

由于我们文章重点不在这里,此处就直接引用菜鸟教程的文章了 Redis 安装 | 菜鸟教程 (runoob.com)

但是可以讲一下报错(因为自己遇到了,而且搞半天(T ^ T) )

  1. 在输入redis-server.exe redis.windows.conf时报错

无法将“D:\Java\Redis>”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

缺少路径——添加完整路径 D:\Java\Redis\redis-server.exe redis.windows.conf

  1. 更改后也没出现图像而是报出类似

[16104] 24 Aug 11:22:41.929 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo

[16104] 24 Aug 11:22:41.929 # Redis version=5.0.14.1, bits=64, commit=ec77f72d, modified=0, pid=16104, just started

[16104] 24 Aug 11:22:41.929 # Configuration loaded

[16104] 24 Aug 11:22:41.931 # Could not create server TCP listening socket 127.0.0.1:6379: bind: 操作成功完成。

原因是端口被占用了

然后按顺序输入如下命令就可以连接成功(重启)

  1. redis-cli.exe
  2. shutdown
  3. exit
  4. redis-server.exe redis.windows.conf

image.png image.png

配置

redis-server是服务器端、redis-cli是客户端 我们需要先打开服务器端才能打开客户端,另打开一个cmd,而且原先的不能关 不然就会报错

Could not connect to Redis at 127.0.0.1:6379: 由于目标计算机积极拒绝,无法连接。

环境配置

系统变量Path增加Redis安装目录的绝对路径

image.png

就不用输入路径了

设置成windows下的服务

我们虽然成功启动了服务端,但只要关闭了cmd,redis服务就会停止,所以要把redis设置成windows下的服务

设置服务命令

redis-server.exe --service-install redis.windows.conf --loglevel verbose

这是我们只需要进入任务管理器,打开服务即可,不用再用一个cmd窗口了(当然你觉得那边更方便也可以继续用)(具体见下一步)

关闭开机自启动

在任务管理器服务模块我们可以看见redis

image.png 在这里我们可以将停止状态直接改成开启

如需改为手动启动,右键此电脑,点击"管理",打开"服务",找到Redis,右键点击"属性",修改启动类型为"手动",点击"应用"“确定”,关闭窗口即可

image.png

卸载服务

redis-server --service-uninstall

修改密码

我们默认是没有密码的,需要修改配置文件设置永久密码

打开安装目录下的redis.windows.conf文件(VSCode或者其他文本编辑器),搜索(Ctrl + F)"requirepass foobared",在下一行输入"requirepass password",保存后重启服务即可。

image.png

配置多个ip地址(远程连接)

  1. 关闭redis的保护模式

image.png

  1. 在redis的配置文件redis.conf中,找到bind localhost注释掉。

注释掉本机,局域网内的所有计算机都能访问。

bind localhost 只能本机访问,局域网内计算机不能访问。

bind 局域网IP 只能局域网内IP的机器访问, 本地localhost都无法访问。

  1. 如果希望允许来自任何 IP 地址的连接,可以将它设置为 0.0.0.0

使用SSH连接Redis

如果设置成0.0.0.0其实是很不安全的,我们可以使用 SSH 隧道(SSH tunnel)在安全的加密通道内连接到远程的 Redis 服务器。SSH 隧道将本地端口与远程端口建立连接,并通过 SSH 加密进行通信,从而增强了连接的安全性。

可以参考:GO语言使用SSH连接Redis_gostream的博客-CSDN博客

  1. SSH 登录远程服务器: 打开终端,并使用 SSH 登录到远程服务器。在终端中运行以下命令,将其中的 your_username 替换为你在远程服务器上的用户名,以及 remote_server_ip 替换为远程服务器的 IP 地址。

    ssh your_username@remote_server_ip
    

    输入你的密码或者 SSH 密钥密码,以登录到远程服务器。

  2. 创建 SSH 隧道: 在 SSH 登录会话中,运行以下命令来创建 SSH 隧道。在这个示例中,我们将本地端口 6379 与远程服务器的 Redis 服务器的端口 6379 建立连接。可以根据你的实际情况修改端口号。

    ssh -L 6379:127.0.0.1:6379 your_username@remote_server_ip
    

    这将在本地计算机上创建一个监听本地端口 6379 的 SSH 隧道,并将其连接到远程服务器的 Redis 服务器的 127.0.0.1:6379

  3. 连接本地 Redis 客户端: 打开另一个终端窗口,在本地计算机上运行 Redis 客户端命令。可以直接使用本地计算机上的 Redis 客户端工具,比如 redis-cli

    此时,本地 Redis 客户端将通过 SSH 隧道连接到远程服务器上的 Redis 服务器。

通过这个 SSH 隧道,我们可以在加密通道内安全地与远程 Redis 服务器通信。当我们不再需要连接时,只需关闭终端中的 SSH 隧道会话即可。这种方式在远程访问 Redis 服务器时提供了额外的安全性。

Redis 基本语法

数据类型

数据类型介绍特性用法
String(字符串)一个可变的字节数组可以包含任何数据,比如jpg图片或者序列化的对象(最大存储512M )初始化、获取、长度、子串、覆盖、追加、计数、过期、删除等操作
Hash(哈希)二维结构,第一维是数组,第二维是链表(HashMap)一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象存储、读取、修改,普通用户属性
List(列表)链表(双向链表),按照插入顺序排序随机定位性能较弱,首尾插入删除性能较优。可以使用自然数或者负下标(从-1开始递减)适合做消息队列,有时间线的列表呈现
Set(集合)哈希表实现无序集合添加,删除,查找的复杂度都是 O(1),重复元素自动忽略利用唯一性,统计访问网站的所有独立ip,不重复的序列呈现
Sorted Set(有序集合)将Set中的元素增加一个权重参数score(double),元素按score有序排列score可重复,按从小到大自动排序排行榜等需要排序的列表呈现

详细介绍和用法: 通俗易懂的Redis数据结构基础教程 - 掘金 (juejin.cn)

Go程序中使用Redis技术

还记得我们开始提出了七个需求吗,我们现在尝试简单实现一下吧。当然了,在此之前,我们需要了解怎么连接 Redis。

Redis 的服务端

当我们需要在程序中连接到 Redis 服务器时,通常会使用配置文件来存储与连接相关的信息,例如服务器地址、端口号、密码等。(使用JSON数据)

设置配置文件

{
  "redis": {
    "address": "redis-server-address",
    "port": 6379,
    "password": "your-password"
  }
}

Redis 的客户端

  1. 安装 go-redis 客户端库:

    go get github.com/go-redis/redis/v8
    
  2. 导入库:

    import "github.com/go-redis/redis/v8"
    
  3. 创建客户端实例: 使用 redis.NewClient 函数创建一个 Redis 客户端实例。需要提供连接 Redis 服务器所需的信息,如地址、密码等。

    options := &redis.Options{
        Addr:     "localhost:6379", // Redis 服务器地址
        Password: "",               // Redis 密码
        DB:       0,                // 数据库编号,默认为0
    }
    
    client := redis.NewClient(options)
    
  4. 执行 Redis 命令: 使用创建的客户端实例来执行 Redis 命令。这里展示了如何使用客户端实例来设置和获取键值对:

    ctx := context.Background()
    
    // 设置键值对
    err := client.Set(ctx, "myKey", "myValue", 0).Err()
    if err != nil {
        panic(err)
    }
    
    // 获取值
    value, err := client.Get(ctx, "myKey").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println("myKey:", value)
    
  5. 关闭客户端连接: 当完成了与 Redis 的交互后,记得关闭连接以释放资源:

    err := client.Close()
    if err != nil {
        panic(err)
    }
    

读取配置文件中的 Redis 配置信息,创建一个 Redis 客户端,并尝试连接到 Redis 服务器进行测试(读取配置文件,而非直接得到)

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"

	"github.com/go-redis/redis/v8"
	"golang.org/x/net/context"
)

// 定义一个结构体来存储配置信息
type Config struct {
	Redis struct {
		Address  string `json:"address"`
		Port     int    `json:"port"`
		Password string `json:"password"`
	} `json:"redis"`
}

func main() {
	// 打开配置文件
	configFile, err := os.Open("config.json")
	if err != nil {
		fmt.Println("Error opening config file:", err)
		return
	}
	defer configFile.Close()

	// 读取配置文件的内容
	configBytes, _ := ioutil.ReadAll(configFile)

	// 解析 JSON 格式的配置文件内容到 Config 结构体
	var config Config
	err = json.Unmarshal(configBytes, &config)
	if err != nil {
		fmt.Println("Error parsing config:", err)
		return
	}

	// 配置 Redis 客户端
	ctx := context.Background()
	options := &redis.Options{
		Addr:     fmt.Sprintf("%s:%d", config.Redis.Address, config.Redis.Port),
		Password: config.Redis.Password,
		DB:       0,
	}
	client := redis.NewClient(options)

	// 测试连接
	pong, err := client.Ping(ctx).Result()
	if err != nil {
		fmt.Println("Error connecting to Redis:", err)
		return
	}
	fmt.Println("Connected to Redis:", pong)
}

Redis场景

假设我们一开始这些功能都是由MySQL完成的,现在需要切换到 Redis。

请注意,以下代码极其简陋,仅做示范,如果看的懵也请不要太过纠结~!!!

  1. 缓存热门视频和用户数据:

可以使用 Sorted Set 来存储热门视频,通过算法排列,这里简单用热度进行说明(将视频的热度作为分数,从高到低排列)。用户数据可以使用 Hash 存储,其中键是用户ID,值是用户信息的字段和值。

// 假设 video 是一个视频对象,包含 videoID 和热度
video := Video{ID: "video123", Popularity: 100}
client.ZAdd(ctx, "hot_videos", &redis.Z{Score: float64(video.Popularity), Member: video.ID})

// 假设 user 是一个用户对象,包含 userID 和用户信息
user := User{ID: "user456", Name: "Alice"}
client.HSet(ctx, "user:"+user.ID, "name", user.Name)

2 - 这行代码创建了一个 Video 对象,并为该视频指定了唯一的ID为 "video123",以及热度值 Popularity 为 100。这个对象表示一个短视频。

3 - client.ZAdd(ctx, "hot_videos", &redis.Z{Score: float64(video.Popularity), Member: video.ID}): 这行代码使用 Redis 客户端 client 调用了 ZAdd 方法,将视频信息添加到名为 "hot_videos" 的 Sorted Set 中。

  • &redis.Z{Score: float64(video.Popularity), Member: video.ID}:这是一个 Redis 的 Z 结构体,其中 Score 是视频的热度分数(类型被转换为浮点数),而 Member 是视频的唯一标识符。

当执行这行代码时,视频的热度将会作为分数加入到 "hot_videos" Sorted Set 中,使得视频能够根据热度值从高到低进行排序。这样,在获取热门视频时,可以直接从 Sorted Set 中获取,并按热度排名展示。

  1. 实时计数和排名:

使用 Redis 的高速读写能力来实现实时计数和排名系统。对于视频的播放次数、点赞数、评论数等,可以使用 Redis 的原子操作(如 INCR)来增加计数。对于排名,可以使用 Sorted Set 存储视频的排名信息,根据分数排序,以展示热门内容。

// 增加视频播放次数
client.Incr(ctx, "video:play_count:"+videoID)

// 增加视频点赞数
client.Incr(ctx, "video:likes:"+videoID)

// 增加视频评论数
client.Incr(ctx, "video:comments:"+videoID)

// 获取热门视频排名
videos, _ := client.ZRevRange(ctx, "hot_videos", 0, 10).Result()

11 - ZRevRange 命令用来获取 Sorted Set 中,按分数从高到低排列的前 11 个元素(索引从 0 到 10)。最终的结果会存储在 videos 变量中。

这样,videos 变量会包含按照热度从高到低排列的前 11 个热门视频的成员标识符(video ID),你可以在后续代码中使用这些标识符来获取详细的视频信息。

  1. 会话管理和登录状态:

使用 Redis 存储用户的会话信息和登录状态,可以将用户的 token 或会话信息存储在 Redis 中,并设置适当的过期时间。这样可以方便地管理用户的登录和登出状态,实现用户认证和权限管理。

// 登录时保存会话信息
sessionToken := generateSessionToken()
client.Set(ctx, "session:"+userID, sessionToken, time.Hour*2)  // 设置过期时间

// 鉴权时验证会话信息
storedToken, _ := client.Get(ctx, "session:"+userID).Result()
if storedToken == providedToken {
    // 用户已登录,执行操作
} else {
    // 用户未登录,需要登录
}

Token详细介绍可看我的上篇文章

2 - generateSessionToken():这是一个用于生成一个随机的会话令牌的函数(此处没体现)

3 - client.Set(ctx, "session:"+userID, sessionToken, time.Hour*2):使用 Redis 客户端 client 调用了 Set 方法,将会话信息存储在 Redis 中。具体说明如下:

  • ctx:上下文,通常用于跟踪请求或操作的状态。
  • "session:"+userID:这是 Redis 中存储会话信息的键,将用户的ID与字符串 "session:" 连接,构成唯一的键。
  • sessionToken:这是生成的会话令牌,它会被存储在 Redis 中。
  • time.Hour*2:这是会话的过期时间,即会话在存储在 Redis 后会在 2 小时后过期。可以根据需要调整过期时间。

6 - storedToken, _ := client.Get(ctx, "session:"+userID).Result():使用 Redis 客户端 client 调用了 Get 方法,从 Redis 中获取之前存储的会话令牌。

7~10-if storedToken == providedToken:这里比较从 Redis 获取的会话令牌和用户提供的令牌(例如,用户在请求中提供的令牌)。如果两者相等,说明用户已经登录,可以执行后续操作;否则,用户需要重新登录。

  1. 消息队列:

使用 Redis 的 List 数据结构作为消息队列,将后台任务(如视频转码、通知推送等)的任务信息以 JSON 格式推送到队列中。后台工作者可以从队列中获取任务并进行处理。

// 将任务信息以 JSON 格式推送到消息队列
task := Task{ID: "task123", Type: "video_transcoding", Data: "..."}
taskJSON, _ := json.Marshal(task)
client.LPush(ctx, "task_queue", taskJSON)

// 后台工作者从队列中获取任务并处理
taskJSON, _ := client.RPop(ctx, "task_queue").Result()
var task Task
json.Unmarshal([]byte(taskJSON), &task)
// 处理任务

2 - Task{ID: "task123", Type: "video_transcoding", Data: "..."}:这是一个假设的任务对象,包含任务的唯一ID、类型以及其他相关数据。你可以根据实际需求定义任务对象的结构。

3 - taskJSON, _ := json.Marshal(task):将任务对象转换为 JSON 格式的字节切片,以便存储到 Redis 中。

4 - client.LPush(ctx, "task_queue", taskJSON):使用 Redis 客户端 client 调用了 LPush 方法,将任务信息以 JSON 格式推送到名为 "task_queue" 的消息队列。

  • "task_queue":这是消息队列的名称,用于存储后台任务信息。
  • taskJSON:包含任务信息的 JSON 格式字节切片。任务将被添加到消息队列的左侧(头部),表示将任务加入队列以待处理。

7 - taskJSON, _ := client.RPop(ctx, "task_queue").Result(): 使用 Redis 客户端 client 调用了 RPop 方法,从名为 "task_queue" 的消息队列中获取并移除队列右侧(尾部)的一个任务。这意味着先进入队列的任务会被后取出来。

8 - var task Task: 这里创建了一个 Task 对象,用于将解析后的任务数据存储。

9 - json.Unmarshal([]byte(taskJSON), &task): 使用 Go 的标准库函数 json.Unmarshal,将获取到的 JSON 格式的任务数据解析并存储到 task 对象中。

  1. 实时评论和聊天:

使用 Redis 的发布订阅机制来实现实时评论和聊天功能。用户发表评论或聊天消息时,将消息发布到特定的频道中,其他用户订阅该频道以接收实时消息。

// 用户发表评论或聊天消息时,将消息发布到频道
message := Message{User: "user123", Content: "Hello"}
messageJSON, _ := json.Marshal(message)
client.Publish(ctx, "chat_channel", messageJSON)

// 其他用户订阅频道以接收实时消息
pubSub := client.Subscribe(ctx, "chat_channel")
channel := pubSub.Channel()
for msg := range channel {
    var receivedMessage Message
    json.Unmarshal([]byte(msg.Payload), &receivedMessage)
    // 处理接收到的消息
}

2 - Message{User: "user123", Content: "Hello"}: 这是一个假设的消息对象,包含用户标识符(User)和消息内容(Content)。

3 - messageJSON, _ := json.Marshal(message): 将消息对象转换为 JSON 格式的字节切片,以便发布到 Redis 频道。

4 - client.Publish(ctx, "chat_channel", messageJSON): 使用 Redis 客户端 client 调用了 Publish 方法,将消息信息以 JSON 格式发布到名为 "chat_channel" 的频道(存储实时聊天消息)中。

7 -pubSub := client.Subscribe(ctx, "chat_channel"): 使用 Redis 客户端 client 调用了 Subscribe 方法,订阅名为 "chat_channel" 的频道,以便接收实时消息。

8 -channel := pubSub.Channel(): 通过 pubSub 对象获取一个用于接收消息的通道 channel,这个通道会持续接收来自 Redis 的消息。

9 -for msg := range channel { ... }: 使用 for 循环不断从 channel 中接收消息。每当有新消息发布到 "chat_channel" 频道时,循环会迭代一次。

11 - json.Unmarshal([]byte(msg.Payload), &receivedMessage): 这里是解析消息内容的逻辑。从 Redis 接收到的消息内容存储在 msg.Payload 中,然后使用 json.Unmarshal 将消息内容解析为 receivedMessage 对象,使得可以使用其中的信息进行后续处理。

12 - 在注释 // 处理接收到的消息 下面,我们可以编写逻辑来处理接收到的实时消息,比如将消息展示在用户界面上,进行相应的处理等。

  1. 限流和防刷:

使用 Redis 来实现限流和防刷机制。例如,使用 Redis 的 INCR 操作来计算用户操作的频率,然后根据设置的阈值来限制用户的操作速率,以防止恶意刷点击、评论等行为。

// 对用户操作进行计数
actionKey := "user_action:" + userID + ":" + actionType
count, _ := client.Incr(ctx, actionKey).Result()

// 设置过期时间,实现限时限制
if count == 1 {
    client.Expire(ctx, actionKey, time.Second*10)
}

// 判断是否超过限制
if count > actionLimit {
    // 触发限制操作
}

2 - actionKey := "user_action:" + userID + ":" + actionType: 根据用户的ID和操作类型生成一个唯一的键,用于存储用户的操作计数。这样可以区分不同用户和不同类型的操作。

3 - count, _ := client.Incr(ctx, actionKey).Result(): 使用 Redis 客户端 client 调用了 Incr 方法,将指定键的值增加 1。该操作会返回增加后的计数值,存储在 count 变量中。

6~8 - 如果 count 的值为 1,表示用户刚刚进行了这种操作。我们可以使用 client.Expire 方法来设置键 actionKey 的过期时间为 10 秒。

11~13 - 如果 count 的值大于操作限制 actionLimit,则表示用户的操作已经超过了限制。在这种情况下,我们可以执行相应的操作,例如触发限制措施,阻止用户继续执行操作。

  1. 地理位置数据:

使用 Redis 的地理位置数据结构(GeoHash)来存储和查询地理位置信息。将视频或用户的地理位置坐标存储在 Redis 中,然后使用 GeoRadius 查询功能来获取附近的视频或用户。

(了解就好,此项目用不到)

// 存储地理位置信息
client.GeoAdd(ctx, "locations", &redis.GeoLocation{
    Name:      "user123",
    Longitude: 123.456,
    Latitude:  45.678,
})

// 查询附近的地理位置信息
locations, _ := client.GeoRadiusByMember(ctx, "locations", "user123", &redis.GeoRadiusQuery{
    Radius:      10000,  // 查询半径,单位为米
    Unit:        "m",    // 单位为米
    WithCoord:   true,
    WithDist:    true,
    Sort:        "ASC",  // 排序方式
    Count:       10,     // 返回数量
})

好了,也写这么多了。如果你有点收获的话,能否赏个赞呢|ू・ω・` )

再次感谢阅读

参考文章:

  1. Go语言技术教程:Redis介绍安装和使用 - 掘金 (juejin.cn)
  2. Redis 安装 | 菜鸟教程 (runoob.com)
  3. Redis 数据类型 | 菜鸟教程 (runoob.com)
  4. 通俗易懂的Redis数据结构基础教程 - 掘金 (juejin.cn)
  5. Redis在windows本地的安装配置_windows本地redis_铁打的阿秀的博客-CSDN博客
  6. Redis下载安装与配置(windows)_pingcode的博客-CSDN博客
  7. GO语言使用SSH连接Redis_gostream的博客-CSDN博客