2012-04-03 56 views
5

我需要在我的應用程序中爲每個項目類別(2000年左右的總類別)維護最近添加的40個最受歡迎/最受歡迎的項目列表。我確實存儲了意見數&沒有喜歡每個項目。爲此,我正在尋找可能在應用程序服務器上維護內存結構,以便存儲&檢索這些項目列表。如何在Web應用程序中維護每個項目類別的「當前最受歡迎」項目列表?

你有關於如何實現這在內存中的數據結構,& 重要任何想法,同時要注意相關的內存佔用&它減少到最遠的程度)?


使用:

的Java 1.6

+7

這裏有很多問題需要解決。我認爲你需要一個衰減功能,以便在6個月前的喜歡和訪問與上週的活動相比打折。另外,你關於佔用最小空間的數據結構的問題......這是你真正想問的問題嗎?在擔心空間之前,您不應該專注於讓您的功能先行嗎? – ControlAltDel 2012-04-03 19:10:33

+0

http://stackoverflow.com/questions/81346/most-efficient-way-to-increment-a-map-value-in-java – sfk 2012-04-12 21:49:03

+1

內存中的排名對象?擰數據。 'select *,(views * .10 + likes)作爲vw_categories_with_decayed_views_and_likes的排名按排名desc的限制40' – Louis 2012-04-12 22:24:19

回答

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 
    ... 
} 

你應該堅持的是作爲單個OB ject(或者如果你的框架變得簡單,就像一個單身人士)。你的服務應該對這個對象採取行動,決定它應該在什麼位置以及如何移動。這將是一個閱讀繁重的解決方案,但不像其他靜態數據那樣重讀,因爲大概人們會做很多觀點。如果您使用的是Hibernate,那麼從項目列表跳轉到數據庫中的實際項目將非常簡單。

請注意,我沒有討論底層算法,因爲您沒有問過這個問題,而是關於如何實現數據結構。如果您可以提供有關您當前框架的詳細信息,則可以討論更多細節。

+0

感謝您的回答納撒尼爾。我正在使用JSF,沒有像Hibernate等任何東西。 – 2012-04-10 20:58:54

+0

關於服務器重新啓動,我將定期將數據保存到數據庫,並且當應用程序被扼殺時,它將保存當前狀態 – 2012-04-10 21:00:29

+0

我一直計劃在應用程序範圍內的託管bean中保存此數據結構 – 2012-04-10 21:02:20

4

您檢查了Priority Queue?看起來這樣可以滿足您的訂購需求,一旦您設置了正確的比較器。如果您的列表大小是動態的,那麼內存大小可能是一個問題。但是由於您知道每個列表中有多少項目,因此您可以將該大小指定爲初始容量。

3

我打算做一個很大的假設,那就是當你說你「存儲視圖數& 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()」方法來更新當前刷新窗口之外的數據,將它設置爲在單個線程上保存並刷新多個緩存,或者甚至可以全部使用第三方緩存庫。

儘管緩存是解決這類問題的常規解決方案,並且長期而言通常更易於理解和維護。

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; 
    } 
} 
相關問題