// RedisConfPipline redis连接的设置方法,例如在流量统计中间件中设置数据和超时时间
func RedisConfPipline(pip ...func(c redis.Conn)) error {
//redis读取方式,创建连接
c, err := lib.RedisConnFactory("default")
if err != nil {
return err
}
defer c.Close()
for _, f := range pip {
f(c)
}
c.Flush()
return nil
}
// RedisConfDo redis执行操作的方法,例如在流量统计中间件中使用get方法获取redis中储存的流量数据
func RedisConfDo(commandName string, args ...interface{}) (interface{}, error) {
c, err := lib.RedisConnFactory("default")
if err != nil {
return nil, err
}
defer c.Close()
return c.Do(commandName, args...)
}
type RedisFlowCountService struct {
AppID string
Interval time.Duration
QPS int64
Unix int64
TickerCount int64
TotalCount int64
}
// NewRedisFlowCountService 参数:设置APPID和统计结果的刷新时间频率
func NewRedisFlowCountService(appID string, interval time.Duration) *RedisFlowCountService {
reqCounter := &RedisFlowCountService{
AppID: appID,
Interval: interval,
QPS: 0,
Unix: 0,
}
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
ticker := time.NewTicker(interval)
for {
<-ticker.C
tickerCount := atomic.LoadInt64(&reqCounter.TickerCount) //获取数据
atomic.StoreInt64(&reqCounter.TickerCount, 0) //重置数据
currentTime := time.Now()
dayKey := reqCounter.GetDayKey(currentTime)
hourKey := reqCounter.GetHourKey(currentTime)
if err := RedisConfPipline(func(c redis.Conn) {
//数据的增加,新建的,都是0,上面数据重置了
c.Send("INCRBY", dayKey, tickerCount)
//超时时间设置
c.Send("EXPIRE", dayKey, 86400*2)
c.Send("INCRBY", hourKey, tickerCount)
c.Send("EXPIRE", hourKey, 86400*2)
}); err != nil {
fmt.Println("RedisConfPipline err", err)
continue
}
totalCount, err := reqCounter.GetDayData(currentTime)
if err != nil {
fmt.Println("reqCounter.GetDayData err", err)
continue
}
nowUnix := time.Now().Unix()
if reqCounter.Unix == 0 {
reqCounter.Unix = time.Now().Unix()
continue
}
tickerCount = totalCount - reqCounter.TotalCount
if nowUnix > reqCounter.Unix {
reqCounter.TotalCount = totalCount
reqCounter.QPS = tickerCount / (nowUnix - reqCounter.Unix)
reqCounter.Unix = time.Now().Unix()
}
}
}()
return reqCounter
}
func (o *RedisFlowCountService) GetDayKey(t time.Time) string {
dayStr := t.In(lib.TimeLocation).Format("20060102")
//设置到redis的key中
return fmt.Sprintf("%s_%s_%s", RedisFlowDayKey, dayStr, o.AppID)
}
func (o *RedisFlowCountService) GetHourKey(t time.Time) string {
hourStr := t.In(lib.TimeLocation).Format("2006010215")
return fmt.Sprintf("%s_%s_%s", RedisFlowHourKey, hourStr, o.AppID)
}
// GetHourData 封装获取方法 redis的get获取数据
func (o *RedisFlowCountService) GetHourData(t time.Time) (int64, error) {
return redis.Int64(RedisConfDo("GET", o.GetHourKey(t)))
}
func (o *RedisFlowCountService) GetDayData(t time.Time) (int64, error) {
return redis.Int64(RedisConfDo("GET", o.GetDayKey(t)))
}
// Increase 原子增加
func (o *RedisFlowCountService) Increase() {
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
atomic.AddInt64(&o.TickerCount, 1)
//fmt.Println("TickerCount---------->", o.TickerCount)
}()
}
使用场景:
通常在分布式系统中,我们经常会从数据库中读取数据和修改数据,然而这不是一个原子操作,在并发时就会导致数据的不正确,例如一会下面的电商秒杀,库存数量的更新就会遇到。
redis是单线程的,单纯的使用同步锁只能保证单体系统下正常运行,但是在微服务架构下没法保证,所以要使用setnx分布式锁实现写入
基本语法:
Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
redis> EXISTS job # job 不存在
(integer) 0
redis> SETNX job "programmer" # job 设置成功
(integer) 1
redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0
redis> GET job # 没有被覆盖
"programmer"
说人话就是:使用setnx命令设置了一个key,之后再次设置覆盖就会报错,除非将这个key删除了,才能重新设置
这样的一个特性就可以用于加锁使得数据同步的功能上
但是单纯这么使用还有缺陷去,一旦中间的业务代码操作出现了异常,就会导致程序无法解锁,而其他请求也会一直无法拿到key,造成程序逻辑死锁
这时可以采取捕获异常的方式解决,保证即使上述逻辑出问题,也能del掉
解决办法:
上锁的同时,利用原子性的操作设置key的时长,过期后就抛出异常
单例模式设计,共用一个,避免重复创建,也为了使一个服务中的数据可以重复地累计
var FlowCounterHandler *FlowCounter
type FlowCounter struct {
RedisFlowCountMap map[string]*RedisFlowCountService
RedisFlowCountSlice []*RedisFlowCountService
Locker sync.RWMutex
}
func NewFlowCounter() *FlowCounter {
return &FlowCounter{
RedisFlowCountMap: map[string]*RedisFlowCountService{},
RedisFlowCountSlice: []*RedisFlowCountService{},
Locker: sync.RWMutex{},
}
}
func init() {
FlowCounterHandler = NewFlowCounter()
}
func (counter *FlowCounter) GetCounter(serverName string) (*RedisFlowCountService, error) {
for _, item := range counter.RedisFlowCountSlice {
if item.AppID == serverName {
return item, nil
}
}
newCounter := NewRedisFlowCountService(serverName, 1*time.Second)
counter.RedisFlowCountSlice = append(counter.RedisFlowCountSlice, newCounter)
counter.Locker.Lock()
defer counter.Locker.Unlock()
counter.RedisFlowCountMap[serverName] = newCounter
return newCounter, nil
}