제7장:zookeeper 기반의 분포식session

13556 단어 zookeepersession분산

1. 장면 사용


분포식 세션 기능은 세션 정보를 집중적으로 관리하고 세션의 고가용성을 강화하는 데 있다. 예를 들어 단기 버전의 세션 저장소는 사용자가 요청한 서버가 고장날 때 사용자의 접근 요청을 다른 서버로 바꾸면 세션이 분실되어 사용자가 종료될 수 있다. 분포식 세션은 집중적으로 관리하고 중심화로 인한 시스템 확장성이 약하고 고가용성이 떨어지는 문제를 제거할 수 있다.일반적인 분포식 캐시 서비스는session 저장을 충당할 수 있다.zookeeper의 노드 최대 저장 내용체는 1M에 달하고session의 특징은 기본적으로 읽기와 쓰기가 적기 때문에zookeeper는 분포식 session이 실현하는 캐리어로도 사용할 수 있다.이 장에서 우리는 Zookeeper가 실현한 분포식session에 대해 상세하게 논술한다.

2. 논리 실현


시스템은 우선session이 zookeeper 인터페이스에 접근하여 봉인해야 한다. 대외적으로 zookeeper를 기반으로 하는 추가, 삭제, session 인터페이스를 제공하는 동시에sessionData의 클래스를 설계하여 session의 원 데이터를 저장해야 한다. 그 중에서 맵은 session의 속성 값을 저장하는 것이다.다음은 필터를 사용자 정의하여 모든 요청을 필터링하고 리퀘스트를 http Request Wapper로 전환해야 합니다. 포장 후 응용 시스템은 리퀘스트를 통해 세션을 조작하는 인터페이스를 통해 사전에 http Request Wapper에 프록시되고 http Request Wapper를 통해 세션 데이터를 Zookeeper에 저장하거나 Zookeeper에서 읽을 수 있습니다.

3. 코드 구현


우선session 메타데이터 클래스를 정의합니다. 모든session의 속성을 조작하는 데 사용됩니다. 아래와 같습니다.
/**
 * 
 */
package com.flykingmz.zookeeper.dSession.model;

import java.io.Serializable;
import java.util.Map;

/**
 * @author flyking
 * 
 */
public class DSessionData implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * sessionId
	 */
	private String id;

	/**
	 * session 
	 */
	private Long createTime;

	/**
	 *  
	 */
	private Long lastAccessTime;
	

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public Long getCreateTime() {
		return createTime;
	}

	public Long getLastAccessTime() {
		return lastAccessTime;
	}

	public void setLastAccessTime(Long lastAccessTime) {
		this.lastAccessTime = lastAccessTime;
	}

	public Map<String, String> getSessionContext() {
		return sessionContext;
	}

	public void setSessionContext(Map<String, String> sessionContext) {
		this.sessionContext = sessionContext;
	}

	/**
	 * session 
	 */
	private Map<String, String> sessionContext;

	public DSessionData() {
		this.createTime = System.currentTimeMillis();
		this.lastAccessTime = this.createTime;
	}

}
다음은zookeeper를 기반으로 하는dao층 봉인을 정의합니다.
/**
 * 
 */
package com.flykingmz.zookeeper.dSession.dao;

import org.I0Itec.zkclient.ZkClient;
import com.flykingmz.zookeeper.dSession.json.Json;
import com.flykingmz.zookeeper.dSession.model.DSessionData;

/**
 * @author flyking
 * 
 */
public class DSessionDaoImpl implements DSessionDao {

	private ZkClient client;

	private String zookeeperURL;

	public void setZookeeperURL(String zookeeperURL) {
		this.zookeeperURL = zookeeperURL;
	}

	public void init() {
		this.client = new ZkClient(zookeeperURL);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.flykingmz.zookeeper.dSession.dao.DSessionDao#addSession(java.lang
	 * .String, com.flykingmz.zookeeper.dSession.model.DSessionData)
	 */
	public void addSession(String sessionId, DSessionData data) {
		this.client.createPersistent(sessionId, Json.toJson(data));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.flykingmz.zookeeper.dSession.dao.DSessionDao#getSession(java.lang
	 * .String)
	 */
	public DSessionData getSession(String sessionId) {
		String sessionData = this.client.readData(sessionId);
		return Json.toObject(sessionData, DSessionData.class);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.flykingmz.zookeeper.dSession.dao.DSessionDao#delSession(java.lang
	 * .String)
	 */
	public void delSession(String sessionId) {
		this.client.delete(sessionId);
	}

}
dao층 실현은 다음에 서비스 구현을 해야 한다. 주로session 획득, 만료, 삭제 등 작업을 처리한다.
/**
 * 
 */
package com.flykingmz.zookeeper.dSession.service;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.flykingmz.zookeeper.dSession.dao.DSessionDao;
import com.flykingmz.zookeeper.dSession.model.DSessionData;

/**
 * @author flyking
 * 
 */
@Service
public class DSessionServiceImpl implements DSessionService {
	@Autowired
	private DSessionDao dSessionDaoImpl;
	
	private Long session_expire_time = 30*60*1000L;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.flykingmz.zookeeper.dSession.service.DSessionService#getSession(java
	 * .lang.String)
	 */
	public Map<String, String> getSession(String sessionId) {
		DSessionData sessionData = dSessionDaoImpl.getSession(sessionId);
		if((System.currentTimeMillis() - sessionData.getCreateTime())>=session_expire_time){
			dSessionDaoImpl.delSession(sessionId);
			return null;
		}
		sessionData.setLastAccessTime(System.currentTimeMillis());
		dSessionDaoImpl.addSession(sessionId, sessionData);
		return sessionData.getSessionContext();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.flykingmz.zookeeper.dSession.service.DSessionService#saveSession(
	 * java.lang.String, java.util.Map)
	 */
	public void saveSession(String sessionId, Map<String, String> session) {
		DSessionData sessionData = new DSessionData();
		sessionData.setSessionContext(session);
		sessionData.setId(sessionId);
		dSessionDaoImpl.addSession(sessionId, sessionData);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.flykingmz.zookeeper.dSession.service.DSessionService#removeSession
	 * (java.lang.String)
	 */
	public void removeSession(String sessionId) {
		dSessionDaoImpl.delSession(sessionId);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.flykingmz.zookeeper.dSession.service.DSessionService#setSessionAttribute
	 * (java.lang.String, java.lang.String, java.lang.String)
	 */
	public void setSessionAttribute(String sessionId, String key, String value) {
		DSessionData sessionData = dSessionDaoImpl.getSession(sessionId);
		sessionData.getSessionContext().put(key, value);
		sessionData.setLastAccessTime(System.currentTimeMillis());
		dSessionDaoImpl.addSession(sessionId, sessionData);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.flykingmz.zookeeper.dSession.service.DSessionService#
	 * removeSessionAttribute(java.lang.String, java.lang.String)
	 */
	public void removeSessionAttribute(String sessionId, String key) {
		DSessionData sessionData = dSessionDaoImpl.getSession(sessionId);
		sessionData.getSessionContext().remove(key);
		sessionData.setLastAccessTime(System.currentTimeMillis());
		dSessionDaoImpl.addSession(sessionId, sessionData);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.flykingmz.zookeeper.dSession.service.DSessionService#getSessionAttribute
	 * (java.lang.String, java.lang.String)
	 */
	public String getSessionAttribute(String sessionId, String key) {
		DSessionData sessionData = dSessionDaoImpl.getSession(sessionId);
		Map<String,String> sessionContext = sessionData.getSessionContext();
		if(sessionContext == null){
			return null;
		}
		sessionData.setLastAccessTime(System.currentTimeMillis());
		dSessionDaoImpl.addSession(sessionId, sessionData);
		return sessionContext.get(key);
	}

}
이상은 업무 차원의 실현에 속하고 다음은 httprequest를 봉인해야 한다. 우선requestWraaper와 HttpSessionSessionIdWrapper를 정의해야 한다.
.
package com.flykingmz.zookeeper.dSession.filter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * @author flyking
 */
public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {
	private String sessionId = "";

	public HttpServletRequestWrapper(String sessionId , HttpServletRequest request) {
		super(request);
		this.sessionId = sessionId;
	}

	public HttpSession getSession(boolean create) {
		return new HttpSessionSessionIdWrapper(this.sessionId, super.getSession(create));
	}

	public HttpSession getSession() {
		return new HttpSessionSessionIdWrapper(this.sessionId, super.getSession());
	}

}
package com.flykingmz.zookeeper.dSession.filter;

import java.util.Enumeration;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.flykingmz.zookeeper.dSession.DistributedContextContainer;
import com.flykingmz.zookeeper.dSession.Enumerator;


/**
 * @author flyking
 */
public class HttpSessionSessionIdWrapper extends HttpSessionWrapper {
	private final static Logger logger = LoggerFactory
			.getLogger(HttpSessionSessionIdWrapper.class);
	
	private String sessionId;

	public HttpSessionSessionIdWrapper(String sessionId, HttpSession session) {
		super(session);
		this.sessionId = sessionId;
	}

	public Object getAttribute(String key) {
		return DistributedContextContainer.getSessionService().getSessionAttribute(
				sessionId, key);
	}

	public Enumeration getAttributeNames() {
		Map<String, String> session = DistributedContextContainer.getSessionService()
				.getSession(sessionId);
		return (new Enumerator(session.keySet(), true));
	}

	public void invalidate() {
		DistributedContextContainer.getSessionService().removeSession(sessionId);
	}

	public void removeAttribute(String key) {
		DistributedContextContainer.getSessionService().removeSessionAttribute(
				sessionId, key);
	}

	@SuppressWarnings("unchecked")
	public void setAttribute(String key, Object value) {
		if (value instanceof String) {
			DistributedContextContainer.getSessionService().setSessionAttribute(
					sessionId, key, (String) value);
		} else {
			logger.warn("session unsupport not serializable string." + "[key="
					+ key + "]" + "[value=" + value + "]");
		}
	}
	
	@Override
	public String getId() {
		return sessionId;
	}
	
}
이렇게 하면 Request는 포장 처리로 실현될 수 있고 이 포장을 시작하는 촉발점은 filter 안에 있습니다. filter의 실현을 보겠습니다.
package com.flykingmz.zookeeper.dSession.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;


public class DistributedSessionFilter implements Filter {
	private final static Logger logger = LoggerFactory
			.getLogger(DistributedSessionFilter.class);
	/**
	 *
	 */
	private static final long serialVersionUID = -1L;

	private String sessionIdName = "D_SESSION_ID";

	private String cookieDomain = "";

	private String cookiePath = "/";

	private List<String> excludeUrl = new ArrayList<String>();

	public void doFilter(ServletRequest servletRequest,
			ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		String uri = ((HttpServletRequest) request).getRequestURI();
		
		if (this.excludeUrl != null && this.isMatchExcludeUrl(uri)) {
			filterChain.doFilter(request, response);
			return;
		}
		
		// cookieDomain
		initCookieDomain(request);
		
		
		String sessionId = getSessionId(request, response);
		HttpServletRequestWrapper httpServletRequestWrapper = new HttpServletRequestWrapper(sessionId, request);
		logger.debug("sessionId:"+sessionId);
        
        
		filterChain.doFilter(httpServletRequestWrapper, response);
	}



	private void initCookieDomain(HttpServletRequest request) {
		String serverName = request.getServerName();
		cookieDomain = serverName;
	}


	private String getSessionId(HttpServletRequest request,
			HttpServletResponse response) {
		Cookie cookies[] = request.getCookies();
		Cookie sCookie = null;
		String sessionId = "";
		if (cookies != null && cookies.length > 0) {
			for (int i = 0; i < cookies.length; i++) {
				sCookie = cookies[i];
				if (sCookie.getName().equals(sessionIdName)) {
					sessionId = sCookie.getValue();
				}
			}
		}
		
		if (sessionId == null || sessionId.length() == 0) {
			sessionId = java.util.UUID.randomUUID().toString();
			response.addHeader("Set-Cookie", sessionIdName + "=" + sessionId
					+ ";domain=" + this.cookieDomain + ";Path="
					+ this.cookiePath + ";HTTPOnly");
		}
		return sessionId;
	}
	
	

	public void init(FilterConfig filterConfig) throws ServletException {
		this.cookieDomain = filterConfig.getInitParameter("cookieDomain");
		if (this.cookieDomain == null) {
			this.cookieDomain = "";
		}
		this.cookiePath = filterConfig.getInitParameter("cookiePath");
		if (this.cookiePath == null || this.cookiePath.length() == 0) {
			this.cookiePath = "/";
		}
		String excludeUrlsString = filterConfig.getInitParameter("excludeUrls");
		if (!StringUtils.isEmpty(excludeUrlsString)) {
			String[] urls = excludeUrlsString.split(",");
			this.excludeUrl = Arrays.asList(urls);
		}
	}

	private boolean isMatchExcludeUrl(String uri) {
		if (StringUtils.isEmpty(uri)) {
			return false;
		}
		//  
		for (String regexUrl : this.excludeUrl) {
			if (uri.endsWith(regexUrl)) {
				return true;
			}
		}
		return false;
	}

	public void destroy() {
		this.excludeUrl = null;
		this.cookieDomain = null;
		this.cookiePath = null;
	}

}
이상은zookeeper를 바탕으로 이루어진 분포식session의 주요 논리 코드로 구체적인 원본 실현은 참고할 수 있다
https://github.com/flykingmz/zookeeper-step.git프로젝트 이름: distributedSession

좋은 웹페이지 즐겨찾기