Google App Engine 会话在实例之间发生变化

Kat*_*Kat 6 java cookies google-app-engine servlets httpsession

帮助您理解问题的视觉辅助工具。

我很困惑,因为我尝试启动的每个应用程序 100% 都会出现此问题,但我找不到有关此问题的任何信息,而且我在互联网上找不到其他任何人有这个问题。

我使用 Google Cloud Platform 学习 Java Web 开发已经大约一年了,我有一个问题已经困扰了几个月了。我似乎在网上找不到任何提及此问题的信息。

我有一个基于 Java servlet 构建的 Web 应用程序,它使用会话来跟踪登录的用户。应用程序中的所有内容都工作正常,但是当我将应用程序部署到 App Engine 时,用户不断通过后续服务器请求随机登录和注销。我在开发窗口中观察,发现会话 ID 不断在两个不同的值之间变化。

由于应用程序引擎使用应用程序的多个实例来扩展流量和资源管理,因此它可能会在一个实例上接收请求并将其从另一实例发送回客户端。这在客户端没有明显的差异,但我发现每个实例对于我的客户端都有不同的会话 ID,导致每个实例中陷入不同的会话属性。

果然,当我将应用程序减少到一个实例时,我的会话运行良好。但这在生产中行不通,我需要我的应用程序能够根据流量进行扩展并按预期利用应用程序引擎资源。

Google 声称应用程序引擎上的会话是使用 Memcache 和 Datastore 自动处理的,并且访问会话所需要做的就是 use request.getSession(),所以我不明白为什么会出现这个问题。如果我无法理解问题,我将永远找不到解决方案:(

[编辑]:该应用程序使用一个名为 DatastoreSessionFilter 的预先存在的 Web 过滤器来使用 cookie 和数据存储来跟踪会话变量,但它似乎无法正常工作,因为我的会话变量被困在各个实例中。这是 DatastoreSessionFilter 类:

@WebFilter(filterName = "DatastoreSessionFilter", 
    urlPatterns = { "", 
    "/LoginEmail", 
    "/Logout", 
    "/ResetPassword",
    "/SignupEmail", 
    "/VerifyEmail", 

    "/About", 
    "/BulkUpload", 
    "/DeleteAlbum", 
    "/DeletePost",
    "/EditAlbum", 
    "/EditPost",
    "/Events", 
    "/EventsScroll", 
    "/Home", 
    "/HomeScroll", 
    "/Info",
    "/ListAlbums",
    "/ListAlbumsScroll",
    "/NewAlbum",
    "/NewPost",
    "/Support",
    "/Videos",
    "/VideosScroll",
    "/ViewAlbum",
    "/ViewAlbumScroll",
    "/ViewPost",
    "/ViewProfile",
  })

public class DatastoreSessionFilter implements Filter {

  private static Datastore datastore;
  private static KeyFactory keyFactory;
  private static final DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyyMMddHHmmssSSS");

  @Override
  public void init(FilterConfig config) throws ServletException {
    // initialize local copy of datastore session variables

    datastore = DatastoreOptions.getDefaultInstance().getService();
    keyFactory = datastore.newKeyFactory().setKind("SessionVariable");
    // Delete all sessions unmodified for over two days
    DateTime dt = DateTime.now(DateTimeZone.UTC);
    Query<Entity> query = Query.newEntityQueryBuilder().setKind("SessionVariable")
        .setFilter(PropertyFilter.le("lastModified", dt.minusDays(2).toString(dtf))).build();
    QueryResults<Entity> resultList = datastore.run(query);
    while (resultList.hasNext()) {
      Entity stateEntity = resultList.next();
      datastore.delete(stateEntity.getKey());
    }
  }
  // [END init]

  @Override
  public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)
      throws IOException, ServletException {

    HttpServletRequest req = (HttpServletRequest) servletReq;
    HttpServletResponse resp = (HttpServletResponse) servletResp;

    // Check if the session cookie is there, if not there, make a session cookie using a unique
    // identifier.
    String sessionId = getCookieValue(req, "bookshelfSessionId");

    if (sessionId.equals("")) {
      String sessionNum = new BigInteger(130, new SecureRandom()).toString(32);
      Cookie session = new Cookie("bookshelfSessionId", sessionNum);
      session.setPath("/");
      resp.addCookie(session);
    }

    Map<String, String> datastoreMap = loadSessionVariables(req); // session variables for request

    chain.doFilter(servletReq, servletResp); // Allow the servlet to process request and response

    HttpSession session = req.getSession(); // Create session map
    Map<String, String> sessionMap = new HashMap<>();
    Enumeration<String> attrNames = session.getAttributeNames();

    while (attrNames.hasMoreElements()) {
      String attrName = attrNames.nextElement();
      String sessName = session.getAttribute(attrName).toString();
      sessionMap.put(attrName, sessName);
      // DEFAULT: sessionMap.put(attrName, (String) session.getAttribute(attrName));
    }

    // Create a diff between the new session variables and the existing session variables
    // to minimize datastore access
    MapDifference<String, String> diff = Maps.difference(sessionMap, datastoreMap);
    Map<String, String> setMap = diff.entriesOnlyOnLeft();
    Map<String, String> deleteMap = diff.entriesOnlyOnRight();

    // Apply the diff
    setSessionVariables(sessionId, setMap);
    deleteSessionVariables(sessionId, FluentIterable.from(deleteMap.keySet()).toArray(String.class));
  }

  @SuppressWarnings("unused")
  private String mapToString(Map<String, String> map) {
    StringBuffer names = new StringBuffer();

    for (String name : map.keySet()) {
      names.append(name + " ");
    }

    return names.toString();
  }

  @Override
  public void destroy() {
  }

  protected String getCookieValue(HttpServletRequest req, String cookieName) {
    Cookie[] cookies = req.getCookies();

    if (cookies != null) {
      for (Cookie cookie : cookies) {
        if (cookie.getName().equals(cookieName)) {
          return cookie.getValue();
        }
      }
    }

    return "";
  }

  // [START deleteSessionVariables]
  /**
   * Delete a value stored in the project's datastore.
   * @param sessionId Request from which the session is extracted.
   */
  protected void deleteSessionVariables(String sessionId, String... varNames) {
    if (sessionId.equals("")) {
      return;
    }

    Key key = keyFactory.newKey(sessionId);
    Transaction transaction = datastore.newTransaction();

    try {
      Entity stateEntity = transaction.get(key);

      if (stateEntity != null) {
        Entity.Builder builder = Entity.newBuilder(stateEntity);
        StringBuilder delNames = new StringBuilder();

        for (String varName : varNames) {
          delNames.append(varName + " ");
          builder = builder.remove(varName);
        }

        datastore.update(builder.build());
      }

    } finally {

      if (transaction.isActive()) {
        transaction.rollback();
      }

    }

  }
  // [END deleteSessionVariables]

  protected void deleteSessionWithValue(String varName, String varValue) {
    Transaction transaction = datastore.newTransaction();

    try {
      Query<Entity> query = Query.newEntityQueryBuilder().setKind("SessionVariable")
          .setFilter(PropertyFilter.eq(varName, varValue)).build();

      QueryResults<Entity> resultList = transaction.run(query);

      while (resultList.hasNext()) {
        Entity stateEntity = resultList.next();
        transaction.delete(stateEntity.getKey());
      }

      transaction.commit();

    } finally {

      if (transaction.isActive()) {
        transaction.rollback();
      }

    }

  }

  // [START setSessionVariables]
  /**
   * Stores the state value in each key-value pair in the project's datastore.
   * @param sessionId Request from which to extract session.
   * @param varName the name of the desired session variable
   * @param varValue the value of the desired session variable
   */
  protected void setSessionVariables(String sessionId, Map<String, String> setMap) {

    if (sessionId.equals("")) {
      return;
    }

    Key key = keyFactory.newKey(sessionId);
    Transaction transaction = datastore.newTransaction();
    DateTime dt = DateTime.now(DateTimeZone.UTC);
    dt.toString(dtf);

    try {
      Entity stateEntity = transaction.get(key);
      Entity.Builder seBuilder;

      if (stateEntity == null) {
        seBuilder = Entity.newBuilder(key);
      } else {
        seBuilder = Entity.newBuilder(stateEntity);
      }

      for (String varName : setMap.keySet()) {
        seBuilder.set(varName, setMap.get(varName));
      }

      transaction.put(seBuilder.set("lastModified", dt.toString(dtf)).build());
      transaction.commit();

    } finally {

      if (transaction.isActive()) {
        transaction.rollback();
      }

    }

  }
  // [END setSessionVariables]

  // [START loadSessionVariables]
  /**
   * Take an HttpServletRequest, and copy all of the current session variables over to it
   * @param req Request from which to extract session.
   * @return a map of strings containing all the session variables loaded or an empty map.
   */
  protected Map<String, String> loadSessionVariables(HttpServletRequest req) throws ServletException {
    Map<String, String> datastoreMap = new HashMap<>();
    String sessionId = getCookieValue(req, "bookshelfSessionId");

    if (sessionId.equals("")) {
      return datastoreMap;
    }

    Key key = keyFactory.newKey(sessionId);
    Transaction transaction = datastore.newTransaction();

    try {
      Entity stateEntity = transaction.get(key);
      StringBuilder logNames = new StringBuilder();

      if (stateEntity != null) {

        for (String varName : stateEntity.getNames()) {
          req.getSession().setAttribute(varName, stateEntity.getString(varName));
          datastoreMap.put(varName, stateEntity.getString(varName));
          logNames.append(varName + " ");
        }

      }

    } finally {

      if (transaction.isActive()) {
        transaction.rollback();
      }

    }

    return datastoreMap;

  }
  // [END loadSessionVariables]
}
Run Code Online (Sandbox Code Playgroud)

小智 0

According to Google App engine Documentation you need to define below configuration in appengine-web.xml 

 App Engine includes an implementation of sessions, using the servlet session interface. The implementation stores session data in the App Engine datastore for persistence, and also uses memcache for speed. As with most other servlet containers, the session attributes that are set with `session.setAttribute()` during the request are persisted at the end of the request. 


  <sessions-enabled>true</sessions-enabled>
  <async-session-persistence enabled="true" />
Run Code Online (Sandbox Code Playgroud)