通用推薦引擎的一種設(shè)計(jì)方案

推薦引擎場(chǎng)景方案的設(shè)計(jì)

說明:

  • 推薦引擎的一般設(shè)計(jì)包括:推薦入口、場(chǎng)景服務(wù)(scene)方案服務(wù)(solution),三者之間通過Map的數(shù)據(jù)結(jié)構(gòu)進(jìn)行組織。

  • 推薦入口通過維護(hù)場(chǎng)景的Map對(duì)象[Map<String, SceneImpl> Scenes],方便通過場(chǎng)景的名字找到對(duì)應(yīng)的場(chǎng)景對(duì)象。

  • 場(chǎng)景服務(wù)(scene)通過維護(hù)方案的Map對(duì)象[Map<String, SolutionImpl> solutions],方便通過方案的名字找到對(duì)應(yīng)的方案對(duì)象。

  • 場(chǎng)景服務(wù)(scene)通過維護(hù)方案的篩選規(guī)則[SolutionSelector selector],方便根據(jù)分流規(guī)則返回方案的名字,進(jìn)而通過方案的Map對(duì)象查找對(duì)應(yīng)的方案對(duì)象。

// 推薦引擎的核心服務(wù)
public class RecomServiceImpl  {
    private Map<String, SceneImpl> Scenes

    public RecomResult recommend(RecomParam param) {
        // 省略相關(guān)代碼
    }
}

// 推薦引擎的場(chǎng)景(scene)服務(wù)
public class SceneImpl implements RecommendHandler {
    private final String sceneName;
    private Map<String, SolutionImpl> solutions;
    private SolutionSelector selector;

    public SceneImpl(EngineContext context, RecomEngineConfig
                 recomEngineConfig, String sceneName) {
        this.sceneName = sceneName;
        this.solutions = ImmutableMap.copyOf(new HashMap<String, Solution>());
    }
}

// 推薦引擎的方案(solution)服務(wù)
public class SolutionImpl implements RecommendHandler {
    private String sceneName;
    private String solutionName;
    private RecommendHandler handler;
    private URLClassLoader classLoader;
    private RecomEngineConfig recomEngineConfig;

    private SolutionImpl(RecomEngineConfig recomEngineConfig, String sceneName, String solutionName)
    {
        this.sceneName = sceneName;
        this.solutionName = solutionName;
        this.recomEngineConfig = recomEngineConfig;
        this.solutionJarPath = File.separator + solutionName + ".jar";

        try {
            File jarFile = new File(solutionJarPath);
            classLoader = new URLClassLoader(new URL[] { new File(solutionJarPath).toURI().toURL() },
                    Thread.currentThread().getContextClassLoader());
            Class<?> clazz = classLoader.loadClass(SolutionUtils.getClassName(sceneName, solutionName));
            handler = (RecommendHandler) (clazz.newInstance());
            handler.init(context);
        } catch (Throwable t) {
          // 省略相關(guān)代碼
        }
    }

    public static SolutionImpl build(final EngineContext context, final RecomEngineConfig recomEngineConfig,
            String sceneName, String solutionName) {
        Solution solution = null;
        try {
            solution = new SolutionImpl(context, recomEngineConfig, sceneName, solutionName);
            
            return solution;
        } catch (Throwable t) {
        }
    }
}


推薦引擎推薦流程

說明:

  • 推薦引擎的執(zhí)行流程按照如下步驟進(jìn)行:根據(jù)場(chǎng)景名找到場(chǎng)景對(duì)象、根據(jù)場(chǎng)景對(duì)象找到方案對(duì)象,執(zhí)行方案的方法。

  • 根據(jù)場(chǎng)景名找到對(duì)應(yīng)的場(chǎng)景,SceneImpl scene = this.Scenes.get(sceneId)。

  • 根據(jù)方案名找到對(duì)應(yīng)的方案,SolutionImpl solution = solutions.get(solutionName)。

  • 執(zhí)行方案的推薦方法進(jìn)行推薦,handler.recommend(param)。

public class RecomServiceImpl  {
    public RecomResult recommend(RecomParam param) {
        
        RecomResult result = null;
        try{
            String sceneId = param.getSceneId();
            Scene scene = this.Scenes.get(sceneId);
            result = scene.recommend(reqId, condition);
        }finally {
            // 省略相關(guān)代碼
        }   
        return result;
    }
}


public class SceneImpl implements RecommendHandler {

    public RecomResult recommend(RecomParam param) {
        return doRecommand(param);
    }
    
    private RecomResult doRecommand(RecomParam param) {
        try {
            String solutionName = selector.selectSolution(condition);
            SolutionImpl solution = solutions.get(solutionName);
            return solution.recommend(reqId, condition);
        } catch (Throwable t) {
            // 省略相關(guān)代碼
        }
    }
}


public class SolutionImpl implements RecommendHandler {

    private RecommendHandler handler;
    private URLClassLoader classLoader;

    private SolutionImpl(RecomEngineConfig recomEngineConfig, String sceneName, String solutionName)
    {
        this.sceneName = sceneName;
        this.solutionName = solutionName;
        this.recomEngineConfig = recomEngineConfig;
        this.solutionJarPath = File.separator + solutionName + ".jar";

        try {
            File jarFile = new File(solutionJarPath);
            classLoader = new URLClassLoader(new URL[] { new File(solutionJarPath).toURI().toURL() },
                    Thread.currentThread().getContextClassLoader());
            Class<?> clazz = classLoader.loadClass(SolutionUtils.getClassName(sceneName, solutionName));
            handler = (RecommendHandler) (clazz.newInstance());
            handler.init(context);
        } catch (Throwable t) {
            // 省略相關(guān)代碼
        }
    }

    public RecomResult recommend(RecomParam param) {

        RecomResult result = null;
        try {
            if (null != engineContext) {
                result = handler.recommend(param);
            }
            return result;
        } catch (Throwable t) {
           // 省略相關(guān)代碼
        } 
    }
}


推薦引擎場(chǎng)景方案管理方案

說明:

  • 推薦引擎方案場(chǎng)景管理主要包括程序啟動(dòng)方案的初始化和程序運(yùn)行過程中方案的更新兩大塊內(nèi)容。

  • 推薦引擎方案場(chǎng)景及對(duì)應(yīng)的配置信息通過zk進(jìn)行管理,通過zk維護(hù)一系列的場(chǎng)景節(jié)點(diǎn),每個(gè)場(chǎng)景節(jié)點(diǎn)配置有這個(gè)場(chǎng)景的所有配置信息包括方案流量配比等。

  • 推薦引擎場(chǎng)景方案的初始化過程就是獲取所有場(chǎng)景節(jié)點(diǎn)配置信息,進(jìn)行配置的保存,和初始化動(dòng)作。

  • 負(fù)責(zé)讀取場(chǎng)景的配置信息zkReadData(zkScenePath);負(fù)責(zé)保持場(chǎng)景的配置信息scenesConfigs.put(sceneName, data);負(fù)責(zé)初始化場(chǎng)景信息scene.update(data)。

  • scene.update(data)負(fù)責(zé)解析場(chǎng)景的配置信息生成方案的對(duì)象,主要邏輯是遍歷場(chǎng)景下所有方案的配置信息生成solution對(duì)象。

  • scene.update(data)的另外一個(gè)核心的作用就是生成場(chǎng)景下所有方案的分流比例對(duì)象SceneSolutionSelector。

public class RecomServiceImpl  {

    private Map<String, Scene> currentScenes;

    protected void syncSceneSolutionsFromZK() {
        try {

            Map<String, String> scenesConfigs = new HashMap<String, String>();
            synchronized (mutex) {
                // 獲取所有場(chǎng)景的配置信息
                List<String> scenes = zkGetChildren(zkSceneDir);
                Map<String, Scene> newScenes = new HashMap<String, Scene>();
                newScenes.putAll(currentScenes);
                // 遍歷所有場(chǎng)景獲取場(chǎng)景的配置信息,并挨個(gè)進(jìn)行處理。
                for (String sceneName : scenes) {

                    String zkScenePath = recomConfig.getSceneZKPath(sceneName);
                    // 讀取場(chǎng)景的配置信息
                    String data = zkReadData(zkScenePath);
                    // 保存場(chǎng)景的配置信息
                    scenesConfigs.put(sceneName, data);

                    try {
                        SceneImpl scene = currentScenes.get(sceneName);
                        // 根據(jù)場(chǎng)景是否為空創(chuàng)建場(chǎng)景對(duì)象
                        if (scene == null) {
                            scene = new SceneImpl(context, recomConfig, sceneName);
                        }
                        // 根據(jù)配置生成場(chǎng)景對(duì)象
                        if (!scene.update(data)) {
                            continue;
                        }

                        newScenes.put(sceneName, scene);
                    } catch (Throwable se) {
                    }
                }

                runningScenes = ImmutableMap.copyOf(newScenes);
            }
        } catch (Throwable t) {
        }
    }



    public boolean update(String data) {

        try {
            SceneConfig newSceneConfig = JSON.parseObject(data, SceneConfig.class);

            Map<String, SolutionImpl> newSolutions = new HashMap<String, SolutionImpl>();
            for (String solutionName : newSceneConfig.allSolutions()) {                
                SolutionImplsolution = SolutionImpl.build(context, recomEngineConfig, sceneName, solutionName);
                newSolutions.put(solutionName, solution);
            }
            // 生成場(chǎng)景所有方案的對(duì)象
            solutions = ImmutableMap.copyOf(newSolutions);
            // 生成分流選擇器
            selector = SceneSolutionSelector.build(newSceneConfig);
            // 保存最新的場(chǎng)景配置信息
            sceneConfig = newSceneConfig;
            return true;
        } catch (Throwable t) {
        }
        return false;
    }
}


推薦引擎方案場(chǎng)景分流方案

說明:

  • 推薦場(chǎng)景方案當(dāng)中按照?qǐng)鼍皠澐值姆桨府?dāng)中,按照白名單用戶的方案分流、按比例的方案分流、兜底的方案分流進(jìn)行劃分。

  • 針對(duì)方案選擇器的SceneSolutionSelector的對(duì)象,我們可以看到內(nèi)部通過SolutionSelectorStrategy這個(gè)對(duì)象進(jìn)行串聯(lián),每個(gè)SolutionSelectorStrategy 對(duì)象內(nèi)部包含指向下一個(gè) 方案的nextStrategy對(duì)象。

  • SolutionSelectorStrategy作為所有方案選擇策略的抽象基類,內(nèi)部實(shí)現(xiàn)通用的方案選擇操作,并提供抽象方法由各個(gè)選擇策略自身去實(shí)現(xiàn)。

public class SceneSolutionSelector {

    private SolutionSelectorStrategy strategy = null;

    public String selectSolution(final RecomParam param) {
        return this.strategy.selectSolution(param);
    }
    
    public static SceneSolutionSelector build(final SceneConfig conf) {
        SceneSolutionSelector selector = new SceneSolutionSelector();
        // 默認(rèn)分流策略
        String defaultSolution = conf.getDefaultSolution();
        DefaultStrategy ds = new DefaultStrategy(defaultSolution);
        selector.strategy = ds;
        
        // 按比例分流策略
        Map<String, Integer> ap = conf.getAccessPortion();
        if (ap != null && ap.size() > 0) {
            SpecifyRateStrategy srs = new SpecifyRateStrategy(ap, conf.isAccessByUid(), conf.getStrategyMap());
            srs.setNext(selector.strategy);
            selector.strategy = srs;
        }
        
        // 白名單策略
        Map<Long, String> dau = conf.getDirectAccessUser();
        if (dau != null && dau.size() > 0) {
            DirectAccessUserStrategy daus = new DirectAccessUserStrategy(dau);
            daus.setNext(selector.strategy);
            selector.strategy = daus;
        }
        
        
        return selector;
    }
}


public abstract class SolutionSelectorStrategy {

    private SolutionSelectorStrategy nextStrategy = null;
    
    public void setNext(SolutionSelectorStrategy strategy) {
        this.nextStrategy = strategy;
    }
    
    public abstract String takeSolution(RecomParam condition);
    
    public String selectSolution(RecomParam condition) {
        String solutionName = takeSolution(condition);
        if (solutionName != null) {
            return solutionName;
        }

        if (nextStrategy  != null) {
            return nextStrategy.selectSolution(condition);
        }

        return null;
    }
}



public class DefaultStrategy extends SolutionSelectorStrategy {
    private String defaultSolution = null;
    
    public DefaultStrategy(String defaultSolution) {
        this.defaultSolution = defaultSolution;
    }
    
    public String takeSolution(RecomParam condition) {
        return defaultSolution;
    }
}



public class DirectAccessUserStrategyextends SolutionSelectorStrategy {

    ImmutableMap<Long, String> directAccessUser = null;
    public DirectAccessUserStrategy(Map<Long, String> directAccessUser) {
        this.directAccessUser = ImmutableMap.copyOf(directAccessUser);
    }
    @Override
    public String takeSolution(RecomParam condition) {
        if (this.directAccessUser == null) {
            return null;
        }
        
        Long uid = condition.getUid();
        if (uid == null) {
            return null;
        }
        
        String solutionName = directAccessUser.get(uid);
        if (solutionName == null) {
            return null;
        }
        return solutionName;
    }

}


public class SpecifyRateStrategy extends SolutionSelectorStrategy {
    String[] solutionPortion = null;

    //是否按照uid進(jìn)行分流
    private boolean accessByUid = false;
    
    //按照uid進(jìn)行分流的分流策略
    Map<Integer, String> strategyMap = new HashMap<Integer,String>();
    
    public SpecifyRateStrategy(Map<String, Integer> solutionRates, boolean accessByUid, Map<Integer, String> strategyMap) {
        
        this.accessByUid = accessByUid;
        
        if (solutionRates != null && solutionRates.size() > 0) {
            solutionPortion = new String[SceneConfig.TOTAL_BUCKET_PORTION];
            int idx = 0;
            
            List<String> solutionList = new ArrayList<String>();
            for(String solutionName : solutionRates.keySet()){
                solutionList.add(solutionName);
            }
            Collections.sort(solutionList);
            
            for (String solutionName : solutionList) {
                int portion = solutionRates.get(solutionName);
                for (int i = 0; i < portion; i++) {
                    solutionPortion[idx++] = solutionName;
                    if (idx > solutionPortion.length) {
                        throw new IllegalArgumentException("Invalidate rate " + solutionRates);
                    }
                }
            }

            while (idx < solutionPortion.length) {
                solutionPortion[idx++] = null;
            }
        }
        
        if(null != strategyMap && strategyMap.size() > 0){
            this.strategyMap = strategyMap;
        }
    }

    public String takeSolution(RecomParam condition) {
        Long uid = condition.getUid();
        if (uid == null) {
            return null;
        }
        if(accessByUid){
            if(null == strategyMap){
                return null;
            }
            int hashCode = Math.abs(uid.toString().hashCode());
            int idx = (int) (hashCode % SceneConfig.UID_TOTAL_BUCKET_PORTION);
            return strategyMap.get(Integer.valueOf(idx));
            
        }else{
            
            String sceneId = condition.getSceneId();
            if(StringUtils.isEmpty(sceneId)){
                return null;
            }
            
            int sceneIdHashCode = sceneId.hashCode();
            if (sceneIdHashCode < 0){
                sceneIdHashCode = -sceneIdHashCode;
            }
    
            if (uid < 0) {
                uid = -uid;
            }
            
            long id = uid + sceneIdHashCode;
            
            int idx = (int) (id % SceneConfig.TOTAL_BUCKET_PORTION);
            return solutionPortion[idx];
        }
    }
}


推薦引擎方案場(chǎng)景同步方案

說明:

  • 推薦的場(chǎng)景方案更新通過監(jiān)聽zk的節(jié)點(diǎn)感知變更,然后重新走syncSceneSolutionsFromZK()初始化流程然后用新生成的場(chǎng)景方案對(duì)象替換舊的場(chǎng)景方案對(duì)象。
public class RecomPlatformService  {

    protected void watchTrigger() {
        client.subscribeDataChanges(recomEngineConfig.getZKTriggerPath(), new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {
                syncSceneSolutionsFromZK();
            }

            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                syncSceneSolutionsFromZK();
            }
        });
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容