推薦引擎場(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();
}
});
}
}