Spring in action - 會話管理

傳統的會話管理是用一個session表保存會話信息,每次請求時讀取、寫入該表。mysql

public function read($sessID) { 
   $hander = is_array($this->hander)?$this->hander[1]:$this->hander;
   $res = mysql_query("SELECT data AS data FROM ".$this->sessionTable." WHERE sid = '$sessID'   AND expire >".time(),$hander); 
   if($res) {
       $row = mysql_fetch_assoc($res);
       return $row['data']; 
   }
   return ""; 
} 

public function write($sessID,$sessData) { 
   $hander = is_array($this->hander)?$this->hander[0]:$this->hander;
   $expire = time() + $this->lifeTime; 
   mysql_query("REPLACE INTO  ".$this->sessionTable." (  sid, expire, data)  VALUES( '$sessID', '$expire',  '$sessData')",$hander); 
   if(mysql_affected_rows($hander)) 
       return true; 
   return false; 
} 

session表的expire是一個時間戳,每次請求時更新。使用此欄位能夠清理會話。sql

public function gc($sessMaxLifeTime) { 
   $hander = is_array($this->hander)?$this->hander[0]:$this->hander;
   mysql_query("DELETE FROM ".$this->sessionTable." WHERE expire < ".time(),$hander); 
   return mysql_affected_rows($hander); 
} 

 

Redis的處理方式

首先看一個JAVA方法:session

public void updateToken(Jedis conn, String token, String user, String item) {
    long timestamp = System.currentTimeMillis() / 1000;
    conn.hset("login:", token, user);
    conn.zadd("recent:", timestamp, token);
    if (item != null) {
        conn.zadd("viewed:" + token, timestamp, item);
    }
}

login是一個哈希表,它的目的是記錄全部在線用戶,等同session表的記錄。

recent是一個有序集合,目的是記錄會話的最新訪問時間,相似session表的expire字段。使用這個集合,能夠對長時間沒有請求的會話作清理。
fetch

viewed也是一個有序集合,存放訪問歷史,與session表的data字段做用相同。
ui

若是viewed集合不清理,隨着用戶瀏覽的商品越多,集合會不斷的增大,必須作清理處理。這裏的處理方式是保持viewed集合只有25筆,其它的都刪除,按照瀏覽時間排序,保存最新的25筆。看看修改後的updateToken()方法:this

public void updateToken(Jedis conn, String token, String user, String item) {
    long timestamp = System.currentTimeMillis() / 1000;
    conn.hset("login:", token, user);
    conn.zadd("recent:", timestamp, token);
    if (item != null) {
        conn.zadd("viewed:" + token, timestamp, item);
        conn.zremrangeByRank("viewed:" + token, 0, -26);  //只保留最後25筆
    }
}

隨着時間的推移,會話愈來愈多,爲了限制會話數量,咱們決定只保留最新的1000萬個會話。rencent集合記錄了每一個會話的最後訪問時間,能夠以此爲清理條件。spa

public class CleanSessionsThread
        extends Thread
{
    private Jedis conn;
    private int limit;
    private boolean quit;

    public CleanSessionsThread(int limit) {
        this.conn = new Jedis("localhost");
        this.conn.select(15);
        this.limit = limit;
    }

    public void quit() {
        quit = true;
    }

    public void run() {
        while (!quit) {
            long size = conn.zcard("recent:");
            if (size <= limit){
                try {
                    sleep(1000);
                }catch(InterruptedException ie){
                    Thread.currentThread().interrupt();
                }
                continue;
            }

            // 每次最多清理100個會話
            long endIndex = Math.min(size - limit, 100);
            
            // recent是按照訪問時間從小到大排列的
            Set<String> tokenSet = conn.zrange("recent:", 0, endIndex - 1);
            
            String[] tokens = tokenSet.toArray(new String[tokenSet.size()]);

            ArrayList<String> sessionKeys = new ArrayList<String>();
            for (String token : tokens) {
                sessionKeys.add("viewed:" + token);
            }

            conn.del(sessionKeys.toArray(new String[sessionKeys.size()]));
            conn.hdel("login:", tokens);
            conn.zrem("recent:", tokens);
        }
    }
}

有時候咱們會統計訪問最多的商品,每次訪問商品時作一個計數。使用有序集合的特性,將全部商品加入到有序集合,訪問一次該商品將就該商品的權值-1,訪問越多,權值就越小。有序集合會按照權值從小到大排序,訪問最多的商品就會排在最前面。3d

public void updateToken(Jedis conn, String token, String user, String item) {
    long timestamp = System.currentTimeMillis() / 1000;
    conn.hset("login:", token, user);
    conn.zadd("recent:", timestamp, token);
    if (item != null) {
        conn.zadd("viewed:" + token, timestamp, item);
        conn.zremrangeByRank("viewed:" + token, 0, -26);  //只保留最後25筆
        conn.zincrby("viewed:", -1, item); // 商品訪問有序集合,訪問越多,權值越小,在集合中月靠前
    }
}

同理,viewed集合也須要定時的清理,否則該集合會不斷的增大,最後將把全部的商品都包含在裏面。咱們單獨開一個進程來清理viewed集合。code

public class CleanViewedThread
        extends Thread
{
    private Jedis conn;
    private int limit;
    private boolean quit;

    public CleanViewedThread(int limit) {
        this.conn = new Jedis("localhost");
        this.conn.select(15);
        this.limit = limit;
    }

    public void quit() {
        quit = true;
    }

    public void run() {
        while (!quit) {
            long size = conn.zcard("viewed:");
            if (size <= limit){
                try {
                    sleep(1000);
                }catch(InterruptedException ie){
                    Thread.currentThread().interrupt();
                }
                continue;
            }

            // 每次最多清理100筆
            long endIndex = Math.min(limit + 100, size);
            
            Set<String> viewedSet = conn.zrange("viewed:", limit, endIndex - 1);
            conn.zrem("viewed:", viewedSet.toArray(new String[viewedSet.size()]));
        }
    }
}
相關文章
相關標籤/搜索