2. 深入理解 Cookie 与 Session ,Facade 设计模式, 分布式 Session

1.什么是 Cookie

Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息

打开百度首页:https://www.baidu.com/

浏览器 console 中输入:

> document.cookie

"BAIDUID=40E9A74CB78CA05206FD128BEB27E014:FG=1; PSTM=1568883435; BIDUPSID=4927A15EB7E602CFABA425A910D5136C; BD_UPN=123253; BDSFRCVID=OvtOJeC62C6a1m7wlk3EjPnnWgA5FqRTH6aowb_SsXNp6D3o4q0aEG0P_U8g0Kub2VhkogKKKgOTHICF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; H_BDCLCKID_SF=tJAj_D-btK03fP36qR6sMJ8thmT22-ustN5RQhcH0hOWsIOF3-Rj5U-q-PneQMc4WjrG-pLXbtQCqj69DUC0DjO-jaKOJjFsb5vfstbHatnjDb7GbKTjhPrM0HQiWMT-0bFHLRO_BxJofUoeDRQY3TkW0tjnbRof-Hn7_JjCbb5Mhq5oMPKh3TtObMrMWUQxtNR--CnjtpvhKJ3D3-oobUPUyUJ9LUvA02cdot5yBbc8eIna5hjkbfJBQttjQn3hfIkj2CKLtC8WhD_mDjRV5-JH-xQ0KnLXKKOLVb38Wh7keq8CDR76QU4q-lJQa5Jd2HRXWf3jJUn_jtQ2y5jHhnIDhUcRXJ3vtaRz0RjFKxTpsIJMMl_WbT8U5ecgJfRuaKviahvjBMb1OqODBT5h2M4qMxtOLR3pWDTm_q5TtUJMeCnTDMFhe6jyDNADJ6FDf5vfL5uat4bqqPbYh4t_hnDsePnq-URZ5mAqoq8KKCjVMP31MTbpMl8HhRJM36Ql-GrnaIQqa-3D_UORWMT2jx3yhNODJPo43bRTMMKy5KJvfJ_4347OhP-UyPRMWh37Wm7lMKoaMp78jR093JO4y4Ldj4oxJpOJ5JbMopCafJOKHICCDj82jUK; H_PS_PSSID=1428_21093_29568_29220; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; ZD_ENTRY=baidu; BD_HOME=1; delPer=0; BD_CK_SAM=1; PSINO=5; COOKIE_SESSION=735862_0_6_5_10_3_0_1_5_2_0_1_69129_0_0_0_1573784783_0_1574520625%7C9%23721633_42_1572833622%7C9; sug=3; sugstore=1; ORIGIN=0; bdime=0"

这就是 Cookie, 文本字符串是用 ; 分隔:

document.cookie.split(';')

["BAIDUID=40E9A74CB78CA05206FD128BEB27E014:FG=1", " PSTM=1568883435", " BIDUPSID=4927A15EB7E602CFABA425A910D5136C", " BD_UPN=123253", " BDSFRCVID=OvtOJeC62C6a1m7wlk3EjPnnWgA5FqRTH6aowb_…8g0Kub2VhkogKKKgOTHICF_2uxOjjg8UtVJeC6EG0Ptf8g0M5", " H_BDCLCKID_SF=tJAj_D-btK03fP36qR6sMJ8thmT22-ustN5…oaMp78jR093JO4y4Ldj4oxJpOJ5JbMopCafJOKHICCDj82jUK", " H_PS_PSSID=1428_21093_29568_29220", " BDORZ=B490B5EBF6F3CD402E515D22BCDA1598", " ZD_ENTRY=baidu", " BD_HOME=1", " delPer=0", " BD_CK_SAM=1", " PSINO=5", " COOKIE_SESSION=735862_0_6_5_10_3_0_1_5_2_0_1_6912…84783_0_1574520625%7C9%23721633_42_1572833622%7C9", " sug=3", " sugstore=1", " ORIGIN=0", " bdime=0"]

计算机 cookie 的目的是帮助网站跟踪您的访问和活动。这并不总是坏事。例如,许多在线零售商在浏览网站时使用 cookie 来跟踪用户购物车中的商品。如果没有 Cookie,每次点击网站上的新链接时,您的购物车都会重置为零。这将使在线购买任何东西变得困难!

网站也可能使用 cookie 来记录您最近的访问记录或记录您的登录信息。许多人发现这很有用,因此他们可以在常用网站上存储密码,或者只是让他们知道他们过去访问或下载的内容。

不同类型的 cookie 跟踪不同的活动。会话 cookie 仅在某人主动浏览网站时使用; 一旦您离开网站,会话 cookie 就会消失。跟踪 cookie 可用于创建对同一站点的多次访问的长期记录。身份验证 cookie 跟踪用户是否已登录,如果是,则以何种名称登录。

Cookie 何时创建? 将数据写入 cookie 通常在加载新网页时完成 – 例如,在按下“提交”按钮后,数据处理页面将负责将值存储在 cookie 中。如果用户已选择禁用 cookie,则写入操作将失败,并且依赖 cookie 的后续站点将必须采取默认操作,或者提示用户重新输入将存储在 cookie 中的信息。

为什么使用 Cookie? Cookie 是一种方便的方式,可以将信息从网站上的一个会话传送到另一个会话,或者在相关网站上的会话之间,而不必为服务器机器带来大量数据存储负担。在不使用 cookie 的情况下将数据存储在服务器上也是有问题的,因为如果不需要在每次访问网站时登录就很难检索特定用户的信息。

如果存储大量信息,则可以简单地将 cookie 用作识别给定用户的手段,以便可以在服务器端数据库上查找进一步的相关信息。例如,当用户第一次访问网站时,他们可以选择存储在 cookie 中的用户名,然后提供密码,名称,地址,首选字体大小,页面布局等数据 – 这些信息都将被存储使用用户名作为密钥在数据库上。随后,当重新访问该站点时,服务器将读取 cookie 以查找用户名,然后从数据库中检索所有用户的信息,而无需重新输入。

Cookie 持续多久了? 可以在创建 cookie 时设置 cookie 到期的时间。默认情况下,cookie 在当前浏览器窗口关闭时被销毁,但在此之后可以使其持续任意长度的时间。

谁可以访问 Cookies? 创建 cookie 时,可以通过设置其“根域”来控制其可见性。然后,属于该根的任何 URL 都可以访问它。例如,root 可以设置为“leiue.com”,然后 cookie 将可用于“www.leiue.com”或“i.leiue.com”或“leiue.com”中的站点。这可以用于允许相关页面彼此“通信”。无法将根域设置为“顶级”域,例如“.com”或“.co.uk”,因为这样可以广泛访问 cookie。

默认情况下,cookie 对其域中的所有路径都是可见的,但在创建时,它们可以被限制到给定的子路径 – 例如“www.leiue.com/news”。

Cookies 的安全性如何? 关于互联网上的隐私和安全性存在很多问题。Cookie 本身不会对隐私构成威胁,因为它们只能用于存储用户自愿提供的信息或 Web 服务器已有的信息。虽然有可能将此信息提供给特定的第三方网站,但这并不比将其存储在中央数据库中更糟糕。如果您担心您提供给网络服务器的信息不会被视为机密信息,那么您应该质疑是否确实需要提供该信息。

什么是跟踪 Cookie? 一些商业网站包括从第三方网站提供的嵌入式广告材料,这些广告可能存储该第三方网站的 cookie,其中包含从包含网站提供给它的信息 – 这些信息可能包括网站名称,正在查看的特定产品,访问过的网页等。当用户稍后访问包含来自同一第三方网站的类似嵌入广告的其他网站时,广告客户将能够阅读该 Cookie 并使用它来确定某些有关用户浏览历史记录的信息。这使得发布者能够提供以用户兴趣为目标的广告,因此理论上更有可能与用户相关。但是,很多人看到这样的’追踪 Cookie’。

Cookie是一段不超过4KB的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制Cookie有效期、安全性、使用范围的可选属性组成。其中 [3] :

(1)Name/Value:设置Cookie的名称及相对应的值,对于认证Cookie,Value值包括Web服务器所提供的访问令牌 [3] 。

(2)Expires属性:设置Cookie的生存期。有两种存储类型的Cookie:会话性与持久性。Expires属性缺省时,为会话性Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久性Cookie会保存在用户的硬盘中,直至生存期到或用户直接在网页中单击“注销”等按钮结束会话时才会失效 [3] 。

(3)Path属性:定义了Web站点上可以访问该Cookie的目录 [3] 。

(4)Domain属性:指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在 Domain 属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围 [3] 。

(5)Secure属性:指定是否使用HTTPS安全协议发送Cookie。使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该方法也可用于Web站点的身份鉴别,即在HTTPS的连接建立阶段,浏览器会检查Web网站的SSL证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到SSL证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。由于许多用户缺乏安全意识,因而仍可能连接到Pharming攻击所伪造的网站 [3] 。

(6)HTTPOnly 属性 :用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头 [3] 。

认证机制

在 Web认证中 ,因为HTTP协议本身的局限,必须采用其他技术将相关认证标记以某种方式持续传送,以免客户从一个页面跳转至另一个页面时重新输入认证信息 [5] 。基于Cookie的认证过程,主要由以下三个阶段组成:

(1)发布Cookie。当用户试图访问某Web站点中需要认证的资源时,Web服务器会检查用户是否提供了认证Cookie,如果没有,则将用户重定向到登录页面。在用户成功登录后,Web服务器会产生认证Cookie,并通过HTTP响应中的Set-Cookie头发送给客户端,

用于对用户随后的请求进行检查和验证,接着将用户重定向到初始请求的资源 [5] 。

(2)检索Cookie。在用户随后的访问请求中,客户端浏览器检索Path和Domain等属性与用户请求资源相匹配的Cookie,并将找到的Cookie通过HTTP请求中的Cookie头提交给Web服务器 [5] 。

(3)验证CookieWeb服务器提取客户端浏览器递交的Cookie,验证其中的访问令牌。若合法,则将访问请求的资源发送给客户端浏览器;反之则拒绝用户的访问请求。Cookie 认证技术简化了用户访问 Web 网站资源的过程,即用户只需在初次登录网站时输入身份信息进行认证,随后便可以访问被授权的所有站点资源,不再需要重复手工提交身份信息 [5] 。

2.什么是 Session

The servlet container uses this interface to create a session between an HTTP client and an HTTP server. The server can maintain a session in many ways such as using cookies or rewriting URLs.

几个问题: 1.session 是啥? 2.怎么保存的? 3.如何运行? 4.有生命周期吗? 5.关闭浏览器会过期吗? 6.Redis代替文件存储session 7.分布式session的同步问题

在计算机科学中,特别是在网络中,会话是两个或更多个通信设备之间或计算机和用户之间的临时和交互式信息交换。会话在某个时间点建立,然后在稍后的时间点拆除。建立的通信会话可以在每个方向上涉及多于一个消息。会话通常是有状态的,这意味着至少一个通信部分需要保存关于会话历史的信息以便能够进行通信,这与无状态通信相反,其中通信由具有响应的独立请求组成。

会话状态仅在支持cookie的浏览器中保留。

已建立的会话是执行面向连接的通信的基本要求。会话也是在无连接通信模式下传输的基本步骤。但是,任何单向传输都不会定义会话。

通信传输可以被实现为在协议和服务的一部分的应用层,在会话层或在传输层中的 OSI 模型。

如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 Session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

Session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 Session 保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就可以了。

Session的作用就是它在Web服务器上保持用户的状态信息供在任何时间从任何设备上的页面进行访问。因为浏览器不需要存储任何这种信息,所以可以使用任何浏览器,即使是像Pad或手机这样的浏览器设备。

持久性方法的限制

随着越来越多用户登录,Session所需要的服务器内存量也会不断增加。

访问Web应用程序的每个用户都生成一个单独的Session对象。每个Session对象的持续时间是用户访问的时间加上不活动的时间。

如果每个Session中保持许多对象,并且许多用户同时使用Web应用程序(创建许多Session),则用于 Session持久性的服务器内存量可能会很大,从而影响了可伸缩性。

在项目实践中,Jsp程序中很多参数需要从数据库中读取,有的参数实际读取一次就可以,如果设计成每个用户每产生一个页面都要读取数据库,很显然,数据库的负载很大,同时也浪费时间,虽然可能有数据库连接池优化,但是尽量少使用数据库是我们编程的原则。

JSP使用一个叫HttpSession的对象实现同样的功能。HTTPSession 建立在cookies 和URL-rewriting 上。Session的信息保存在服务器端,Session的id保存在客户机的cookie中。事实上,在许多服务器上,如果浏览器支持的话它们就使用cookies,但是如果不支持或废除了的话就自动转化为URL-rewriting,session自动为每个流程提供了方便地存储信息的方法。

3.Tomcat 源码分析

Cookie.java

源码注释说明:

package javax.servlet.http;

import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.BitSet;
import java.util.Locale;
import java.util.ResourceBundle;

/**
 * Creates a cookie, a small amount of information sent by a servlet to a Web
 * browser, saved by the browser, and later sent back to the server. A cookie's
 * value can uniquely identify a client, so cookies are commonly used for
 * session management.
 * <p>
 * A cookie has a name, a single value, and optional attributes such as a
 * comment, path and domain qualifiers, a maximum age, and a version number.
 * Some Web browsers have bugs in how they handle the optional attributes, so
 * use them sparingly to improve the interoperability of your servlets.
 * <p>
 * The servlet sends cookies to the browser by using the
 * {@link HttpServletResponse#addCookie} method, which adds fields to HTTP
 * response headers to send cookies to the browser, one at a time. The browser
 * is expected to support 20 cookies for each Web server, 300 cookies total, and
 * may limit cookie size to 4 KB each.
 * <p>
 * The browser returns cookies to the servlet by adding fields to HTTP request
 * headers. Cookies can be retrieved from a request by using the
 * {@link HttpServletRequest#getCookies} method. Several cookies might have the
 * same name but different path attributes.
 * <p>
 * Cookies affect the caching of the Web pages that use them. HTTP 1.0 does not
 * cache pages that use cookies created with this class. This class does not
 * support the cache control defined with HTTP 1.1.
 * <p>
 * This class supports both the RFC 2109 and the RFC 6265 specifications.
 * By default, cookies are created using RFC 6265.
 */
public class Cookie implements Cloneable, Serializable {}

Cookie 的模型属性与构造函数

    private static final long serialVersionUID = 1L;

    private final String name;
    private String value;

    private int version = 0; // ;Version=1 ... means RFC 2109 style

    //
    // Attributes encoded in the header's cookie fields.
    //
    private String comment; // ;Comment=VALUE ... describes cookie's use
    private String domain; // ;Domain=VALUE ... domain that sees cookie
    private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
    private String path; // ;Path=VALUE ... URLs that see the cookie
    private boolean secure; // ;Secure ... e.g. use SSL
    private boolean httpOnly; // Not in cookie specs, but supported by browsers

    /**
     * Constructs a cookie with a specified name and value.
     * <p>
     * The name must conform to RFC 2109. That means it can contain only ASCII
     * alphanumeric characters and cannot contain commas, semicolons, or white
     * space or begin with a $ character. The cookie's name cannot be changed
     * after creation.
     * <p>
     * The value can be anything the server chooses to send. Its value is
     * probably of interest only to the server. The cookie's value can be
     * changed after creation with the <code>setValue</code> method.
     * <p>
     * By default, cookies are created according to the Netscape cookie
     * specification. The version can be changed with the
     * <code>setVersion</code> method.
     *
     * @param name
     *            a <code>String</code> specifying the name of the cookie
     * @param value
     *            a <code>String</code> specifying the value of the cookie
     * @throws IllegalArgumentException
     *             if the cookie name contains illegal characters (for example,
     *             a comma, space, or semicolon) or it is one of the tokens
     *             reserved for use by the cookie protocol
     * @see #setValue
     * @see #setVersion
     */
    public Cookie(String name, String value) {
        validation.validate(name);
        this.name = name;
        this.value = value;
    }

HttpSession.java

package javax.servlet.http;

import java.util.Enumeration;

import javax.servlet.ServletContext;

/**
 * Provides a way to identify a user across more than one page request or visit
 * to a Web site and to store information about that user.
 * <p>
 * The servlet container uses this interface to create a session between an HTTP
 * client and an HTTP server. The session persists for a specified time period,
 * across more than one connection or page request from the user. A session
 * usually corresponds to one user, who may visit a site many times. The server
 * can maintain a session in many ways such as using cookies or rewriting URLs.
 * <p>
 * This interface allows servlets to
 * <ul>
 * <li>View and manipulate information about a session, such as the session
 * identifier, creation time, and last accessed time
 * <li>Bind objects to sessions, allowing user information to persist across
 * multiple user connections
 * </ul>
 * <p>
 * When an application stores an object in or removes an object from a session,
 * the session checks whether the object implements
 * {@link HttpSessionBindingListener}. If it does, the servlet notifies the
 * object that it has been bound to or unbound from the session. Notifications
 * are sent after the binding methods complete. For session that are invalidated
 * or expire, notifications are sent after the session has been invalidated or
 * expired.
 * <p>
 * When container migrates a session between VMs in a distributed container
 * setting, all session attributes implementing the
 * {@link HttpSessionActivationListener} interface are notified.
 * <p>
 * A servlet should be able to handle cases in which the client does not choose
 * to join a session, such as when cookies are intentionally turned off. Until
 * the client joins the session, <code>isNew</code> returns <code>true</code>.
 * If the client chooses not to join the session, <code>getSession</code> will
 * return a different session on each request, and <code>isNew</code> will
 * always return <code>true</code>.
 * <p>
 * Session information is scoped only to the current web application (
 * <code>ServletContext</code>), so information stored in one context will not
 * be directly visible in another.
 *
 * @see HttpSessionBindingListener
 */
public interface HttpSession {

    /**
     * Returns the time when this session was created, measured in milliseconds
     * since midnight January 1, 1970 GMT.
     *
     * @return a <code>long</code> specifying when this session was created,
     *         expressed in milliseconds since 1/1/1970 GMT
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     */
    public long getCreationTime();

    /**
     * Returns a string containing the unique identifier assigned to this
     * session. The identifier is assigned by the servlet container and is
     * implementation dependent.
     *
     * @return a string specifying the identifier assigned to this session
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     */
    public String getId();

    /**
     * Returns the last time the client sent a request associated with this
     * session, as the number of milliseconds since midnight January 1, 1970
     * GMT, and marked by the time the container received the request.
     * <p>
     * Actions that your application takes, such as getting or setting a value
     * associated with the session, do not affect the access time.
     *
     * @return a <code>long</code> representing the last time the client sent a
     *         request associated with this session, expressed in milliseconds
     *         since 1/1/1970 GMT
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     */
    public long getLastAccessedTime();

    /**
     * Returns the ServletContext to which this session belongs.
     *
     * @return The ServletContext object for the web application
     * @since 2.3
     */
    public ServletContext getServletContext();

    /**
     * Specifies the time, in seconds, between client requests before the
     * servlet container will invalidate this session. A zero or negative time
     * indicates that the session should never timeout.
     *
     * @param interval
     *            An integer specifying the number of seconds
     */
    public void setMaxInactiveInterval(int interval);

    /**
     * Returns the maximum time interval, in seconds, that the servlet container
     * will keep this session open between client accesses. After this interval,
     * the servlet container will invalidate the session. The maximum time
     * interval can be set with the <code>setMaxInactiveInterval</code> method.
     * A zero or negative time indicates that the session should never timeout.
     *
     * @return an integer specifying the number of seconds this session remains
     *         open between client requests
     * @see #setMaxInactiveInterval
     */
    public int getMaxInactiveInterval();

    /**
     * Do not use.
     * @return A dummy implementation of HttpSessionContext
     * @deprecated As of Version 2.1, this method is deprecated and has no
     *             replacement. It will be removed in a future version of the
     *             Java Servlet API.
     */
    @Deprecated
    public HttpSessionContext getSessionContext();

    /**
     * Returns the object bound with the specified name in this session, or
     * <code>null</code> if no object is bound under the name.
     *
     * @param name
     *            a string specifying the name of the object
     * @return the object with the specified name
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     */
    public Object getAttribute(String name);

    /**
     * @param name
     *            a string specifying the name of the object
     * @return the object with the specified name
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #getAttribute}.
     */
    @Deprecated
    public Object getValue(String name);

    /**
     * Returns an <code>Enumeration</code> of <code>String</code> objects
     * containing the names of all the objects bound to this session.
     *
     * @return an <code>Enumeration</code> of <code>String</code> objects
     *         specifying the names of all the objects bound to this session
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     */
    public Enumeration<String> getAttributeNames();

    /**
     * @return an array of <code>String</code> objects specifying the names of
     *         all the objects bound to this session
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #getAttributeNames}
     */
    @Deprecated
    public String[] getValueNames();

    /**
     * Binds an object to this session, using the name specified. If an object
     * of the same name is already bound to the session, the object is replaced.
     * <p>
     * After this method executes, and if the new object implements
     * <code>HttpSessionBindingListener</code>, the container calls
     * <code>HttpSessionBindingListener.valueBound</code>. The container then
     * notifies any <code>HttpSessionAttributeListener</code>s in the web
     * application.
     * <p>
     * If an object was already bound to this session of this name that
     * implements <code>HttpSessionBindingListener</code>, its
     * <code>HttpSessionBindingListener.valueUnbound</code> method is called.
     * <p>
     * If the value passed in is null, this has the same effect as calling
     * <code>removeAttribute()</code>.
     *
     * @param name
     *            the name to which the object is bound; cannot be null
     * @param value
     *            the object to be bound
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     */
    public void setAttribute(String name, Object value);

    /**
     * @param name
     *            the name to which the object is bound; cannot be null
     * @param value
     *            the object to be bound; cannot be null
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #setAttribute}
     */
    @Deprecated
    public void putValue(String name, Object value);

    /**
     * Removes the object bound with the specified name from this session. If
     * the session does not have an object bound with the specified name, this
     * method does nothing.
     * <p>
     * After this method executes, and if the object implements
     * <code>HttpSessionBindingListener</code>, the container calls
     * <code>HttpSessionBindingListener.valueUnbound</code>. The container then
     * notifies any <code>HttpSessionAttributeListener</code>s in the web
     * application.
     *
     * @param name
     *            the name of the object to remove from this session
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     */
    public void removeAttribute(String name);

    /**
     * @param name
     *            the name of the object to remove from this session
     * @exception IllegalStateException
     *                if this method is called on an invalidated session
     * @deprecated As of Version 2.2, this method is replaced by
     *             {@link #removeAttribute}
     */
    @Deprecated
    public void removeValue(String name);

    /**
     * Invalidates this session then unbinds any objects bound to it.
     *
     * @exception IllegalStateException
     *                if this method is called on an already invalidated session
     */
    public void invalidate();

    /**
     * Returns <code>true</code> if the client does not yet know about the
     * session or if the client chooses not to join the session. For example, if
     * the server used only cookie-based sessions, and the client had disabled
     * the use of cookies, then a session would be new on each request.
     *
     * @return <code>true</code> if the server has created a session, but the
     *         client has not yet joined
     * @exception IllegalStateException
     *                if this method is called on an already invalidated session
     */
    public boolean isNew();
}

放入Session 中的对象要序列化.

checks the value for serializability

    /**
     * {@inheritDoc}
     * <p>
     * This implementation simply checks the value for serializability.
     * Sub-classes might use other distribution technology not based on
     * serialization and can override this check.
     */
    @Override
    public boolean isAttributeDistributable(String name, Object value) {
        return value instanceof Serializable;
    }

StandardSession.java

/**
 * Standard implementation of the <b>Session</b> interface.  This object is
 * serializable, so that it can be stored in persistent storage or transferred
 * to a different JVM for distributable session support.
 * <p>
 * <b>IMPLEMENTATION NOTE</b>:  An instance of this class represents both the
 * internal (Session) and application level (HttpSession) view of the session.
 * However, because the class itself is not declared public, Java logic outside
 * of the <code>org.apache.catalina.session</code> package cannot cast an
 * HttpSession view of this instance back to a Session view.
 * <p>
 * <b>IMPLEMENTATION NOTE</b>:  If you add fields to this class, you must
 * make sure that you carry them over in the read/writeObject methods so
 * that this class is properly serialized.
 *
 * @author Craig R. McClanahan
 * @author Sean Legassick
 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
 */
public class StandardSession implements HttpSession, Session, Serializable {}

    private static final long serialVersionUID = 1L;
    ...
    /**
     * The collection of user data attributes associated with this Session.
     */
    protected ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>();


    /**
     * The authentication type used to authenticate our cached Principal,
     * if any.  NOTE:  This value is not included in the serialized
     * version of this object.
     */
    protected transient String authType = null;


    /**
     * The time this session was created, in milliseconds since midnight,
     * January 1, 1970 GMT.
     */
    protected long creationTime = 0L;


    /**
     * We are currently processing a session expiration, so bypass
     * certain IllegalStateException tests.  NOTE:  This value is not
     * included in the serialized version of this object.
     */
    protected transient volatile boolean expiring = false;


    /**
     * The facade associated with this session.  NOTE:  This value is not
     * included in the serialized version of this object.
     */
    protected transient StandardSessionFacade facade = null;
    ...

}

StandardSessionFacade.java

/**
 * Facade for the StandardSession object.
 *
 * @author Remy Maucherat
 */
public class StandardSessionFacade implements HttpSession {

    // ----------------------------------------------------------- Constructors

    /**
     * Construct a new session facade.
     *
     * @param session The session instance to wrap
     */
    public StandardSessionFacade(HttpSession session) {
        this.session = session;
    }


    // ----------------------------------------------------- Instance Variables

    /**
     * Wrapped session object.
     */
    private final HttpSession session;


    // ---------------------------------------------------- HttpSession Methods

    @Override
    public long getCreationTime() {
        return session.getCreationTime();
    }


    @Override
    public String getId() {
        return session.getId();
    }


    @Override
    public long getLastAccessedTime() {
        return session.getLastAccessedTime();
    }


    @Override
    public ServletContext getServletContext() {
        // FIXME : Facade this object ?
        return session.getServletContext();
    }


    @Override
    public void setMaxInactiveInterval(int interval) {
        session.setMaxInactiveInterval(interval);
    }


    @Override
    public int getMaxInactiveInterval() {
        return session.getMaxInactiveInterval();
    }
    ...
}

Facade 设计模式: facade外观模式是一种非常常用的模式,特别是在组织一些复杂的相互调用的逻辑的时候,为外界提供统一的接口(API),可以看到在设计模式中,最常用的应该就是模板方法和facade模式了,很多时候很多需求需要我们认真的取舍,人无远虑必有近忧,同样的,只有为以后的可复用性、可扩展性来考虑,我们的代码才是好的代码。

带着几个问题来看:

1.为什么 StandardSession 要搞一个外观模式? 答:因为他的功能实现了session,但是其中Session和Serializable接口的方法是内部处理的东西,无需对外界开放。而外观模式可以屏蔽不想让外界看到的东西。

2.为什么不把那些不让外界查看的东西设置为private呢? 答:因为接口中的函数必须为public,因此他实现的方法域必须为public。

3.外观模式怎么实现屏蔽的呢? 答:首先确定要对外开放的函数是哪些接口,确定HttpSession之后,则在StandardSession内部存放一个外观类,在对外获取Session的时候将StandardSessionFacade返回给外界的处理者,如代码:

StandardSession类中:

    /**
     * Return the <code>HttpSession</code> for which this object
     * is the facade.
     */
    @Override
    public HttpSession getSession() {
        if (facade == null) {
            if (SecurityUtil.isPackageProtectionEnabled()) {
                facade = AccessController.doPrivileged(new PrivilegedNewSessionFacade(this));
            } else {
                facade = new StandardSessionFacade(this);
            }
        }
        return facade;
    }

类图层次结构如下

Session.png

而外界的处理怎么能够用到StandardSession的处理呢,其中StandardSessionFacade跟StandardSession一样继承自HttpSession,同时我们查看StandardSessionFacade源码会发现里面有一个指向HttpSession的对象session,根据里氏替换原则其实就是StandardSession对象,以及下面的处理都是StandardSession的处理,这样就实现了StandardSession向外界需要看到的东西,如下:

/**
 * Facade for the StandardSession object.
 *
 * @author Remy Maucherat
 */
public class StandardSessionFacade implements HttpSession {

    // ----------------------------------------------------------- Constructors

    /**
     * Construct a new session facade.
     *
     * @param session The session instance to wrap
     */
    public StandardSessionFacade(HttpSession session) {
        this.session = session;
    }


    // ----------------------------------------------------- Instance Variables

    /**
     * Wrapped session object.
     */
    private final HttpSession session;


    // ---------------------------------------------------- HttpSession Methods

    @Override
    public long getCreationTime() {
        return session.getCreationTime();
    }


    @Override
    public String getId() {
        return session.getId();
    }
    ...
}

通过这个我们可以感觉到其中StandardSession的getSession()其实就是用的双重派分的方式,让他人代替自己处理一些自己本身处理但是又不想处理的一些东西。

看到这里感觉到以后如果设计系统的时候就可以参考这个例子,通过外观模式和双重派分的方式来实现。

分布式 Session ( distributable session )

在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理。如果不做任何处理的话,用户将出现频繁登录的现象,比如集群中存在A、B两台服务器,用户在第一次访问网站时,Nginx通过其负载均衡机制将用户请求转发到A服务器,这时A服务器就会给用户创建一个Session。当用户第二次发送请求时,Nginx将其负载均衡到B服务器,而这时候B服务器并不存在Session,所以就会将用户踢到登录页面。这将大大降低用户体验度,导致用户的流失,这种情况是项目绝不应该出现的。

session共享机制

使用分布式缓存方案比如memcached、Redis,但是要求Memcached或Redis必须是集群。

分布式情况下,如果每台服务器都session存在自己的内存中,不同服务器之间就会造成数据不一致问题,

这时候就需要session共享。单机情况下,不存在Session共享的情况。分布式情况下,

如果不进行Session共享会出现数据不一致,比如:会导致请求落到不同服务器要重复登录的情况。

分布式 session 解决方案

①tomcat+redis方案 这个其实还挺方便的,就是使用session的代码跟以前一样,还是基于tomcat原生的session支持即可,然后就是用一个叫做Tomcat RedisSessionManager的东西,让所有我们部署的tomcat都将session数据存储到redis即可。

在tomcat的配置文件中,配置一下

<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />

<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
         host="{redis.host}"
         port="{redis.port}"
         database="{redis.dbnum}"
         maxInactiveInterval="60"/>

还可以用基于redis哨兵支持的redis高可用集群来保存session数据,都是ok的.

<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
     sentinelMaster="mymaster"
     sentinels="<sentinel1-ip>:26379,<sentinel2-ip>:26379,<sentinel3-ip>:26379"
     maxInactiveInterval="60"/>

②spring session+redis方案 分布式会话的东西和容器耦合在一起,比如要将tomcat容器迁移成jetty,就需要修改session同步的配置,这样会比较麻烦。

可以使用spring的解决方案spring session,使用方法如下 1.在pom中引入依赖

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
  <version>1.2.1.RELEASE</version>
</dependency>

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.8.1</version>
</dependency>

2.配置spring配置文件

<bean id="redisHttpSessionConfiguration"
     class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
    <property name="maxInactiveIntervalInSeconds" value="600"/>
</bean>

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxTotal" value="100" />
    <property name="maxIdle" value="10" />
</bean>

<bean id="jedisConnectionFactory"
      class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy">
    <property name="hostName" value="${redis_hostname}"/>
    <property name="port" value="${redis_port}"/>
    <property name="password" value="${redis_pwd}" />
    <property name="timeout" value="3000"/>
    <property name="usePool" value="true"/>
    <property name="poolConfig" ref="jedisPoolConfig"/>
</bean>

3.修改web.xml

<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

使用示例:

@Controller
@RequestMapping("/test")
public class TestController {

@RequestMapping("/putIntoSession")
@ResponseBody
    public String putIntoSession(HttpServletRequest request, String username){
        request.getSession().setAttribute("name",  “leo”);

        return "ok";
    }

@RequestMapping("/getFromSession")
@ResponseBody
    public String getFromSession(HttpServletRequest request, Model model){
        String name = request.getSession().getAttribute("name");
        return name;
    }
}

上面的代码就是ok的,给sping session配置基于redis来存储session数据,然后配置了一个spring session的过滤器,这样的话,session相关操作都会交给spring session来管了。接着在代码中,就用原生的session操作,就是直接基于spring sesion从redis中获取数据了。

实现分布式的会话,有很多种很多种方式,我说的只不过比较常见的两种方式,tomcat + redis早期比较常用;近些年,重耦合到tomcat中去,通过spring session来实现。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏奔跑的人生

[springboot 开发单体web shop] 4. Swagger生成Javadoc

在日常的工作中,特别是现在前后端分离模式之下,接口的提供造成了我们前后端开发人员的沟通 成本大量提升,因为沟通不到位,不及时而造成的[撕币]事件都成了日常工作...

8820
来自专栏Java3y

5分钟理解SpringBoot响应式的核心-Reactor

关于 响应式 Reactive,前面的两篇文章谈了不少概念,基本都离不开下面两点:

9710
来自专栏玩转JavaEE

Spring Boot2 系列教程(二十一)整合 MyBatis

前面两篇文章和读者聊了 Spring Boot 中最简单的数据持久化方案 JdbcTemplate,JdbcTemplate 虽然简单,但是用的并不多,因为它没...

8420
来自专栏芋道源码1024

Spring 体系常用项目一览

如今做Java尤其是web几乎是避免不了和Spring打交道了,但是Spring是这样的大而全,新鲜名词不断产生,学起来给人一种凌乱的感觉,我就在这里总结一下,...

8910
来自专栏知了一笑

微服务架构案例(06):通过业务、应用、技术、存储方面,聊聊架构

架构分类可细化的分为业务架构、应用架构、技术选型、代码规划、部署环境架构等。业务架构是核心的驱动力,应用架构是实现的思路,技术选型落地是结果。根据用户需求,设计...

10820
来自专栏JAVA葵花宝典

为什么redis哨兵集群只有2个节点无法正常工作?

由于redis的响应速度快,每秒支持的并发极高(号称10万),现在redis越来越流行了

21020
来自专栏JAVA葵花宝典

Spring Boot 集成 Ehcache 缓存,三步搞定!

本次内容主要介绍基于Ehcache 3.0来快速实现Spring Boot应用程序的数据缓存功能。在Spring Boot应用程序中,我们可以通过Spring ...

8310
来自专栏纯洁的微笑

感受 Docker 魅力, 排解决多应用部署之疼,Docker Compose + Spring Boot 实践

我知道大家这段时间看了 docker 相关的几篇文章,不疼不痒的,仍然没有感受 docker 的便利,是的,我也是这样认为的,I know your felli...

9520
来自专栏Java技术栈

Spring是如何使用责任链模式的?

关于责任链模式,其有两种形式,一种是通过外部调用的方式对链的各个节点调用进行控制,从而进行链的各个节点之间的切换。

11710
来自专栏好好学java的技术栈

Java 开发进销存管理系统

上面简单的展示了完成后的系统测试截图,你可以下载war包部署到自己的tomcat上看,下面开始进入正文。

18320

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励