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

时间:2012-04-03 19:01:50

标签: java web-applications data-structures collections guava

我需要在我的应用程序中维护每个项目类别(大约2000个类别)的40个最近添加的,最受欢迎/最喜欢的项目的列表。我存储了视图数量&不喜欢每件商品。为此,我希望在app服务器上维护一个内存结构,以便存储&检索这些项目列表。

您对如何实现此内存数据结构有任何想法,& 重要,请记住相关的内存占用量。最小化它)?


使用:

Java 1.6

4 个答案:

答案 0 :(得分:7)

在确定内存结构之前,请考虑当服务器必须重新启动时会发生什么。那种内存结构将会消失。如果可以,那么你应该使用内存结构。如果不是,您可能需要考虑使用单独的域对象来处理这种元数据。

<强>在内存

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这样的东西,那么很容易从项目列表跳转到数据库中的实际项目。

请注意,我没有讨论基础算法,因为您没有问过这个问题,而是讨论如何实现数据结构。如果您可以提供有关当前框架的详细信息,我们可以讨论更多细节。

答案 1 :(得分:4)

你签出了Priority Queue吗?一旦您设置了正确的比较器,这似乎可以满足您的订购需求。如果列表大小是动态的,那么内存大小可能是个问题。但是,既然您知道每个列表中有多少项,您只需将该大小指定为初始容量即可。

答案 2 :(得分: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,in你的情况,40)。

然后启动一个可以执行上述操作的线程(或者一个计划任务,或一个cron库任务,无论你用什么来管理应用程序中的定时事件),你的缓存都会定期刷新。如果您的系统意外关闭,那么一旦重新启动线程,它将自动从数据库重建。

这是一个非常简单的缓存的基础。如果您愿意,可以将其设置为更复杂,将其设置为单例,添加“forceRefresh()”方法以更新当前刷新窗口之外的数据,将其设置为保持并刷新单个线程上的多个缓存,或者甚至整个猪都使用第三方缓存库。

缓存是解决此类问题的正常方法,而且从长远来看通常更容易理解和维护。

答案 3 :(得分: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;
    }
}