package config
type Configuration struct {
// +++++++++++++++日志相关+++++++++++++++++
// 日志级别,这里使用了 beego 的 log 包
Log *LogConfigure `yaml:"log"`
// 服务端消息chan长度
ServerMsgChanBufferLength int `yaml:"server-msgchan-bufferlength"`
ServerConnTimeout int `yaml:"server-conn-timeout"`
//proxy agent读写server超时时间,单位秒
ServerRWTimeout int `yaml:"server-rw-timeout"`
}
// 定义嵌套的块级配置
type LogConfigure struct{
// [0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug]
LogLevel int `yaml:"log-level"`
// 日志输出位置,默认日志输出到控制台
// 目前只支持['console', 'file']两种形式,如非console形式这里需要指定文件的路径,可以是相对路径
LogOutput string `yaml:"log-output"`
// 日志最大保留天数
LogMaxDays int `yaml:"log-maxdays"`
}
// 初始化默认值
var Config = &Configuration{
ServerMsgChanBufferLength 10,
ServerConnTimeout 1,
ServerRWTimeout 1,
}
log:
# 日志级别,默认为3:Error
# [0:Emergency, 1:Alert, 2:Critical, 3:Error, 4:Warning, 5:Notice, 6:Informational, 7:Debug]
log-level: 7
# 日志最大保留天数
log-maxdays: 7
# 日志输出位置,默认日志输出到控制台
# 目前只支持['console', 'file']两种形式,如非console形式这里需要指定文件的路径,可以是相对路径
log-output: log/redis-agent.log
server-msgchan-bufferlength: 3
# 连接server超时时间,单位秒
server-conn-timeout: 5
# 读写server超时时间,单位秒
server-rw-timeout: 5
// 加载配置文件
func (conf *Configuration) readConfigFile(path string) error {
configFile, err := os.Open(path)
if err != nil {
os.Stderr.WriteString(fmt.Sprintf("readConfigFile(%s) os.Open failed: %v", path, err))
return err
}
defer configFile.Close()
content, err := ioutil.ReadAll(configFile)
if err != nil {
os.Stderr.WriteString(fmt.Sprintf("readConfigFile(%s) ioutil.ReadAll failed: %v", path, err))
return err
}
// 解析byte编码的数据并将结果存入Config指向的值
err = yaml.Unmarshal(content, Config)
if err != nil {
os.Stderr.WriteString(fmt.Sprintf("readConfigFile(%s) yaml.Unmarshal failed: %v", path, err))
return err
}
return nil
}
// 配置初始化
func ParseConfig(configFile string) error {
var err error
// 如果未传入配置文件,则返回报错
if "" == configFile {
return errors.New("No config file input")
}
// 配置文件状态检查
if _, err = os.Stat(configFile); err != nil {
os.Stderr.WriteString(fmt.Sprintf("%v [redis-agent start failed] Check config file failed."+
" err=%v ConfFile=%v \n", time.Now().Format(TIME_FORMAT), err, configFile))
return err
}
// 配置文件解析
if err = Config.readConfigFile(configFile); err != nil {
os.Stderr.WriteString(fmt.Sprintf("%v [redis-agent start failed]"+
" Parse config file failed. ConfFile=%v err=%v \n", time.Now().Format(TIME_FORMAT), err, configFile))
return err
}
return LoggerInit()
}
redisgo 接口使用文档
一开始用尝试用go-redis包,但是依赖较多且遇到很多不兼容问题,于是转而该用redisgo。支持自定义命令,支持各种协议的redis命令,包括sentinel相关的命令。需要注意的地方
package redis
import (
"context"
"fmt"
"redis-agent/common"
"time"
"github.com/gomodule/redigo/redis"
)
// 初始化proxy的密码设置
var ProxyPwd = &common.Config.ProxyPwd
var Conf = common.Config
func InitRedis(serverType string, port int64) (redis.Conn, error) {
// 初始化连接参数
var options []redis.DialOption
redisOpt := append([]redis.DialOption{
redis.DialReadTimeout(time.Duration(Conf.ServerRWTimeout) * time.Second),
redis.DialWriteTimeout(time.Duration(Conf.ServerRWTimeout) * time.Second),
redis.DialConnectTimeout(time.Duration(Conf.ServerConnTimeout) * time.Second)},
options...)
var ctx = context.Background()
var addr = fmt.Sprintf("127.0.0.1:%d", port)
c, err := redis.DialContext(ctx, "tcp", addr, redisOpt...)
if err != nil {
return nil, err
}
// 如果服务类型为proxy则设置密码
var password = ""
if serverType == "Proxy" {
// 如果密码不为空则进行权限认证
if password = *ProxyPwd; password != "" {
if _, err := c.Do("AUTH", password); err != nil {
return nil, err
}
}
}
return c, nil
}
func Exec(serverType string, port int64, cmd string, key interface{}, args ...interface{}) (interface{}, error) {
var con redis.Conn
var err error
con, err = InitRedis(serverType, port)
if err != nil {
return nil, err
}
defer con.Close()
parmas := make([]interface{}, 0)
parmas = append(parmas, key)
if len(args) > 0 {
for _, v := range args {
parmas = append(parmas, v)
}
}
reply, err := con.Do(cmd, parmas...)
if err != nil {
errMsg := fmt.Sprintf("exec cmd error,cmd=%v parmas=%v error=%v", cmd, parmas, err)
fmt.Println(errMsg)
return reply, err
}
/* 可视化结果集
string类型的key的访问及管理命令默认返回的数据类型为[]uint8,将其转化为string显示
集合类的结果集通过redis.Strings转化为[]string
*/
switch reply.(type) {
case []uint8:
reply, err = redis.String(reply, err)
case []interface{}:
reply, err = redis.Strings(reply, err)
}
return reply, err
}
在代码文件所在的同一目录下创建$(文件名)_test.go文件。
使用了测试框架convey,使得测试代码也更简洁了一些,
Convery有三个参数,第一个是描述,第二个是测试表testing.T结构体,第三个是闭包,
So是固定用法,用来校验所给参数是否符合预期
package redis
import (
"fmt"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestExec(t *testing.T) {
res, err := Exec("proxy", 1111, "get", "a")
fmt.Printf("res: %v,type=%T\n", res, res)
Convey("Exec", t, func() {
// So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
命令行执行测试代码
# 进入到项目一级目录下,执行,这里代码和测试代码均在二级目录redis下,以下命令会自动执行指定目录下的所有测试文件
go test -v ./redis/
# 输出结果
=== RUN TestExec
res: aa,type=string
Exec ✔
1 total assertion
--- PASS: TestExec (0.02s)
PASS
ok redis-agent/redis 0.040s
如果只执行特定的测试文件,指定测试文件执行
go test -v ./redis/connect_test.go ./redis/connect.go
如果只执行特定测试文件中的某些测试函数
# -run 指定待执行函数名的前缀
go test -v ./redis/ -run TestExec
一定要注意测试结果默认是被缓存的,不适用于动态修改测试代码debug结果的场景,关闭缓存功能加参数-count=1