1. 程式人生 > >分散式應用session會話管理-基於redis

分散式應用session會話管理-基於redis

session會話在單臺伺服器下不會出現共享問題,現在應用部署方式都是分散式,或者叢集部署,這樣必然會面臨一個問題,session共享

session共享的解決方案也有很多,

一、web伺服器的粘性請求,比如採用nginx請求分發,使用ip_hash這種負載均衡方式,客戶端請求只會被分發到相同的後臺server,這樣可以避免session共享的問題。但是缺點也很明顯

二、基於資料庫儲存(網站使用者量大的情況下,頻繁dml資料,對db壓力大)

三、基於cookie儲存(安全問題、雖然可以加密儲存、但是我覺得永遠不能將敏感資料放在客戶端,不信任啊O(∩_∩)O哈哈~)

四、伺服器內建的session複製域(比如was下提供session複製功能、但這個損耗伺服器記憶體)

五、基於nosql(memcache、redis都可以)



http請求是無狀態的

這裡要引入一個概念sessionidsession物件當客戶端首次訪問時,建立一個新的session物件.並同時生成一個sessionId,並在此次響應中將sessionId以響應報文的方式些回客戶端瀏覽器記憶體或以重寫url方式送回客戶端,來保持整個會話

也就是說客戶端request請求時候,如果獲取了session,就預設分配一個jessionid,然後通過response響應到客戶端cookie,然後客戶端下一次請求,預設會攜帶這個jessionid請求到服務端,服務端拿到這個jessionid來區分不同的客戶端。

說清楚這些,就可以從sessionid入手了,要實現將session資訊存入redis,總結了下大概是三點:

1.實現httpsession介面,重寫我們需要用到的方法,比如set get這些。。balabala一堆......

2.繼承HttpServletRequestWrapper,這個類裡面有getSession()方法,我們需要重寫,來取自定義session

3.實現filter,用來攔截客戶端請求,獲取我們自定義的request,從而獲得session

具體實現:

1.實現httpsession介面

public class HttpSessionWrapper implements HttpSession {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
	private String sid = "";
	private HttpServletRequest request;
	private HttpServletResponse response;
	private final long creationTime = System.currentTimeMillis();
	private final long lastAccessedTime = System.currentTimeMillis();
	private SessionMeta meta;
	public HttpSessionWrapper() {
	}
    public HttpSessionWrapper(String sid,SessionMeta meta, HttpServletRequest request,
			HttpServletResponse response) {
    	this.sid=sid;
    	this.request=request;
    	this.response=response;
    	this.meta=meta;
	}
	public Object getAttribute(String name) {
    	logger.info(getClass()+"getAttribute(),name:"+name);
	  	Jedis jedis =null;
	  	Object obj =null;
	  	String jsonStr = null;
    	try {
    		jedis =JedisPoolStore.getInstance().getJedis(meta.getHost(), meta.getPort());
    		jsonStr = jedis.get(name);
        	if(jsonStr!=null||StringUtils.isNotEmpty(jsonStr)){
            	jedis.expire(name, meta.getSeconds());// 重置過期時間
    			obj =JSON.parseObject(jsonStr, User.class); //反序列物件
        	}
        	if (jedis != null) {
    			JedisPoolStore.getInstance().returnJedis(jedis);
    		}
        	return obj;
		} 
    	catch (JSONException je) {
			logger.error(je.getMessage());
				if (null != jedis)
					JedisPoolStore.getInstance().returnJedis(jedis);
        	return jsonStr;
		}
    	catch (Exception e) {
			logger.error(e.getMessage());
			if (e instanceof JedisException) {
				if (null != jedis)
					JedisPoolStore.getInstance().returnBrokenJedis(jedis);
			} else {
				if (null != jedis)
					JedisPoolStore.getInstance().returnJedis(jedis);
			}
			throw new HttpSessionException(" session 異常  getAttribute() name:"+name);
		}
    }
    public void setAttribute(String name, Object value) {
    	logger.info(getClass()+"setAttribute(),name:"+name);
    	Jedis jedis =null;
    	try {
    		jedis =JedisPoolStore.getInstance().getJedis(meta.getHost(), meta.getPort());
    		if(value instanceof String){
    			String value_ =(String) value;
    			jedis.set(name,value_);//普通字串物件
    		}else{
    			jedis.set(name, JSON.toJSONString(value));//序列化物件
    		}
    		
        	jedis.expire(name, meta.getSeconds());// 重置過期時間
        	if (jedis != null) {
    			JedisPoolStore.getInstance().returnJedis(jedis);
    		}
		} catch (Exception e) {
			logger.error(e.getMessage());
			if (e instanceof JedisException) {
				if (null != jedis)
					JedisPoolStore.getInstance().returnBrokenJedis(jedis);
			} else {
				if (null != jedis)
					JedisPoolStore.getInstance().returnJedis(jedis);
			}
			throw new HttpSessionException(" session 異常  setAttribute() name:"+name+",value:"+value);
		}
    	
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public void invalidate() {
    	logger.info(getClass()+"invalidate()");
    }

    public void removeAttribute(String name) {
    	logger.info(getClass()+"removeAttribute(),name:"+name);
    	if(StringUtils.isNotEmpty(name)){
    		Jedis jedis =null;
        	try {
        		jedis =JedisPoolStore.getInstance().getJedis(meta.getHost(), meta.getPort());
            	jedis.del(name);
            	if (jedis != null) {
        			JedisPoolStore.getInstance().returnJedis(jedis);
        		}
    		} catch (Exception e) {
    			logger.error(e.getMessage());
    			if (e instanceof JedisException) {
    				if (null != jedis)
    					JedisPoolStore.getInstance().returnBrokenJedis(jedis);
    			} else {
    				if (null != jedis)
    					JedisPoolStore.getInstance().returnJedis(jedis);
    			}
    			throw new HttpSessionException(" session 異常  removeAttribute() name:"+name);
    		}
    	}
    
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public Object getValue(String name) {
        return null;
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public Enumeration getAttributeNames() {
    	 return  null;

    }			 
    /**
     * 不可用
     * @deprecated
     * 
     */
    public String[] getValueNames() {
    	 return  null;    
	 }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public void putValue(String name, Object value) {
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public void removeValue(String name) {
    }

    public long getCreationTime() {
    	return  creationTime;
    }

    public String getId() {
    	logger.info(getClass()+"getId():"+sid);
        return sid;
    }

    public long getLastAccessedTime() {
        return lastAccessedTime;
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public ServletContext getServletContext() {
        return null;
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public void setMaxInactiveInterval(int interval) {
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public int getMaxInactiveInterval() {
        return 0;
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public HttpSessionContext getSessionContext() {
        return null;
    }
    /**
     * 不可用
     * @deprecated
     * 
     */
    public boolean isNew() {
    	logger.info(getClass()+"isNew()");
        return false;
    }
}

2.繼承HttpServletRequestWrapper
/***
 * 
 * @author xiaoshuai
 *
 */
public class DefinedHttpServletRequestWrapper extends HttpServletRequestWrapper{
	    protected final Logger logger = LoggerFactory.getLogger(getClass());

	    private HttpSessionWrapper currentSession;
	    private HttpServletRequest request;
	    private HttpServletResponse response;
	    private String sid = "";
		private  SessionMeta meta;
	     
	    public DefinedHttpServletRequestWrapper(HttpServletRequest request) {
	        super(request);
	    }

	    public DefinedHttpServletRequestWrapper(String sid, HttpServletRequest request) {
	        super(request);
	        this.sid = sid;
	    }

	    public DefinedHttpServletRequestWrapper(String sid, SessionMeta meta,HttpServletRequest request,
	            HttpServletResponse response) {
	        super(request);
	        this.request = request;
	        this.response = response;
	        this.sid = sid;
	        this.meta=meta;
	    }

	    @Override
	    public HttpSession getSession(boolean create) {
	    	if(currentSession != null) {
	    	       return currentSession;
	    	     }
    	     if(!create) {
    	       return null;
    	     }
    	     currentSession = new HttpSessionWrapper(sid,meta, request, response);
    	     return currentSession;
	    }

	    @Override
	    public HttpSession getSession() {
	      return getSession(true);
	    }
    
}

3.實現filter

public class SessionFilter implements Filter{
	protected final Logger logger = LoggerFactory.getLogger(getClass());
	private static SessionMeta meta = new SessionMeta();
	private static final String host ="host";
	private static final String port ="port";
	private static final String seconds="seconds";
	
	public void init(FilterConfig filterConfig) throws ServletException {
		logger.debug("init filterConfig info");
		meta.setHost(filterConfig.getInitParameter(host));
		meta.setPort(Integer.parseInt(filterConfig.getInitParameter(port)));
		meta.setSeconds(Integer.parseInt(filterConfig.getInitParameter(seconds)));
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		 //從cookie中獲取sessionId,如果此次請求沒有sessionId,重寫為這次請求設定一個sessionId
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;
        String sid = CookieHelper.findCookieInfo(httpRequest,CookieHelper.COOKIE_DOMAIN_NAME);
        if(StringUtils.isEmpty(sid) ){
        	try {
        		sid =CookieHelper.saveCookie(SessionId.doIds(), httpResponse);
			} catch (Exception e) {
				e.printStackTrace();
			}
        }
        logger.info("JESSIONID:"+sid);
        chain.doFilter(new DefinedHttpServletRequestWrapper(sid,meta,httpRequest, httpResponse), response);
	}

	public void destroy() {
	}

}


3.配置web.xml

	<!-- session過濾器 -->
	<filter>
		<filter-name>sessionFilter</filter-name>
		<filter-class>cn.session.filter.SessionFilter</filter-class>
		<init-param>
			<param-name>host</param-name>
			<param-value>10.1.193.1</param-value>
		</init-param>
		<init-param>
			<param-name>port</param-name>
			<param-value>6372</param-value>
		</init-param>
		<init-param>
			<param-name>seconds</param-name>
			<param-value>1800</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>sessionFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>



第一次產生sessionid訪問:


系統登入後存使用者資訊至redis,以及產生令牌:


關於安全這塊,因為不管登入系統與否,sessionid都會產生,這時候就會產生一個問題,因為cookie是可以被修改的,就會產生一個問題,撞session的分享。。。換成不同的sessionid去請求系統。。。總有一天會撞上。。

SO,我這邊是這樣處理的,

當登入成功之後,產生一個token令牌,產生規則的話自己定,一堆金鑰比如系統名字+sessionid+userid+固定字元,產生一個加密的字串,放入cookie。

這樣當我們獲取當前登入使用者時,解密token,獲取sessionid,然後取redis使用者資訊。。(切記這裡不要直接通過sessionid取使用者資訊,有風險!!!)

使用者沒有登入成功,自然也沒有這個令牌。。。

這裡又有另外一個問題,使用者如果禁用cookie呢????? so what.....   下次再說這個。。。

水平有限,如果你有更好的方式,請聯絡我,交流................................tks!!!!

相關推薦

分散式應用session會話管理-基於redis

session會話在單臺伺服器下不會出現共享問題,現在應用部署方式都是分散式,或者叢集部署,這樣必然會面臨一個問題,session共享。 session共享的解決方案也有很多, 一、web伺服器的粘性請求,比如採用nginx請求分發,使用ip_hash這種負載均衡方式,

分散式Tomcat session會話Sticky Sessions問題

分散式session會話Sticky Sessions - tomcat_baby的專欄 - CSDN部落格https://blog.csdn.net/tomcat_baby/article/details/52787679 關於 tomcat 叢集中 session 共享的三種方法 - 銳洋智慧 - 部落

分散式鎖與實現(一)——基於Redis實現

概述 目前幾乎很多大型網站及應用都是分散式部署的,分散式場景中的資料一致性問題一直是一個比較重要的話題。分散式的CAP理論告訴我們“任何一個分散式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分割槽容錯性(Partit

使用shiro的會話管理redis快取管理來構建登入模組spring+struts+hibernate(SSH)

     shiro是一個很好用的安全框架,主要表現在使用者認證,許可權認證,會話管理,如果想優化還可以做Cache管理,我們不需要做太多工作在使用者身份token安全方面(記錄shiro及用redis開發的步驟及一些問題,因為網上很多資料都不給全程式碼讓小白沒法理解,這裡我

Spring-Session基於Redis管理Session

在上文Tomcat Session管理分析介紹了使用tomcat-redis-session-manager來集中式管理session,其中一個侷限性就是必須使用tomcat容器;本文介紹的spring-session也能實現session的集中式管理,並且不侷限於某種容器; spring-se

基於redis叢集實現的分散式鎖,可用於秒殺商品的庫存數量管理,有測試程式碼(何志雄)

轉載請標明出處。 在分散式系統中,經常會出現需要競爭同一資源的情況,本程式碼基於redis3.0.1+jedis2.7.1實現了分散式鎖。 可用於例如秒殺系統中的商品庫存的管理。付完整程式碼及測試用例。 package com.gaojiasoft.gaojiaRe

redis學習之六】基於Redis分散式session實現

    在web應用中,我們經常會用session來儲存已登入使用者的相關資訊,在單機應用中,由於所有的使用者都訪問同一個應用,而session都儲存在此單機應用中所以並無不妥。但是隨著使用者併發量的上升,分散式系統勢在必行,這就導致一個使用者的訪問請求可能會分發到不同的叢集部署

分散式會話追蹤[基於Cookie和Redis的實現]

一. 叢集遇到的問題 從使用者端來解釋,就是當一個使用者第一次訪問被負載均衡代理到後端伺服器A並登入後,伺服器A上保留了使用者的登入資訊;當用戶再次傳送請求時,根據負載均衡策略可能被代理到後端不同的伺服器,例如伺服器B,由於這臺伺服器B沒有使用者的登入資訊,所以導致使用者需要重新

Tomcat7叢集共享Session 基於redis進行統一管理(轉)

背景:       很多時候,生產環境,需要多個tomcat協作,那麼session的統一管理是一個首先需要解決的問題。session的統一管理有很多解決方案,比如儲存至資料庫、memcache、redis。那麼我想給大家介紹的是將session儲存至redis這個方案。

架構設計之Spring-Session分散式叢集會話管理

前言 通常在web開發中,會話管理是很重要的一部分,用於儲存與使用者相關的一些資料。對於JAVA開發者來說,專案中的session一般由Tomcat或者jetty容器來管理。 特點介紹 儘管使用特定的容器可以很好地實現會話管理,但是獨立容器掛掉或者由於其他原因重啟會導致使用者資訊丟失,並且無法支援分散式叢集會

基於 Redis 實現分散式應用限流

限流的目的是通過對併發訪問/請求進行限速或者一個時間視窗內的的請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務 實際場景中常用的限流策略: Nginx接入層限流 按照一定的規則如帳號、

springBoot+redis 實現session共享理解,應用場景單點登入,分散式應用

Springboot+redis 實現session共享也是利用了cookie在域名,路徑相同的情況下可以共享內容。第一次請求會將SESSION儲存在redis中,並將SESSIONID返回到瀏覽器的cookie中,第二次請求會攜帶上第一次請求的JSESSIONID。服務端拿

php 基於redis分散式應用

高併發的時候,對關鍵業務的資料保護,一般是用mysql加鎖,有表鎖行鎖共享排鎖一堆。。我選擇了redis分散式鎖,have a look at the code:composer require signe/redlock-php引入這個包之後,程式碼裡面可以這樣寫:priv

Cookie & Session會話管理與控制】

有效期 unset font 重置 也有 姓名 本地 tro 讀取 用現實生活 類比Cookie 和 Session : 兩個關於開會的故事: 在幾十年前人們開會的時候,都需要帶上一個參會證。這個參會證上有這個人的職務、姓名、單位、照片等信息。在開會的時候,會議安保人員

為Tornado框架加上基於Redis或Memcached的session 【第三方】

clas secret () res sdh cti utf nec 安裝 Tornado 沒有session,只有cookie_secret,這在一些情況下是不利於開發的。所有我們可以給Tornado加上session的功能。 這篇文章講解的是依靠第三方包來實現。以後的文

會話管理cookie&session

getpath 什麽 響應 setvalue 每次 瀏覽器中 一段 臺電 atm 1.會話技術:從瀏覽器開始訪問服務器,到關閉瀏覽器,這期間發生了許多次請求和響應,這個過程就叫做一次會話。2.問題:如何在一次會話中保存會話相關的數據。3.Cookie:將會話相關的數據保存到

實現tomcat基於session會話保持

linux tomcat java session會話保持 nginx 實驗環境:兩臺服務器,分別實現java其中一臺作為nginx代理實驗原理圖:一、實現java環境1、實現java的運行環境 (1)安裝 JDK 可以在網上下載包 yum localinstall j

tomcat基於session會話保持以及msm會話保持

tomcat會話保持實驗一、基於tomcat集群會話保持一、實驗環境:一臺nginx服務器,兩臺tomcat集群,出於實驗的原因,我就把nginx也放在其中一臺的tomcat服務器上。也就是一共兩臺機器,都是centos7的環境。二、實驗原理:DeltaManager會話管理器是tomcat默認的集群會話管理

把php session 會話保存到redis

hand 計算機 close gpo pat div 優化 io讀寫 鎖定 php的session會話默認時以文件形式保存在php.ini配置文件設置的會話緩存目錄,文件保存會話的效率很低,每當每個用戶登錄一次就會在服務器上生成一個唯一的session_id文件,當用戶登錄

基於hi-nginx的web開發(python篇)——cookie和會話管理

class status domain 登陸 edi 模板引擎 log 怎麽辦 cache hi-nginx通過redis管理會話。 要開啟管理,需要做三件事。 第一件開啟userid: userid on;