go-common/app/job/main/reply-feed/dao/redis.go
2019-04-22 18:49:16 +08:00

428 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package dao
import (
"context"
"fmt"
"time"
"go-common/app/job/main/reply-feed/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
// r_<实验组名>_<oid>_<type>
// 用redis ZSet存储热门评论列表score为热评分数member为rpID
_replyZSetFormat = "r_%s_%d_%d"
// c_<oid>_<type>
_refreshCheckerFormat = "c_%d_%d"
// h_<oid>_<type>
// 用一个set来存某一个评论区下的应该存在于热门评论列表的评论ID
// 上热评的最低门槛点赞数大于3且该评论区根评论数目大于20
_replyListFormat = "h_%d_%d"
// 行为, 小时slot, 种类(全量或者针对热评)
_uvFormat = "uv_%s_%d_%d_%s"
_uvExp = 3600
)
func keyRefreshChecker(oid int64, tp int) string {
return fmt.Sprintf(_refreshCheckerFormat, oid, tp)
}
func keyReplyZSet(name string, oid int64, tp int) string {
return fmt.Sprintf(_replyZSetFormat, name, oid, tp)
}
func keyReplySet(oid int64, tp int) string {
return fmt.Sprintf(_replyListFormat, oid, tp)
}
// KeyUV ...
func (d *Dao) KeyUV(action string, hour, slot int, kind string) string {
return keyUV(action, hour, slot, kind)
}
func keyUV(action string, hour, slot int, kind string) string {
return fmt.Sprintf(_uvFormat, action, hour, slot, kind)
}
// PingRedis redis health check.
func (d *Dao) PingRedis(ctx context.Context) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
_, err = conn.Do("SET", "ping", "pong")
return
}
// AddUV ...
func (d *Dao) AddUV(ctx context.Context, action string, hour, slot int, mid int64, kind string) (err error) {
key := keyUV(action, hour, slot, kind)
conn := d.redis.Get(ctx)
defer conn.Close()
if err = conn.Send("SADD", key, mid); err != nil {
log.Error("redis SADD(%s, %d) error(%v)", key, mid, err)
return
}
if err = conn.Send("EXPIRE", key, _uvExp); err != nil {
log.Error("redis EXPIRE(%s) error(%v)", key, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("redis Flush() error(%v)", err)
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("redis Receive() error(%v)", err)
return
}
}
return
}
// CountUV ...
func (d *Dao) CountUV(ctx context.Context, keys []string) (counts []int64, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
j := 0
for _, key := range keys {
if err = conn.Send("SCARD", key); err != nil {
log.Error("redis SCARD(%s) error(%v)", key, err)
return
}
j++
}
if err = conn.Flush(); err != nil {
log.Error("redis Flush() error(%v)", err)
return
}
for i := 0; i < j; i++ {
var count int64
if count, err = redis.Int64(conn.Receive()); err != nil && err != redis.ErrNil {
log.Error("redis Receive() error(%v)", err)
return
}
counts = append(counts, count)
}
return
}
// ExpireCheckerRds expire checker.
func (d *Dao) ExpireCheckerRds(ctx context.Context, oid int64, tp int) (ok bool, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyRefreshChecker(oid, tp)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisRefreshExpire)); err != nil {
if err == redis.ErrNil {
err = nil
return
}
log.Error("redis EXPIRE key(%s) error(%v)", key, err)
}
return
}
// ExpireReplyZSetRds expire reply list.
func (d *Dao) ExpireReplyZSetRds(ctx context.Context, name string, oid int64, tp int) (ok bool, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplyZSet(name, oid, tp)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisReplyZSetExpire)); err != nil {
if err == redis.ErrNil {
err = nil
return
}
log.Error("redis EXPIRE key(%s) error(%v)", key, err)
}
return
}
// ExpireReplySetRds expire reply set.
func (d *Dao) ExpireReplySetRds(ctx context.Context, oid int64, tp int) (ok bool, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplySet(oid, tp)
if ok, err = redis.Bool(conn.Do("EXPIRE", key, d.redisReplySetExpire)); err != nil {
if err == redis.ErrNil {
err = nil
return
}
log.Error("redis EXPIRE key(%s) error(%v)", key, err)
}
return
}
// ReplySetRds get reply rpIDs from redis set.
func (d *Dao) ReplySetRds(ctx context.Context, oid int64, tp int) (rpIDs []int64, err error) {
key := keyReplySet(oid, tp)
var startCursor, endCursor int64
endCursor = 1
for endCursor != 0 {
var (
conn = d.redis.Get(ctx)
chunkedRpIDs []int64
values []interface{}
)
if values, err = redis.Values(conn.Do("SSCAN", key, startCursor)); err != nil {
log.Error("redis SSCAN(%s) error(%v)", key, err)
conn.Close()
return
}
if _, err = redis.Scan(values, &endCursor, &chunkedRpIDs); err != nil {
log.Error("redis Scan(%v) error(%v)", values, err)
conn.Close()
return
}
startCursor = endCursor
rpIDs = append(rpIDs, chunkedRpIDs...)
conn.Close()
}
return
}
// SetReplySetRds set reply list batch, call it when back to source.
func (d *Dao) SetReplySetRds(ctx context.Context, oid int64, tp int, rpIDs []int64) (err error) {
if len(rpIDs) < 1 {
return
}
for _, chunkedRpIDs := range split(rpIDs, 5000) {
var (
key = keyReplySet(oid, tp)
args = make([]interface{}, 0, len(chunkedRpIDs)+1)
conn = d.redis.Get(ctx)
)
args = append(args, key)
for _, rpID := range chunkedRpIDs {
args = append(args, rpID)
}
if err = conn.Send("SADD", args...); err != nil {
log.Error("redis SADD(%v) error(%v)", args, err)
conn.Close()
return
}
if err = conn.Send("EXPIRE", key, d.redisReplySetExpire); err != nil {
log.Error("redis EXPIRE(%s) error(%v)", key, err)
conn.Close()
return
}
if err = conn.Flush(); err != nil {
log.Error("redis Flush() error(%v)", err)
conn.Close()
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("redis Receive() error(%v)", err)
conn.Close()
return
}
}
conn.Close()
}
return
}
// RemReplySetRds remove one rp from set.
func (d *Dao) RemReplySetRds(ctx context.Context, oid, rpID int64, tp int) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplySet(oid, tp)
if _, err = redis.Int64(conn.Do("SREM", key, rpID)); err != nil {
if err == redis.ErrNil {
err = nil
return
}
log.Error("SREM conn.Do(%s,%d) err(%v)", key, rpID, err)
}
return
}
// DelReplySetRds delete a set key.
func (d *Dao) DelReplySetRds(ctx context.Context, oid int64, tp int) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplySet(oid, tp)
if _, err = conn.Do("DEL", key); err != nil {
log.Error("conn.Do(DEL %s) error(%v)", key, err)
}
return
}
// AddReplySetRds add a reply into redis set, make sure expire the key first.
func (d *Dao) AddReplySetRds(ctx context.Context, oid int64, tp int, rpID int64) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplySet(oid, tp)
if _, err = conn.Do("SADD", key, rpID); err != nil {
log.Error("redis SADD(%s, %d) error(%v)", key, rpID, err)
}
return
}
// ReplyZSetRds get reply list from redis sorted set.
func (d *Dao) ReplyZSetRds(ctx context.Context, name string, oid int64, tp, start, end int) (rpIDs []int64, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplyZSet(name, oid, tp)
values, err := redis.Values(conn.Do("ZREVRANGE", key, start, end))
if err != nil {
log.Error("redis ZREVRANGE(%s, %d, %d) error(%v)", key, start, end, err)
return
}
if err = redis.ScanSlice(values, &rpIDs); err != nil {
log.Error("redis ScanSlice(%v) error(%v)", values, err)
}
return
}
// SetReplyZSetRds set reply list batch, call it when back to source.
func (d *Dao) SetReplyZSetRds(ctx context.Context, name string, oid int64, tp int, rs []*model.ReplyScore) (err error) {
if len(rs) < 1 {
return
}
for _, chunkedReplyStats := range splitReplyScore(rs, 5000) {
var (
count = 0
key = keyReplyZSet(name, oid, tp)
conn = d.redis.Get(ctx)
args = make([]interface{}, 0, len(chunkedReplyStats)*2+1)
)
args = append(args, key)
for _, s := range chunkedReplyStats {
args = append(args, s.Score)
args = append(args, s.RpID)
}
if err = conn.Send("ZADD", args...); err != nil {
log.Error("redis ZADD(%s, %v) error(%v)", key, args, err)
conn.Close()
return
}
count++
if err = conn.Send("EXPIRE", key, d.redisReplyZSetExpire); err != nil {
log.Error("redis EXPIRE(%s) error(%v)", key, err)
conn.Close()
return
}
count++
if err = conn.Flush(); err != nil {
log.Error("redis Flush error(%v)", err)
conn.Close()
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("redis Receive (key: %s, %f, %d) error(%v)", key, rs[i].Score, rs[i].RpID, err)
conn.Close()
return
}
}
conn.Close()
}
return
}
// RangeReplyZSetRds ...
func (d *Dao) RangeReplyZSetRds(ctx context.Context, name string, oid int64, tp, start, end int) (rpIDs []int64, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplyZSet(name, oid, tp)
values, err := redis.Values(conn.Do("ZREVRANGE", key, start, end))
if err != nil {
log.Error("conn.Do(ZREVRANGE, %s) error(%v)", key, err)
return
}
if len(values) == 0 {
return
}
if err = redis.ScanSlice(values, &rpIDs); err != nil {
log.Error("redis.ScanSlice(%v) error(%v)", values, err)
return
}
return
}
// AddReplyZSetRds add a reply into redis sorted set, make sure expire the key first.
func (d *Dao) AddReplyZSetRds(ctx context.Context, name string, oid int64, tp int, rs *model.ReplyScore) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplyZSet(name, oid, tp)
if _, err = conn.Do("ZADD", key, rs.Score, rs.RpID); err != nil {
log.Error("redis ZADD(%s, %f, %d) error(%v)", key, rs.Score, rs.RpID, err)
}
return
}
// RemReplyZSetRds remove one rpID from reply ZSet.
func (d *Dao) RemReplyZSetRds(ctx context.Context, name string, oid int64, tp int, rpID int64) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyReplyZSet(name, oid, tp)
if _, err = conn.Do("ZREM", key, rpID); err != nil {
if err == redis.ErrNil {
err = nil
return
}
log.Error("redis ZREM(%s, %d) error(%v)", key, rpID, err)
}
return
}
// DelReplyZSetRds del a key from reply ZSet.
func (d *Dao) DelReplyZSetRds(ctx context.Context, names []string, oid int64, tp int) (err error) {
if len(names) < 1 {
return
}
conn := d.redis.Get(ctx)
defer conn.Close()
count := 0
for _, name := range names {
key := keyReplyZSet(name, oid, tp)
if err = conn.Send("DEL", key); err != nil {
log.Error("conn.Do(DEL %s) error(%v)", key, err)
return
}
count++
}
if err = conn.Flush(); err != nil {
log.Error("redis Flush error(%v)", err)
return
}
for i := 0; i < count; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("redis Receive error(%v)", err)
return
}
}
return
}
// CheckerTsRds get refresh checker timestamp from redis, if time.Now()-ts > strategy.time, then refresh reply list.
func (d *Dao) CheckerTsRds(ctx context.Context, oid int64, tp int) (ts int64, err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyRefreshChecker(oid, tp)
if ts, err = redis.Int64(conn.Do("GET", key)); err != nil {
if err == redis.ErrNil {
err = nil
return
}
log.Error("redis GET(%s) error(%v)", key, err)
}
return
}
// SetCheckerTsRds set refresh checker's timestamp as time.Now(), call it when refresh reply list.
func (d *Dao) SetCheckerTsRds(ctx context.Context, oid int64, tp int) (err error) {
conn := d.redis.Get(ctx)
defer conn.Close()
key := keyRefreshChecker(oid, tp)
if err = conn.Send("SETEX", key, d.redisRefreshExpire, time.Now().Unix()); err != nil {
log.Error("redis SETEX(%s) error(%v)", key, err)
}
return
}