如何在Web应用程序中为每个项目类别维护“当前最受欢迎”的项目列表?

6
我需要在我的应用程序中维护每个项目类别(总类别约为2000)的最近添加的40个、最受欢迎/最受喜爱的项目列表。我确实存储了每个项目的浏览次数和点赞数。为此,我打算在应用服务器上维护一个内存结构,以存储和检索这些项目列表。
您有关于如何实现这种内存数据结构的任何想法,尤其要考虑相关的内存占用和将其最小化的重要性吗?
使用:Java 1.6

7
这里需要解决一些问题。我认为你需要一个衰减函数,这样6个月前的点赞和访问量就会比上周的活动量低。此外,你关于占用最小空间的数据结构的问题……那真的是你想问的问题吗?在担心空间之前,你不应该先专注于使功能正常运作吗? - ControlAltDel
https://dev59.com/5HVD5IYBdhLWcg3wHnyd - sfk
1
内存中的排名对象?扔掉它。select *,(views*.10 + likes) as rank from vw_categories_with_decayed_views_and_likes order by rank desc limit 40 - lsl
4个回答

8
在选择内存结构之前,请考虑当您的服务器需要重新启动时会发生什么。该内存结构将会消失。如果这没问题,那么您应该选择内存结构。如果不行,您可能需要考虑使用单独的域对象来处理此类元数据。 内存 Apache处理器线程不共享内存,因此最好的方法可能是安装类似 memcached 的东西。每次您想要获取当前项目时,都会调用特定的键(“topforty”)。Memcached保持持久性,任何一个线程都可以同时调用它。这是一种高度可扩展的解决方案。
然而,为了使其工作,您必须进行额外的工作。某些程序需要评估当前的喜欢和浏览量,并更新 topforty 键。这可以通过您的管理 Web 应用程序或作为每小时或每天基础上的 cron 作业来完成。下面定义的服务也可以做到这一点,只需使用 memcached 而不是正在持久化的对象即可。 域对象 如果持久性更为关键,而您愿意将并发处理交给您的Web应用程序框架,则需要创建一个处理此任务的服务:
public interface PopularityService {
  public List<Item> getTopItems(int count);//gets n top items

  //lets the service know someone liked a thing
  public void registerLike(Item item, Person liker);

  //lets the service know someone viewed a 
  public void registerView(Item item, Person viewer);thing
}

这将需要一些支持对象:
public class PopularStuff {
  public List<Item> popularItems
  ...
}

你应该将该对象作为单个对象持久化(或者如果你的框架可以轻松实现,作为单例)。你的服务应该对该对象进行操作,决定应该放入什么内容以及如何移动内容。这将是一个读取密集型解决方案,但不像其他静态数据那样读取密集,因为人们很可能会查看很多内容。如果你正在使用类似Hibernate的东西,从项目列表跳转到数据库中的实际项目将非常容易。
请注意,我没有讨论底层算法,因为你没有询问它,而是询问如何实现数据结构。如果你能提供有关当前框架的详细信息,我们可以讨论更具体的内容。

谢谢你的回答,Nathaniel。我正在使用JSF,没有像Hibernate等任何东西! - Rajat Gupta
关于服务器重启,我将定期将数据保存到数据库中,并在应用程序关闭时保存当前状态。 - Rajat Gupta
我一直计划将这个数据结构保存在应用程序范围的托管bean中。 - Rajat Gupta
你需要关注的主要问题是对bean的写入。只要多个线程可以在不互相覆盖的情况下写入它,那么就没问题了! - Nathaniel Ford

4
你有没有看过优先队列?如果你设置正确的比较器,它似乎可以满足你的排序需求。如果你的列表大小是动态的,那么内存大小可能是一个问题。但是既然你知道每个列表中会有多少项,你可以将其指定为初始容量。

3
我会做一个很大的假设,即当你说“存储视图计数和喜欢数量”时,你的意思是它们以查询友好的格式存储(即SQL数据库或等效物)。那么,在内存中存储信息的主要原因是缓存数据,从而减少生成页面所需的数据库调用次数。我的假设正确吗?
如果是这样,那么我认为你正在过于复杂化问题。与其使用复杂的数据结构来维护信息,不如将其视为简单的缓存结构。下面是一个高度简化的伪代码示例,说明它的工作原理:
class TopXCache extends Runnable
{
  Object[] cachedData;

  int secondsToTimeOut;
  String sqlQueryToRefreshCache;

  boolean killSwitch = false;

  constructor(int itemsToKeepInCache, int secondsToTimeOut, String sqlQueryToRefreshCache)
  {
     this.secondsToTimeOut = secondsToTimeOut;
     this.sqlQueryToRefreshCache = sqlQueryToRefreshCache;

     this.cachedData = new Object[itemsToKeepInCache];
  }

  void run() // The method the thread will execute
  {
     while(!killSwitch) // Allows for "poison pill" shutdown
     {
       cachedData = executeQuery(sqlQueryToRefreshCache);
       wait(secondsToTimeOut);
     }
  }

  void kill()
  {
     killSwitch = true;
  }
}

要创建一个列表,请使用轮询时间(秒)(secondsToTimeOut),运行SQL查询以返回数据的最新副本(sqlQueryToRefresh)和您想在列表中保留的项目数(itemsToKeepInCache,在这种情况下为40)对其进行实例化。
然后启动一个线程,可以执行上述操作(或计划任务或cron库任务,您正在使用其他管理应用程序中定时事件的方式),并定期刷新缓存。如果系统意外关闭,则一旦重新启动线程,它将自动从数据库中重建。
这是非常简单的缓存的基础。如果您愿意,可以使它变得更加复杂,将其设置为单例,添加“forceRefresh()”方法以在当前刷新窗口之外更新数据,将其设置为在单个线程上保存和刷新多个缓存,甚至可以全面采用第三方缓存库。
缓存通常是解决这类问题的常规方法,并且在长期内通常更易于理解和维护。

0

我和 @Erica 有相同的假设,但提供了不同的解决方案:

我也假设项目-类别关系是多对多的。

import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import javax.ejb.EJB;

@ManagedBean
@RequestScoped
public class ItemBean
{
    @EJB
    private DbService dbService;

    @ManagedProperty("#{categoryCache}")
    private CategoryCache cache;

    public void incrementViewCounter(Item item)
    {
        item.setViewCount(item.getViewCount() + 1);
        dbService.update(item);
        cache.update(item);
    }

    public void incrementLikeCounter(Item item)
    {
        item.setLikeCount(item.getViewCount() + 1);
        dbService.update(item);
        cache.update(item);
    }
}


@ManagedBean
@ApplicationScoped
class CategoryCache
{
    private Map<Integer, ItemSet> categoryMap;

    public void update(Item item)
    {
        ItemReference ref = new ItemReference(item);

        for(Category c : item.getCategoryList())
        {
            ItemSet set = categoryMap.get(c.getId());
            if(set == null)
            {
                set = new ItemSet();
                categoryMap.put(c.getId(), set);
            }

            set.add(ref);
        }
    }
}

class ItemSet extends TreeSet<ItemReference>
{
    private static final int MAX_ENTRIES = 40;

    @Override
    public boolean add(ItemReference ref)
    {
        if(contains(ref)) remove(ref);

        super.add(ref);

        if(size() > MAX_ENTRIES)
        {
            remove(last());
        }

        return true;
    }
}

class ItemReference implements Comparable<ItemReference>
{
    private final Integer id;
    private final Double rank;

    public ItemReference(Item item)
    {
        this.id = item.getId();
        this.rank = item.getViewCount().doubleValue() * 0.1 + item.getLikeCount().doubleValue();
    }

    @Override
    public int compareTo(ItemReference that)
    {
        return -this.getRank().compareTo(that.getRank());
    }

    @Override
    public int hashCode()
    {
        return id.hashCode();
    }

    @Override
    public boolean equals(Object that)
    {
        if(that instanceof ItemReference)
        {
            return this.getId().equals(((ItemReference)that).getId());
        }

        return false;
    }

    public Integer getId()
    {
        return id;
    }

    public Double getRank()
    {
        return rank;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接