在GAE上实施新闻Feed - 我应该使用前瞻性搜索吗?

时间:2013-12-13 01:39:08

标签: java google-app-engine

我有一个问题,我现在正在努力一段时间。我正在尝试使用GAE云端点和Java在我的应用中实现新闻Feed功能。常见的概念是追随者和追随者,其追随者可以看到追随者的行为。一个新的追随者也应该看到他的追随者过去的行动,而不仅仅是他开始追随的时候。

我尝试了以下组件。每次尝试都很有效,但缺乏一些东西:

  1. 在每个用户操作上,我将“日志”实体添加到包含用户ID的数据存储区中。当用户显示他的新闻源时,我根据用户的跟随者列表通过他们的用户ID查询所有这些实体。一切都很好,直到我意识到'IN'查询不能被诅咒。所以这个选项不见了。
  2. 在这个尝试上,这也是应用程序的当前状态,即时使用Search API。在每个用户操作时,我不再将“日志”实体存储到数据存储区中,而是将文档存储到搜索索引中。复杂的查询可以在这里被诅咒,世界又在微笑。但是......我不太确定,明智的说法,这是一个聪明的决定。似乎在documented 日常限制的同时搜索/添加/删除文档的成本使得整个事情有点过于粗略。
  3. 下一次尝试应该是Prospective Search API。从我在documents中阅读的内容来看,它似乎是为此目的选择的正确组件。遗憾的是,文档非常糟糕,并且只提供很少的示例。结算信息也不清楚。
  4. 所以我要求stackoverflow社区的建议。你能告诉我这件事吗?如果准备搜索是正确的选择,您能否提供一些使用云端点的明确示例Java代码?

    编辑:只是为了强调此处的主要设计要求 - 新闻Feed功能需要能够使用游标获取已排序的跟随者动作(以避免查询整个批次)。

2 个答案:

答案 0 :(得分:2)

使用pull-aggregate-per-follower模型:定期(或按需)查询所有跟随者操作一次,然后将其缓存在专用的每个跟随者实体中。记住上次查询的时间,因此下次您只需从该点开始查询(假设操作无法添加/更改为过去的时间)。

这将为您提供以下功能(和限制):

  1. 如果查询是按需的,则不需要查询不活动的用户。
  2. 由于查询是“new-only”(仅查找新操作),如果返回零结果,则不会花费任何费用。
  3. 您只会查询每个关注者的每个跟随者操作一次。之后,所有最近的操作都将缓存在一个实体中,并使用一个get加载到内存中。这应该是一个可观的成本和时间。
  4. 您可以按照自己的意愿对内存中的操作进行排序/过滤。
  5. 限制:

    1. 实体的限制为1MB,因此您可以在一个实体中缓存​​最多的操作。因此,您需要限制每个用户最近操作的缓存,或者将操作缓存分散到多个实体上。
    2. 您需要对跟随者(最多30个)使用IN查询,并使用并行线程来获得不错的性能。在查询超过1000-2000个跟随者时,这可能轻松达到3-5秒。此外,在同时为多个用户提供服务时,您可以轻松地为每个实例命中RPC限制(也就是最大并发API调用)。

答案 1 :(得分:0)

我希望我能正确理解这个问题 - 您希望在您的应用中实施新闻Feed,并允许用户互相关注。新粉丝需要能够看到用户的操作。我确信有多种其他方法可以解决这个问题,但我会尝试通过提供一个使用JAVA JDO来访问数据存储区的解决方案来帮助您。

我首先设计entity relationships in JDO如下:

1 User to many actions.
1 User to many followers (User).
1 User to many following (User). 

以下是简单的JDO类:

用户类:

@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class User {

    @PrimaryKey
    @Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String userId; // Google unique user ID, could also store user email. 

    @Persistent
    private Set<Key> actions;

    @Persistent
    private Set<Key> followers;

    @Persistent
    private List<Key> following;

    public User(Key key, String userId) {
        this.key       = key;
        this.userId    = userId;
        this.actions   = new HashSet<Key>();
        this.followers = new HashSet<Key>();
        this.following = new HashSet<Key>();  
    }

    public Key getKey() {
        return this.key;
    }

    public void addAction(Key actionKey) {
        this.actions.add(actionKey);
    }

    public void addActions(Set<Key> actionKeys) {
        this.actions.addAll(actionKeys);
    }

    public Set<Key> getActions() {
        return this.actions;
    }

    public void addFollower(Key followerKey) {
        this.followers.add(followerKey);
    }

    public void addFollowers(Set<Key> followerKeys) {
        this.followers.addAll(followerKeys);
    }

    public Set<Key> getFollowers() {
        return this.followers;
    }

    public void addFollowing(Key followingKey) {
        this.following.add(followingKey);
    }

    public void addAllFollowing(Set<Key> followingKeys) {
        this.following.addAll(followingKeys);
    }

    public Set<Key> getFollowing() {
        return this.following;
    }

}

行动类:

@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class Action {

    @PrimaryKey
    @Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    Date date;

    @Persistent
    private String title;    

    public Action(Key key, String title) {

        this.key   = key;
        this.title = title;

        this.date  = new Date(); // date of creation (now). 

    }

    public Key getKey() {
        return this.key;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTitle() {
        return this.title;
    }

}

Action类使用Date属性,您可以参考documentation获取数据存储区中适用的数据类型。创建操作时a Date object is allocated and initialized so that it represents the time at which it was allocated, measured to the nearest millisecond

在我上面的例子中,我通过他们的键链接了实体,你可以用它们的类来链接它们,如下所示:

List<Action> actions;

我的例子中的关系是一个无主的一对多关系,也许它应该是一对多的。更多信息here供您查看,或许可以决定哪种方案最适合您的解决方案。

一旦定义了关系,您就可以create your endpoint classes around the JDO model classes。这将创建基本的api方法。您可能希望更改端点类方法以满足您的需要,例如更改操作的创建方式。一个基本的例子是从actions标题创建密钥,如下所示(ActionEnpoint.java):

...
@ApiMethod(name = "insertAction")
public Action insertAction( @Named("title") String title ) {

    PersistenceManager pm = getPersistenceManager();

    Key key = KeyFactory.createKey(Action.class.getSimpleName(), title);

    Action action = null;

    try {
        action = new Action(key, title);
        pm.makePersistent(action);
    } finally {
        pm.close();
    }

    return action;

}
...

如果您愿意,可以向UserEndpoint类添加一个方法来查询数据存储区,并使用datastore query objects返回属于该用户和每个日期的所有操作。

您需要向UserEndpoint类添加一个方法,允许您向该用户添加操作,这是一个简单的示例:

...
@ApiMethod(name = "addActionToUser")
public Achiever addActionToUser(
    @Named("userId") String userId, 
    @Named("actionTitle") String actionTitle) {

    PersistenceManager pm = getPersistenceManager();

    Key userKey   = KeyFactory.createKey(User.class.getSimpleName(), userId);
    Key actionKey = KeyFactory.createKey(Action.class.getSimpleName(), actionTitle);

    User user = null;

    try {   
        user = (User) pm.getObjectById(User.class, userKey);
        user.addAction(actionKey);
        pm.makePersistent(user);
    } catch (Exception e) {

    } 

    return user;
}
...

完成上述所有操作后,您可以通过调用UserEndpoint类中的getUser方法轻松获取每个用户的操作列表,该类返回User对象。然后,您可以调用[ReturnedUserObject] .getActions()。现在,新的关注者可以通过调用api方法来获取“跟随者”对象并获取他/她的行为来查看所有“跟随者”操作。然后,您可以按日期对行动进行排序,或者您想象它。

我希望我能正确理解你的问题,我不确定你提到的第一个组件,但似乎你的关系混乱了。我希望这个解决方案至少能指出你正确的方向:)。

如果您需要任何其他帮助或澄清,或者我的回答完全不符合您的要求,请告知我们。

亲切的问候, 三木