<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從橫向和縱向兩個維度尋求復(fù)雜問題的答案

          共 21610字,需瀏覽 44分鐘

           ·

          2021-08-22 01:04


          JAVA前線 


          歡迎大家關(guān)注公眾號「JAVA前線」查看更多精彩分享,主要內(nèi)容包括源碼分析、實際應(yīng)用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學(xué)習(xí)



          1 多維度思維

          在知乎上看到了這個有意思的問題:一頭牛重800公斤,一座橋承重700公斤,牛應(yīng)該怎么過橋。初看題目我們不難得出兩個方案:橋梁加固、等待牛體重降至700公斤。

          這兩個方案顯然是正確的,但是我們不能就此止步。因為這類問題考察的是思維方法論,直接給出答案反而非最重要,對于這個問題我們可以從合理性、結(jié)構(gòu)化、可行性三個維度進行分析。


          1.1 合理性分析

          一頭800公斤的牛要通過承重700公斤的橋,這個需求本身合理嗎?我們可以從必要性、緊迫性、替代性這三個維度提出三個問題:

          第一個問題問必要性:牛為什么要過橋,到底什么事情非要過橋不可

          第二個問題問緊迫性:如果非要過橋,那么這個過橋的需求是否緊急

          第三個問題問替代性:有沒有什么替代方案,是否可以坐船或者繞路走


          1.2 結(jié)構(gòu)化分析

          如果經(jīng)過討論結(jié)果是牛非過橋不可,那么我們再思考牛怎么過橋的方案。這里可以使用結(jié)構(gòu)化思維,將大問題拆分為小維度,盡量做到不遺漏和不重復(fù)。影響過橋的因素有這幾個維度:橋的維度、牛的維度、資源維度、環(huán)境維度。

          橋的維度:加固橋使承重大于800公斤

          牛的維度:等待牛的體重小于700公斤

          資源維度:使用一臺吊機把牛運過去

          環(huán)境維度:取消環(huán)境重力


          1.3 可行性分析

          我們從橋的維度、牛的維度、資源維度、環(huán)境維度給出了方案,那么選擇哪個方案呢?這就需要我們進行可行性評估,因時因地在資源制約下選擇當前最合適的方案。

          加固橋方案經(jīng)濟成本較高,等待牛的體重小于700公斤時間成本較高,取消環(huán)境重力技術(shù)難度較高,所以使用一臺吊機把牛運過去這個方案目前看來最合適。


          1.4 多維度思考

          經(jīng)過我們從合理性、結(jié)構(gòu)化、可行性三個維度梳理之后,雖然答案沒有什么新穎之處,但是思維過程很清晰,思考方法也可以應(yīng)用在其它問題。之所以思維過程清晰,是因為我們沒有一上來直接給出答案,而是從多個維度對為題進行了分析,所以增加維度可以使思考過程更清晰。


          2 縱向思維與橫向思維

          思考維度可以從多方面進行充實,其中最常見的是增加橫向和縱向兩個維度,本文也著重討論兩個維度??傮w而言,橫向擴展的是思考廣度,縱向擴展的是思考深度,而應(yīng)用在不同場景中細節(jié)又各有不同。


          2.1 時間管理四象限

          時間管理理論四象限法則根據(jù)重要和緊急兩個維度,建立了一個四象限坐標,可以幫助我們解決主次不分的問題。我們分配工作時間時可以結(jié)合四象限法則,重要且緊急的任務(wù)優(yōu)先級最高,而不要急于處理不重要且不緊急的任務(wù)。



          2.2 金字塔原理

          金字塔思維的核心思想并不復(fù)雜:一件事情可以總結(jié)出一個中心思想,這個中心思想可以由三至七個論點支持,每個論點再可以由三至七個論據(jù)支持,基本結(jié)構(gòu)如下圖:



          金字塔原理內(nèi)在結(jié)構(gòu)可以從縱向和橫向兩個維度分析,縱向結(jié)構(gòu)體現(xiàn)了結(jié)論先行和以上統(tǒng)下原則,橫向結(jié)構(gòu)體現(xiàn)了歸類分組和邏輯遞進原則。關(guān)于金字塔原理詳細分析請參看我的文章:結(jié)構(gòu)化思維如何指導(dǎo)技術(shù)系統(tǒng)優(yōu)化。

          文章分析到這里,我們發(fā)現(xiàn)縱向和橫向思維有助于厘清思路和增加條理性,下面我們看看縱向和橫向思維怎樣幫助程序員處理復(fù)雜問題。


          3 架構(gòu)設(shè)計如何應(yīng)用縱橫思維

          我們分析一個創(chuàng)建訂單業(yè)務(wù)場景,當前有ABC三種訂單類型,A類型訂單價格9折,物流最大重量不能超過8公斤,不支持退款。B類型訂單價格8折,物流最大重量不能超過5公斤,支持退款。C類型訂單價格7折,物流最大重量不能超過1公斤,支持退款。按照需求字面含義平鋪直敘地寫代碼也并不難:

          public class OrderServiceImpl implements OrderService {

              @Resource
              private OrderMapper orderMapper;

              @Override
              public void createOrder(OrderBO orderBO) {
                  if (null == orderBO) {
                      throw new RuntimeException("參數(shù)異常");
                  }
                  if (OrderTypeEnum.isNotValid(orderBO.getType())) {
                      throw new RuntimeException("參數(shù)異常");
                  }
                  // A類型訂單
                  if (OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType())) {
                      orderBO.setPrice(orderBO.getPrice() * 0.9);
                      if (orderBO.getWeight() > 9) {
                          throw new RuntimeException("超過物流最大重量");
                      }
                      orderBO.setRefundSupport(Boolean.FALSE);
                  }
                  // B類型訂單
                  else if (OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType())) {
                      orderBO.setPrice(orderBO.getPrice() * 0.8);
                      if (orderBO.getWeight() > 8) {
                          throw new RuntimeException("超過物流最大重量");
                      }
                      orderBO.setRefundSupport(Boolean.TRUE);
                  }
                  // C類型訂單
                  else if (OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType())) {
                      orderBO.setPrice(orderBO.getPrice() * 0.7);
                      if (orderBO.getWeight() > 7) {
                          throw new RuntimeException("超過物流最大重量");
                      }
                      orderBO.setRefundSupport(Boolean.TRUE);
                  }
                  // 保存數(shù)據(jù)
                  OrderDO orderDO = new OrderDO();
                  BeanUtils.copyProperties(orderBO, orderDO);
                  orderMapper.insert(orderDO);
              }
          }

          上述代碼從功能上完全可以實現(xiàn)業(yè)務(wù)需求,但是程序員不僅要滿足功能,還需要思考代碼的可維護性。如果新增一種訂單類型,或者新增一個訂單屬性處理邏輯,那么我們就要在上述邏輯中新增代碼,如果處理不慎就會影響原有邏輯。

          為了避免牽一發(fā)而動全身這種情況,設(shè)計模式中的開閉原則要求我們面向新增開放,面向修改關(guān)閉,我認為這是設(shè)計模式中最重要的一條原則:

          當需求變化時通過擴展而不是通過修改已有代碼來實現(xiàn)變化,這樣就保證代碼穩(wěn)定性。擴展也不是隨意擴展,因為事先定義了算法,擴展也是根據(jù)算法擴展,用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié)。標準意義的二十三種設(shè)計模式說到底最終都是在遵循開閉原則

          如何改變平鋪直敘的思考方式?這就要為問題分析加上縱向和橫向兩個維度,我選擇使用分析矩陣方法,其中縱向表示策略,橫向表示場景。



          3.1 縱向做隔離

          縱向維度表示策略,不同策略在邏輯上和業(yè)務(wù)上應(yīng)該是隔離的,本實例包括優(yōu)惠策略、物流策略和退款策略,策略作為抽象,不同訂單類型去擴展這個抽象,策略模式非常適合這種場景。

          3.1.1 優(yōu)惠策略

          // 優(yōu)惠策略
          public interface DiscountStrategy {
              public void discount(OrderBO orderBO);
          }

          // A類型訂單優(yōu)惠策略
          @Component
          public class TypeADiscountStrategy implements DiscountStrategy {

              @Override
              public void discount(OrderBO orderBO) {
                  orderBO.setPrice(orderBO.getPrice() * 0.9);
              }
          }

          // A類型訂單優(yōu)惠策略
          @Component
          public class TypeBDiscountStrategy implements DiscountStrategy {

              @Override
              public void discount(OrderBO orderBO) {
                  orderBO.setPrice(orderBO.getPrice() * 0.8);
              }
          }

          // A類型訂單優(yōu)惠策略
          @Component
          public class TypeCDiscountStrategy implements DiscountStrategy {

              @Override
              public void discount(OrderBO orderBO) {
                  orderBO.setPrice(orderBO.getPrice() * 0.7);
              }
          }

          // 優(yōu)惠策略工廠
          @Component
          public class DiscountStrategyFactory implements InitializingBean {
              private Map<String, DiscountStrategy> strategyMap = new HashMap<>();

              @Resource
              private TypeADiscountStrategy typeADiscountStrategy;
              @Resource
              private TypeBDiscountStrategy typeBDiscountStrategy;
              @Resource
              private TypeCDiscountStrategy typeCDiscountStrategy;

              public DiscountStrategy getStrategy(String type) {
                  return strategyMap.get(type);
              }

              @Override
              public void afterPropertiesSet() throws Exception {
                  strategyMap.put(OrderTypeEnum.A_TYPE.getCode(), typeADiscountStrategy);
                  strategyMap.put(OrderTypeEnum.B_TYPE.getCode(), typeBDiscountStrategy);
                  strategyMap.put(OrderTypeEnum.C_TYPE.getCode(), typeCDiscountStrategy);
              }
          }

          // 優(yōu)惠策略執(zhí)行器
          @Component
          public class DiscountStrategyExecutor {
              private DiscountStrategyFactory discountStrategyFactory;

              public void discount(OrderBO orderBO) {
                  DiscountStrategy discountStrategy = discountStrategyFactory.getStrategy(orderBO.getType());
                  if (null == discountStrategy) {
                      throw new RuntimeException("無優(yōu)惠策略");
                  }
                  discountStrategy.discount(orderBO);
              }
          }

          3.1.2 物流策略

          // 物流策略
          public interface ExpressStrategy {
              public void weighing(OrderBO orderBO);
          }

          // A類型訂單物流策略
          @Component
          public class TypeAExpressStrategy implements ExpressStrategy {

              @Override
              public void weighing(OrderBO orderBO) {
                  if (orderBO.getWeight() > 9) {
                      throw new RuntimeException("超過物流最大重量");
                  }
              }
          }

          // B類型訂單物流策略
          @Component
          public class TypeBExpressStrategy implements ExpressStrategy {

              @Override
              public void weighing(OrderBO orderBO) {
                  if (orderBO.getWeight() > 8) {
                      throw new RuntimeException("超過物流最大重量");
                  }
              }
          }

          // C類型訂單物流策略
          @Component
          public class TypeCExpressStrategy implements ExpressStrategy {

              @Override
              public void weighing(OrderBO orderBO) {
                  if (orderBO.getWeight() > 7) {
                      throw new RuntimeException("超過物流最大重量");
                  }
              }
          }

          // 物流策略工廠
          @Component
          public class ExpressStrategyFactory implements InitializingBean {
              private Map<String, ExpressStrategy> strategyMap = new HashMap<>();

              @Resource
              private TypeAExpressStrategy typeAExpressStrategy;
              @Resource
              private TypeBExpressStrategy typeBExpressStrategy;
              @Resource
              private TypeCExpressStrategy typeCExpressStrategy;

              @Override
              public void afterPropertiesSet() throws Exception {
                  strategyMap.put(OrderTypeEnum.A_TYPE.getCode(), typeAExpressStrategy);
                  strategyMap.put(OrderTypeEnum.B_TYPE.getCode(), typeBExpressStrategy);
                  strategyMap.put(OrderTypeEnum.C_TYPE.getCode(), typeCExpressStrategy);
              }

              public ExpressStrategy getStrategy(String type) {
                  return strategyMap.get(type);
              }
          }

          // 物流策略執(zhí)行器
          @Component
          public class ExpressStrategyExecutor {
              private ExpressStrategyFactory expressStrategyFactory;

              public void weighing(OrderBO orderBO) {
                  ExpressStrategy expressStrategy = expressStrategyFactory.getStrategy(orderBO.getType());
                  if (null == expressStrategy) {
                      throw new RuntimeException("無物流策略");
                  }
                  expressStrategy.weighing(orderBO);
              }
          }

          3.1.3 退款策略

          // 退款策略
          public interface RefundStrategy {
              public void supportRefund(OrderBO orderBO);
          }

          // A類型訂單退款策略
          @Component
          public class TypeARefundStrategy implements RefundStrategy {

              @Override
              public void supportRefund(OrderBO orderBO) {
                  orderBO.setRefundSupport(Boolean.FALSE);
              }
          }

          // B類型訂單退款策略
          @Component
          public class TypeBRefundStrategy implements RefundStrategy {

              @Override
              public void supportRefund(OrderBO orderBO) {
                  orderBO.setRefundSupport(Boolean.TRUE);
              }
          }

          // C類型訂單退款策略
          @Component
          public class TypeCRefundStrategy implements RefundStrategy {

              @Override
              public void supportRefund(OrderBO orderBO) {
                  orderBO.setRefundSupport(Boolean.TRUE);
              }
          }

          // 退款策略工廠
          @Component
          public class RefundStrategyFactory implements InitializingBean {
              private Map<String, RefundStrategy> strategyMap = new HashMap<>();

              @Resource
              private TypeARefundStrategy typeARefundStrategy;
              @Resource
              private TypeBRefundStrategy typeBRefundStrategy;
              @Resource
              private TypeCRefundStrategy typeCRefundStrategy;

              @Override
              public void afterPropertiesSet() throws Exception {
                  strategyMap.put(OrderTypeEnum.A_TYPE.getCode(), typeARefundStrategy);
                  strategyMap.put(OrderTypeEnum.B_TYPE.getCode(), typeBRefundStrategy);
                  strategyMap.put(OrderTypeEnum.C_TYPE.getCode(), typeCRefundStrategy);
              }

              public RefundStrategy getStrategy(String type) {
                  return strategyMap.get(type);
              }
          }

          // 退款策略執(zhí)行器
          @Component
          public class RefundStrategyExecutor {
              private RefundStrategyFactory refundStrategyFactory;

              public void supportRefund(OrderBO orderBO) {
                  RefundStrategy refundStrategy = refundStrategyFactory.getStrategy(orderBO.getType());
                  if (null == refundStrategy) {
                      throw new RuntimeException("無退款策略");
                  }
                  refundStrategy.supportRefund(orderBO);
              }
          }

          3.2 橫向做編排

          橫向維度表示場景,一種訂單類型在廣義上可以認為是一種業(yè)務(wù)場景,在場景中將獨立的策略進行串聯(lián),模板方法設(shè)計模式適用于這種場景。

          模板方法模式定義一個操作中的算法骨架,一般使用抽象類定義算法骨架。抽象類同時定義一些抽象方法,這些抽象方法延遲到子類實現(xiàn),這樣子類不僅遵守了算法骨架約定,也實現(xiàn)了自己的算法。既保證了規(guī)約也兼顧靈活性。這就是用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié)。

          // 創(chuàng)建訂單服務(wù)
          public interface CreateOrderService {
              public void createOrder(OrderBO orderBO);
          }

          // 抽象創(chuàng)建訂單流程
          public abstract class AbstractCreateOrderFlow {

              @Resource
              private OrderMapper orderMapper;

              public void createOrder(OrderBO orderBO) {
                  // 參數(shù)校驗
                  if (null == orderBO) {
                      throw new RuntimeException("參數(shù)異常");
                  }
                  if (OrderTypeEnum.isNotValid(orderBO.getType())) {
                      throw new RuntimeException("參數(shù)異常");
                  }
                  // 計算優(yōu)惠
                  discount(orderBO);
                  // 計算重量
                  weighing(orderBO);
                  // 退款支持
                  supportRefund(orderBO);
                  // 保存數(shù)據(jù)
                  OrderDO orderDO = new OrderDO();
                  BeanUtils.copyProperties(orderBO, orderDO);
                  orderMapper.insert(orderDO);
              }

              public abstract void discount(OrderBO orderBO);

              public abstract void weighing(OrderBO orderBO);

              public abstract void supportRefund(OrderBO orderBO);
          }

          // 實現(xiàn)創(chuàng)建訂單流程
          @Service
          public class CreateOrderFlow extends AbstractCreateOrderFlow {

              @Resource
              private DiscountStrategyExecutor discountStrategyExecutor;
              @Resource
              private ExpressStrategyExecutor expressStrategyExecutor;
              @Resource
              private RefundStrategyExecutor refundStrategyExecutor;

              @Override
              public void discount(OrderBO orderBO) {
                  discountStrategyExecutor.discount(orderBO);
              }

              @Override
              public void weighing(OrderBO orderBO) {
                  expressStrategyExecutor.weighing(orderBO);
              }

              @Override
              public void supportRefund(OrderBO orderBO) {
                  refundStrategyExecutor.supportRefund(orderBO);
              }
          }

          3.3 復(fù)雜架構(gòu)設(shè)計

          上述實例業(yè)務(wù)和代碼并不復(fù)雜,其實復(fù)雜業(yè)務(wù)場景也不過是簡單場景的疊加、組合和交織,無外乎也是通過縱向做隔離、橫向做編排尋求答案。



          縱向維度抽象出能力池這個概念,能力池中包含許多能力,不同的能力按照不同業(yè)務(wù)維度聚合,例如優(yōu)惠能力池,物流能力池,退款能力池。我們可以看到兩種程度的隔離性,能力池之間相互隔離,能力之間也相互隔離。

          橫向維度將能力從能力池選出來,按照業(yè)務(wù)需求串聯(lián)在一起,形成不同業(yè)務(wù)流程。因為能力可以任意組合,所以體現(xiàn)了很強的靈活性。除此之外,不同能力既可以串行執(zhí)行,如果不同能力之間沒有依賴關(guān)系,也可以如同流程Y一樣并行執(zhí)行,提升執(zhí)行效率。


          4 數(shù)據(jù)分片如何應(yīng)用縱橫思維

          現(xiàn)在有一個電商數(shù)據(jù)庫存放訂單、商品、支付三張業(yè)務(wù)表。隨著業(yè)務(wù)量越來越大,這三張業(yè)務(wù)數(shù)據(jù)表也越來越大,查詢性能顯著降低,數(shù)據(jù)拆分勢在必行。那么數(shù)據(jù)拆分也可以從縱向和橫向兩個維度進行。


          4.1 縱向分表

          縱向拆分就是按照業(yè)務(wù)拆分,我們將電商數(shù)據(jù)庫拆分成三個庫,訂單庫、商品庫。支付庫,訂單表在訂單庫,商品表在商品庫,支付表在支付庫。這樣每個庫只需要存儲本業(yè)務(wù)數(shù)據(jù),物理隔離不會互相影響。



          4.2 橫向分表

          按照縱向拆分方案之后我們已經(jīng)有三個庫了,平穩(wěn)運行了一段時間。但是隨著業(yè)務(wù)增長,每個單庫單表的數(shù)據(jù)量也越來越大,逐漸到達瓶頸。

          這時我們就要對數(shù)據(jù)表進行橫向拆分,所謂橫向拆分就是根據(jù)某種規(guī)則將單庫單表數(shù)據(jù)分散到多庫多表,從而減小單庫單表的壓力。

          橫向拆分策略有很多方案,最重要的一點是選好ShardingKey,也就是按照哪一列進行拆分,怎么分取決于我們訪問數(shù)據(jù)的方式。


          4.2.1 范圍分片

          如果我們選擇的ShardingKey是訂單創(chuàng)建時間,那么分片策略是拆分四個數(shù)據(jù)庫分別存儲每季度數(shù)據(jù),每個庫包含三張表分別存儲每個月數(shù)據(jù):



          這個方案的優(yōu)點是對范圍查詢比較友好,例如我們需要統(tǒng)計第一季度的相關(guān)數(shù)據(jù),查詢條件直接輸入時間范圍即可。這個方案的問題是容易產(chǎn)生熱點數(shù)據(jù)。例如雙11當天下單量特別大,就會導(dǎo)致11月這張表數(shù)據(jù)量特別大從而造成訪問壓力。


          4.2.2 查表分片

          查表法是根據(jù)一張路由表決定ShardingKey路由到哪一張表,每次路由時首先到路由表里查到分片信息,再到這個分片去取數(shù)據(jù)。我們分析一個查表法思想應(yīng)用實際案例。

          Redis官方在3.0版本之后提供了官方集群方案RedisCluster,其中引入了哈希槽(slot)這個概念。一個集群固定有16384個槽,在集群初始化時這些槽會平均分配到Redis集群節(jié)點上。每個key請求最終落到哪個槽計算公式是固定的:

          SLOT = CRC16(key) mod 16384

          一個key請求過來怎么知道去哪臺Redis節(jié)點獲取數(shù)據(jù)?這就要用到查表法思想:

          (1) 客戶端連接任意一臺Redis節(jié)點,假設(shè)隨機訪問到節(jié)點A
          (2) 節(jié)點A根據(jù)key計算出slot值
          (3) 每個節(jié)點都維護著slot和節(jié)點映射關(guān)系表
          (4) 如果節(jié)點A查表發(fā)現(xiàn)該slot在本節(jié)點,直接返回數(shù)據(jù)給客戶端
          (5) 如果節(jié)點A查表發(fā)現(xiàn)該slot不在本節(jié)點,返回給客戶端一個重定向命令,告訴客戶端應(yīng)該去哪個節(jié)點請求這個key的數(shù)據(jù)
          (6) 客戶端向正確節(jié)點發(fā)起連接請求

          查表法方案優(yōu)點是可以靈活制定路由策略,如果我們發(fā)現(xiàn)有的分片已經(jīng)成為熱點則修改路由策略。缺點是多一次查詢路由表操作增加耗時,而且路由表如果是單點也可能會有單點問題。


          4.2.3 哈希分片

          相較于范圍分片,哈希分片可以較為均勻?qū)?shù)據(jù)分散在數(shù)據(jù)庫中。我們現(xiàn)在將訂單庫拆分為4個庫編號為[0,3],每個庫包含3張表編號為[0,2],如下圖如所示:



          我們選擇使用orderId作為ShardingKey,那么orderId=100這個訂單會保存在哪張表?因為是分庫分表,第一步確定路由到哪一個庫,取模計算結(jié)果表示庫表序號:

          db_index = 100 % 4 = 0

          第二步確定路由到哪一張表:

          table_index = 100 % 3 = 1

          第三步數(shù)據(jù)路由到0號庫1號表:


          在實際開發(fā)中路由邏輯并不需要我們手動實現(xiàn),因為有許多開源框架通過配置就可以實現(xiàn)路由功能,例如ShardingSphere、TDDL框架等等。


          5 文章總結(jié)

          復(fù)雜問題不過是簡單問題的疊加、組合和交織,橫向和縱向兩個維度拆分問題不失為一種好方法。縱向做隔離是指將不同業(yè)務(wù)形態(tài)進行隔離,能力池之間進行隔離,能力之間也進行隔離。橫向做編排是指從能力池中靈活選擇出能力,進行組合和編排,形成形態(tài)各異的業(yè)務(wù)流程,希望本文對大家有所幫助。



          JAVA前線 


          歡迎大家關(guān)注公眾號「JAVA前線」查看更多精彩分享,主要內(nèi)容包括源碼分析、實際應(yīng)用、架構(gòu)思維、職場分享、產(chǎn)品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學(xué)習(xí)


          瀏覽 283
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码内射国产 | 蜜桃人妻无码 | 国产一级片免费在线观看 | 亚州午夜双飞 | 成人毛片18女人在线播放 |