婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av

主頁 > 知識庫 > 淺談Tomcat Session管理分析

淺談Tomcat Session管理分析

熱門標簽:高德地圖標注好做嗎 電銷機器人怎么接線路 電銷機器人價值 達亞電銷機器人官網(wǎng) 撫順地圖標注 如何分析地圖標注 大連400電話如何申請 外呼系統(tǒng)坐席費計入會計哪個科目 新余高德地圖標注怎么修改

前言

在上文Nginx+Tomcat關(guān)于Session的管理中簡單介紹了如何使用redis來集中管理session,本文首先將介紹默認的管理器是如何管理Session的生命周期的,然后在此基礎(chǔ)上對Redis集中式管理Session進行分析。

Tomcat Manager介紹

上文中在Tomcat的context.xml中配置了Session管理器RedisSessionManager,實現(xiàn)了通過redis來存儲session的功能;Tomcat本身提供了多種Session管理器,如下類圖:

1.Manager接口類

定義了用來管理session的基本接口,包括:createSession,findSession,add,remove等對session操作的方法;還有g(shù)etMaxActive,setMaxActive,getActiveSessions活躍會話的管理;還有Session有效期的接口;以及與Container相關(guān)聯(lián)的接口;

2.ManagerBase抽象類

實現(xiàn)了Manager接口,提供了基本的功能,使用ConcurrentHashMap存放session,提供了對session的create,find,add,remove功能,并且在createSession中了使用類SessionIdGenerator來生成會話id,作為session的唯一標識;

3.ClusterManager接口類

實現(xiàn)了Manager接口,集群session的管理器,Tomcat內(nèi)置的集群服務(wù)器之間的session復(fù)制功能;

4.ClusterManagerBase抽象類

繼承了ManagerBase抽象類,實現(xiàn)ClusterManager接口類,實現(xiàn)session復(fù)制基本功能;

5.PersistentManagerBase抽象類

繼承了ManagerBase抽象類,實現(xiàn)了session管理器持久化的基本功能;內(nèi)部有一個Store存儲類,具體實現(xiàn)有:FileStore和JDBCStore;

6.StandardManager類

繼承ManagerBase抽象類,Tomcat默認的Session管理器(單機版);對session提供了持久化功能,tomcat關(guān)閉的時候會將session保存到j(luò)avax.servlet.context.tempdir路徑下的SESSIONS.ser文件中,啟動的時候會從此文件中加載session;

7.PersistentManager類

繼承PersistentManagerBase抽象類,如果session空閑時間過長,將空閑session轉(zhuǎn)換為存儲,所以在findsession時會首先從內(nèi)存中獲取session,獲取不到會多一步到store中獲取,這也是PersistentManager類和StandardManager類的區(qū)別;

8.DeltaManager類

繼承ClusterManagerBase,每一個節(jié)點session發(fā)生變更(增刪改),都會通知其他所有節(jié)點,其他所有節(jié)點進行更新操作,任何一個session在每個節(jié)點都有備份;

9.BackupManager類

繼承ClusterManagerBase,會話數(shù)據(jù)只有一個備份節(jié)點,這個備份節(jié)點的位置集群中所有節(jié)點都可見;相比較DeltaManager數(shù)據(jù)傳輸量較小,當(dāng)集群規(guī)模比較大時DeltaManager的數(shù)據(jù)傳輸量會非常大;

10.RedisSessionManager類

繼承ManagerBase抽象類,非Tomcat內(nèi)置的管理器,使用redis集中存儲session,省去了節(jié)點之間的session復(fù)制,依賴redis的可靠性,比起sessin復(fù)制擴展性更好;

Session的生命周期

1.解析獲取requestedSessionId

當(dāng)我們在類中通過request.getSession()時,tomcat是如何處理的,可以查看Request中的doGetSession方法:

protected Session doGetSession(boolean create) {
 
  // There cannot be a session if no context has been assigned yet
  Context context = getContext();
  if (context == null) {
    return (null);
  }
 
  // Return the current session if it exists and is valid
  if ((session != null) && !session.isValid()) {
    session = null;
  }
  if (session != null) {
    return (session);
  }
 
  // Return the requested session if it exists and is valid
  Manager manager = context.getManager();
  if (manager == null) {
    return null;    // Sessions are not supported
  }
  if (requestedSessionId != null) {
    try {
      session = manager.findSession(requestedSessionId);
    } catch (IOException e) {
      session = null;
    }
    if ((session != null) && !session.isValid()) {
      session = null;
    }
    if (session != null) {
      session.access();
      return (session);
    }
  }
 
  // Create a new session if requested and the response is not committed
  if (!create) {
    return (null);
  }
  if ((response != null) &&
      context.getServletContext().getEffectiveSessionTrackingModes().
      contains(SessionTrackingMode.COOKIE) &&
      response.getResponse().isCommitted()) {
    throw new IllegalStateException
    (sm.getString("coyoteRequest.sessionCreateCommitted"));
  }
 
  // Re-use session IDs provided by the client in very limited
  // circumstances.
  String sessionId = getRequestedSessionId();
  if (requestedSessionSSL) {
    // If the session ID has been obtained from the SSL handshake then
    // use it.
  } else if (("/".equals(context.getSessionCookiePath())
      && isRequestedSessionIdFromCookie())) {
    /* This is the common(ish) use case: using the same session ID with
     * multiple web applications on the same host. Typically this is
     * used by Portlet implementations. It only works if sessions are
     * tracked via cookies. The cookie must have a path of "/" else it
     * won't be provided for requests to all web applications.
     *
     * Any session ID provided by the client should be for a session
     * that already exists somewhere on the host. Check if the context
     * is configured for this to be confirmed.
     */
    if (context.getValidateClientProvidedNewSessionId()) {
      boolean found = false;
      for (Container container : getHost().findChildren()) {
        Manager m = ((Context) container).getManager();
        if (m != null) {
          try {
            if (m.findSession(sessionId) != null) {
              found = true;
              break;
            }
          } catch (IOException e) {
            // Ignore. Problems with this manager will be
            // handled elsewhere.
          }
        }
      }
      if (!found) {
        sessionId = null;
      }
    }
  } else {
    sessionId = null;
  }
  session = manager.createSession(sessionId);
 
  // Creating a new session cookie based on that session
  if ((session != null) && (getContext() != null)
      && getContext().getServletContext().
      getEffectiveSessionTrackingModes().contains(
          SessionTrackingMode.COOKIE)) {
    Cookie cookie =
        ApplicationSessionCookieConfig.createSessionCookie(
            context, session.getIdInternal(), isSecure());
 
    response.addSessionCookieInternal(cookie);
  }
 
  if (session == null) {
    return null;
  }
 
  session.access();
  return session;
}

如果session已經(jīng)存在,則直接返回;如果不存在則判定requestedSessionId是否為空,如果不為空則通過requestedSessionId到Session manager中獲取session,如果為空,并且不是創(chuàng)建session操作,直接返回null;否則會調(diào)用Session manager創(chuàng)建一個新的session;

關(guān)于requestedSessionId是如何獲取的,Tomcat內(nèi)部可以支持從cookie和url中獲取,具體可以查看CoyoteAdapter類的postParseRequest方法部分代碼:

String sessionID;
if (request.getServletContext().getEffectiveSessionTrackingModes()
    .contains(SessionTrackingMode.URL)) {
 
  // Get the session ID if there was one
  sessionID = request.getPathParameter(
      SessionConfig.getSessionUriParamName(
          request.getContext()));
  if (sessionID != null) {
    request.setRequestedSessionId(sessionID);
    request.setRequestedSessionURL(true);
  }
}
 
// Look for session ID in cookies and SSL session
parseSessionCookiesId(req, request);

可以發(fā)現(xiàn)首先去url解析sessionId,如果獲取不到則去cookie中獲取,此處的SessionUriParamName=jsessionid;在cookie被瀏覽器禁用的情況下,我們可以看到url后面跟著參數(shù)jsessionid=xxxxxx;下面看一下parseSessionCookiesId方法:

String sessionCookieName = SessionConfig.getSessionCookieName(context);
 
for (int i = 0; i < count; i++) {
  ServerCookie scookie = serverCookies.getCookie(i);
  if (scookie.getName().equals(sessionCookieName)) {
    // Override anything requested in the URL
    if (!request.isRequestedSessionIdFromCookie()) {
      // Accept only the first session id cookie
      convertMB(scookie.getValue());
      request.setRequestedSessionId
        (scookie.getValue().toString());
      request.setRequestedSessionCookie(true);
      request.setRequestedSessionURL(false);
      if (log.isDebugEnabled()) {
        log.debug(" Requested cookie session id is " +
          request.getRequestedSessionId());
      }
    } else {
      if (!request.isRequestedSessionIdValid()) {
        // Replace the session id until one is valid
        convertMB(scookie.getValue());
        request.setRequestedSessionId
          (scookie.getValue().toString());
      }
    }
  }
}

sessionCookieName也是jsessionid,然后遍歷cookie,從里面找出name=jsessionid的值賦值給request的requestedSessionId屬性;

2.findSession查詢session

獲取到requestedSessionId之后,會通過此id去session Manager中獲取session,不同的管理器獲取的方式不一樣,已默認的StandardManager為例:

protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
 
public Session findSession(String id) throws IOException {
  if (id == null) {
    return null;
  }
  return sessions.get(id);
}

3.createSession創(chuàng)建session

沒有獲取到session,指定了create=true,則創(chuàng)建session,已默認的StandardManager為例:

public Session createSession(String sessionId) {
   
  if ((maxActiveSessions >= 0) &&
      (getActiveSessions() >= maxActiveSessions)) {
    rejectedSessions++;
    throw new TooManyActiveSessionsException(
        sm.getString("managerBase.createSession.ise"),
        maxActiveSessions);
  }
   
  // Recycle or create a Session instance
  Session session = createEmptySession();
 
  // Initialize the properties of the new session and return it
  session.setNew(true);
  session.setValid(true);
  session.setCreationTime(System.currentTimeMillis());
  session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
  String id = sessionId;
  if (id == null) {
    id = generateSessionId();
  }
  session.setId(id);
  sessionCounter++;
 
  SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
  synchronized (sessionCreationTiming) {
    sessionCreationTiming.add(timing);
    sessionCreationTiming.poll();
  }
  return (session);
 
}

如果傳的sessionId為空,tomcat會生成一個唯一的sessionId,具體可以參考類StandardSessionIdGenerator的generateSessionId方法;這里發(fā)現(xiàn)創(chuàng)建完session之后并沒有把session放入ConcurrentHashMap中,其實在session.setId(id)中處理了,具體代碼如下:

public void setId(String id, boolean notify) {
 
  if ((this.id != null) && (manager != null))
    manager.remove(this);
 
  this.id = id;
 
  if (manager != null)
    manager.add(this);
 
  if (notify) {
    tellNew();
  }
}

4.銷毀Session

Tomcat會定期檢測出不活躍的session,然后將其刪除,一方面session占用內(nèi)存,另一方面是安全性的考慮;啟動tomcat的同時會啟動一個后臺線程用來檢測過期的session,具體可以查看ContainerBase的內(nèi)部類ContainerBackgroundProcessor:

protected class ContainerBackgroundProcessor implements Runnable {
 
   @Override
   public void run() {
     Throwable t = null;
     String unexpectedDeathMessage = sm.getString(
         "containerBase.backgroundProcess.unexpectedThreadDeath",
         Thread.currentThread().getName());
     try {
       while (!threadDone) {
         try {
           Thread.sleep(backgroundProcessorDelay * 1000L);
         } catch (InterruptedException e) {
           // Ignore
         }
         if (!threadDone) {
           Container parent = (Container) getMappingObject();
           ClassLoader cl =
             Thread.currentThread().getContextClassLoader();
           if (parent.getLoader() != null) {
             cl = parent.getLoader().getClassLoader();
           }
           processChildren(parent, cl);
         }
       }
     } catch (RuntimeException e) {
       t = e;
       throw e;
     } catch (Error e) {
       t = e;
       throw e;
     } finally {
       if (!threadDone) {
         log.error(unexpectedDeathMessage, t);
       }
     }
   }
 
   protected void processChildren(Container container, ClassLoader cl) {
     try {
       if (container.getLoader() != null) {
         Thread.currentThread().setContextClassLoader
           (container.getLoader().getClassLoader());
       }
       container.backgroundProcess();
     } catch (Throwable t) {
       ExceptionUtils.handleThrowable(t);
       log.error("Exception invoking periodic operation: ", t);
     } finally {
       Thread.currentThread().setContextClassLoader(cl);
     }
     Container[] children = container.findChildren();
     for (int i = 0; i < children.length; i++) {
       if (children[i].getBackgroundProcessorDelay() <= 0) {
         processChildren(children[i], cl);
       }
     }
   }
 }

backgroundProcessorDelay默認值是10,也就是每10秒檢測一次,然后調(diào)用Container的backgroundProcess方法,此方法又調(diào)用Manager里面的backgroundProcess:

public void backgroundProcess() {
  count = (count + 1) % processExpiresFrequency;
  if (count == 0)
    processExpires();
}
 
/**
 * Invalidate all sessions that have expired.
 */
public void processExpires() {
 
  long timeNow = System.currentTimeMillis();
  Session sessions[] = findSessions();
  int expireHere = 0 ;
   
  if(log.isDebugEnabled())
    log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
  for (int i = 0; i < sessions.length; i++) {
    if (sessions[i]!=null && !sessions[i].isValid()) {
      expireHere++;
    }
  }
  long timeEnd = System.currentTimeMillis();
  if(log.isDebugEnabled())
     log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
  processingTime += ( timeEnd - timeNow );
 
}

processExpiresFrequency默認值是6,那其實最后就是6*10=60秒執(zhí)行一次processExpires,具體如何檢測過期在session的isValid方法中:

public boolean isValid() {
 
  if (!this.isValid) {
    return false;
  }
 
  if (this.expiring) {
    return true;
  }
 
  if (ACTIVITY_CHECK && accessCount.get() > 0) {
    return true;
  }
 
  if (maxInactiveInterval > 0) {
    long timeNow = System.currentTimeMillis();
    int timeIdle;
    if (LAST_ACCESS_AT_START) {
      timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);
    } else {
      timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
    }
    if (timeIdle >= maxInactiveInterval) {
      expire(true);
    }
  }
 
  return this.isValid;
}

主要是通過對比當(dāng)前時間到上次活躍的時間是否超過了maxInactiveInterval,如果超過了就做expire處理;

Redis集中式管理Session分析

在上文中使用tomcat-redis-session-manager來管理session,下面來分析一下是如果通過redis來集中式管理Session的;圍繞session如何獲取,如何創(chuàng)建,何時更新到redis,以及何時被移除;

1.如何獲取

RedisSessionManager重寫了findSession方法

public Session findSession(String id) throws IOException {
  RedisSession session = null;
 
  if (null == id) {
   currentSessionIsPersisted.set(false);
   currentSession.set(null);
   currentSessionSerializationMetadata.set(null);
   currentSessionId.set(null);
  } else if (id.equals(currentSessionId.get())) {
   session = currentSession.get();
  } else {
   byte[] data = loadSessionDataFromRedis(id);
   if (data != null) {
    DeserializedSessionContainer container = sessionFromSerializedData(id, data);
    session = container.session;
    currentSession.set(session);
    currentSessionSerializationMetadata.set(container.metadata);
    currentSessionIsPersisted.set(true);
    currentSessionId.set(id);
   } else {
    currentSessionIsPersisted.set(false);
    currentSession.set(null);
    currentSessionSerializationMetadata.set(null);
    currentSessionId.set(null);
   }
  }

sessionId不為空的情況下,會先比較sessionId是否等于currentSessionId中的sessionId,如果等于則從currentSession中取出session,currentSessionId和currentSession都是ThreadLocal變量,這里并沒有直接從redis里面取數(shù)據(jù),如果同一線程沒有去處理其他用戶信息,是可以直接從內(nèi)存中取出的,提高了性能;最后才從redis里面獲取數(shù)據(jù),從redis里面獲取的是一段二進制數(shù)據(jù),需要進行反序列化操作,相關(guān)序列化和反序列化都在JavaSerializer類中:

public void deserializeInto(byte[] data, RedisSession session, SessionSerializationMetadata metadata)
    throws IOException, ClassNotFoundException {
  BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data));
  Throwable arg4 = null;
 
  try {
    CustomObjectInputStream x2 = new CustomObjectInputStream(bis, this.loader);
    Throwable arg6 = null;
 
    try {
      SessionSerializationMetadata x21 = (SessionSerializationMetadata) x2.readObject();
      metadata.copyFieldsFrom(x21);
      session.readObjectData(x2);
    } catch (Throwable arg29) {
  ......
}

二進制數(shù)據(jù)中保存了2個對象,分別是SessionSerializationMetadata和RedisSession,SessionSerializationMetadata里面保存的是Session中的attributes信息,RedisSession其實也有attributes數(shù)據(jù),相當(dāng)于這份數(shù)據(jù)保存了2份;

2.如何創(chuàng)建

同樣RedisSessionManager重寫了createSession方法,2個重要的點分別:sessionId的唯一性問題和session保存到redis中;

// Ensure generation of a unique session identifier.
if (null != requestedSessionId) {
 sessionId = sessionIdWithJvmRoute(requestedSessionId, jvmRoute);
 if (jedis.setnx(sessionId.getBytes(), NULL_SESSION) == 0L) {
  sessionId = null;
 }
} else {
 do {
  sessionId = sessionIdWithJvmRoute(generateSessionId(), jvmRoute);
 } while (jedis.setnx(sessionId.getBytes(), NULL_SESSION) == 0L); // 1 = key set; 0 = key already existed
}

分布式環(huán)境下有可能出現(xiàn)生成的sessionId相同的情況,所以需要確保唯一性;保存session到redis中是最核心的一個方法,何時更新,何時過期都在此方法中處理;

3.何時更新到redis

具體看saveInternal方法

protected boolean saveInternal(Jedis jedis, Session session, boolean forceSave) throws IOException {
  Boolean error = true;
 
  try {
   log.trace("Saving session " + session + " into Redis");
 
   RedisSession redisSession = (RedisSession)session;
 
   if (log.isTraceEnabled()) {
    log.trace("Session Contents [" + redisSession.getId() + "]:");
    Enumeration en = redisSession.getAttributeNames();
    while(en.hasMoreElements()) {
     log.trace(" " + en.nextElement());
    }
   }
 
   byte[] binaryId = redisSession.getId().getBytes();
 
   Boolean isCurrentSessionPersisted;
   SessionSerializationMetadata sessionSerializationMetadata = currentSessionSerializationMetadata.get();
   byte[] originalSessionAttributesHash = sessionSerializationMetadata.getSessionAttributesHash();
   byte[] sessionAttributesHash = null;
   if (
      forceSave
      || redisSession.isDirty()
      || null == (isCurrentSessionPersisted = this.currentSessionIsPersisted.get())
      || !isCurrentSessionPersisted
      || !Arrays.equals(originalSessionAttributesHash, (sessionAttributesHash = serializer.attributesHashFrom(redisSession)))
     ) {
 
    log.trace("Save was determined to be necessary");
 
    if (null == sessionAttributesHash) {
     sessionAttributesHash = serializer.attributesHashFrom(redisSession);
    }
 
    SessionSerializationMetadata updatedSerializationMetadata = new SessionSerializationMetadata();
    updatedSerializationMetadata.setSessionAttributesHash(sessionAttributesHash);
 
    jedis.set(binaryId, serializer.serializeFrom(redisSession, updatedSerializationMetadata));
 
    redisSession.resetDirtyTracking();
    currentSessionSerializationMetadata.set(updatedSerializationMetadata);
    currentSessionIsPersisted.set(true);
   } else {
    log.trace("Save was determined to be unnecessary");
   }
 
   log.trace("Setting expire timeout on session [" + redisSession.getId() + "] to " + getMaxInactiveInterval());
   jedis.expire(binaryId, getMaxInactiveInterval());
 
   error = false;
 
   return error;
  } catch (IOException e) {
   log.error(e.getMessage());
 
   throw e;
  } finally {
   return error;
  }
 }

以上方法中大致有5中情況下需要保存數(shù)據(jù)到redis中,分別是:forceSave,redisSession.isDirty(),null == (isCurrentSessionPersisted = this.currentSessionIsPersisted.get()),!isCurrentSessionPersisted以及!Arrays.equals(originalSessionAttributesHash, (sessionAttributesHash = serializer.attributesHashFrom(redisSession)))其中一個為true的情況下保存數(shù)據(jù)到reids中;

3.1重點看一下forceSave,可以理解forceSave就是內(nèi)置保存策略的一個標識,提供了三種內(nèi)置保存策略:DEFAULT,SAVE_ON_CHANGE,ALWAYS_SAVE_AFTER_REQUEST

  • DEFAULT:默認保存策略,依賴其他四種情況保存session,
  • SAVE_ON_CHANGE:每次session.setAttribute()、session.removeAttribute()觸發(fā)都會保存,
  • ALWAYS_SAVE_AFTER_REQUEST:每一個request請求后都強制保存,無論是否檢測到變化;

3.2redisSession.isDirty()檢測session內(nèi)部是否有臟數(shù)據(jù)

public Boolean isDirty() {
  return Boolean.valueOf(this.dirty.booleanValue() || !this.changedAttributes.isEmpty());
}

每一個request請求后檢測是否有臟數(shù)據(jù),有臟數(shù)據(jù)才保存,實時性沒有SAVE_ON_CHANGE高,但是也沒有ALWAYS_SAVE_AFTER_REQUEST來的粗暴;

3.3后面三種情況都是用來檢測三個ThreadLocal變量;

4.何時被移除

上一節(jié)中介紹了Tomcat內(nèi)置看定期檢測session是否過期,ManagerBase中提供了processExpires方法來處理session過去的問題,但是在RedisSessionManager重寫了此方法

public void processExpires() {
}

直接不做處理了,具體是利用了redis的設(shè)置生存時間功能,具體在saveInternal方法中:

jedis.expire(binaryId, getMaxInactiveInterval());

總結(jié)

本文大致分析了Tomcat Session管理器,以及tomcat-redis-session-manager是如何進行session集中式管理的,但是此工具完全依賴tomcat容器,如果想完全獨立于應(yīng)用服務(wù)器的方案,

Spring session是一個不錯的選擇。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

標簽:海東 楊凌 南通 新鄉(xiāng) 湖南 黃石 遼源 衡水

巨人網(wǎng)絡(luò)通訊聲明:本文標題《淺談Tomcat Session管理分析》,本文關(guān)鍵詞  淺談,Tomcat,Session,管理,分析,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《淺談Tomcat Session管理分析》相關(guān)的同類信息!
  • 本頁收集關(guān)于淺談Tomcat Session管理分析的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    婷婷综合国产,91蜜桃婷婷狠狠久久综合9色 ,九九九九九精品,国产综合av
    欧美美女一区二区三区| 欧美mv日韩mv国产网站| 欧美午夜寂寞影院| 精品欧美一区二区三区精品久久| 亚洲一区在线电影| 日本乱码高清不卡字幕| 亚洲一区二区三区精品在线| 国产精品乱码人人做人人爱| 91精品国产91久久综合桃花| 欧美精品自拍偷拍动漫精品| 成人三级在线视频| 日韩一区二区三区在线视频| 日韩成人av影视| 欧美日韩激情在线| 日本最新不卡在线| 免费成人在线视频观看| 午夜精品福利一区二区三区av| 国产主播一区二区| 久久成人免费日本黄色| 日韩免费在线观看| 制服丝袜激情欧洲亚洲| 欧美中文字幕一二三区视频| 久草精品在线观看| 久久99精品国产麻豆婷婷| 污片在线观看一区二区| 亚洲成在线观看| 激情伊人五月天久久综合| 久久久精品欧美丰满| 国产精品日韩成人| 欧美精品一区二区三区很污很色的 | 一本久道久久综合中文字幕| 色综合久久久网| 成人一区二区三区在线观看| 日韩欧美一级二级三级 | 人妖欧美一区二区| 成人性生交大片| 欧美色图在线观看| 精品美女被调教视频大全网站| 日韩一区二区免费在线电影| 国产精品免费久久久久| 男人的j进女人的j一区| 国产精品中文字幕一区二区三区| 色综合久久66| 日韩一卡二卡三卡| a4yy欧美一区二区三区| 色94色欧美sute亚洲线路一ni| 日韩三级视频在线看| 精品久久久久香蕉网| 久久久久久99久久久精品网站| 亚洲高清免费观看| 91免费小视频| 国产精品美日韩| 亚洲一区二区黄色| 青娱乐精品视频| 制服丝袜激情欧洲亚洲| av在线播放一区二区三区| 亚洲成人av免费| 日韩欧美一级片| 婷婷丁香激情综合| 亚洲免费av高清| 国产成人福利片| 一区二区国产盗摄色噜噜| 91福利精品视频| www.日韩在线| 国产一区二区三区免费看| 国产精品久久久久三级| 一区二区视频在线| 激情久久久久久久久久久久久久久久 | 日韩欧美国产系列| 亚洲成人高清在线| 欧美mv日韩mv| 午夜电影网一区| 欧美日韩国产综合久久| 亚洲sss视频在线视频| 美女一区二区视频| 欧美高清视频一二三区| 亚洲一二三四区| 国产精品视频你懂的| 天堂va蜜桃一区二区三区漫画版| 91精品中文字幕一区二区三区 | 欧美色视频在线| 亚洲一区二区三区四区在线观看 | 亚洲日本护士毛茸茸| 色婷婷激情一区二区三区| 亚洲一区二区在线视频| 精品国产区一区| 欧美日本一区二区三区四区| 亚洲mv在线观看| 亚洲免费av高清| 精品蜜桃在线看| 欧美性色欧美a在线播放| 国产一区二区伦理片| 在线观看网站黄不卡| 国产成人8x视频一区二区| 日本成人在线不卡视频| 国产欧美精品在线观看| 波波电影院一区二区三区| 日韩二区在线观看| 中文字幕亚洲区| 国产日产欧美一区二区三区| 91精品欧美综合在线观看最新| 91免费观看国产| 韩国v欧美v日本v亚洲v| 午夜视频久久久久久| 亚洲另类在线视频| 亚洲精品一卡二卡| 国产精品久久久久久久第一福利| 久久青草欧美一区二区三区| 26uuu亚洲| 久久综合999| 制服丝袜中文字幕一区| 欧美视频第二页| 在线观看欧美精品| 欧美体内she精高潮| 一本色道亚洲精品aⅴ| 一道本成人在线| 色综合久久中文字幕| 在线免费不卡电影| 国产午夜精品一区二区三区视频| 欧美电视剧免费全集观看| 久久久综合视频| 夜夜嗨av一区二区三区网页| 波多野结衣91| 91麻豆高清视频| 91蝌蚪国产九色| 欧美性xxxxx极品少妇| 91丨porny丨中文| 日韩午夜小视频| 一区二区三区欧美视频| 国产·精品毛片| 欧美一区二区三区男人的天堂| 中文字幕一区免费在线观看| 久草热8精品视频在线观看| 欧美亚洲一区三区| 国产精品丝袜久久久久久app| 亚洲精品videosex极品| 男女男精品网站| 成人深夜在线观看| 欧美一区永久视频免费观看| 国产精品不卡视频| 三级亚洲高清视频| 99热精品一区二区| 在线播放一区二区三区| 国产喂奶挤奶一区二区三区| 又紧又大又爽精品一区二区| 久久精品国产秦先生| 极品瑜伽女神91| 一本大道综合伊人精品热热| 91精品国产高清一区二区三区蜜臀 | 樱桃国产成人精品视频| 99综合电影在线视频| 日韩一区二区影院| 亚洲精品一区二区三区香蕉| 亚洲高清在线视频| 成人激情小说网站| 欧美xfplay| 日精品一区二区| 91久久香蕉国产日韩欧美9色| 久久久91精品国产一区二区精品 | 久久婷婷国产综合精品青草| 麻豆精品精品国产自在97香蕉| 色哟哟精品一区| 国产精品色呦呦| 国产精品1区2区3区在线观看| 在线综合亚洲欧美在线视频 | 中文字幕一区二区三区av| 综合自拍亚洲综合图不卡区| 国产很黄免费观看久久| xnxx国产精品| 丰满亚洲少妇av| 国产欧美精品一区二区三区四区| 国产麻豆一精品一av一免费| 精品国产乱码久久久久久蜜臀 | 日韩黄色一级片| 在线看国产一区| 一区二区三区色| 色哟哟日韩精品| 亚洲国产精品自拍| 欧美日韩亚洲国产综合| 五月激情综合网| 欧美在线|欧美| 亚洲二区在线视频| 精品欧美一区二区三区精品久久 | 中文字幕一区二区三区视频| 成人免费毛片app| 亚洲黄色性网站| 欧美在线一区二区| 中文字幕五月欧美| 欧美福利视频导航| 国产成人福利片| 午夜天堂影视香蕉久久| 激情综合网激情| 精品国产sm最大网站| jlzzjlzz亚洲日本少妇| 日韩在线播放一区二区| 日韩美女久久久| 国产亚洲午夜高清国产拍精品| 欧美私人免费视频| 在线视频欧美区| 99riav一区二区三区|