- 列表项目
原文地址 : https://cloud.tencent.com/developer/article/2017632
Go
实战项目-Beego
的Session
、日志文件的使用和[redis][1]
的选择使用
session的简单使用
go标准库里面没有实现这功能,只能靠自己实现了,哦,不,是第三方库。好在beego就自带session功能,这个之前就说过了。我们只是简单使用下,高并发场景估计还得自己来实现,单纯的靠这个框架,够呛。来看下怎么使用:
1、在调用之前就需要开启
beego.BConfig.WebConfig.Session.SessionOn = true //开始session
beego
目前支持四种session
的存储引擎 memory
、file
、Redis
和 MySQL
默认就是memory
,但是,你重启之后就失效了,这除了写demo可以用之外,就算是保活的进程也是很肉痛,基于之前PHP框架保存文件的处理方式,我这边也是存放文件中。
2、设置存储引擎
beego.BConfig.WebConfig.Session.SessionProvider = “file” //指定文件存储方式
3、设置存储路径
beego.BConfig.WebConfig.Session.SessionProviderConfig = “./.tmp” //指定文件存储路径地址,也可以不指定,有默认的地址。
建议,存储的文件夹名称加上“.”,这样方便git提交的时候直接过滤,但是一般情况下,没事不要去下载,或者放在项目以外的其他路径也是可以的。这样就是永久保存了,重启依然有效。
本地日志的使用
和PHP不同,常驻内存的代码调试错误或者发现线上问题等等都是需要看日志记录的,毕竟控制台那时候咱也看不到了。靠控制台发现问题也不太现实。所以很有必要加上日志,这对于习惯于PHP开发的同学来说是个不顺手的习惯,毕竟脚本调试太简单轻松了,修改立即生效。beego的启动日志也是很简单的,直接设置就好,支持多文件,按照规则来分割,默认也会按照日期来进行分割的。
logs.SetLogger(logs.AdapterFile, {"filename":"./logs/callout.log"})
按照这样设置后,每天也是会有一个单独的日志,名称默认就是callout.2020-10-13.001.log
,我们也可以动态更改文件夹,按照日期做,这样对于排查问题是可以提高效率的,省去了很多麻烦。多文件设置只需要把logs.AdapterFile
改成logs.AdapterMultiFile
,后面的基本一致,可以添加分割规则。官方文档有简单的描述,我们不深究这些,能用就好。程序员经典的几句话:又不是不能用。
redis的使用
redis
,很多库都是自带连接池的,所以我们没必要自己亲自去造轮子,当然,造一遍自己的提升也是相当明显的。开始使用的时候,必须是最主流的第三方框架,redigo,引入还是照旧:”github.com/garyburd/redigo/redis”,直接导入就好。但是由于笔者公司的redis采用的是集群的方式部署的,所以,考虑到这个的使用,我别无选择的使用谷歌的亲儿子库go-redis
。两个都用上吧,毕竟都是主流的两个框架
redigo的使用
直接连接
func ConnectRedis() redis.Conn {
conn, _ := redis.Dial("tcp", "127.0.0.1:6379")
return conn
}
连接池连接
func ConnectRedisPool() redis.Conn {
connPool := &redis.Pool{
Dial: func() (conn redis.Conn, err error) {
conn, err = redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
return nil, err
}
return conn, nil
},
TestOnBorrow: nil,
MaxIdle: 1,
MaxActive: 10,
IdleTimeout: 180 * time.Second,
Wait: true,
MaxConnLifetime: 0,
}
return connPool.Get()
}
使用的时候记得defer
调用close()
函数,正常的使用是Do
的方式,举个简单的设置和获取的栗子
//@router /process/test [get]
func (c *ProcessControllers) SetRedis() {//redigo 连接获取方式
redisPool := redisClient.ConnectRedisPool()
defer redisPool.Close()
_, err := redisPool.Do("SET", "wjw_key", "wjw")
if err == nil {
c.Data["json"] = ReturnSuccess("请求成功", nil, 0)
} else {
c.Data["json"] = ReturnError(-4007, err.Error())
}
c.ServeJSON()
}
// @router /process/getRedis [get]
func (c *ProcessControllers) GetRedis() {
redisPool := redisClient.ConnectRedisPool()
defer redisPool.Close()
data, err := redis.String(redisPool.Do("GET", "wjw_key"))
if err == nil {
c.Data["json"] = ReturnSuccess("请求成功", data, 0)
} else {
c.Data["json"] = ReturnError(-4007, err.Error())
}
c.ServeJSON()
}
redigo
,并不支持集群的使用,也不知道为什么这么多选择使用的,是不是也说明很多企业并没有集群或者哨兵模式?那容灾容错怎么处理的呢?是不是恰恰说明,很多公司都是实用型的,没必要整这么多的弯弯绕。
go-redis的使用
谷歌出品的,支持集群和哨兵方式等的连接。这也是比redigo更吸引的地方。
直接连接
func ConnectRedis() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "", // 不设置密码
DB: 0, // 使用默认的
})
return client
}
实际上直接连接的方式,效率很低,除了写Demo验证功能之外,实际开发应该不会想不开的用吧,大多是连接池的方式使用。
连接池连接
func ConnectRedisPool() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "", // 不设置密码
DB: 0, // 使用默认的
PoolSize: 15, // 连接池最大socket连接数,默认为5倍CPU数, 5 * runtime.NumCPU
MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量
DialTimeout: 5 * time.Second, //连接建立超时时间,默认5秒。
ReadTimeout: 3 * time.Second, //读超时,默认3秒, -1表示取消读超时
WriteTimeout: 3 * time.Second, //写超时,默认等于读超时
PoolTimeout: 4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。
//闲置连接检查包括IdleTimeout,MaxConnAge
IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期,默认为1分钟,-1表示不做周期性检查,只在客户端获取连接时对闲置连接进行处理。
IdleTimeout: 5 * time.Minute, //闲置超时,默认5分钟,-1表示取消闲置超时检查
MaxConnAge: 0 * time.Second, //连接存活时长,从创建开始计时,超过指定时长则关闭连接,默认为0,即不关闭存活时长较长的连接
//命令执行失败时的重试策略
MaxRetries: 0, // 命令执行失败时,最多重试多少次,默认为0即不重试
MinRetryBackoff: 8 * time.Millisecond, //每次计算重试间隔时间的下限,默认8毫秒,-1表示取消间隔
MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限,默认512毫秒,-1表示取消间隔
Dialer: func() (conn net.Conn, err error) {
netDialer := &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 5 * time.Minute,
}
return netDialer.Dial("tcp", "127.0.0.1:6379")
},
OnConnect: func(conn *redis.Conn) error { //仅当客户端执行命令时需要从连接池获取连接时,如果连接池需要新建连接时则会调用此钩子函数
fmt.Printf("conn=%v\n", conn)
return nil
},
})
return client
}
这些注释,可以在源码里面看得到的,都有比较详细的英文描述,稍微借助工具翻译下就知道了。重点在下面的,也是笔者使用的。
集群/哨兵模式下的连接
func ConnectRedisClusterPool() *redis.ClusterClient {
client := redis.NewClusterClient(&redis.ClusterOptions{//哨兵模式:NewSentinelClient
//Addrs: []string{ "127.0.0.1:6379", "127.0.0.1:6380"},//集群节点地址,理论上只要填一个可用的节点客户端就可以自动获取到集群的所有节点信息。但是最好多填一些节点以增加容灾能力,因为只填一个节点的话,如果这个节点出现了异常情况,则Go应用程序在启动过程中无法获取到集群信息。
Addrs: []string{"127.0.0.1:6379"},
Password: "", // 不设置密码
MaxRedirects: 8, // 当遇到网络错误或者MOVED/ASK重定向命令时,最多重试几次,默认8
PoolSize: 15, // 连接池最大socket连接数,默认为5倍CPU数, 5 * runtime.NumCPU
MinIdleConns: 10, //在启动阶段创建指定数量的Idle连接,并长期维持idle状态的连接数不少于指定数量
DialTimeout: 5 * time.Second, //连接建立超时时间,默认5秒。
ReadTimeout: 3 * time.Second, //读超时,默认3秒, -1表示取消读超时
WriteTimeout: 3 * time.Second, //写超时,默认等于读超时
PoolTimeout: 4 * time.Second, //当所有连接都处在繁忙状态时,客户端等待可用连接的最大等待时长,默认为读超时+1秒。
//闲置连接检查包括IdleTimeout,MaxConnAge
IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期,默认为1分钟,-1表示不做周期性检查,只在客户端获取连接时对闲置连接进行处理。
IdleTimeout: 5 * time.Minute, //闲置超时,默认5分钟,-1表示取消闲置超时检查
MaxConnAge: 0 * time.Second, //连接存活时长,从创建开始计时,超过指定时长则关闭连接,默认为0,即不关闭存活时长较长的连接
//命令执行失败时的重试策略
MaxRetries: 0, // 命令执行失败时,最多重试多少次,默认为0即不重试
MinRetryBackoff: 8 * time.Millisecond, //每次计算重试间隔时间的下限,默认8毫秒,-1表示取消间隔
MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限,默认512毫秒,-1表示取消间隔
//只含读操作的命令的"节点选择策略"。默认都是false,即只能在主节点上执行。
ReadOnly: false, // 置为true则允许在从节点上执行只含读操作的命令
// 默认false。 置为true则ReadOnly自动置为true,表示在处理只读命令时,可以在一个slot对应的主节点和所有从节点中选取Ping()的响应时长最短的一个节点来读数据
RouteByLatency: false,
// 默认false。置为true则ReadOnly自动置为true,表示在处理只读命令时,可以在一个slot对应的主节点和所有从节点中随机挑选一个节点来读数据
RouteRandomly: false,
//用户可定制读取节点信息的函数,比如在非集群模式下可以从zookeeper读取。
//但如果面向的是redis cluster集群,则客户端自动通过cluster slots命令从集群获取节点信息,不会用到这个函数。
//ClusterSlots: func() ([]redis.ClusterSlot, error) {
// return nil,nil
//},
OnConnect: func(conn *redis.Conn) error {
fmt.Printf("conn=%v\n", conn)
return nil
},
})
return client
}
使用方式和连接池的一样,只是调用的类型换成了NewClusterClient,记住,如果是哨兵模式,换成NewSentinelClient连接就行了。
调用方式,同样直接贴出两段代码:
// @router /process/setRedis [get]
func (c *ProcessControllers) SetRedis() { //go-redis 连接获取方式
redisPool := redisClient.ConnectRedisClusterPool()
defer redisPool.Close()
_, err := redisPool.Ping().Result()
if err == nil {
err = redisPool.Set("wjw_key", "wjw hello redis", 0).Err()
if err == nil {
c.Data["json"] = ReturnSuccess("请求成功", nil, 0)
} else {
c.Data["json"] = ReturnError(-4007, err.Error())
}
} else {
c.Data["json"] = ReturnError(-4007, err.Error())
}
c.ServeJSON()
}
// @router /process/getRedis [get]
func (c *ProcessControllers) GetRedis() {
redisPool := redisClient.ConnectRedisClusterPool()
defer redisPool.Close()
_, err := redisPool.Ping().Result()
if err == nil {
res, err := redisPool.Get("wjw_key").Result()
if err == nil {
c.Data["json"] = ReturnSuccess("请求成功", res, 0)
} else {
c.Data["json"] = ReturnError(-4008, err.Error())
}
} else {
c.Data["json"] = ReturnError(-4009, err.Error())
}
c.ServeJSON()
}
不是什么难点技术,后续的使用才是考验技巧的时候。