service/general_rank/general_rank.go (179 lines of code) (raw):
package general_rank
import (
"fmt"
"sync"
"time"
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/log"
"github.com/alibaba/pairec/v2/module"
"github.com/alibaba/pairec/v2/recconf"
"github.com/alibaba/pairec/v2/utils"
)
type GeneralRank struct {
*BaseGeneralRank
}
func NewGeneralRankWithConfig(scene string, config recconf.GeneralRankConfig) (*GeneralRank, error) {
preRank := GeneralRank{
BaseGeneralRank: NewBaseGeneralRankWithConfig(scene, config),
}
return &preRank, nil
}
func (r *GeneralRank) Rank(user *module.User, items []*module.Item, context *context.RecommendContext) (ret []*module.Item) {
ret = r.DoRank(user, items, context, "")
log.Info(fmt.Sprintf("requestId=%s\tmodule=BaseGeneralRank\tcount=%d", context.RecommendId, len(ret)))
return
}
// LoadGeneralRankWithConfig load general rank config by the recconf config
// When the function invoked, shows a new config reload, avoid using old filter or sort in the actions, we will clear the rank map,
// because when request come, GetColdStartGeneralRankByContext or GetGeneralRankByContext will get the new config
// and regiter the rank in the map.
func LoadGeneralRankWithConfig(config *recconf.RecommendConfig) {
DefaultGeneralRankService().Clear()
/**
for sceneName, rankConfig := range config.GeneralRankConfs {
d, _ := json.Marshal(rankConfig)
id := sceneName + "#" + utils.Md5(string(d))
_, ok := DefaultGeneralRankService().GetGeneralRank(id)
if ok {
continue
}
if generalRank, err := NewGeneralRankWithConfig(sceneName, rankConfig); err == nil {
RegisterGeneralRank(id, generalRank)
} else {
log.Error(fmt.Sprintf("load general rank error:%v", err))
}
}
for sceneName, rankConfig := range config.ColdStartGeneralRankConfs {
d, _ := json.Marshal(rankConfig)
id := sceneName + "#" + utils.Md5(string(d))
_, ok := DefaultGeneralRankService().GetColdStartGeneralRank(id)
if ok {
continue
}
if coldStartGeneralRank, err := NewColdStartGeneralRankWithConfig(sceneName, rankConfig); err == nil {
RegisterColdStartGeneralRank(id, coldStartGeneralRank)
} else {
log.Error(fmt.Sprintf("load coldstart general rank error:%v", err))
}
}
**/
}
var generalRankService *GeneralRankService
func init() {
generalRankService = NewGeneralRankService()
}
type GeneralRankService struct {
generalRanks map[string]*GeneralRank
coldStartGeneralRanks map[string]*ColdStartGeneralRank
}
func NewGeneralRankService() *GeneralRankService {
rank := GeneralRankService{
generalRanks: make(map[string]*GeneralRank, 0),
coldStartGeneralRanks: make(map[string]*ColdStartGeneralRank, 0),
}
return &rank
}
func DefaultGeneralRankService() *GeneralRankService {
return generalRankService
}
func RegisterGeneralRank(rankId string, rank *GeneralRank) {
DefaultGeneralRankService().AddGeneralRank(rankId, rank)
}
func RegisterColdStartGeneralRank(rankId string, rank *ColdStartGeneralRank) {
DefaultGeneralRankService().AddColdStartGeneralRank(rankId, rank)
}
func (r *GeneralRankService) Clear() {
r.generalRanks = make(map[string]*GeneralRank, 0)
r.coldStartGeneralRanks = make(map[string]*ColdStartGeneralRank, 0)
}
func (r *GeneralRankService) AddGeneralRank(rankId string, rank *GeneralRank) {
r.generalRanks[rankId] = rank
}
func (r *GeneralRankService) AddColdStartGeneralRank(rankId string, rank *ColdStartGeneralRank) {
r.coldStartGeneralRanks[rankId] = rank
}
func (r *GeneralRankService) GetGeneralRank(rankId string) (*GeneralRank, bool) {
rank, ok := r.generalRanks[rankId]
return rank, ok
}
func (r *GeneralRankService) GetColdStartGeneralRank(rankId string) (*ColdStartGeneralRank, bool) {
rank, ok := r.coldStartGeneralRanks[rankId]
return rank, ok
}
// GetGeneralRank return the GeneralRank by the context
// If not found, return nil
func (r *GeneralRankService) GetGeneralRankByContext(context *context.RecommendContext) *GeneralRank {
scene := context.GetParameter("scene").(string)
// find rank config
var rankConfig recconf.GeneralRankConfig
found := false
if context.ExperimentResult != nil {
rankconf := context.ExperimentResult.GetExperimentParams().Get("generalRankConf", "")
if rankconf != "" {
d, _ := json.Marshal(rankconf)
if err := json.Unmarshal(d, &rankConfig); err == nil {
found = true
}
}
}
if !found {
if rankConfigs, ok := recconf.Config.GeneralRankConfs[scene]; ok {
rankConfig = rankConfigs
found = true
}
}
// not find any generalRankconf, return nil
if !found {
return nil
}
d, _ := json.Marshal(rankConfig)
id := scene + "#" + utils.Md5(string(d))
if generalRank, ok := r.generalRanks[id]; ok {
return generalRank
}
generalRank, err := NewGeneralRankWithConfig(scene, rankConfig)
if err != nil {
log.Error(fmt.Sprintf("create generalRank error, err:%v", err))
return nil
}
r.generalRanks[id] = generalRank
return generalRank
}
func (r *GeneralRankService) GetColdStartGeneralRankByContext(context *context.RecommendContext) *ColdStartGeneralRank {
scene := context.GetParameter("scene").(string)
// find rank config
var rankConfig recconf.ColdStartGeneralRankConfig
found := false
if context.ExperimentResult != nil {
rankconf := context.ExperimentResult.GetExperimentParams().Get("coldStartGeneralRankConf", "")
if rankconf != "" {
d, _ := json.Marshal(rankconf)
if err := json.Unmarshal(d, &rankConfig); err == nil {
found = true
}
}
}
if !found {
if rankConfigs, ok := recconf.Config.ColdStartGeneralRankConfs[scene]; ok {
rankConfig = rankConfigs
found = true
}
}
// not find any generalRankconf, return nil
if !found {
return nil
}
d, _ := json.Marshal(rankConfig)
id := scene + "#" + utils.Md5(string(d))
if coldStartGeneralRank, ok := r.coldStartGeneralRanks[id]; ok {
return coldStartGeneralRank
}
coldStartGeneralRank, err := NewColdStartGeneralRankWithConfig(scene, rankConfig)
if err != nil {
log.Error(fmt.Sprintf("create ColdStartGeneralRank error, err:%v", err))
return nil
}
r.coldStartGeneralRanks[id] = coldStartGeneralRank
return coldStartGeneralRank
}
func (r *GeneralRankService) Rank(user *module.User, items []*module.Item, context *context.RecommendContext) (ret []*module.Item) {
start := time.Now()
generalRank := r.GetGeneralRankByContext(context)
if generalRank == nil {
return items
}
var (
coldStartItems []*module.Item
rankItems []*module.Item
coldStartRankResult []*module.Item
generalRankResult []*module.Item
)
coldGeneralRank := r.GetColdStartGeneralRankByContext(context)
if coldGeneralRank == nil {
rankItems = items
} else {
for _, item := range items {
if coldGeneralRank.Filter(user, item, context) {
coldStartItems = append(coldStartItems, item)
} else {
rankItems = append(rankItems, item)
}
}
}
var wg sync.WaitGroup
if coldGeneralRank != nil {
wg.Add(1)
go func() {
defer wg.Done()
coldStartRankResult = coldGeneralRank.Rank(user, coldStartItems, context)
}()
}
generalRankResult = generalRank.Rank(user, rankItems, context)
wg.Wait()
ret = append(ret, generalRankResult...)
ret = append(ret, coldStartRankResult...)
log.Info(fmt.Sprintf("requestId=%s\tmodule=GeneralRank\tcount=%d\tcost=%d", context.RecommendId, len(ret), utils.CostTime(start)))
return
}