From 98d8482cb89dec4dc204d96443e91f9714fb049e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E8=8C=B6=E6=B8=85=E6=AC=A2?= Date: Wed, 15 Jun 2022 11:50:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 21 ++++ client.go | 256 +++++++++++++++++++++++++++++++++++++++ context.go | 141 ++++++++++++++++++++++ define.go | 301 ++++++++++++++++++++++++++++++++++++++++++++++ error.go | 64 ++++++++++ go.mod | 45 +++++++ go.sum | 232 +++++++++++++++++++++++++++++++++++ logger.go | 39 ++++++ monitor.go | 326 ++++++++++++++++++++++++++++++++++++++++++++++++++ redis_test.go | 53 ++++++++ 10 files changed, 1478 insertions(+) create mode 100644 .gitignore create mode 100644 client.go create mode 100644 context.go create mode 100644 define.go create mode 100644 error.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 logger.go create mode 100644 monitor.go create mode 100644 redis_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9f6b44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Created by .ignore support plugin (hsz.mobi) +### Go template +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.xlsx + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ +.idea +.vscode +mail_test.go diff --git a/client.go b/client.go new file mode 100644 index 0000000..c65c69a --- /dev/null +++ b/client.go @@ -0,0 +1,256 @@ +// Package redis ... +// +// Description : redis 客户端 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-02-27 4:49 下午 +package redis + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "git.zhangdeman.cn/zhangdeman/logger" + redisInstance "github.com/go-redis/redis/v8" + "go.uber.org/zap" +) + +// defaultParseError ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:59 下午 2021/2/27 +func defaultParseError(err error) error { + if nil == err { + return nil + } + errMsg := err.Error() + if errMsg == "nil" || errMsg == "" { + return nil + } + strArr := strings.Split(errMsg, ":") + if len(strArr) != 2 { + return err + } + msg := strings.ToLower(strings.TrimSpace(strArr[1])) + if msg == "nil" || msg == "" { + return nil + } + return err +} + +// Options 连接选项,百分之百兼容第三方包的选项 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 4:57 下午 2021/2/27 +type Options struct { + Conf *redisInstance.Options // 第三方包的选项 + Logger *LoggerConfig // 日志的配置 + LoggerFieldConfig *LogFieldConfig // 日志字段的配置 +} + +// RealClient 包装好的 redis client +type RealClient struct { + Flag string // redis 标识 + Instance *redisInstance.Client // redis 实例 + Logger *zap.Logger // 日志实例 + LoggerFieldConfig *LogFieldConfig // 日志字段的配置 +} + +// NewClient 获取redis client实例 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 5:05 下午 2021/2/27 +func NewClient(config map[string]Options, parseErrorFunc func(err error) error) (ClientInterface, error) { + c := &Client{ + instanceTable: make(map[string]*RealClient), + confTable: config, + parseErrorFunc: parseErrorFunc, + } + if nil == c.parseErrorFunc { + c.parseErrorFunc = defaultParseError + } + return c, c.init() +} + +// Client 包装的redis client +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 4:52 下午 2021/2/27 +type Client struct { + instanceTable map[string]*RealClient // redis 实例 + confTable map[string]Options // redis 配置 + parseErrorFunc func(err error) error // 解析err的function,解析执行结果是否为失败,有的场景,执行成功,返回 redis:nil / redis: +} + +// init 初始化redis连接 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 5:31 下午 2021/2/27 +func (c *Client) init() error { + var ( + err error + ) + + for flag, conf := range c.confTable { + c.instanceTable[flag] = &RealClient{ + Flag: flag, + Instance: redisInstance.NewClient(conf.Conf), + Logger: nil, + LoggerFieldConfig: conf.LoggerFieldConfig, + } + if c.instanceTable[flag].Logger, err = c.getLogger(conf.Logger); nil != err { + return LoggerInitFail(flag, err) + } + if nil == c.instanceTable[flag].LoggerFieldConfig { + c.instanceTable[flag].LoggerFieldConfig = &LogFieldConfig{ + Message: "", + UsedTimeField: "", + CommandField: "", + FlagField: "", + } + } + if len(c.instanceTable[flag].LoggerFieldConfig.Message) == 0 { + c.instanceTable[flag].LoggerFieldConfig.Message = defaultMessage + } + if len(c.instanceTable[flag].LoggerFieldConfig.CommandField) == 0 { + c.instanceTable[flag].LoggerFieldConfig.CommandField = defaultCommandField + } + if len(c.instanceTable[flag].LoggerFieldConfig.UsedTimeField) == 0 { + c.instanceTable[flag].LoggerFieldConfig.UsedTimeField = defaultUsedTimeField + } + if len(c.instanceTable[flag].LoggerFieldConfig.FlagField) == 0 { + c.instanceTable[flag].LoggerFieldConfig.FlagField = defaultFlagField + } + } + return nil +} + +// getLogger ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 7:07 下午 2021/2/27 +func (c *Client) getLogger(conf *LoggerConfig) (*zap.Logger, error) { + if nil == conf || nil == conf.SplitConfig { + return nil, nil + } + optionFuncList := make([]logger.SetLoggerOptionFunc, 0) + if conf.ConsoleOutput { + optionFuncList = append(optionFuncList, logger.WithConsoleOutput()) + } + + if conf.Encoder != nil { + optionFuncList = append(optionFuncList, logger.WithEncoder(conf.Encoder)) + } + + splitConfigFuncList := []logger.SetRotateLogConfigFunc{ + logger.WithTimeIntervalType(conf.SplitConfig.TimeIntervalType), + logger.WithDivisionChar(conf.SplitConfig.DivisionChar), + logger.WithMaxAge(conf.SplitConfig.MaxAge), + } + + splitConfig, _ := logger.NewRotateLogConfig(conf.SplitConfig.LogPath, conf.SplitConfig.LogFileName, splitConfigFuncList...) + return logger.NewLogger( + conf.LoggerLevel, + splitConfig, + optionFuncList..., + ) +} + +// GetRedisClient 获取redis实例 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 5:16 下午 2021/2/27 +func (c *Client) GetRedisClient(flag string) (*RealClient, error) { + redisClient, exist := c.instanceTable[flag] + if !exist { + return nil, FlagNotFound(flag) + } + return redisClient, nil +} + +// log 记录redis请求日志 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:52 下午 2021/2/27 +func (c *Client) log(ctx *Context, realClient *RealClient, cmdResult redisInstance.Cmder, startTime int64, finishTime int64) { + if nil == realClient || nil == realClient.Logger { + return + } + realClient.Logger.Info( + "执行redis命令日志记录", + zap.Any(ctx.RequestIDField, ctx.RequestID), // 上下文串联的requestID + zap.String(realClient.LoggerFieldConfig.CommandField, cmdResult.String()), // 执行的命令 + zap.Float64(realClient.LoggerFieldConfig.UsedTimeField, float64(finishTime-startTime)/1e6), // 耗时,单位: ms + zap.Error(cmdResult.Err()), // 异常信息 + ) +} + +// CommandProxy 执行命令的代理 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 9:41 下午 2021/2/27 +func (c *Client) CommandProxy(ctx *Context, flag string, cmd string, param ...interface{}) (string, error) { + var ( + realClient *RealClient + err error + ) + if len(cmd) == 0 { + return "", EmptyCmd() + } + + if nil == ctx { + ctx = NewContext(flag) + } + if realClient, err = c.GetRedisClient(ctx.Flag); nil != err { + return "", err + } + redisCmd := append([]interface{}{cmd}, param...) + startTime := time.Now().Unix() + cmdResult := realClient.Instance.Do(ctx.Ctx, redisCmd...) + go c.log(ctx, realClient, cmdResult, startTime, time.Now().UnixNano()) + return fmt.Sprintf("%v", cmdResult.Val()), c.parseErrorFunc(cmdResult.Err()) +} + +// CommandProxyWithReceiver 执行命令,并解析结果 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:00 下午 2021/2/27 +func (c *Client) CommandProxyWithReceiver(ctx *Context, flag string, receiver interface{}, cmd string, param ...interface{}) error { + if nil == receiver { + return ReceiverISNIL() + } + var ( + err error + result string + ) + + if result, err = c.CommandProxy(ctx, flag, cmd, param...); nil != err { + return err + } + + return ResultConvertFail(json.Unmarshal([]byte(result), receiver)) +} + +// ClientInterface 定义redis client的接口实现,方便单元测试数据mock +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:49 下午 2021/2/27 +type ClientInterface interface { + GetRedisClient(flag string) (*RealClient, error) + CommandProxy(ctx *Context, flag string, cmd string, param ...interface{}) (string, error) + CommandProxyWithReceiver(ctx *Context, flag string, receiver interface{}, cmd string, param ...interface{}) error +} diff --git a/context.go b/context.go new file mode 100644 index 0000000..ef1c8ae --- /dev/null +++ b/context.go @@ -0,0 +1,141 @@ +// Package redis... +// +// Description : redis... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-02-27 8:22 下午 +package redis + +import ( + "context" + + "git.zhangdeman.cn/zhangdeman/easymap" + + "github.com/gin-gonic/gin" +) + +const ( + // 默认的 request_id 字段名 + defaultRequestIDField = "request_id" + // 默认的message + defaultMessage = "执行redis命令日志记录" + // 耗时字段 + defaultUsedTimeField = "used_field" + // 默认的命令字段 + defaultCommandField = "command" + // 默认记录 redis标识的字段 + defaultFlagField = "flag" +) + +// Context 请求上下文 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:25 下午 2021/2/27 +type Context struct { + Flag string // 哪个模块的上下文 + Ctx context.Context // ctx + GinCtx *gin.Context // http 请求绑定的gin.context + RequestIDField string // requestID 字段名 + RequestID string // requestID 此字段有值, 直接使用此值,无值, 去GinCtx 中读取 RequestIDField + Extra easymap.EasyMap // 扩展信息 +} + +// NewContext 生成一个上下文 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:26 下午 2021/2/27 +func NewContext(flag string, of ...SetContextFunc) *Context { + ctx := &Context{ + Flag: flag, + Ctx: nil, + GinCtx: nil, + RequestIDField: "", + RequestID: "", + Extra: nil, + } + for _, f := range of { + f(ctx) + } + if nil == ctx.Ctx { + ctx.Ctx = context.Background() + } + if len(ctx.RequestIDField) == 0 { + ctx.RequestIDField = defaultRequestIDField + } + if nil == ctx.Extra { + ctx.Extra = easymap.NewNormal(true) + } + // requestID 填充 + if len(ctx.RequestID) == 0 { + // 先从 gin 读 + if nil != ctx.GinCtx { + ctx.RequestID = ctx.GinCtx.GetString(ctx.RequestIDField) + } + // 再从extra读取 + if len(ctx.RequestID) == 0 { + ctx.RequestID, _ = ctx.Extra.GetString(ctx.RequestID) + } + } + return ctx +} + +// SetContextFunc 设置context参数 +type SetContextFunc func(rc *Context) + +// WithCtx 设置context +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:30 下午 2021/2/27 +func WithCtx(ctx context.Context) SetContextFunc { + return func(rc *Context) { + rc.Ctx = ctx + } +} + +// WithGinCtx 设置gin上下文 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:34 下午 2021/2/27 +func WithGinCtx(ginCtx *gin.Context) SetContextFunc { + return func(rc *Context) { + rc.GinCtx = ginCtx + } +} + +// WithExtra 设置扩展信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:36 下午 2021/2/27 +func WithExtra(extra easymap.EasyMap) SetContextFunc { + return func(rc *Context) { + rc.Extra = extra + } +} + +// WithRequestIDField 设置request_id参数名 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:41 下午 2021/2/27 +func WithRequestIDField(requestIDField string) SetContextFunc { + return func(rc *Context) { + rc.RequestIDField = requestIDField + } +} + +// WithRequestID ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 8:42 下午 2021/2/27 +func WithRequestID(requestID string) SetContextFunc { + return func(rc *Context) { + rc.RequestID = requestID + } +} diff --git a/define.go b/define.go new file mode 100644 index 0000000..b2853fa --- /dev/null +++ b/define.go @@ -0,0 +1,301 @@ +// Package redis ... +// +// Description : redis ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-11-26 21:07 下午 +package redis + +// FullServerInfo 获取服务器信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:07 下午 2021/11/26 +type FullServerInfo struct { + ServerInfo *ServerInfo `json:"server_info"` + ClientInfo *ClientInfo `json:"client_info"` + MemoryInfo *MemoryInfo `json:"memory_info"` + Persistence *Persistence `json:"persistence"` + Stats *Stats `json:"stats"` + Replication *Replication `json:"replication"` + CPU *CPU `json:"cpu"` + CommandStats []CmdStat `json:"command_stats"` + Keyspace []DB `json:"keyspace"` + Cluster *Cluster `json:"cluster"` + ErrorStats []Error `json:"error_stats"` + Modules interface{} `json:"modules"` +} + +// ServerInfo 服务器信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:09 下午 2021/11/26 +type ServerInfo struct { + RedisVersion string `json:"redis_version" yaml:"redis_version"` // Redis 服务器版本 + RedisGitSha1 string `json:"redis_git_sha1" yaml:"redis_git_sha1"` // Git SHA1 + RedisGitDirty string `json:"redis_git_dirty" yaml:"redis_git_dirty"` // Git dirty flag + RedisBuildID string `json:"redis_build_id" yaml:"redis_build_id"` // 构建ID + RedisMode string `json:"redis_mode" yaml:"redis_mode"` // 运行模式(“独立”,“哨兵”或“集群”) + OS string `json:"os" yaml:"os"` // Redis 服务器的宿主操作系统 + ArchBits string `json:"arch_bits" yaml:"arch_bits"` // 架构(32 或 64 位) + MultiplexingApi string `json:"multiplexing_api" yaml:"multiplexing_api"` // Redis 所使用的事件处理机制 + AtomicvarApi string `json:"atomicvar_api" yaml:"atomicvar_api"` // 原子处理api + GCCVersion string `json:"gcc_version" yaml:"gcc_version"` // 编译 Redis 时所使用的 GCC 版本 + ProcessID int `json:"process_id" yaml:"process_id"` // 服务器进程的 PID + ProcessSupervised string `json:"process_supervised" yaml:"process_supervised"` // 是否有进程监控 + RunID string `json:"run_id" yaml:"run_id"` // Redis 服务器的随机标识符(用于 Sentinel 和集群) + TCPPort int `json:"tcp_port" yaml:"tcp_port"` // TCP/IP 监听端口 + ServerTimeUsec int64 `json:"server_time_usec" yaml:"server_time_usec"` // 当前服务器时间,微秒 + UptimeInSeconds int64 `json:"uptime_in_seconds" yaml:"uptime_in_seconds"` // 自 Redis 服务器启动以来,经过的秒数 + UptimeInDays int64 `json:"uptime_in_days" yaml:"uptime_in_days"` // 自 Redis 服务器启动以来,经过的天数 + Hz int64 `json:"hz" yaml:"hz"` // redis内部调度(进行关闭timeout的客户端,删除过期key等等)频率,程序规定serverCron每秒运行10次。 + ConfiguredHz int64 `json:"configured_hz" yaml:"configured_hz"` // 服务器的频率设置 + LRUClock int64 `json:"lru_clock" yaml:"lru_clock"` // 以秒为单位进行自增的时钟,用于 LRU 管理 + Executable string `json:"executable" yaml:"executable"` // 可执行文件位置 + ConfigFile string `json:"config_file" yaml:"config_file"` // 服务所使用的配置文件 + IOThreadsActive int `json:"io_threads_active" yaml:"io_threads_active"` // 活跃的io线程数 +} + +// ClientInfo 客户端信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 21:29 下午 2021/11/26 +type ClientInfo struct { + ConnectedClients int `json:"connected_clients" yaml:"connected_clients"` // 客户端连接数 + ClusterConnections int `json:"cluster_connections" yaml:"cluster_connections"` // 集群连接数 + MaxClients int `json:"maxclients" yaml:"max_clients"` // 最大客户端连接数 + ClientRecentMaxInputBuffer int64 `json:"client_recent_max_input_buffer" yaml:"client_recent_max_input_buffer"` // 最近最大输入缓存 + ClientRecentMaxOutputBuffer int64 `json:"client_recent_max_output_buffer" yaml:"client_recent_max_output_buffer"` // 最近最大输出缓存 + BlockedClients int `json:"blocked_clients" yaml:"blocked_clients"` // 阻塞客户端数量, 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量 + TrackingClients int `json:"tracking_clients" yaml:"tracking_clients"` // tracking_clients + ClientsInTimeoutTable int `json:"clients_in_timeout_table" yaml:"clients_in_timeout_table"` // clients_in_timeout_table +} + +// MemoryInfo 内存使用信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 22:08 下午 2021/11/26 +type MemoryInfo struct { + UsedMemory int64 `json:"used_memory" yaml:"used_memory"` // 由 Redis 分配器分配的内存总量,以字节(byte)为单位 + UsedMemoryHuman string `json:"used_memory_human" yaml:"used_memory_human"` // UsedMemory 可读化表示 + UsedMemoryRss int64 `json:"used_memory_rss" yaml:"used_memory_rss"` // 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致。 + UsedMemoryRssHuman string `json:"used_memory_rss_human" yaml:"used_memory_rss_human"` // UsedMemoryRssHuman 可读化表示 + UsedMemoryPeak int64 `json:"used_memory_peak" yaml:"used_memory_peak"` // Redis 的内存消耗峰值(以字节为单位) + UsedMemoryPeakHuman string `json:"used_memory_peak_human" yaml:"used_memory_peak_human"` // UsedMemoryPeak 可读化表示 + UsedMemoryPeakPerc string `json:"used_memory_peak_perc" yaml:"used_memory_peak_perc"` // 峰值系统内存使用率 + UsedMemoryOverhead int64 `json:"used_memory_overhead" yaml:"used_memory_overhead"` // Redis为了维护数据集的内部机制所需的内存开销,包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog + UsedMemoryStartup int64 `json:"used_memory_startup" yaml:"used_memory_startup"` // Redis服务器启动时消耗的内存 + UsedMemoryDataset int64 `json:"used_memory_dataset" yaml:"used_memory_dataset"` // 数据占用的内存大小,即used_memory-used_memory_overhead + UsedMemoryDatasetPerc int64 `json:"used_memory_dataset_perc" yaml:"used_memory_dataset_perc"` // 数据占用的内存大小的百分比,100%*(used_memory_dataset/(used_memory-used_memory_startup)) + AllocatorAllocated int64 `json:"allocator_allocated" yaml:"allocator_allocated"` // 内存分配器申请的内存,以字节为单位 + AllocatorActive int64 `json:"allocator_active" yaml:"allocator_active"` // 内存分配器正在使用的内存,以字节为单位 + AllocatorResident int64 `json:"allocator_resident" yaml:"allocator_resident"` // 内存分配器的常驻内存,以字节为单位 + TotalSystemMemory int64 `json:"total_system_memory" yaml:"total_system_memory"` // 操作系统内存(以字节为单位) + TotalSystemMemoryHuman string `json:"total_system_memory_human" yaml:"total_system_memory_human"` // TotalSystemMemory 可读化表示 + UsedMemoryLua int64 `json:"used_memory_lua" yaml:"used_memory_lua"` // Lua脚本存储占用的内存(以字节为单位) + UsedMemoryLuaHuman int64 `json:"used_memory_lua_human" yaml:"used_memory_lua_human"` // UsedMemoryLua 可读化表示 + UsedMemoryScripts int64 `json:"used_memory_scripts" yaml:"used_memory_scripts"` // Lua脚本使用的内存大小(以字节为单位) + UsedMemoryScriptsHuman int64 `json:"used_memory_scripts_human" yaml:"used_memory_scripts_human"` // UsedMemoryScripts 可读化表示 + NumberOfCachedScripts int64 `json:"number_of_cached_scripts" yaml:"number_of_cached_scripts"` // 缓存的lua脚本数量 + Maxmemory int64 `json:"maxmemory" yaml:"maxmemory"` // Redis实例的最大内存配置(以字节为单位) + MaxmemoryHuman int64 `json:"maxmemory_human" yaml:"maxmemory_human"` // Maxmemory 可读化表示 + MaxmemoryPolicy string `json:"maxmemory_policy" yaml:"maxmemory_policy"` // 当数据达到最大内存之后的淘汰策略 + AllocatorFragRatio float64 `json:"allocator_frag_ratio" yaml:"allocator_frag_ratio"` // 内存分配器碎片比例 + AllocatorFragBytes int64 `json:"allocator_frag_bytes" yaml:"allocator_frag_bytes"` // 内存分配器碎片大小,以字节为单位 + AllocatorRssRatio float64 `json:"allocator_rss_ratio" yaml:"allocator_rss_ratio"` // 从操作系统角度看, 内存分配器碎片比例 + AllocatorRssBytes int64 `json:"allocator_rss_bytes" yaml:"allocator_rss_bytes"` // 从操作系统角度看, 内存分配器碎片大小,以字节为单位 + RssOverheadRatio float64 `json:"rss_overhead_ratio" yaml:"rss_overhead_ratio"` // 从操作系统角度看, 开销的比例 + RssOverheadBytes int64 `json:"rss_overhead_bytes" yaml:"rss_overhead_bytes"` // 从操作系统角度看, 开销的大小, 以字节为单位 + MemFragmentationRatio float64 `json:"mem_fragmentation_ratio" yaml:"mem_fragmentation_ratio"` // 碎片率,used_memory_rss/ used_memory,正常情况下稍大于1。低于1,Redis实例可能会把部分数据交换到硬盘上,内存交换会严重影响Redis的性能,所以应该增加可用物理内存。大于1.5表示碎片过多。额外碎片的产生是由于Redis释放了内存块,但内存分配器并没有返回内存给操作系统,这个内存分配器是在编译时指定的,可以是libc、jemalloc或者tcmalloc。 + MemFragmentationBytes int64 `json:"mem_fragmentation_bytes" yaml:"mem_fragmentation_bytes"` // 内存碎片大小(字节表示) + MemNotCountedForEvict int64 `json:"mem_not_counted_for_evict" yaml:"mem_not_counted_for_evict"` // 不应驱逐的内存大小,以字节为单位 + MemReplicationBacklog int64 `json:"mem_replication_backlog" yaml:"mem_replication_backlog"` // 复制backlog的内存大小, 以字节为单位 + MemClientsSlaves int64 `json:"mem_clients_slaves" yaml:"mem_clients_slaves"` // mem_clients_slaves + MemClientsNormal int64 `json:"mem_clients_normal" yaml:"mem_clients_normal"` // mem_clients_normal + MemAofBuffer int64 `json:"mem_aof_buffer" yaml:"mem_aof_buffer"` // AOF内存缓冲区大小 + MemAllocator string `json:"mem_allocator" yaml:"mem_allocator"` // 内存分配器,Redis支持glibc’s malloc、jemalloc11、tcmalloc几种不同的内存分配器,每个分配器在内存分配和碎片上都有不同的实现。不建议普通管理员修改Redis默认内存分配器,因为这需要完全理解这几种内存分配器的差异,也要重新编译Redis。 + ActiveDefragRunning int64 `json:"active_defrag_running" yaml:"active_defrag_running"` // defrag:表示内存碎片整理, 0表示没有活动的defrag任务正在运行,1表示有活动的defrag任务正在运行 + LazyfreePendingObjects int64 `json:"lazyfree_pending_objects" yaml:"lazyfree_pending_objects"` // 延迟释放的挂起对象, 0表示不存在 + LazyfreedObjects int64 `json:"lazyfreed_objects" yaml:"lazyfreed_objects"` // 延迟释放的对象数量 +} + +// Persistence 持久化信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 22:15 下午 2021/11/26 +type Persistence struct { + Loading int `json:"loading" yaml:"loading"` // 服务器是否正在进行持久化 0 - 否 1 -是 + CurrentCowSize int `json:"current_cow_size" yaml:"current_cow_size"` // current_cow_size + CurrentCowSizeAge int `json:"current_cow_size_age" yaml:"current_cow_size_age"` // current_cow_size_age + CurrentForkPerc float64 `json:"current_fork_perc" yaml:"current_fork_perc"` // current_fork_perc + CurrentSaveKeysProcessed int `json:"current_save_keys_processed" yaml:"current_save_keys_processed"` // current_save_keys_processed + CurrentSaveKeysTotal int64 `json:"current_save_keys_total" yaml:"current_save_keys_total"` // current_save_keys_total + RdbChangesSinceLastSave int64 `json:"rdb_changes_since_last_save" yaml:"rdb_changes_since_last_save"` // 离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化 + RdbBgsaveInProgress int `json:"rdb_bgsave_in_progress" yaml:"rdb_bgsave_in_progress"` // 服务器是否正在创建rdb文件 0 - 否 1 - 是 + RdbLastSaveTime int64 `json:"rdb_last_save_time" yaml:"rdb_last_save_time"` // 最近一次创建rdb文件的时间戳,单位秒 + RdbLastBgsaveStatus string `json:"rdb_last_bgsave_status" yaml:"rdb_last_bgsave_status"` // 最近一次rdb持久化是否成功 ok 成功 + RdbLastBgsaveTimeSec int64 `json:"rdb_last_bgsave_time_sec" yaml:"rdb_last_bgsave_time_sec"` // 最近一次成功生成rdb文件耗时秒数 + RdbCurrentBgsaveTimeSec int64 `json:"rdb_current_bgsave_time_sec" yaml:"rdb_current_bgsave_time_sec"` // 如果服务器正在创建rdb文件,那么这个字段记录的就是当前的创建操作已经耗费的秒数 + RdbLastCowSize int64 `json:"rdb_last_cow_size" yaml:"rdb_last_cow_size"` // RDB过程中父进程与子进程相比执行了多少修改(包括读缓冲区,写缓冲区,数据修改等)。 + AofEnabled int `json:"aof_enabled" yaml:"aof_enabled"` // 是否开启了AOF 0 - 否 1 - 是 + AofRewriteInProgress int `json:"aof_rewrite_in_progress" yaml:"aof_rewrite_in_progress"` // 标识aof的rewrite操作是否在进行中 0 - 否 1- 是 + AofRewriteScheduled int `json:"aof_rewrite_scheduled" yaml:"aof_rewrite_scheduled"` // rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite + AofLastRewriteTimeSec int `json:"aof_last_rewrite_time_sec" yaml:"aof_last_rewrite_time_sec"` // 最近一次aof rewrite耗费的时长 + AofCurrentRewriteTimeSec int `json:"aof_current_rewrite_time_sec" yaml:"aof_current_rewrite_time_sec"` // 如果rewrite操作正在进行,则记录所使用的时间,单位秒 + AofLastBgrewriteStatus string `json:"aof_last_bgrewrite_status" yaml:"aof_last_bgrewrite_status"` // 上次 bgrewrite aof 操作的状态 ok 成功 + AofLastWriteStatus string `json:"aof_last_write_status" yaml:"aof_last_write_status"` // 上次aof写入状态 + AofLastCowSize int64 `json:"aof_last_cow_size" yaml:"aof_last_cow_size"` // AOF过程中父进程与子进程相比执行了多少修改(包括读缓冲区,写缓冲区,数据修改等) + IOThreadedReadsProcessed int `json:"io_threaded_reads_processed" yaml:"io_threaded_reads_processed"` // 读取线程数 + IOThreadedWritesProcessed int `json:"io_threaded_writes_processed" yaml:"io_threaded_writes_processed"` // 写入线程数 +} + +// Stats 服务运行状态 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:57 下午 2021/11/26 +type Stats struct { + TotalConnectionsReceived int64 `json:"total_connections_received" yaml:"total_connections_received"` // 所有连接数, 累积值, 只增不减, 连接断开也不会减少 + TotalCommandsProcessed int64 `json:"total_commands_processed" yaml:"total_commands_processed"` // 服务器执行的命令数 累积值, 只增不减 + InstantaneousPpsPerSec int `json:"instantaneous_pps_per_sec" yaml:"instantaneous_pps_per_sec"` // 每秒执行的命令数 + TotalNetInputBytes int64 `json:"total_net_input_bytes" yaml:"total_net_input_bytes"` // 网络流量-流入 以字节(byte)为单位 + TotalNetOutputBytes int64 `json:"total_net_output_bytes" yaml:"total_net_output_bytes"` // 网络流量-流出 以字节(byte)为单位 + InstantaneousInputKbps float64 `json:"instantaneous_input_kbps" yaml:"instantaneous_input_kbps"` // 网络流量-流入-KB/s + InstantaneousOutputKbps float64 `json:"instantaneous_output_kbps" yaml:"instantaneous_output_kbps"` // 网络流量-流出-KB/s + RejectedConnections int64 `json:"rejected_connections" yaml:"rejected_connections"` // 因达到最大连接数而被拒绝的连接数量 + SyncFull int64 `json:"sync_full" yaml:"sync_full"` // 主从全量同步的次数 + SyncPartialOk int64 `json:"sync_partial_ok" yaml:"sync_partial_ok"` // 主从部分同步成功的次数 + SyncPartialErr int64 `json:"sync_partial_err" yaml:"sync_partial_err"` // 主从部分同步失败次数 + ExpiredKeys int `json:"expired_keys" yaml:"expired_keys"` // 过期key的数量 + ExpiredStalePerc float64 `json:"expired_stale_perc" yaml:"expired_stale_perc"` // 过期过时的百分比 + ExpiredTimeCapReachedCount int64 `json:"expired_time_cap_reached_count" yaml:"expired_time_cap_reached_count"` // 过期时间达到上限的数量 + ExpireCycleCpuMilliseconds int64 `json:"expire_cycle_cpu_milliseconds" yaml:"expire_cycle_cpu_milliseconds"` // 过期循环CPU毫秒数 + EvictedKeys int64 `json:"evicted_keys" yaml:"evicted_keys"` // 超过 maxmemory 之后, 剔除的 key 的数量 + KeyspaceHits int64 `json:"keyspace_hits" yaml:"keyspace_hits"` // 访问命中次数 + KeyspaceMisses int64 `json:"keyspace_misses" yaml:"keyspace_misses"` // 访问未命中次数 + PubsubChannels int64 `json:"pubsub_channels" yaml:"pubsub_channels"` // 当前频道数量 发布 - 订阅 模式 + PubsubPatterns int64 `json:"pubsub_patterns" yaml:"pubsub_patterns"` // 当前使用中的模式数量 + LatestForkUsec int64 `json:"latest_fork_usec" yaml:"latest_fork_usec"` // 最近一次fork 操作消耗的时间, 单位微秒 + TotalForks int64 `json:"total_forks" yaml:"total_forks"` // fork 的总次数 + MigrateCachedSockets int64 `json:"migrate_cached_sockets" yaml:"migrate_cached_sockets"` // 记录当前 Redis 正在 migrate 操作的目标 Redis 个数, 例如 A 向 B 和 C 执行 migrate操作, 这个值为2 + SlaveExpiresTrackedKeys int64 `json:"slave_expires_tracked_keys" yaml:"slave_expires_tracked_keys"` // 从实例到期的 key 的数量 + ActiveDefragHits int64 `json:"active_defrag_hits" yaml:"active_defrag_hits"` // 主动碎片整理命中次数 + ActiveDefragMisses int64 `json:"active_defrag_misses" yaml:"active_defrag_misses"` // 主动碎片整理未命中次数 + ActiveDefragKeyHits int64 `json:"active_defrag_key_hits" yaml:"active_defrag_key_hits"` // 主动整理碎片, key命中次数 + ActiveDefragKeyMisses int64 `json:"active_defrag_key_misses" yaml:"active_defrag_key_misses"` // 主动整理碎片, key未命中次数 + TrackingTotalKeys int64 `json:"tracking_total_keys" yaml:"tracking_total_keys"` // key 查询的总数 + TrackingTotalItems int64 `json:"tracking_total_items" yaml:"tracking_total_items"` // item查询的总数 + TrackingTotalPrefixes int64 `json:"tracking_total_prefixes" yaml:"tracking_total_prefixes"` // 前缀查询的总数 + UnexpectedErrorReplies int64 `json:"unexpected_error_replies" yaml:"unexpected_error_replies"` // unexpected 异常响应次数 + TotalErrorReplies int64 `json:"total_error_replies" yaml:"total_error_replies"` // 异常响应总次数 + DumpPayloadSanitizations int64 `json:"dump_payload_sanitizations" yaml:"dump_payload_sanitizations"` // dump_payload_sanitizations + TotalReadsProcessed int64 `json:"total_reads_processed" yaml:"total_reads_processed"` // 正在读取的请求数 + TotalWritesProcessed int64 `json:"total_writes_processed" yaml:"total_writes_processed"` // 正在写入的请求数 + IOThreadedReadsProcessed int64 `json:"io_threaded_reads_processed" yaml:"io_threaded_reads_processed"` // 正在读取的线程数 + IOThreadedWritesProcessed int64 `json:"io_threaded_writes_processed" yaml:"io_threaded_writes_processed"` // 正在写入的线程数 +} + +// Replication 复制相关 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 12:42 上午 2021/11/27 +type Replication struct { + Role string `json:"role" yaml:"role"` // 节点的角色 master / slave + ReplBacklogActive int `json:"repl_backlog_active" yaml:"repl_backlog_active"` // 复制缓冲区是否开启 0 - 未开启 1 - 已开启 + ReplBacklogSize int64 `json:"repl_backlog_size" yaml:"repl_backlog_size"` // 复制缓冲区大小(以字节为单位) + ReplBacklogFirstByteOffset int64 `json:"repl_backlog_first_byte_offset" yaml:"repl_backlog_first_byte_offset"` // 复制缓冲区里偏移量的大小 + ReplBacklogHistlen int64 `json:"repl_backlog_histlen" yaml:"repl_backlog_histlen"` // 此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小 + ConnectedSlaves int `json:"connected_slaves" yaml:"connected_slaves"` // 仅主节点属性 : 连接的从节点数量 + MasterFailoverState string `json:"master_failover_state" yaml:"master_failover_state"` // 仅主节点 : 故障转移状态 no-failover 无故障转移 + MasterReplid string `json:"master_replid" yaml:"master_replid"` // 仅主节点 : 实例启动的随机字符串 + MasterReplid2 string `json:"master_replid2" yaml:"master_replid2"` // 仅主节点 : 实例启动的随机字符串2 + MasterReplOffset int64 `json:"master_repl_offset" yaml:"master_repl_offset"` // 仅主节点 : 主从同步偏移量 + SecondReplOffset int64 `json:"second_repl_offset" yaml:"second_repl_offset"` // 仅主节点 : 主从同步偏移量2 + MasterHost string `json:"master_host" yaml:"master_host"` // 仅从节点 : 主节点host + MasterPort int `json:"master_port" yaml:"master_port"` // 仅从节点 : 主节点端口 + MasterLinkStatus string `json:"master_link_status" yaml:"master_link_status"` // 仅从节点 : 与主节点连接状态 up - 正常连接 down - 断开 + MasterLastIOSecondsAgo int `json:"master_last_io_seconds_ago" yaml:"master_last_io_seconds_ago"` // 仅从节点 : 主节点与从节点最后通信的时间间隔, 单位: s + MasterSyncInProgress int `json:"master_sync_in_progress" yaml:"master_sync_in_progress"` // 仅从节点 : 从节点是否正在全量同步主节点rdb文件 0 - 否 1 - 是 + SlaveReplOffset int64 `json:"slave_repl_offset" yaml:"slave_repl_offset"` // 仅从节点 : 复制偏移量 + SlavePriority int `json:"slave_priority" yaml:"slave_priority"` // 仅从节点 : 从节点优先级 + SlaveReadOnly int `json:"slave_read_only" yaml:"slave_read_only"` // 仅从节点 : 从节点是否只读 0 - 否 1 - 是 + SlaveList []SlaveNode `json:"slave_list" yaml:"slave_list"` // 从节点列表 +} + +// SlaveNode 从库数据结构,基于原始数据解析 eg : slave0:ip=x.x.x.x,port=6379,state=online,offset=123456,lag=1 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:51 上午 2021/11/27 +type SlaveNode struct { + ID string `json:"id" yaml:"id"` // 从库ID + IP string `json:"ip" yaml:"ip"` // 从库IP + Port int `json:"port" yaml:"port"` // 从库端口 + State string `json:"state" yaml:"state"` // 从库状态 online - 在线 offline - 离线 + Offset int64 `json:"offset" yaml:"offset"` // 数据偏移量 + Lag int64 `json:"lag" yaml:"lag"` // 数据延迟量大小 +} + +// CPU 信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:54 上午 2021/11/27 +type CPU struct { + UsedCpuSys float64 `json:"used_cpu_sys" yaml:"used_cpu_sys"` // Redis主进程在内核态所占用CPU时钟总和 + UsedCpuUser float64 `json:"used_cpu_user" yaml:"used_cpu_user"` // Redis主进程在用户态所占用CPU时钟总和 + UsedCpuSysChildren float64 `json:"used_cpu_sys_children" yaml:"used_cpu_sys_children"` // Redis子进程在内核态所占用CPU时钟总和 + UsedCpuUserChildren float64 `json:"used_cpu_user_children" yaml:"used_cpu_user_children"` // Redis子进程在用户态所占用CPU时钟总和 +} + +// CmdStat 指令状态 eg : cmdstat_get:calls=1,usec=42121,usec_per_call=42121.00,rejected_calls=0,failed_calls=0 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2:02 上午 2021/11/27 +type CmdStat struct { + Cmd string `json:"cmd" yaml:"cmd"` // 执行的指令 + Calls int64 `json:"calls" yaml:"calls"` // 执行了多少次 + TotalUsedTime float64 `json:"total_used_time" yaml:"total_used_time"` // 累计总耗时 微秒 + AvgUsedTime float64 `json:"avg_used_time" yaml:"avg_used_time"` // 平均耗时 微秒 + RejectedCalls int64 `json:"rejected_calls" yaml:"rejected_calls"` // 拒绝执行指令次数 + FailedCalls int64 `json:"failed_calls" yaml:"failed_calls"` // 指令执行失败次数 + SuccessCalls int64 `json:"success_calls" yaml:"success_calls"` // 指令执行成功次数 Calls - FailedCalls +} + +// DB 数据库的数据结构, eg : db0:keys=3,expires=0,avg_ttl=0 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2:09 上午 2021/11/27 +type DB struct { + Name string `json:"name" yaml:"name"` // 数据库名称 + Keys int64 `json:"keys" yaml:"keys"` // 数据库key的数量 + Expires int64 `json:"expires" yaml:"expires"` // 数据库过期key的数量 + AvgTTL int64 `json:"avg_ttl" yaml:"avg_ttl"` // 平均存活时间 +} + +// Cluster ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2:13 上午 2021/11/27 +type Cluster struct { + ClusterEnabled int `json:"cluster_enabled" yaml:"cluster_enabled"` // 是否启用 0 - 否 1 - 是 +} + +// Error 错误的状态 eg : errorstat_WRONGTYPE:count=4615 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2:16 上午 2021/11/27 +type Error struct { + Type string `json:"type" yaml:"type"` // 错误类型 + Count int64 `json:"count" yaml:"count"` // 错误出现次数 +} diff --git a/error.go b/error.go new file mode 100644 index 0000000..0349e84 --- /dev/null +++ b/error.go @@ -0,0 +1,64 @@ +// Package redis... +// +// Description : redis... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-02-27 5:13 下午 +package redis + +import "github.com/pkg/errors" + +// FlagNotFound flag不存在异常 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 5:15 下午 2021/2/27 +func FlagNotFound(flag string) error { + return errors.Errorf("标识为 %s 的redis未找到", flag) +} + +// LoggerInitFail 日志初始化失败 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 7:30 下午 2021/2/27 +func LoggerInitFail(flag string, err error) error { + return errors.Wrapf(err, "标识为 %s 的redis日志初始化失败", flag) +} + +// EmptyCmd 未设置要执行的命令 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 9:46 下午 2021/2/27 +func EmptyCmd() error { + return errors.Errorf("未设置要执行的命令") +} + +// CommandExecuteFail 命令执行失败 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 9:58 下午 2021/2/27 +func CommandExecuteFail(err error) error { + return errors.Wrapf(err, "命令执行异常") +} + +// ReceiverISNIL 数据接收者是空指针 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:05 下午 2021/2/27 +func ReceiverISNIL() error { + return errors.Errorf("数据接收者指针为空") +} + +// ResultConvertFail 数据结果解析失败 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:07 下午 2021/2/27 +func ResultConvertFail(err error) error { + return errors.Wrapf(err, "数据结果解析失败") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e20ff0c --- /dev/null +++ b/go.mod @@ -0,0 +1,45 @@ +module git.zhangdeman.cn/zhangdeman/redis + +go 1.17 + +require ( + git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20220514080721-7713928d9258 + git.zhangdeman.cn/zhangdeman/logger v0.0.0-20220612104513-3f63f6c673f5 + git.zhangdeman.cn/zhangdeman/util v0.0.0-20220609072516-022a755fdf2f + github.com/gin-gonic/gin v1.8.1 + github.com/go-redis/redis/v8 v8.11.5 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.7.1 + go.uber.org/zap v1.21.0 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.10.0 // indirect + github.com/goccy/go-json v0.9.7 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect + github.com/lestrrat-go/strftime v1.0.6 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa3341e --- /dev/null +++ b/go.sum @@ -0,0 +1,232 @@ +git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20220514080721-7713928d9258 h1:HfsZL23OR8TYaeWPpq4zTyzaulBMzE7Sy4Eb44IQde8= +git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20220514080721-7713928d9258/go.mod h1:dB/awTFhiytRxtWC5WQVqDumkvw3ZSAMm3mEGhjcEwc= +git.zhangdeman.cn/zhangdeman/logger v0.0.0-20220612104513-3f63f6c673f5 h1:avCnxHO3ubhYwz+DNFE+0OjHUJq+P2R53pKiq8dFLjM= +git.zhangdeman.cn/zhangdeman/logger v0.0.0-20220612104513-3f63f6c673f5/go.mod h1:0A5BV9pE31nuFE60TLbP7BIhhV/fcWoi+fHrcV2clJw= +git.zhangdeman.cn/zhangdeman/util v0.0.0-20220514075609-25936d457a72/go.mod h1:YI/XeTmrr9+8dxa4ThPkmNcEE8WHG5pZkKujpSWwIxM= +git.zhangdeman.cn/zhangdeman/util v0.0.0-20220609072516-022a755fdf2f h1:yAxxukVUdSM5wn264el+QiAEB0OBN/5H7Xw9Z6rLzUY= +git.zhangdeman.cn/zhangdeman/util v0.0.0-20220609072516-022a755fdf2f/go.mod h1:YI/XeTmrr9+8dxa4ThPkmNcEE8WHG5pZkKujpSWwIxM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= +github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= +github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ= +github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..2c4e161 --- /dev/null +++ b/logger.go @@ -0,0 +1,39 @@ +// Package redis... +// +// Description : redis... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-02-27 5:26 下午 +package redis + +import ( + "git.zhangdeman.cn/zhangdeman/logger" + "go.uber.org/zap/zapcore" +) + +// LoggerConfig 日志配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 5:26 下午 2021/2/27 +type LoggerConfig struct { + LoggerPath string + LoggerFile string + LoggerLevel zapcore.Level + ConsoleOutput bool + Encoder zapcore.Encoder + SplitConfig *logger.RotateLogConfig +} + +// LogFieldConfig 日志字段配置 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 9:20 下午 2021/2/27 +type LogFieldConfig struct { + Message string + UsedTimeField string + CommandField string + FlagField string +} diff --git a/monitor.go b/monitor.go new file mode 100644 index 0000000..75334f2 --- /dev/null +++ b/monitor.go @@ -0,0 +1,326 @@ +// Package redis ... +// +// Description : redis 系统信息监控 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-11-27 12:22 下午 +package redis + +import ( + "context" + "strings" + + "git.zhangdeman.cn/zhangdeman/util" + + yml "gopkg.in/yaml.v2" + + "github.com/go-redis/redis/v8" +) + +// GetRedisServerInfo 获取 redis server info +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 12:24 下午 2021/11/27 +func GetRedisServerInfo(client *redis.Client) (*ServerInfo, error) { + var result ServerInfo + if err := infoToStruct(client, "server", &result); nil != err { + return nil, err + } + return &result, nil +} + +// GetMemoryInfo 获取内存信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 12:59 下午 2021/11/27 +func GetMemoryInfo(client *redis.Client) (*MemoryInfo, error) { + var result MemoryInfo + if err := infoToStruct(client, "memory", &result); nil != err { + return nil, err + } + return &result, nil +} + +// GetClientInfo 获取客户端信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:10 下午 2021/11/27 +func GetClientInfo(client *redis.Client) (*ClientInfo, error) { + var result ClientInfo + if err := infoToStruct(client, "clients", &result); nil != err { + return nil, err + } + return &result, nil +} + +// GetPersistence 获取持久化相关信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:11 下午 2021/11/27 +func GetPersistence(client *redis.Client) (*Persistence, error) { + var result Persistence + if err := infoToStruct(client, "persistence", &result); nil != err { + return nil, err + } + return &result, nil +} + +// GetStats 获取状态 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:19 下午 2021/11/27 +func GetStats(client *redis.Client) (*Stats, error) { + var result Stats + if err := infoToStruct(client, "stats", &result); nil != err { + return nil, err + } + return &result, nil +} + +// GetReplication 复制相关信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:23 下午 2021/11/27 +func GetReplication(client *redis.Client) (*Replication, error) { + var result Replication + if err := infoToStruct(client, "replication", &result); nil != err { + return nil, err + } + result.SlaveList = GetSlaveList(client) + return &result, nil +} + +// GetSlaveList 获取从库信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 4:26 下午 2021/11/27 +func GetSlaveList(client *redis.Client) []SlaveNode { + // 解析从库信息 + slaveList := make([]SlaveNode, 0) + var data map[string]string + _ = infoToStruct(client, "replication", &data) + slaveKey := []string{"slave0", "slave1", "slave2", "slave3", "slave4", "slave5", "slave6", "slave7", "slave8", "slave9"} + for k, v := range data { + isSlave := false + for _, item := range slaveKey { + if strings.Contains(k, item) { + isSlave = true + break + } + } + if !isSlave { + continue + } + + vArr := strings.Split(v, ",") + if len(vArr) < 3 { + continue + } + slave := SlaveNode{ + ID: k, + IP: "", + Port: 0, + State: "", + Offset: 0, + Lag: 0, + } + for _, prop := range vArr { + propArr := strings.Split(prop, "=") + if len(propArr) != 2 { + continue + } + switch strings.ToLower(propArr[0]) { + case "ip": + slave.IP = propArr[1] + case "port": + _ = util.ConvertAssign(&slave.Port, propArr[1]) + case "state": + slave.State = propArr[1] + case "offset": + _ = util.ConvertAssign(&slave.Offset, propArr[1]) + case "lag": + _ = util.ConvertAssign(&slave.Lag, propArr[1]) + } + } + slaveList = append(slaveList, slave) + } + return slaveList +} + +// GetCPUInfo 获取cpu信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:24 下午 2021/11/27 +func GetCPUInfo(client *redis.Client) (*CPU, error) { + var result CPU + if err := infoToStruct(client, "cpu", &result); nil != err { + return nil, err + } + return &result, nil +} + +// GetCommandStats 命令状态 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:27 下午 2021/11/27 +func GetCommandStats(client *redis.Client) (*Stats, error) { + var result Stats + if err := infoToStruct(client, "stats", &result); nil != err { + return nil, err + } + return &result, nil +} + +// GetCommandInfo 获取命令信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 1:32 下午 2021/11/27 +func GetCommandInfo(client *redis.Client) ([]CmdStat, error) { + var result map[string]string + if err := infoToStruct(client, "commandstats", &result); nil != err { + return make([]CmdStat, 0), err + } + cmdList := make([]CmdStat, 0) + // 解析数据 + for cmd, detail := range result { + infoArr := strings.Split(detail, ",") + if len(infoArr) < 2 { + // 去掉开始的描述信息 + continue + } + info := CmdStat{ + Cmd: strings.ReplaceAll(strings.ToLower(cmd), "cmdstat_", ""), + Calls: 0, + TotalUsedTime: 0, + AvgUsedTime: 0, + RejectedCalls: 0, + FailedCalls: 0, + SuccessCalls: 0, + } + for _, item := range infoArr { + itemArr := strings.Split(item, "=") + if len(itemArr) != 2 { + continue + } + switch strings.ToLower(itemArr[0]) { + case "calls": + _ = util.ConvertAssign(&info.Calls, itemArr[1]) + case "usec": + _ = util.ConvertAssign(&info.TotalUsedTime, itemArr[1]) + case "usec_per_call": + _ = util.ConvertAssign(&info.AvgUsedTime, itemArr[1]) + case "rejected_calls": + _ = util.ConvertAssign(&info.RejectedCalls, itemArr[1]) + case "failed_calls": + _ = util.ConvertAssign(&info.FailedCalls, itemArr[1]) + } + } + info.SuccessCalls = info.Calls - info.FailedCalls - info.RejectedCalls + cmdList = append(cmdList, info) + } + return cmdList, nil +} + +// GetKeyspace 获取 keyspace 信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2:44 下午 2021/11/27 +func GetKeyspace(client *redis.Client) ([]DB, error) { + var result map[string]string + if err := infoToStruct(client, "keyspace", &result); nil != err { + return make([]DB, 0), err + } + dbList := make([]DB, 0) + for dbName, item := range result { + itemArr := strings.Split(item, ",") + if len(itemArr) < 3 { + continue + } + dbInfo := DB{ + Name: dbName, + Keys: 0, + Expires: 0, + AvgTTL: 0, + } + for _, kv := range itemArr { + kvArr := strings.Split(kv, "=") + if len(kvArr) != 2 { + continue + } + switch strings.ToLower(kvArr[0]) { + case "keys": + _ = util.ConvertAssign(&dbInfo.Keys, kvArr[1]) + case "expires": + _ = util.ConvertAssign(&dbInfo.Expires, kvArr[1]) + case "avg_ttl": + _ = util.ConvertAssign(&dbInfo.AvgTTL, kvArr[1]) + } + } + dbList = append(dbList, dbInfo) + } + return dbList, nil +} + +// GetCluster 获取 cluster 信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 3:30 下午 2021/11/27 +func GetCluster(client *redis.Client) (*Cluster, error) { + var result Cluster + if err := infoToStruct(client, "cluster", &result); nil != err { + return nil, err + } + return &result, nil +} + +// GetErrorStats 获取错误状态信息 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 3:35 下午 2021/11/27 +func GetErrorStats(client *redis.Client) ([]Error, error) { + var result map[string]string + if err := infoToStruct(client, "errorstats", &result); nil != err { + return make([]Error, 0), err + } + + errList := make([]Error, 0) + for errType, item := range result { + itemArr := strings.Split(item, "=") + if len(itemArr) != 2 { + continue + } + errInfo := Error{ + Type: strings.ReplaceAll(errType, "errorstat_", ""), + Count: 0, + } + _ = util.ConvertAssign(&errInfo.Count, itemArr[1]) + errList = append(errList, errInfo) + } + return errList, nil +} + +// infoToStruct 读取到的数据,解析到结构体 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 12:28 下午 2021/11/27 +func infoToStruct(client *redis.Client, module string, receiver interface{}) error { + data := client.Info(context.Background(), module).String() + // yaml 文件规范要求, key: val , 注意 : 的后面有空格 + data = strings.ReplaceAll(data, ":", ": ") + return yml.Unmarshal([]byte(data), receiver) +} diff --git a/redis_test.go b/redis_test.go new file mode 100644 index 0000000..06bc115 --- /dev/null +++ b/redis_test.go @@ -0,0 +1,53 @@ +// Package redis... +// +// Description : redis... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2021-02-27 10:14 下午 +package redis + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + redisInstance "github.com/go-redis/redis/v8" +) + +// TestCommandProxy ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 10:22 下午 2021/2/27 +func TestCommandProxy(t *testing.T) { + instance, err := NewClient(map[string]Options{ + "test_redis": { + Conf: &redisInstance.Options{ + Addr: "127.0.0.1:6379", + }, + Logger: &LoggerConfig{ + LoggerPath: "/tmp/test-log", + LoggerFile: "test-pkg-redis-client.log", + LoggerLevel: 0, + ConsoleOutput: true, + Encoder: nil, + SplitConfig: nil, + }, + LoggerFieldConfig: nil, + }, + }, nil) + if nil != err { + panic(err.Error()) + } + r, cmdErr := instance.CommandProxy(nil, "test_redis", "set", "command_proxy", "hello world") + c, _ := instance.GetRedisClient("test_redis") + fmt.Println(GetRedisServerInfo(c.Instance)) + fmt.Println(GetCommandInfo(c.Instance)) + fmt.Println(GetKeyspace(c.Instance)) + fmt.Println(GetCluster(c.Instance)) + fmt.Println(GetErrorStats(c.Instance)) + assert.Nil(t, cmdErr, "命令执行成功") + assert.Equal(t, "OK", fmt.Sprintf("%v", r)) +}