亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 服務器 > Web服務器 > 正文

淺談Tomcat Session管理分析

2024-09-01 13:55:15
字體:
來源:轉載
供稿:網友

前言

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

Tomcat Manager介紹

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

Tomcat,Session

1.Manager接口類

定義了用來管理session的基本接口,包括:createSession,findSession,add,remove等對session操作的方法;還有getMaxActive,setMaxActive,getActiveSessions活躍會話的管理;還有Session有效期的接口;以及與Container相關聯的接口;

2.ManagerBase抽象類

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

3.ClusterManager接口類

實現了Manager接口,集群session的管理器,Tomcat內置的集群服務器之間的session復制功能;

4.ClusterManagerBase抽象類

繼承了ManagerBase抽象類,實現ClusterManager接口類,實現session復制基本功能;

5.PersistentManagerBase抽象類

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

6.StandardManager類

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

7.PersistentManager類

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

8.DeltaManager類

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

9.BackupManager類

繼承ClusterManagerBase,會話數據只有一個備份節點,這個備份節點的位置集群中所有節點都可見;相比較DeltaManager數據傳輸量較小,當集群規模比較大時DeltaManager的數據傳輸量會非常大;

10.RedisSessionManager類

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

Session的生命周期

1.解析獲取requestedSessionId

當我們在類中通過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已經存在,則直接返回;如果不存在則判定requestedSessionId是否為空,如果不為空則通過requestedSessionId到Session manager中獲取session,如果為空,并且不是創建session操作,直接返回null;否則會調用Session manager創建一個新的session;

關于requestedSessionId是如何獲取的,Tomcat內部可以支持從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 sessionparseSessionCookiesId(req, request);

可以發現首先去url解析sessionId,如果獲取不到則去cookie中獲取,此處的SessionUriParamName=jsessionid;在cookie被瀏覽器禁用的情況下,我們可以看到url后面跟著參數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創建session

沒有獲取到session,指定了create=true,則創建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方法;這里發現創建完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占用內存,另一方面是安全性的考慮;啟動tomcat的同時會啟動一個后臺線程用來檢測過期的session,具體可以查看ContainerBase的內部類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秒檢測一次,然后調用Container的backgroundProcess方法,此方法又調用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秒執行一次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;}

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

Redis集中式管理Session分析

在上文中使用tomcat-redis-session-manager來管理session,下面來分析一下是如果通過redis來集中式管理Session的;圍繞session如何獲取,如何創建,何時更新到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里面取數據,如果同一線程沒有去處理其他用戶信息,是可以直接從內存中取出的,提高了性能;最后才從redis里面獲取數據,從redis里面獲取的是一段二進制數據,需要進行反序列化操作,相關序列化和反序列化都在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) {  ......}

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

2.如何創建

同樣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}

分布式環境下有可能出現生成的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中情況下需要保存數據到redis中,分別是:forceSave,redisSession.isDirty(),null == (isCurrentSessionPersisted = this.currentSessionIsPersisted.get()),!isCurrentSessionPersisted以及!Arrays.equals(originalSessionAttributesHash, (sessionAttributesHash = serializer.attributesHashFrom(redisSession)))其中一個為true的情況下保存數據到reids中;

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

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

3.2redisSession.isDirty()檢測session內部是否有臟數據

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

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

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

4.何時被移除

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

public void processExpires() {}

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

jedis.expire(binaryId, getMaxInactiveInterval());

總結

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

Spring session是一個不錯的選擇。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到服務器教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
青青草99啪国产免费| 欧美国产欧美亚洲国产日韩mv天天看完整| 亚洲成av人影院在线观看| 亚洲女同精品视频| 亚洲第一页在线| 亚洲视频在线免费看| 在线观看亚洲视频| 一本大道久久加勒比香蕉| 欧美夫妻性生活xx| 亚洲三级黄色在线观看| 亚洲成人av片在线观看| 北条麻妃一区二区在线观看| 精品国产欧美一区二区五十路| 日本精品久久电影| 国产精品成人免费电影| 久青草国产97香蕉在线视频| 欧美精品在线免费| 欧美大奶子在线| 日韩精品中文字幕视频在线| 日韩av黄色在线观看| 中文字幕久热精品在线视频| 国语自产偷拍精品视频偷| 亚洲精品久久久久久久久| 国产99久久久欧美黑人| 最近2019中文字幕大全第二页| 久久99热精品这里久久精品| 欧美裸体xxxx| 日韩禁在线播放| 青青草99啪国产免费| 国产精品va在线播放| 国产成人aa精品一区在线播放| 亚洲国产欧美一区二区三区同亚洲| 亚洲黄色在线观看| 伊人久久大香线蕉av一区二区| 亚洲最大的av网站| 欧美壮男野外gaytube| 国产99视频在线观看| 国产999精品视频| 一区二区亚洲欧洲国产日韩| 精品亚洲一区二区三区在线播放| 日韩电视剧免费观看网站| 美日韩精品视频免费看| 国内精品久久久久| 国产精品久久久久久久久久小说| 国产欧美一区二区三区久久人妖| 欧洲成人在线观看| 国产欧美一区二区三区久久| 在线免费观看羞羞视频一区二区| 国产精品激情av电影在线观看| 日韩av影片在线观看| 成人激情视频小说免费下载| 97香蕉超级碰碰久久免费软件| 91色中文字幕| 日韩中文字幕在线播放| 久久精品国产清自在天天线| 不卡毛片在线看| 日本一区二区三区四区视频| 久久伊人91精品综合网站| 91av免费观看91av精品在线| 91九色蝌蚪国产| 欧美日韩加勒比精品一区| 日韩精品在线观看一区| 中文字幕在线日韩| 久久精品一本久久99精品| 久精品免费视频| 国产亚洲精品久久久久久777| 91中文在线观看| 亚洲精品国产精品自产a区红杏吧| 992tv成人免费影院| 欧美色视频日本高清在线观看| 亚洲永久免费观看| 国产日韩av在线播放| 狠狠躁18三区二区一区| 亚洲国产成人久久综合| 国模私拍视频一区| 久久手机精品视频| 最近2019年中文视频免费在线观看| 日韩二区三区在线| 精品成人在线视频| 久久免费精品日本久久中文字幕| 欧美黑人狂野猛交老妇| 亚洲r级在线观看| 欧美日韩视频在线| 午夜精品福利在线观看| 最近2019中文字幕一页二页| 国产精品美女www爽爽爽视频| 亚洲国模精品私拍| 亚洲人成电影网站色| 国产精品久久综合av爱欲tv| 国产亚洲欧美视频| 最近2019好看的中文字幕免费| 奇米一区二区三区四区久久| 久久精品国产清自在天天线| 久久久精品国产网站| 亚洲国产另类久久精品| 精品露脸国产偷人在视频| 国产精品爱啪在线线免费观看| 久久久精品国产| 热re99久久精品国产66热| 在线看日韩av| 成人久久久久爱| 大荫蒂欧美视频另类xxxx| 欧美成人激情视频| 奇米影视亚洲狠狠色| 欧美日韩激情视频8区| 精品久久久久久亚洲国产300| 亚洲无av在线中文字幕| 亚洲一区精品电影| 亚洲第一精品自拍| 国产91色在线| 亚洲二区在线播放视频| 亚洲一区二区三区香蕉| 自拍偷拍免费精品| 国产精品国产三级国产专播精品人| 亚洲激情在线观看视频免费| 亚洲天堂网站在线观看视频| 久久久亚洲影院| 中文欧美在线视频| 亚洲精品资源在线| 2025国产精品视频| 国产成人精品免费久久久久| 国产成人精品久久二区二区91| 国产精品444| 亚洲精品自拍视频| 日韩免费观看视频| 午夜精品久久久久久久久久久久| 国产伦精品免费视频| 日韩中文娱乐网| 国产日韩欧美日韩| 精品精品国产国产自在线| 不卡av日日日| 午夜精品久久久久久久99黑人| 成人福利在线观看| 亚洲a一级视频| 国产精品久久久久久婷婷天堂| 色偷偷av亚洲男人的天堂| 宅男66日本亚洲欧美视频| 亚洲图片欧洲图片av| 欧美在线视频一区| 欧美在线视频一区| 最近日韩中文字幕中文| 亚洲成人精品久久| 亚洲精品一区二区久| 精品国产一区二区三区久久狼黑人| 亚洲电影免费在线观看| 亚洲free性xxxx护士hd| 亚洲综合国产精品| 深夜精品寂寞黄网站在线观看| 91理论片午午论夜理片久久| 久久国内精品一国内精品| 91手机视频在线观看| 美女久久久久久久| 亚洲aa在线观看| 亚洲男人的天堂在线播放| 欧美成人全部免费| 久久精品视频在线观看| 欧美日韩精品在线视频| 欧洲亚洲女同hd| 成人黄色免费在线观看| 成人性教育视频在线观看| 色先锋资源久久综合5566| 成人a视频在线观看| 日韩国产精品一区| 日韩在线视频线视频免费网站|