精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

MySQL億級數(shù)據(jù)平滑遷移實戰(zhàn)

數(shù)據(jù)庫
本文介紹了一次 MySQL 數(shù)據(jù)遷移的流程,通過方案選型、業(yè)務(wù)改造、雙寫遷移最終實現(xiàn)了億級數(shù)據(jù)的遷移。

一、背景

預(yù)約業(yè)務(wù)是 vivo 游戲中心的重要業(yè)務(wù)之一。由于歷史原因,預(yù)約業(yè)務(wù)數(shù)據(jù)表與其他業(yè)務(wù)數(shù)據(jù)表存儲在同一個數(shù)據(jù)庫中。當(dāng)其他業(yè)務(wù)出現(xiàn)慢 SQL 等異常情況時,可能會直接影響到預(yù)約業(yè)務(wù),從而降低系統(tǒng)整體的可靠性和穩(wěn)定性。為了盡可能提高系統(tǒng)的穩(wěn)定性和數(shù)據(jù)隔離性,我們迫切需要將預(yù)約相關(guān)數(shù)據(jù)表從原來的數(shù)據(jù)庫中遷移出來,單獨建立一個預(yù)約業(yè)務(wù)的數(shù)據(jù)庫。

二、方案選型

常見的遷移方案大致可以分為以下幾類:

圖片

而預(yù)約業(yè)務(wù)有以下特點:

  • 讀寫場景多,頻率高,在用戶預(yù)約/取消預(yù)約/福利發(fā)放等場景均涉及到大量的讀寫。
  • 不可接受停機,停機不可避免的會造成經(jīng)濟損失,在有其他方案的情況下不適合選擇此方案。
  • 大部分的場景能接受秒級的數(shù)據(jù)不一致,少部分不能。

結(jié)合這些特點,我們再評估下上面的方案:

圖片

停機遷移方案需要停機,不適用于預(yù)約場景。預(yù)約場景存在不活躍的用戶數(shù)據(jù),如果用漸進式遷移方案的話很難遷移干凈,可能還需要再寫一個遷移任務(wù)來輔助完成遷移。而雙寫方案最大的優(yōu)勢在于每一步操作都可向上回滾,能盡可能的保證業(yè)務(wù)不出問題。

因此,最終選擇的是雙寫方案。預(yù)約業(yè)務(wù)涉及到的讀寫場景多,每一個場景單獨進行改造的成本大,采用 Mybatis 插件來實現(xiàn)遷移所需的雙寫等功能,可以有效降低改造成本。

三、前期準備

3.1 全量同步&增量同步&一致性校驗

這幾步使用了公司提供的數(shù)據(jù)同步工具。全量同步基于 MySQLDump 實現(xiàn);增量同步基于 binlog 實現(xiàn);一致性校驗通過在新老庫各選一個分塊,然后聚合列數(shù)據(jù)計算并對比其特征值實現(xiàn)。

3.2 代碼改造

引入了新庫,那自然就需要在項目里新建數(shù)據(jù)源,并創(chuàng)建表對應(yīng)的 Mybatis Mapper 類。這里有一個小細節(jié)需要注意,Mybatis 默認的 BeanNameGenerator 是

 AnnotationBeanNameGenerator,它會使用類名作為 BeanName 注冊到 Spring 的 ioc 容器中,Spring 啟動時如果發(fā)現(xiàn)有了兩個重名 Bean 就會啟動失敗,筆者這里給 Mybatis 設(shè)置了一個新的 BeanNameGenerator ,使用類的全路徑名作為 BeanName 解決了問題。

public class FullPathBeanNameGenerator implements BeanNameGenerator {
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        return definition.getBeanClassName();
    }
}

還有一點是主鍵 id,本次預(yù)約遷移需要保證新老庫主鍵 id 一致,預(yù)約業(yè)務(wù)沒做分庫分表,id 都是直接用 MySQL 的自增 id,沒有用 id 生成器之類的中間件。因此插入新表時只需要使用插入老表后 Mybatis 自動設(shè)置好的 id 即可,這次遷移前先檢查了一遍業(yè)務(wù)代碼,確保插入語句都用了 Mybatis 的 useGeneratedKeys 功能來自動設(shè)置 id。

3.3 插件實現(xiàn)

Mybatis 插件可以攔截 SQL 語句執(zhí)行過程中的某一點進行干預(yù)和處理,而 Executor 是 Mybatis 中負責(zé)執(zhí)行 SQL 語句的核心組件。我們可以對 Executor 的 update 和 query 方法進行代理以實現(xiàn)遷移所需的功能。

插件需要為讀寫場景分別實現(xiàn)以下功能:

圖片

考慮到開關(guān)切換部分的代碼邏輯較為簡單,因此在下文中,筆者將不再過多介紹該部分的具體實現(xiàn),而是著重介紹如何在插件中使用老庫的執(zhí)行語句來訪問新的數(shù)據(jù)庫。此外,代碼里會涉及到 Mybatis 相關(guān)的一些概念,由于網(wǎng)上已經(jīng)有較多詳盡的資料,這里就不再贅述。

遷移插件代理了 Executor 的 query 和 update 方法,首先在插件里獲取到當(dāng)前執(zhí)行的 SQL 語句所在的 Mapper 路徑。

@Intercepts(
        {
                @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
public class AppointMigrateInterceptor implements Interceptor {
 
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
 
        Object[] args = invocation.getArgs();
        // Mybatis插件代理的Executor的update或者query方法,第一個參數(shù)就是MappedStatement
        MappedStatement ms = (MappedStatement) args[0];
        SqlCommandType sqlCommandType = ms.getSqlCommandType();
        String id = ms.getId();
        // 從MappedStatement id中獲取對應(yīng)的Mapper接口文件全路徑
        String sourceMapper = id.substring(0, id.lastIndexOf("."));
 
        // ...
    }
     
    // ...
}

得到老庫 Mapper 路徑后,將其轉(zhuǎn)換為新庫 Mapper 路徑,再使用 Class.forName 獲取到新庫 Mapper 類,然后用新庫的 sqlSessionFactory 開啟 sqlSession,再獲取反射調(diào)用所需的方法、對象、參數(shù),在新庫上執(zhí)行語句。

protected Object invoke(Invocation invocation, TableConfiguration tableConfiguration) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    // 獲取 MappedStatement
    MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
 
    // 獲取 Mybatis 封裝好的入?yún)ⅲ庋b函數(shù) MapperMethod.convertArgsToSqlCommandParam(Object[] args)
    Object parameter = invocation.getArgs()[1];
 
    // 使用 Class.forName 獲取到的新庫 Mapper
    Class<?> targetMapperClass = tableConfiguration.getTargetMapperClazz();
 
    // 使用新庫的 sqlSessionFactory 創(chuàng)建 sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    Object result = null;
    try{
        // 使用新庫的 Mapper 路徑獲取對應(yīng)的 MapperProxy 對象
        Object mapper = sqlSession.getMapper(targetMapperClass);
 
        // 將 Mybatis 封裝好的參數(shù)轉(zhuǎn)換為原始參數(shù)
        Object[] paramValues = getParamValue(parameter);
 
        // 使用 mappedStatement Id 從新庫對應(yīng)的 Mapper 里獲取對應(yīng)的方法
        Method method = getMethod(ms.getId(), targetMapperClass, paramValues);
        paramValues = fixNullParam(method, paramValues);
 
        // 反射調(diào)用新庫 Mapper 的方法,本質(zhì)上執(zhí)行的是 MapperProxy.invoke
        result = method.invoke(mapper, paramValues);
    } finally {
        sqlSession.close();
    }
    return result;
}
 
private Object[] fixNullParam(Method method, Object[] paramValues) {
    if (method.getParameterTypes().length > 0 && paramValues.length == 0) {
        return new Object[]{null};
    }
    return paramValues;
}

上述代碼里,getMethod 方法負責(zé)從新庫 Mapper 類里找到對應(yīng)的方法,以用于后續(xù)的反射調(diào)用。

private Method getMethod(String id, Class mapperClass) throws NoSuchMethodException {
    //獲取參數(shù)對應(yīng)的 class
    String methodName = id.substring(id.lastIndexOf(".") + 1);
    String key = id;
    // methodCache 用來緩存 MappedStatement 和對應(yīng)的 Method,避免每次都從 Mapper 里查找
    Method method = methodCache.get(key);
    if (method == null){
        method = findMethodByMethodSignature(mapperClass, methodName);
        if (method == null){
            throw new NoSuchMethodException("No such method " + methodName + " in class " + mapperClass.getName());
        }
        methodCache.put(key,method);
    }
    return method;
}
 
private Method findMethodByMethodSignature(Class mapperClass,String methodName) throws NoSuchMethodException {
    // mybatis 的 Mapper 內(nèi)的方法不支持重載,所以這里只要方法名匹配到了就行,不用進行參數(shù)的匹配
    Method method = null;
    for (Method m : mapperClass.getMethods()) {
        if (m.getName().equals(methodName)) {
            method = m;
            break;
        }
    }
    return method;
}

得到方法后,還需要得到反射調(diào)用所需的參數(shù)。Mybatis 執(zhí)行到 Executor.update/query 方法時,參數(shù)已經(jīng)經(jīng)過   MapperMethod.convertArgsToSqlCommandParam(Object[] args) 方法封裝,不能直接用來執(zhí)行  MapperProxy.invoke ,需要轉(zhuǎn)換后才可用。下圖是MapperMethod.convertArgsToSqlCommandParam(Object[] args) 的封裝過程,而下面的 getParamValue 是這個函數(shù)的逆過程。

圖片

private Object[] getParamValue(Object parameter) {
    List<Object> paramValues = new ArrayList<>();
 
    if (parameter instanceof Map) {
        Map<String, Object> paramMap = (Map<String, Object>) parameter;
        if (paramMap.containsKey("collection")) {
            paramValues.add(paramMap.get("collection"));
        } else if (paramMap.containsKey("array")) {
            paramValues.add(paramMap.get("array"));
        } else {
            int count = 1;
            while (count <= paramMap.size() / 2){
                try {
                    paramValues.add(paramMap.get("param"+(count++)));
                }catch (BindingException e){
                    break;
                }
            }
        }
    } else if (parameter != null){
        paramValues.add(parameter);
    }
    return paramValues.toArray();
}

通過上述流程,我們就能使用 Mybatis 插件攔截老庫的執(zhí)行過程,實現(xiàn)遷移所需的讀寫數(shù)據(jù)源切換/新老庫查詢結(jié)果對比/先寫老庫再異步寫新庫等功能。

四、雙寫流程

4.1 上線雙寫改造后的業(yè)務(wù)代碼,上線時只讀寫老庫

  1. 讀開關(guān):只讀老庫
  2. 寫開關(guān):只寫老庫
  3. 新老庫查詢結(jié)果對比開關(guān):關(guān)

此時業(yè)務(wù)仍只讀寫老庫。

4.2 使用公司中間件平臺提供的數(shù)據(jù)工具同步老庫數(shù)據(jù)到新庫

  1. 讀開關(guān):只讀老庫
  2. 寫開關(guān):只寫老庫
  3. 新老庫查詢結(jié)果對比開關(guān):關(guān)

第1步和第2步并沒有嚴格的順序要求,只要在切換為雙寫前做完第1步和第2步就好。

條件允許的情況下,全量+增量同步時應(yīng)選擇不對外提供服務(wù)的離線從庫作為數(shù)據(jù)源,避免主從延遲等問題對線上業(yè)務(wù)造成影響。

4.3 停止同步程序,然后開啟雙寫

  1. 讀開關(guān):只讀老庫(開啟查詢結(jié)果對比開關(guān))
  2. 寫開關(guān):雙寫
  3. 新老庫查詢結(jié)果對比開關(guān):開

老庫追上新庫后,對數(shù)據(jù)做一次全量校驗,避免出現(xiàn)數(shù)據(jù)不一致的情況。此外還需要開啟新老庫查詢結(jié)果對比開關(guān),通過日志監(jiān)控觀察新老庫的查詢結(jié)果是否一致。

停止數(shù)據(jù)同步和切換雙寫之間必然有時間差,如果先開啟雙寫再停止數(shù)據(jù)同步,則可能出現(xiàn)插入重復(fù)數(shù)據(jù)或數(shù)據(jù)被覆蓋的情況。因此需要對數(shù)據(jù)同步工具和遷移插件進行改造,以處理數(shù)據(jù)異常的情況,但是這樣改造需要處理的情況較多,改造成本較高。所以這里選擇先停止同步,再切換到雙寫,中間丟失的數(shù)據(jù)使用對比&補償任務(wù)恢復(fù),由于此時仍然全量讀老庫,所以對業(yè)務(wù)不會有影響。需要注意的是,雙寫階段的時間不應(yīng)太長,只要確保新老庫數(shù)據(jù)一致就應(yīng)該前進到下一步。

這一步在實際操作過程中需要注意以下情況:

4.3.1 自增主鍵

預(yù)約業(yè)務(wù)新庫的主鍵 id 需要和老庫保持一致,因此在遷移前檢查了一遍業(yè)務(wù)代碼,確保插入語句都用了 Mybatis 的 useGeneratedKeys 功能來返回 id ,這樣插入新庫時可以直接用設(shè)置好 id 的對象。但是這里有一個問題,批量插入時 Mybatis 自動設(shè)置的 id 和數(shù)據(jù)庫生成的自增主鍵不一定完全一致,比如批量 insert ignore 和 on duplicate key update 語句。

這個問題和 useGeneratedKeys 的實現(xiàn)有關(guān),代碼可參考

com.mysql.jdbc.StatementImpl#getGeneratedKeysInternal(long) 函數(shù),以下是其執(zhí)行邏輯:

  1. Mybatis 執(zhí)行完插入語句后,MySQL 會返回這次插入影響的數(shù)據(jù)行數(shù),注意,使用 insert ignore 插入時,忽略的那部分數(shù)據(jù)不會加到影響的行數(shù)上。
  2. Mybatis 使用 SELECT LAST_INSERT_ID() 查詢這次插入的最小 id 。
  3. Mybatis 循環(huán)遍歷插入時用的對象列表,循環(huán)的最大次數(shù)為第1步里獲取的這次插入影響的行數(shù),使用 n 代表當(dāng)前的循環(huán)次數(shù),列表中的每個對象的 id 被賦值為 LAST_INSERT_ID() + n*AUTO_INCREMENT 。

舉例來說,假設(shè)老庫的某張表里有數(shù)據(jù) b ,其 id=1,此時往該表使用 insert ignore 批量插入三條數(shù)據(jù) a,b,c,其在表內(nèi)的 id 為 a:2、b:1、c:3,返回的影響行數(shù)為2,SELECT LAST_INSERT_ID() 返回的是2,因此 Mybatis 往對象里設(shè)置的主鍵分別為 a:2、b:3、c:null,再使用這個設(shè)置好 id 的對象列表插入新庫時會導(dǎo)致新老庫 id 不一致。

解決方案:由于直接刪除 ignore 會改變這條 SQL 的語義,無法通過修改語句來解決問題。所以我們只能在遷移插件里跳過這條語句,使其固定寫入老庫。然后在業(yè)務(wù)層單獨對其進行遷移改造,將插入新庫的流程修改為先使用 id 以外的唯一鍵查詢一次老庫的數(shù)據(jù),獲取到 id 以后設(shè)置到對象列表里,再插入新庫。

4.3.2 事務(wù)

預(yù)約業(yè)務(wù)有部分邏輯用到了事務(wù),但這部分邏輯在雙寫期間均可以暫停功能,因此遷移插件沒有實現(xiàn)事務(wù)的支持。如果需要支持業(yè)務(wù)的話可以不依賴插件,在業(yè)務(wù)層單獨對那部分代碼進行改造。

4.3.3 異步寫入新庫引起的問題

雙寫過程中是異步寫新庫,需要重點關(guān)注是否會有線程安全問題。舉例來說,假設(shè)有個業(yè)務(wù)需要往表里插入一個列表,插入完列表后又對列表進行了修改,比如執(zhí)行了 List.clear() 函數(shù)或者其中的對象發(fā)生了變更,由于是異步寫新庫,所以實際的執(zhí)行流程可能如下:

  1. 老庫 insert(list)
  2. list.clear()
  3. 新庫 insert(list)

這會導(dǎo)致新庫執(zhí)行操作時,傳入的對象和老庫執(zhí)行操作時不一樣,導(dǎo)致新老庫數(shù)據(jù)不一致。建議在遷移前人為的確認業(yè)務(wù)邏輯,避免異步寫入導(dǎo)致新老庫數(shù)據(jù)不一致。

4.4 開啟對比和補償程序,補償切換開關(guān)的過程中遺失的數(shù)據(jù)

  1. 讀開關(guān):只讀老庫(對比開關(guān)開啟)
  2. 寫開關(guān):雙寫
  3. 新老庫查詢結(jié)果對比開關(guān):開
  4. 對比&補償任務(wù):開啟

圖片

該對比&補償任務(wù)有一個缺陷,其不能處理數(shù)據(jù)被刪除的情況,如果老庫里的數(shù)據(jù)被刪除但是新庫的數(shù)據(jù)刪除失敗,那使用更新時間區(qū)間就無法從老庫查出這條數(shù)據(jù),自然也無法進行對比&補償。

雙寫期間,如果出現(xiàn)刪老庫成功但是刪新庫失敗的情況會有日志告警,所以不會有問題。但是停止數(shù)據(jù)同步工具 → 開啟雙寫開關(guān)這一過程中刪除的數(shù)據(jù)無法補償。不過大部分業(yè)務(wù)用的都是邏輯刪除,只有一處用了物理刪除,筆者在這一處添加了日志,如果切換過程中出現(xiàn)刪除數(shù)據(jù)的日志,就需要手動進行補償操作。實際操作過程中,開關(guān)的切換的耗時較短,只花了30秒左右,在這過程中沒有打印刪除數(shù)據(jù)的日志。

4.5 逐步切量請求到新庫上

  1. 讀開關(guān):部分讀新庫 → 只讀新庫
  2. 寫開關(guān):雙寫
  3. 新老庫查詢結(jié)果對比開關(guān):開
  4. 對比&補償任務(wù):開啟

雙寫時,由于數(shù)據(jù)先寫入老庫再異步寫入新庫,因此新庫的數(shù)據(jù)肯定會滯后于老庫。如果將一部分讀流量切換到新庫上,就可能會在一些對延遲要求較高的業(yè)務(wù)場景中出現(xiàn)問題。對于這種場景,我們不能采用逐步切量的策略,只能同時切換讀寫開關(guān),將其修改為只寫老庫+只讀新庫。

4.6 停止對比補償程序,關(guān)閉雙寫,讀寫都切換到新庫,開啟反向補償任務(wù)

  1. 讀開關(guān):只讀新庫
  2. 寫開關(guān):只寫新庫
  3. 新老庫查詢結(jié)果對比開關(guān):關(guān)
  4. 對比&補償任務(wù):開啟反向補償

反向補償是從新庫補償數(shù)據(jù)到老庫,由于該任務(wù)是定時執(zhí)行,開啟后,新庫和老庫的數(shù)據(jù)會有 1~2 分鐘的延遲,萬一寫新庫的邏輯有問題,可以切回老庫。至于為什么用反向補償任務(wù)而不是使用先寫新庫再異步寫老庫的策略,是因為雙寫是用 MyBatis 插件實現(xiàn)的,插件代理的是 excutor 的 update 和 query 方法,如果異步寫入老庫,有可能會發(fā)生以下情況:

  1. 假設(shè)有兩個線程,業(yè)務(wù)線程 A 需要寫入一條數(shù)據(jù),遷移插件攔截后,先同步寫入新庫,寫完新庫后提交任務(wù)給線程 B 中異步寫入老庫,提交完任務(wù)后插件立刻返回。
  2. 由于插件已返回結(jié)果,executor 上層的 sqlsession 調(diào)用 close() 方法關(guān)閉 executor (見 org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke ),此時線程 B 可能還沒執(zhí)行完寫老庫的操作。
  3. 線程 B 執(zhí)行過程中,由于 executor 已經(jīng)關(guān)閉,導(dǎo)致其寫老庫失敗。

因此無法使用 Mybatis 插件來實現(xiàn)異步寫老庫。

4.7 停止反向補償任務(wù),刪除表遷移相關(guān)代碼

停止反向補償前,需要關(guān)注是否還有業(yè)務(wù)在讀老庫。觀察一段時間,確認老庫沒有補償任務(wù)以外的讀寫流量后,可以關(guān)閉補償任務(wù),清理遷移過程中產(chǎn)生的代碼,清理老庫數(shù)據(jù)。

五、總結(jié)

在進行數(shù)據(jù)表遷移的過程中,雖然遇到了一些問題,但是制定的方案中每一步都有回退措施,即使出現(xiàn)問題也不會影響業(yè)務(wù)的正常運行。此外,筆者在遷移過程中對各種異常情況進行了監(jiān)控,能及時發(fā)現(xiàn)并解決問題。如果其他業(yè)務(wù)需要進行類似的遷移,需要關(guān)注以下幾個方面:

  1. 遷移插件實現(xiàn):在對遷移過程進行反思后,筆者人為通過代理或重寫 MapperProxy 的方式來實現(xiàn)遷移插件可能是更加合理的方案。這種方案有兩個優(yōu)點:一方面,可以避免處理 Mybatis 復(fù)雜的參數(shù)轉(zhuǎn)換流程,從而減少潛在的錯誤和異常;另一方面,可以實現(xiàn)先寫新庫再異步寫老庫的操作。但是這個方案沒有經(jīng)過實踐,還不能確定是否有可行性。
  2. 自增主鍵:需要確定業(yè)務(wù)是否需要保證新老庫的 id 一致。
  3. 事務(wù):雙寫過程中應(yīng)該結(jié)合業(yè)務(wù)考慮是否需要實現(xiàn)事務(wù)支持。本次遷移過程中,我們暫停了部分需要事務(wù)支持的業(yè)務(wù)。
  4. 異步寫入:先寫老庫再異步寫入新庫的方式可能導(dǎo)致新老庫數(shù)據(jù)不一致,遷移插件自身無法解決這個問題,只能人工提前排查可能存在的隱患。
責(zé)任編輯:龐桂玉 來源: vivo互聯(lián)網(wǎng)技術(shù)
相關(guān)推薦

2019-05-27 09:56:00

數(shù)據(jù)庫高可用架構(gòu)

2025-10-15 09:20:00

2019-03-05 10:16:54

數(shù)據(jù)分區(qū)表SQLserver

2021-06-29 08:12:22

MySQL數(shù)據(jù)分頁數(shù)據(jù)庫

2021-06-08 08:51:50

Redis 數(shù)據(jù)類型數(shù)據(jù)統(tǒng)計

2017-03-24 14:46:50

數(shù)據(jù)架構(gòu)數(shù)據(jù)庫

2011-03-03 10:32:07

Mongodb億級數(shù)據(jù)量

2023-03-27 09:14:34

2024-08-12 12:07:18

2022-09-19 16:22:43

數(shù)據(jù)庫方案

2019-05-28 09:31:05

Elasticsear億級數(shù)據(jù)ES

2021-03-11 10:55:41

MySQL數(shù)據(jù)庫索引

2022-07-27 22:48:29

消息中間件RocketMQ架構(gòu)設(shè)計

2019-07-29 10:18:17

數(shù)據(jù)庫高可用架構(gòu)

2025-04-14 02:00:00

2018-04-19 09:10:17

數(shù)據(jù)分析列式存儲

2021-03-16 07:41:00

數(shù)據(jù)分頁優(yōu)化

2019-06-05 14:30:21

MySQL數(shù)據(jù)庫索引

2020-10-23 09:38:41

數(shù)據(jù)分庫分表數(shù)據(jù)遷移

2024-02-19 00:06:06

數(shù)據(jù)分析系統(tǒng)Doris
點贊
收藏

51CTO技術(shù)棧公眾號

欧美日韩一区二| 欧美二区在线播放| 国内外成人免费在线视频| 激情影院在线观看| 国产盗摄视频一区二区三区| 性欧美亚洲xxxx乳在线观看| 一区二区三区在线观看免费视频| 日韩精品久久久久久久软件91| 亚洲成人免费影院| 亚洲精品一区二区毛豆| 亚洲老妇色熟女老太| 玖玖在线精品| 欧美激情国产精品| 调教驯服丰满美艳麻麻在线视频| 99精品中文字幕在线不卡| 91成人在线免费观看| 国产激情在线看| 男男电影完整版在线观看| 国产精品资源在线| 国产精品亚洲欧美导航| 6080午夜伦理| 在线 亚洲欧美在线综合一区| 国产亚洲a∨片在线观看| 2018国产精品| www.久久热| 在线视频亚洲一区| 亚洲 高清 成人 动漫| 麻豆视频免费在线观看| 国产日韩欧美电影| 精品国产一二| 好吊色一区二区三区| 国内成人自拍视频| 成人黄色av免费在线观看| 久久国产乱子伦精品| 一区二区精品| 欧美国产视频一区二区| 免费高清在线观看电视| 色135综合网| 在线午夜精品自拍| 国产人妻大战黑人20p| 日韩有码av| 亚洲国产成人在线播放| 国产无套精品一区二区三区| 综合欧美精品| 欧美丰满一区二区免费视频| 制服丝袜综合网| 粉嫩91精品久久久久久久99蜜桃| 色呦呦日韩精品| 亚洲中文字幕无码不卡电影| 超碰99在线| 午夜精品久久久久| 国产综合中文字幕| 国产污视频在线播放| 亚洲va韩国va欧美va精品| 国产主播自拍av| 久久久男人天堂| 色一情一伦一子一伦一区| www.亚洲天堂网| 日韩av福利| 在线免费观看日韩欧美| 天天干天天操天天做| 欧美久久久网站| 91精品久久久久久蜜臀| 一区二区三区人妻| 国产精品xxx在线观看| 亚洲国产精品大全| 亚洲第一香蕉网| 精品一二三区| 久久夜色精品国产| 久久免费小视频| 亚洲国产片色| 国产91亚洲精品| 夜夜嗨aⅴ一区二区三区| 久久99精品国产.久久久久 | 欧洲美女女同性互添| 国产精品久久观看| 欧美精品在线免费观看| 91精品国产高潮对白| 亚洲综合国产| 国产欧美久久久久久| a级片在线播放| 91网上在线视频| 亚洲国产精品一区二区第一页| 精品176二区| 婷婷开心激情综合| 中文字幕视频在线免费观看| 欧美a在线观看| 精品呦交小u女在线| 欧美福利在线视频| 亚洲高清在线| 国产精品福利在线观看网址| 国产高中女学生第一次| 91免费视频网| 国产精品88久久久久久妇女| 在线天堂中文资源最新版| 欧美丰满一区二区免费视频 | 国产又粗又猛又黄又爽| 东方欧美亚洲色图在线| 免费看国产精品一二区视频| 日本成人网址| 一本到三区不卡视频| 国产精品探花在线播放| 国产一区二区三区91| 久久69精品久久久久久国产越南| 一级片免费在线播放| 国产成人一区在线| 亚洲欧美日韩另类精品一区二区三区 | 欧美成人四级hd版| 无码人妻久久一区二区三区| 国产精品小仙女| 日本成人黄色| sm捆绑调教国产免费网站在线观看 | 三年中国国语在线播放免费| 91精品啪在线观看国产爱臀| 中文字幕亚洲综合| 久久久精品福利| 国产精品一二三四| 中国成人在线视频| 美女福利一区二区| 亚洲国内高清视频| 乱h高h女3p含苞待放| 免费看黄色91| 茄子视频成人在线观看 | 影音先锋日韩在线| 国产精品久久久久影院日本 | 日韩性xxxx爱| 免费看日韩毛片| 国产一区二区三区香蕉| 亚洲午夜在线观看| 日本在线中文字幕一区二区三区| 亚洲二区在线播放视频| 欧美久久久久久久久久久久| 精久久久久久久久久久| 亚洲精品乱码视频| 不卡亚洲精品| 中文字幕久久精品| 日本丰满少妇做爰爽爽| 久久精品亚洲精品国产欧美 | 久久99精品久久久久久动态图| 鲁丝片一区二区三区| av漫画网站在线观看| 精品美女被调教视频大全网站| 91杏吧porn蝌蚪| 国产在线视频不卡二| 在线一区高清| 91精品福利观看| 久久精品亚洲94久久精品| 在线观看毛片av| 国产精品热久久久久夜色精品三区| 成人一区二区三| 少妇精品久久久| 国产精品极品在线| 色综合久久影院| 欧美日产国产精品| 亚洲成人生活片| 国产999精品久久久久久绿帽| 日本成人在线不卡| 97超碰成人| 国产69精品久久久久久| 韩国三级av在线免费观看| 欧美影院一区二区| 国产性生活大片| 国产91露脸合集magnet| 亚洲美免无码中文字幕在线| 少妇高潮一区二区三区| 国产精品高精视频免费| 成人在线播放免费观看| 日韩精品自拍偷拍| 日韩黄色一级大片| 国产欧美一区二区在线| 日本中文字幕观看| 亚洲小说欧美另类婷婷| 日本成人黄色| 免费一级欧美在线大片| 91国内精品久久| 丁香婷婷在线观看| 欧美一级日韩不卡播放免费| 麻豆国产尤物av尤物在线观看| 成人成人成人在线视频| 男女无套免费视频网站动漫| 欧美777四色影| 久久av一区二区| 黄色成人在线观看网站| 色综合五月天导航| 精彩国产在线| 日韩一区二区电影在线| 五月天婷婷激情| 自拍视频在线观看一区二区| japanese在线观看| 美女网站一区二区| 国产96在线 | 亚洲| 欧美三级情趣内衣| 亚洲一区二区三区毛片| 中文在线а√在线8| 久久中文字幕一区| 欧美视频综合| 欧美草草影院在线视频| 国产真人无遮挡作爱免费视频| 亚洲人成7777| 亚洲黄色小说视频| 成人综合在线观看| 不卡的av中文字幕| 亚洲视频www| 日本久久高清视频| 国产最新精品| 黑人另类av| 欧美午夜在线播放| 国产欧美 在线欧美| 中文在线а√天堂| 国模叶桐国产精品一区| 日本中文字幕在线视频| 精品亚洲精品福利线在观看| www.天堂av.com| 欧美日韩一区二区在线视频| 中文字幕激情小说| 亚洲五月六月丁香激情| 中日韩一级黄色片| 欧美韩国日本一区| 蜜桃无码一区二区三区| 99久久伊人网影院| 少妇性l交大片7724com| 极品美女销魂一区二区三区免费| 久久久久狠狠高潮亚洲精品| 最新亚洲激情| 久久久久99精品成人片| 影视亚洲一区二区三区| 波多野结衣激情| 欧美顶级大胆免费视频| 亚洲人体一区| 不卡中文字幕| 日韩亚洲视频| 欧美精品一二| 日韩国产伦理| 欧美精品一区二区久久| 日韩精品另类天天更新| 国产一区二区三区网| 鲁丝一区二区三区免费| 亚欧日韩另类中文欧美| 久久综合九色欧美狠狠| 亚洲电影一级片| 免费国产在线精品一区二区三区| 天堂成人娱乐在线视频免费播放网站| 国产精品乱码视频| 永久免费精品视频| 成人在线视频网址| 波多野结衣欧美| 国产欧美亚洲日本| 免费看成人人体视频| 久久国产精品一区二区三区四区 | 亚洲人成人77777线观看| 精品国产乱码久久久久久蜜坠欲下 | 激情黄色小视频| 黄色精品一二区| 人妻精品久久久久中文字幕69| 国产伦理精品不卡| 麻豆短视频在线观看| 99国产麻豆精品| japanese中文字幕| 国产精品伦理在线| 日韩成人毛片视频| 亚洲福中文字幕伊人影院| 日韩成人免费在线视频| 欧美日韩免费看| 波多野结衣大片| 欧美日本一道本在线视频| 国产超碰人人模人人爽人人添| 精品日本一线二线三线不卡| 午夜视频福利在线观看| 亚洲免费电影在线观看| jizz日韩| 欧美人成在线视频| 亚洲欧洲自拍| 国产在线拍偷自揄拍精品| 亚洲**毛片| 欧美aaaaa喷水| 91亚洲一区| 免费拍拍拍网站| 日韩 欧美一区二区三区| 亚洲精品在线网址| 91啪亚洲精品| 亚洲xxxx3d动漫| 欧美午夜影院在线视频| 91精品中文字幕| 亚洲激情视频网| 日本福利专区在线观看| 国内精品视频久久| 国产福利亚洲| 国产精品一区视频网站| 青草国产精品| 青青草国产免费| 蜜桃视频一区二区| 中文字幕一区二区久久人妻网站 | 在线观看欧美激情| 国产日韩视频| 日韩欧美理论片| 久久久精品一品道一区| 欧美日韩综合一区二区| 91久久精品网| 日韩在线观看视频网站| 色老头一区二区三区在线观看| av影视在线| 成人激情在线观看| 精品视频免费在线观看| 欧美久久久久久久久久久久久| 久久91精品国产91久久小草| 亚洲天堂网一区二区| 一区二区三区丝袜| 中文字幕精品在线观看| 精品夜色国产国偷在线| 尤物在线网址| 国产日韩欧美日韩| 国产99久久久国产精品成人免费 | 亚洲国产一区二区三区a毛片| 亚洲精品久久久中文字幕| 97精品电影院| 久一区二区三区| 91麻豆精品国产91久久久| 国产三级电影在线| 欧美一区第一页| 国产图片一区| youjizz.com在线观看| 久久成人免费电影| 日韩不卡av在线| 在线影院国内精品| 国产精品久久一区二区三区不卡| 午夜伦理精品一区| 国产96在线亚洲| 日韩久久久久久久久久久久| 精品一区二区三区在线播放视频 | 国产日韩欧美在线观看| 国产日产精品一区二区三区四区的观看方式 | 91久久精品国产| 97精品视频| 天天操狠狠操夜夜操| 中文字幕电影一区| 樱花视频在线免费观看| 亚洲人成免费电影| 久久野战av| 涩涩涩999| 青青草伊人久久| 成人在线手机视频| 欧美综合欧美视频| 99reav在线| 国产精品亚洲精品| 香蕉精品视频在线观看| 亚洲制服中文字幕| 一区二区三区四区视频精品免费| av在线亚洲天堂| 色综合久久88| 久本草在线中文字幕亚洲| 大陆极品少妇内射aaaaa| 26uuu久久天堂性欧美| 区一区二在线观看| 在线观看免费高清视频97| 欧美亚洲黄色| 国产一级片91| k8久久久一区二区三区| 国产精品老女人| 亚洲天堂久久av| 男人亚洲天堂| 在线观看17c| av电影天堂一区二区在线观看| 草久久免费视频| 国产一区二区美女视频| 日韩欧美三区| 美女扒开大腿让男人桶| 99久久婷婷国产精品综合| 无码人妻av一区二区三区波多野| 中文字幕在线看视频国产欧美在线看完整 | 日韩一区二区高清| 一个人看的www视频在线免费观看 一个人www视频在线免费观看 | 欧美精品在线播放| 日本亚洲不卡| 粉色视频免费看| 亚洲大片一区二区三区| 精品久久久久一区二区三区| 国产日韩欧美中文| 怡红院精品视频在线观看极品| 女尊高h男高潮呻吟| 欧美三级日韩在线| 青青在线视频| 欧美一区二区高清在线观看| 精品亚洲porn| 偷偷操不一样的久久| 日韩在线视频线视频免费网站| 午夜视频在线观看精品中文| 男人操女人免费软件| 成人免费在线观看入口| 日本啊v在线| 91麻豆桃色免费看| 老司机一区二区三区| 午夜精品福利在线视频| 亚洲欧美在线免费观看| 精品中文字幕一区二区三区四区| 男人日女人逼逼| 亚洲欧美日本韩国| 国产一区二区三区福利| 高清国语自产拍免费一区二区三区| 日韩精品三区四区| 国产亚洲精品久久久久久打不开 |