我需要在我的应用程序中为每个项目类别(2000年左右的总类别)维护最近添加的40个最受欢迎/最受欢迎的项目列表。我确实存储了意见数&没有喜欢每个项目。为此,我正在寻找可能在应用程序服务器上维护内存结构,以便存储&检索这些项目列表。如何在Web应用程序中维护每个项目类别的“当前最受欢迎”项目列表?
你有关于如何实现这在内存中的数据结构,& 重要任何想法,同时要注意相关的内存占用&它减少到最远的程度)?
使用:
的Java 1.6
我需要在我的应用程序中为每个项目类别(2000年左右的总类别)维护最近添加的40个最受欢迎/最受欢迎的项目列表。我确实存储了意见数&没有喜欢每个项目。为此,我正在寻找可能在应用程序服务器上维护内存结构,以便存储&检索这些项目列表。如何在Web应用程序中维护每个项目类别的“当前最受欢迎”项目列表?
你有关于如何实现这在内存中的数据结构,& 重要任何想法,同时要注意相关的内存占用&它减少到最远的程度)?
使用:
的Java 1.6
在您解决内存中的结构问题之前,请考虑您的服务器必须重新引导时会发生什么情况。这种内存结构将会消失。如果这没有问题,那么你应该使用内存结构。如果不是,您可能需要考虑简单地使用单独的域对象来处理这种类型的元数据。
在内存
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
...
}
你应该坚持的是作为单个OB ject(或者如果你的框架变得简单,就像一个单身人士)。你的服务应该对这个对象采取行动,决定它应该在什么位置以及如何移动。这将是一个阅读繁重的解决方案,但不像其他静态数据那样重读,因为大概人们会做很多观点。如果您使用的是Hibernate,那么从项目列表跳转到数据库中的实际项目将非常简单。
请注意,我没有讨论底层算法,因为您没有问过这个问题,而是关于如何实现数据结构。如果您可以提供有关您当前框架的详细信息,则可以讨论更多细节。
感谢您的回答纳撒尼尔。我正在使用JSF,没有像Hibernate等任何东西。 – 2012-04-10 20:58:54
关于服务器重新启动,我将定期将数据保存到数据库,并且当应用程序被扼杀时,它将保存当前状态 – 2012-04-10 21:00:29
我一直计划在应用程序范围内的托管bean中保存此数据结构 – 2012-04-10 21:02:20
您检查了Priority Queue?看起来这样可以满足您的订购需求,一旦您设置了正确的比较器。如果您的列表大小是动态的,那么内存大小可能是一个问题。但是由于您知道每个列表中有多少项目,因此您可以将该大小指定为初始容量。
我打算做一个很大的假设,那就是当你说你“存储视图数& no的喜欢”时,你的意思是它们以一种查询友好的格式存储(即,一个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()”方法来更新当前刷新窗口之外的数据,将它设置为在单个线程上保存并刷新多个缓存,或者甚至可以全部使用第三方缓存库。
尽管缓存是解决这类问题的常规解决方案,并且长期而言通常更易于理解和维护。
我做@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;
}
}
这里有很多问题需要解决。我认为你需要一个衰减功能,以便在6个月前的喜欢和访问与上周的活动相比打折。另外,你关于占用最小空间的数据结构的问题......这是你真正想问的问题吗?在担心空间之前,您不应该专注于让您的功能先行吗? – ControlAltDel 2012-04-03 19:10:33
http://stackoverflow.com/questions/81346/most-efficient-way-to-increment-a-map-value-in-java – sfk 2012-04-12 21:49:03
内存中的排名对象?拧数据。 'select *,(views * .10 + likes)作为vw_categories_with_decayed_views_and_likes的排名按排名desc的限制40' – Louis 2012-04-12 22:24:19