我需要在我的應用程序中爲每個項目類別(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