整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          億級流量紅包雨緩存實戰

          • 標1:掌握清理Nginx緩存
          • 目標2:掌握Lua流程控制語法
          • 目標3:能實現Nginx+Lua多級緩存控制
          • 目標4:掌握紅包雨的緩存架構設計
          • 目標5:掌握隊列溢出控制設計方案
          • 目標6:掌握隊列削峰填谷的設計思想
          • 目標7:Nginx限流

          1 Nginx緩存清理

          很多時候我們如果不想等待緩存的過期,想要主動清除緩存,可以采用第三方的緩存清除模塊清除緩存nginx_ngx_cache_purge 。安裝nginx的時候,需要添加 purge 模塊, purge 模塊我們已經下載了,在 /usr/local/server 目錄下,添加該模塊 --add-module=/usr/local/server/ngx_cache_purge-2.3/ ,這一個步驟我們在安裝 OpenRestry 的時候已經實現了。

          安裝好了后,我們配置一個清理緩存的地址:

          這里 代表的是 (/.*) 的數據。

          每次請求 $host$key 就可以刪除指定緩存,我們可以先查看緩存文件的可以:

          此時訪問 <http://192.168.211.141/purge/user/wangwu>

          此時再查看緩存文件,已經刪除了。

          參數說明:

          2 Lua高性能腳本語言

          2.1 Lua介紹

          Lua是一門以其性能著稱的腳本語言,被廣泛應用在很多方面。Lua一般用于嵌入式應用,現在越來越多應用于游戲當中,魔獸世界,憤怒的小鳥都有用到。

          優勢:

          Lua腳本的作用:嵌入到應用程序中,給應用程序提供擴展功能。

          2.2 Lua安裝

          依賴安裝: yum install libtermcap-devel ncurses-devel libevent-devel readline-devel pcre-devel gccopenssl openssl-devel

          下載安裝包: curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz

          解壓安裝: tar zxf lua-5.3.5.tar.gz

          安裝:

          此時原來系統自帶的lua其實是5.1.4,我們需要替換原來系統自帶的lua:

          查看版本

          2.3 Lua常用知識

          2.3.1 Lua案例

          創建hello.lua文件,內容為

          編輯文件hello.lua

          在文件中輸入:

          保存并退出。

          執行命令

          輸出為:

          效果如下:

          lua有交互式編程(在控制臺編寫代碼)和腳本式編程(在文件中寫代碼叫腳本編程)。

          交互式編程就是直接輸入語法,就能執行。

          腳本式編程需要編寫腳本,然后再執行命令 執行腳本才可以。

          一般采用腳本式編程。(例如:編寫一個hello.lua的文件,輸入文件內容,并執行lua hell.lua即可)

          (1)交互式編程

          Lua 提供了交互式編程模式。我們可以在命令行中輸入程序并立即查看效果。

          Lua 交互式編程模式可以通過命令 lua -i 或 lua 來啟用:

          如下圖:

          (2)腳本式編程

          我們可以將 Lua 程序代碼保持到一個以 lua 結尾的文件,并執行,該模式稱為腳本式編程,例如上面入門程序中將lua語法寫到hello.lua文件中。

          2.3.2 Lua語法

          注釋

          一行注釋:兩個減號是單行注釋:

          多行注釋

          定義變量

          全局變量,默認的情況下,定義一個變量都是全局變量,

          如果要用局部變量 需要聲明為local.例如:

          如果變量沒有初始化:則 它的值為nil 這和java中的null不同。

          如下圖案例:

          Lua中的數據類型

          Lua 是動態類型語言,變量不要類型定義,只需要為變量賦值。 值可以存儲在變量中,作為參數傳遞或結果返回。

          Lua 中有 8 個基本類型分別為:nil、boolean、number、string、userdata、function、thread 和 table。

          lua紅有個type函數,能出對象的類型。

          實例:

          2.3.3 流程控制

          if語句

          Lua if 語句 由一個布爾表達式作為條件判斷,其后緊跟其他語句組成。

          語法:

          實例:

          if..else語句

          Lua if 語句可以與 else 語句搭配使用, 在 if 條件表達式為 false 時執行 else 語句代碼塊。

          語法:

          實例:

          循環[練習]

          while循環[滿足條件就循環]

          Lua 編程語言中 while 循環語句在判斷條件為 true 時會重復執行循環體語句。 語法:

          實例:

          效果如下:

          for循環[練習]

          Lua 編程語言中 for 循環語句可以重復執行指定語句,重復次數可在 for 語句中控制。

          語法: 1->10 1:exp1 10:exp2 2:exp3:遞增的數量

          var 從 exp1 變化到 exp2,每次變化以 exp3 為步長遞增 var,并執行一次 "執行體"。exp3 是可選的,如果不指定,默認為1。

          例子:

          for i=1,9,2 :i=1從1開始循環,9循環數據到9結束,2每次遞增2

          repeat...until語句[滿足條件結束][練習]

          Lua 編程語言中 repeat...until 循環語句不同于 for 和 while循環,for 和 while 循環的條件語句在當前循環執行開始時判斷,而 repeat...until 循環的條件語句在當前循環結束后判斷。

          語法:

          案例:

          函數

          lua中也可以定義函數,類似于java中的方法。例如:

          執行之后的結果:

          ..:表示拼接

          table 是 Lua 的一種數據結構用來幫助我們創建不同的數據類型,如:數組、字典等。

          Lua也是通過table來解決模塊(module)、包(package)和對象(Object)的。

          案例:

          模塊

          (1)模塊定義

          模塊類似于一個封裝庫,從 Lua 5.1 開始,Lua 加入了標準的模塊管理機制,可以把一些公用的代碼放在一個文件里,以 API 接口的形式在其他地方調用,有利于代碼的重用和降低代碼耦合度。

          創建一個文件叫module.lua,在module.lua中創建一個獨立的模塊,代碼如下:

          由上可知,模塊的結構就是一個 table 的結構,因此可以像操作調用 table 里的元素那樣來操作調用模塊里的常量或函數。

          上面的 func2 聲明為程序塊的局部變量,即表示一個私有函數,因此是不能從外部訪問模塊里的這個私有函數,必須通過模塊里的公有函數來調用.

          (2)require 函數

          require 用于 引入其他的模塊,類似于java中的類要引用別的類的效果。

          用法:

          兩種都可以。

          我們可以將上面定義的module模塊引入使用,創建一個test_module.lua文件,代碼如下:

          2 多級緩存架構

          任何項目中我們都有一些頻繁的查詢,而那些頻繁的查詢數據基本都是相同的,比如項目中查看用戶個人信息,購物狂歡查詢活動信息,這些功能點使用頻率非常高,我們一般需要對他們做特殊處理。

          我們以狂歡活動信息為例,當雙十一的時候,很多人會去查看今天優惠活動有哪些,對這些活動信息我們可以采用多級緩存架構來抗住高并發。

          2.1 多級緩存架構對比

          基于Java版的緩存架構實現流程如下:

          優點:

          缺點:

          優點:

          2.2 Nginx+Lua多級緩存實戰

          我們以雙十一活動信息加載為例,通過多級緩存架構來加載雙十一活動信息,雙十一活動信息表結構如下:

          2.2.1 鏈接MySQL封裝

          創建一個lua腳本 mysql.lua ,用于提供根據SQL語句執行查詢操作,代碼如下:

          2.2.2 鏈接Redis集群封裝

          我們需要安裝 lua-resty-redis-cluster ,用該依賴庫實現Redis集群的操作,這里提供github地址,大家下載后按操作配置即可,下載地址: <https://github.com/cuiweixie/lua-resty-redis-cluster> ,下載該文件配置后即可實現Redis集群操作。

          接下來我們實現Redis集群操作,創建一個腳本 redis.lua ,腳本中實現Redis中數據的查詢和添加操作,代碼如下:

          2.2.3 多級緩存操作

          配置創建Nginx緩存共享空間

          創建多級緩存操作腳本 activity.lua ,代碼如下:

          Nginx配置:

          3 紅包雨案例剖析

          每次在雙十一或者618的時候,各大電商平臺為了吸引用戶,都會給與用戶很大的優惠,比如送優惠券、搶紅包等,搶優惠券或者紅包的用戶群體非常大,這時候會給服務器帶來的并發也是非常大,解決這塊問題,架構是關鍵。

          3.1 搶紅包案例分析

          雙十一搶紅包或者過年微信、QQ紅包雨活動的并發量非常龐大,紅包雨存在的規則也是非常多的,我們先來分析一下紅包雨的特點。

          3.2 紅包雨多級緩存架構設計

          上面我們已經分析過紅包雨的特點,要想實現一套高效的紅包雨系統,緩存架構是關鍵。我們根據紅包雨的特點設計了如上圖所示的紅包雨緩存架構體系。

          4 緩存隊列-并發溢出高效控制設計

          并發量非常大的系統,例如秒殺、搶紅包、搶票等操作,都是存在溢出現象,比如秒殺超賣、搶紅包超額、一票多單等溢出現象,如果采用數據庫鎖來控制溢出問題,效率非常低,在高并發場景下,很有可能直接導致數據庫崩潰,因此針對高并發場景下數據溢出解決方案我們可以采用Redis緩存提升效率。

          4.1 設計分析

          用戶搶紅包的時候,我們會分批次發放紅包,每次發放紅包會先根據發放紅包的金額和紅包的個數把每個紅包計算好,然后存入到Redis隊列中,存入到Redis隊列后,用戶每次搶紅包都直接從Redis隊列中獲取一個紅包即可,由于Redis是單線程,在這里可以有效的避免多個人同時搶到了一個紅包,類似一種超賣現象。

          表結構設計:

          4.2 紅包定時導入緩存隊列

          初始化讀取

          創建容器監聽類 com.itheima.quartz.MoneyPushTask ,讓該類實現接口 ApplicationListener ,當容器初始化完成后,會調用 onApplicationEvent 方法, 我們可以在該方法中實現初始化讀取數據,將數據填充到變量中。

          MoneyPushTask 代碼如下:

          定時加載

          定時加載我們需要開

          搶紅包緩存隊列溢出控制

          上面已經實現將紅包存入到Redis緩存隊列中,用戶每次搶紅包的時候,只需要從Redis緩存隊列中獲取即可。com.itheima.controller.RedPacketController 代碼:

          com/itheima/service/RedPacketService.java 代碼:

          com.itheima.service.impl.RedPacketServiceImpl 代碼:

          5 隊列術限流

          5.1 高并發場景分析

          用戶搶紅包的高并發場景下,如果讓后端服務器直接處理所有搶紅包操作,服務器很有可能會崩潰,就像高鐵站如果很多人蜂擁擠進站而不排隊,很有可能導致整個高鐵站秩序混亂,高鐵站服務崩潰,程序也是如此。

          解決大量并發用戶蜂擁而上的方法可以采用隊列術將用戶的請求用隊列緩存起來,后端服務從隊列緩存中有序消費,可以防止后端服務同時面臨處理大量請求。緩存用戶請求可以用RabbitMQ、Kafka、RocketMQ、ActiveMQ,我們這里采用RabbitMQ即可。

          5.2 隊列削峰實戰

          用戶搶紅包的時候,我們用Lua腳本實現將用戶搶紅包的信息以生產者角色將消息發給RabbitMQ,后端應用服務以消費者身份從RabbitMQ獲取消息并搶紅包,再將搶紅包信息以WebSocket方式通知給用戶。

          如果想使用Lua識別用戶令牌,我們需要引入 lua-resty-jwt 模塊,是用于 ngx_lua 和 LuaJIT 的 Lua 實現庫,在該模塊能實現Jwt令牌生成、Jwt令牌校驗,依賴庫的地址: https://github.com/SkyLothar/lua-resty-jwt

          lua-resty-jwt安裝

          在 資料\lua 中已經下載好了該依賴庫 lua-resty-jwt-master.zip ,我們將該庫文件上傳到服務器上,并解壓,當然,我們也可以使用opm直接安裝 lua-resty-jwt ,配置 lua-resty-jwt 之前,我們需要先安裝resty和opm。

          安裝倉庫管理工具包:

          添加倉庫地址:

          安裝resty:

          安裝opm:

          安裝Jwt組件:

          此時 lua-resty-jwt 安裝好了,可以直接使用了。

          令牌校驗庫token.lua

          搶紅包隊列腳本:mq.lua,參考地址 <https://github.com/wingify/lua-resty-rabbitmqstomp>

          搶紅包隊列腳本:mq.lua,參考地址 <https://github.com/wingify/lua-resty-rabbitmqstomp>

          Java監聽

          5.3 搶紅包測試

          打開資料中的 html/websocket.html ,執行搶紅包測試,效果如下:

          6 Nginx限流(作業)

          一般情況下,雙十一很多查詢的并發量是比較大的,即使 有了多級緩存,當用戶不停的刷新頁面的時候,也是沒有必要的,另外如果有惡意的請求 大量達到,也會對系統造成影響。而限流就是保護措施之一。

          6.1 限流理解

          限流和生活中很多現實場景類似,如下場景:

          • 水壩泄洪,通過閘口限制洪水流量(控制流量速度)。
          • 辦理銀行業務:所有人先領號,各窗口叫號處理。每個窗口處理速度根據客戶具體業務而定,所有人排隊等待叫號即可。若快下班時,告知客戶明日再來(拒絕流量)
          • 火車站排隊買票安檢,通過排隊 的方式依次放入。(緩存帶處理任務)

          而程序限流,主要是控制應對高并發和惡意操作的一種技術手段。

          6.2 Nginx限流實戰

          nginx提供兩種限流的方式:

          • 一是控制速率
          • 二是控制并發連接數

          6.2.1 速率限流

          控制速率的方式之一就是采用漏桶算法。

          (1)漏桶算法實現控制速率限流

          漏桶(Leaky Bucket)算法思路很簡單,水(請求)先進入到漏桶里,漏桶以一定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(訪問頻率超過接口響應速率),然后就拒絕請求,可以看出漏桶算法能強行限制數據的傳輸速率.示意圖如下:

          (2)nginx的配置

          配置示如下:

          創建限流緩存空間:

          配置限流:

          參數說明:

          如果惡意一直刷新會出現如下現象:

          (3)處理突發流量

          上面例子限制 2r/s,如果有時正常流量突然增大,超出的請求將被拒絕,無法處理突發流量,可以結合 burst 參數使用來解決該問題。

          例如,如下配置表示:

          burst 譯為突發、爆發,表示在超過設定的處理速率后能額外處理的請求數,當 rate=10r/s 時,將1s拆成10份,即每100ms可處理1個請求。

          此處,burst=4 ,若同時有4個請求到達,Nginx 會處理第一個請求,剩余3個請求將放入隊列,然后每隔500ms從隊列中獲取一個請求進行處理。若請求數大于4,將拒絕處理多余的請求,直接返回503.

          不過,單獨使用 burst 參數并不實用。假設 burst=50 ,rate依然為10r/s,排隊中的50個請求雖然每100ms會處理一個,但第50個請求卻需要等待 50 * 100ms即 5s,這么長的處理時間自然難以接受。

          因此,burst 往往結合 nodelay 一起使用。

          例如:如下配置:

          如上表示:

          平均每秒允許不超過2個請求,突發不超過4個請求,并且處理突發4個請求的時候,沒有延遲,等到完成之后,按照正常的速率處理。

          完整配置如下:

          6.2.2 控制并發量(連接數)

          ngx_http_limit_conn_module 提供了限制連接數的能力。主要是利用limit_conn_zone和limit_conn兩個指令。利用連接數限制 某一個用戶的ip連接的數量來控制流量。

          注意:并非所有連接都被計算在內 只有當服務器正在處理請求并且已經讀取了整個請求頭時,才會計算有效連接。此處忽略測試。

          配置語法:

          (1)配置限制固定連接數

          如下,配置如下:

          配置限流緩存空間:

          location配置:

          參數說明:

          (2)限制每個客戶端IP與服務器的連接數,同時限制與虛擬服務器的連接總數。

          限流緩存空間配置:

          location配置

          每個IP限流 3個

          總量5個

          外!CFM-Hello語音杯團隊賽開戰,就在今天!16個民間大手子隊要開始神仙打架了!比賽看個熱鬧就完事兒了,冠軍那幾萬大獎跟咱沒關系;直播間搞得抽獎活動,我等屁民還是可以去爭一爭滴,聽說發的是現金喔!


          Hello語音這次組織的比賽真是聲勢浩大,一個星期前250支隊伍打海選,打到現在剩16支隊伍爭冠;這16支隊伍的平民玩家不光過足了電競的癮,享受到比賽的刺激,還能馬上在斗魚直播里當一回明星選手,簡直不要太爽!


          Hello語音為這次團隊賽請來了CFM官方解說西瓜丶SV,另外馬上開打的個人賽請到官方解說朗大朝;都是官方解說界的大紅人了,有這倆專業解說,觀賽體驗都上了一個檔次了,這民間賽的辦賽規格怕是跟官方專業比賽有一拼噢!


          (團隊賽解說西瓜丶SV直播地址:https://www.douyu.com/5961790)


          (個人賽解說朗大朝直播地址:https://www.douyu.com/6368510)

          比賽相關的事簡單說下吧,4月8號16強賽正式開打,打到18號決冠軍,基本是晚上7點開打,具體的比賽信息hello語音里都有,要了解的朋友自己去看;這次冠軍獎金達到15000,總獎金也有40000,前8都有錢,搞得我都想組隊參加下一屆比賽了!


          (團隊賽獎勵)


          (官方地址:https://hstatic.ppx520.com/front/online/project/HYC-CFM/index.html)

          另外,直播間有抽獎的事兒已經是板上釘釘了,聽說下現金紅包雨和貴重的游戲道具;我估摸著不光是OB間隙的常規抽獎,比如高光擊殺、選手采訪啥的也會來個突擊抽獎;所以各位觀眾老爺別怪我沒提醒,好好守著比賽時間吧,搞不好準點開播先來一手抽獎!


          路人朋友的觀賽指南就這些了,下面聊聊圈內人的事兒,有興趣的朋友可以繼續閱讀。Hello語音這個比賽從海選到現在也打了一個星期了,講道理精彩程度還真不輸職業比賽;現金獎勵多的好處就是選手都特認真,而打出的高光操作都來自我們身邊的普通人,這種觀賽代入感遠比看職業比賽要強的多;說不定下一屆我們也能成為賽事的其中一員,去追逐屬于自己的電競夢!

          . 背景

          隨著公司業務的不斷擴張,用戶流量在不斷提升,研發體系的規模和復雜性也隨之增加。線上服務的穩定性也越來越重要,服務性能問題,以及容量問題也越發明顯。

          因此有必要搭建一個有效壓測系統,提供安全、高效、真實的線上全鏈路壓測服務,為線上服務保駕護航。

          關于全鏈路壓測的建設,業界已經有了非常多文章,但是涉及到具體的技術實現方面,卻很少介紹。本文想從全鏈路壓測系統,從設計到落地整個實踐過程,來詳細介紹下全鏈路壓測系統是具體是如何設計,以及如何落地的。希望能從技術落地實踐的角度,給同行業的同學一些參考和啟發。

          2. 解決方案

          2.1 業內實踐

          全鏈路壓測在業內已經有了廣泛的實踐,如阿里的 Amazon、PTS[1][2],美團的 Quake[3][4],京東的的 ForceBOT[5],高德的 TestPG[6]等等,都為我們提供豐富的實踐經驗,和大量優秀的技術方案。我們廣泛吸收了各大互聯網公司的全鏈路壓測建設經驗,并基于字節跳動業務需求,設計開發了一個全鏈路壓測系統 Rhino。

          2.1 架構圖

          Rhino 平臺作為公司級的全鏈路壓測平臺,它的目標是對全公司所有業務,提供單服務、全鏈路,安全可靠、真實、高效的壓測,來幫助業務高效便捷的完成性能測試任務,更精確評估線上服務性能&容量方面風險。

          因此在 Rhino 平臺設計之初,我們就定下以下目標:

          • 安全:所有壓測都是在線上完成的,所以理論上所有的壓測對線上用戶都是有損的。壓測平臺將從服務狀態,以及壓測數據兩方面去保證壓測的安全性。
          • 高效:較少壓測腳本編寫成本,數據構造和壓測監控成本,盡量自動化完成壓測過程的各個階段。
          • 準確:精確的壓力控制,準確的鏈路壓測監控,精確的壓測報告結果,以及性能&容量數據。
          • 高覆蓋:需要支撐公司內不同的業務線的壓測需求,如搜索,廣告,電商,教育,游戲等等。

          Rhino 是一個分布式全鏈路壓測系統,可以通過水平擴展,來實現模擬海量用戶真實的業務操作場景,對線上各種業務進行全方位的性能測試。它主要分為控制中心(Rhino Master)模塊,壓測鏈路服務模塊,監控系統模塊,壓測引擎模塊,如圖。(每一個模塊都是由多個微服務來完成的。如下圖每個實線圖都代表一個微服務或多個微服務)。

          3. 核心功能介紹

          • 搭建全鏈路壓測平臺,最核心主要有:數據構造、壓測隔離、鏈路治理、任務調度、壓測熔斷、壓測引擎、壓測監控等。下面我們將從這些方面詳細介紹下,在 Rhino 平臺中是如何設計和實現的。

          3.1 數據構造

          壓測過程中數據構造是最重要,也是最為復雜的環節。壓測數據的建模,直接影響了壓測結果的準確性。

          • 對于服務性能缺陷掃描,性能調優,以及新上線服務,推薦構造 Fake 數據,來壓測指定路徑。
          • 對于線上容量規劃,性能能力驗證,以及性能 Diff,推薦使用線上真實流量,使壓測結果更貼近真實情況。
          • 對于涉及到用戶賬號,用戶登錄態保持的情況,推薦使用壓測專屬測試賬號,避免影響線上真實用戶。

          基礎數據構造

          為了高效的構造特定的 Fake 壓測數據,Rhino 壓測平臺提供大量數據構造方式:

          • CSV 文件:按列分割數據,字段名取 CSV 文件第一行。數據讀取方式是按行遞增循環。如果一個壓測任務會拆分成多個 Job,那么數據文件也會拆分,避免 Job 之間的數據重復。
          • 自增:變量類型均為數字類型。每次發壓時+1,到最大值后從最小值循環使用。
          • 隨機:變量類型均為數字類型,每次發壓時隨機生成。
          • 常量:Constant,可自定義為任意值。

          壓測賬號

          在壓測過程中,有些壓測請求需要進行登錄,并保持會話;此外在很多壓測請求中涉及到用戶賬號信息 UserID,DeviceID 等數據。用戶賬號的構造問題,一直是壓測過程中非常棘手的問題。Rhino 平臺打通的用戶中心,設置了壓測專屬的賬號服務,完美地解決了壓測過程中的登錄態,以及測試賬號等問題。具體流程和使用界面,如下圖。

          3.2 壓測隔離

          壓測隔離中需要解決的壓測流量隔離,以及壓測數據的隔離。

          壓測流量隔離,主要是通過構建壓測環境來解決,如線下壓測環境,或泳道化/Set 化建設,將壓測流量與線上流程完全隔離。優點是壓測流量與線上流量完全隔離,不會影響到線上用戶。缺點:機器資源及維護成本高,且壓測結果需要經過一定的換算,才能得線上容量,結果準確性存在一定的問題。目前公司內壓測都是在線上集群上完成的,線上泳道化正在建設中。

          壓測數據隔離,主要是通過對壓測流量進行染色,讓線上服務能識別哪些是壓測流量,哪些是正常流量,然后對壓測流量進行特殊處理,以達到數據隔離的目的。目前 Rhino 平臺整體壓測隔離框架如圖。

          壓測標記

          壓測標記就是最常見的壓測流量染色的方式。

          • 對于 RPC 協議,會在請求的頭部中增加一個 Key:Value 的字段作為壓測標記。
          • 對于 HTTP 和其他協議,會在請求頭,自動注入一個 Stress 標記(Key-Value) 。
          • 壓測標記 Key:Value,其中 key 是固定的 Stress_Tag 值,但是每個壓測任務都有唯一的 Stress_Value 值,主要用于解決壓測數據沖突,以及性能問題定位。

          壓測標記透傳

          目前公司內各個基礎組件、存儲組件,以及 RPC 框架都已經支持了壓測標記的透傳。其原理是將壓測標記的 KV 值存入 Context 中,然后在所有下游請求中都帶上該 Context,下游服務可以根據 Context 中壓測標記完成對壓測流量的處理。在實際業務中,代碼改造也非常簡單,只需要透傳 Context 即可。

          Golang 服務: 將壓測標記寫入 Context 中。

          Python 服務:利用 threading.local()存儲線程 Context。

          Java 服務:利用 ThreadLocal 存儲線程 Context。

          壓測開關

          為了解決線上壓測安全問題,我們還引入了壓測開關組件。

          • 每個服務每個集群,都有一個壓測開關。只有打開壓測開關時,壓測流量才能流入到服務內,否則就會被底層微服務框架直接拒絕,業務層無感知。
          • 在每個 IDC 區域,都會有一個全局的壓測總開關。只有打開了這個全局壓測開關,壓測流量才被允許在這個 IDC 內流轉。
          • 當線上出現壓測問題,除了從源頭關閉壓測流量以外,關閉目標服務的壓測開關,也能立即阻斷壓測流量。

          壓測數據隔離

          線上壓測中,最復雜的問題就是壓測鏈路中涉及到寫操作,如何避免污染線上數據,并且能保證壓測請求保持和線上相同的請求路徑。業界有很多解決方案,常見的有影子表,影子庫,以及數據偏移,如圖[7]。

          Rhino 平臺針對不同存儲,有不同的解決方案:

          • MySQL、MongoDB:影子表。SDK 判斷是否是壓測流量,若是則根據配置映射至新表名。配置策略有兩種,一是讀寫影子表,二是讀線上表、寫影子表。
          • Redis:Redis Key 加上 Stress 前綴。如 Stress_Tag=Valuex,那么讀寫 Redis 的 Key=Valuex_Key。這樣可以解決多個壓測任務數據沖突的問題。壓測結束后,只需要對 Prefix=Valuex 做清除或過期操作即可。
          • MQ:對于消息隊列,Rhino 平臺有兩種策略。一是直接丟棄,然后針對消息隊列的性能,單獨進行壓測;二是在 Header 中透傳壓測標記,Consumer 根據壓測標記和業務需求,再做特殊處理。默認走丟棄策略,業務方可根據需求進行配置。
          • 其他存儲,如 ES,ClickHouse 等,都有壓測集群。壓測時,會將壓測請求打到指定的壓測集群中。

          服務壓測改造

          在壓測之前,需要對服務進行壓測驗證。對于不滿足壓測要求(即壓測數據隔離)的服務,需要進行壓測改造。

          1. 壓測驗證:對于存儲服務,在不打開壓測開關的前提下,通過壓測請求,發送讀寫操作都是會被拒絕。如果沒有拒絕,說明在操作存儲服務時,沒有帶上壓測 Context,需要進行改造。
          2. 壓測改造:壓測改造是線上全鏈路壓測推進中非常關鍵,而又非常困難的一個環節。對于已經上線的服務,壓測改造還極有可能會引入新的 BUG,所以經常推動起來比較困難。因此為了解決這些問題,Rhino 平臺有以下幾個解決方案:

          a. 盡量減少代碼改動,并給出完整的指導手冊及代碼示例,減少 RD 的工作量,降低代碼錯誤的可能性

          b. 提供簡單便捷的線上線下 HTTP&RPC 的壓測請求 Debug 工具,方便代碼改動的驗證

          c. 對于新項目,在項目開始初期,就將壓測改造加入項目開發規范中,減少后期的代碼改動

          3.3 鏈路治理

          鏈路梳理

          請求調用鏈,對于線上壓測是非常重要的:

          • 提供清晰壓測流量地圖,并提供完整的鏈路監控。
          • 完成服務依賴的梳理,檢測壓測所依賴的服務/中臺是否具備壓測的條件,是否需要壓測改造。
          • 鏈接壓測開關管理,壓測上下游周知等。

          Rhino 平臺通過公司的流式日志系統來完成調用鏈檢索的。一個服務在被請求或者請求下游時,都會透傳一個 LogID。RPC 框架會打印調用鏈日志(包括 RPC 日志-調用者日志,Access 日志-被調用者日志),所有日志中都會包含這個 LogID。通過 LogID 將一個請求所經過的所有服務日志串起來,就完成調用鏈檢索。

          Rhino 平臺在公司流式日志系統提供的鏈路梳理功能基礎上,進行了進一步優化,以滿足壓測需要:

          • 自動梳理:由于公司采用微服務架構,每個請求背后的調用鏈路及其復雜,單純靠人工維護是無法完成的。 用戶只需要提供請求中 LogID,Rhino 平臺就能快速梳理出該請求經過的服務節點,如圖。
          • 實時梳理:由于線上服務不斷在變化,上線下線新增等,因此同一個請求的調用鏈也是不斷變化的。Rhino 平臺建議一般使用 1 個小時內的 LogID 進行梳理。
          • 多調鏈路合并:同一個接口,不同參數下的調用鏈是不盡相同的。Rhino 平臺會將多個 LogID 梳理的結果自動進行合并,來補全調用鏈,保證鏈路梳理結果的準確性和完整性。

          測周知

          雖然 Rhino 平臺對于壓測有很多的安全保障措施,但是對于大型壓測,保證信息的通暢流通也是非常重要的。因此在壓測周知方面,Rhino 平臺也提供了很多解決方案:

          • 一鍵拉群:梳理完鏈路后,在壓測前可以一鍵拉群,將鏈路中上下游服務的 Owner 拉到同一個群里,同步壓測信息。
          • 壓測周知:每個壓測開始執行時,都會向壓測周知群里推送消息,如壓測 QPS,壓測時長等信息。

          • 壓測事件:在壓測開始執行時,Rhino 平臺還會向目標服務的事件隊列中發送一個壓測事件,方便快速評估/定位穩定性問題是否是壓測導致,減少 RD 線上問題排查的干擾。

          壓測開關管理

          在壓測之前,需要開啟整體鏈路的壓測開關的,否則壓測流量就會被服務拒絕,導致壓測失敗。

          • 一鍵開啟:在壓測執行之前,Rhino 平臺可以一鍵開啟鏈接上所有節點的壓測開關。
          • 壓測開關開啟周知:壓測開關開啟時,Rhino 平臺會自動給對應服務 Owner 推送相關信息,確保服務 Owner 了解相關壓測信息,上游會有壓測流量會經過其服務。
          • 靜默關閉:壓測開關到期后,Rhino 會自動靜默關閉壓測開關,以保證線上服務的安全。

          服務 Mock

          對于調用鏈中不能壓測的服務(敏感服務),或者第三方服務,為了壓測請求的完整性,就需要對這些服務進行 Mock。業界通用的 Mock 方案有:

          1. 修改業務代碼,修改服務調用為空轉代碼。優點:實現成本低。 缺點:返回值固定,代碼&業務入侵高,推動困難。如要 Mock 位置比較靠下游,超出部門覆蓋業務范圍,推動就非常麻煩。
          2. 通用 Mock 服務。通用 MockServer,會根據不同用戶配置不同 Mock 規則,執行對應的響應延時,并返回對應響應數據。優點:無代碼入侵,業務方無感知。 缺點:實現成本高。

          由于字節整個公司都采用微服務架構,導致一次壓測涉及鏈路都比較長,快速無業務入侵的 Mock

          方式成為了首選。Rhino 平臺是通過公司 Service Mesh 和 ByteMock 系統來實現了高效的,對業務透明的服務 Mock。

          壓測執行前,Rhino 平臺需要向 Service Mesh 注冊染色轉發規則,并向 Mock 服務注冊 Mock 規則。然后在壓測流量中注入 Mock 染色標記,才能完成服務 Mock:

          1. 基于 Service Mesh 的染色流量轉發。首先需要在壓測流量中注入轉發染色標記,并在 Service Mesh 中注冊對應的轉發規則。Service Mesh 檢測到染色流量后,就會將其轉發到指定的 Mock Server 上,如圖。
          2. 基于 Mock Server 的請求規則匹配。首先在 Mock Server 上注冊 Mock 規則,以及匹配的 Response 和響應時延。當 Mock Server 接收到請求后,會根據規則進行響應,如圖。

          3.4 發壓模式

          最小調度單元

          Rhino 平臺中,壓測 Agent 就是一個最小調度單元。一次壓測任務,通常會拆分成多個子 Job,然后下發到多個 Agent 上來完成。

          • 最小化容器部署,減少資源浪費。壓測對機器資源消耗是非常高的,通常 CPU &Memory 的使用率都在 80%以上。但是沒有壓測執行時間內,機器資源使用率<5%。如果長期占用大量的資源,將會對機器資源造成極大的浪費。壓測 Agent 都采用容器化部署,并且每個容器的資源規格也盡可能小,這樣既能滿足日常壓測需求,也不會占用太多的機器資源。
          • 獨占 Agent,增加壓測執行穩定性:單個容器內只啟動一個 Agent 進程,單個 Agent 同時只能被一個壓測任務占用,避免多任務多進程的干擾和資源競爭,增加壓測的穩定性。
          • 動態擴容,支撐海量 QPS 發壓:壓測高峰期,Rhino 平臺會臨時申請機器資源,快速擴容,完成海量 QPS 的支撐。壓測完成后,會立即釋放機器資源,減少資源浪費。

          2020 年春節搶紅包壓測中,Rhino 臨時擴容在 4000+個實例,支撐了單次 3kw+QPS 的壓測,但日常 Rhino 平臺只部署了 100+個實例,就能滿足日常壓測需求。

          智能壓力調節

          • 動態分配壓測 Agent:在壓測過程,經常出現壓測 Agent 的 CPU/Memory 使用率過高(>90%),導致壓力上不去,達不到目標 QPS;或者壓測延時過高,壓測結果不準確的問題。Rhino 平臺在發壓的過程中,會實時監控每個壓測 Agent 的 CPU/Memory 使用率,當超過閾值時(>90%),會動態分配額外的 Agent,以降低每個 Agent 的負載,保證壓測的穩定性。
          • 智能調節壓力:在壓測過程,通常需要不斷的調節 QPS 大小,以達到性能壓測目標。這過程非常耗費精力和時間。Rhino 平臺,可以根據壓測任務設定的性能指標,智能調節 QPS 大小,當達到壓測目標后,會自動熔斷,停止壓測。

          壓測鏈路模擬

          Rhino 平臺默認將全鏈路壓測分為公網壓測和內網壓測。公網壓測主要 IDC 網絡帶寬,延時,IDC 網關新建連接、轉發等能力;內網壓測,主要是壓測目標服務,目標集群的 性能,容量等。

          • 對于內網壓測,默認都要求同 IDC 內發壓,減少網絡延時的干擾。
          • 對于公網壓測,Rhino 平臺在公司 CDN 節點上都有部署 Agent 節點,利用了 CDN 節點剩余計算能力,完成了公網壓測能力的建設。

          同城多機房,異地多機房

          Rhino 平臺在各個 IDC 都有部署 Agent 集群。各個 IDC 內服務的壓測,默認會就近選擇壓測 Agent,來減少網絡延時對壓測結果的干擾,使得壓測結果更精準,壓測問題定位更簡單。

          邊緣計算節點 Agent

          除了多機房部署之外,Rhino 平臺還在邊緣計算節點上也部署了壓測 Agent,來模擬各種不同地域不同運營商的流量請求,確保流量來源,流量分布更貼近真實情況。在 Rhino 平臺上可以選擇不同地域不同運營商,從全國各個地區發起壓測流量。

          3.5 壓測熔斷

          為了應對線上壓測風險,Rhino 平臺提供兩種熔斷方式,來應對壓測過程中的突發事件,來降低對線上服務造成的影響。

          基于告警監控的熔斷

          每個壓測任務,都可以關聯調用鏈中任意服務的告警規則。在壓測任務執行過程,Rhino 平臺會主動監聽告警服務。 當調用鏈中有服務出現了告警,會立即停止壓測。對于沒有關聯的告警,Rhino 平臺也會記錄下來,便于壓測問題定位。

          基于 Metric 的熔斷

          自定義監控指標及閾值,到達閾值后,也會自動停止壓測。目前支持 CPU、Memory、 上游穩定性、錯誤日志,以及其他自定義指標。

          此外,除了 Rhino 平臺自身提供的熔斷機制以外,公司服務治理架構也提供了很多額外的熔斷機制,如壓測開關,一鍵切斷壓測流量;過載保護,服務過載時自動丟棄壓測流量。

          3.6 任務模型

          HTTP 任務

          對于 HTTP 協議,參考了 Postman,全部可視化操作,保證所有人都能上手操作,極大降低了壓測的使用門檻和成本。

          RPC 任務

          對于 RPC 任務,Rhino 也自動完成了對 IDL 的解析,然后轉換成 JSON 格式,便于用戶參數化處理。

          自定義-Go Plugin

          對于非 HTTP/RPC 的協議,以及有復雜邏輯的壓測任務,Rhino 平臺也提供了完善的解決方案——Go Plugin。

          Go Plugin 提供了一種方式,通過在主程序和共享庫直接定義一系列的約定或者接口,就可以動態加載其他人編譯的 Go 語言共享對象,使得主程序可以在編譯后動態加載共享庫,實現熱插拔的插件系統。此外主程序和共享庫的開發者不需要共享代碼,只要雙方的約定不變,修改共享庫后也不再需要重新編譯主程序。

          用戶只要根據規范要求,實現一段發壓業務邏輯代碼即可。Rhino 平臺可以自動拉取代碼,觸發編譯。并將編譯后的插件 SO 文件分發到多個壓測 Agent。 Agent 動態加載 SO 文件,并發運行起來,就可以達到壓測的目的。此外,Rhino 還針對常見 Go Plugin 壓測場景,建立了壓測代碼示例代碼庫。對于壓測新手,簡單修改下業務邏輯代碼,就可以完成壓測了。這樣就解決了非常見協議,以及復雜壓測場景等的壓測問題。

          3.7 壓測引擎

          單 Agent 多引擎

          壓測調度的最小單元是壓測 Agent,但是實際每個 Agent 中有掛載多種壓測引擎的,來支撐不同的壓測場景。Rhino 平臺在壓測數據和壓測引擎之間增加了一個壓測引擎適配層,實現了壓測數據與壓測引擎的解耦。壓測引擎適配層,會根據選擇不同的壓測引擎,生成不同 Schema 的壓測數據,啟用不同的引擎來完成壓測,而這些對用戶是透明的。

          壓測引擎

          在壓測引擎上,我們有開源的壓測引擎,也有自研的壓測引擎。

          開源壓測引擎的優點是維護人多,功能比較豐富,穩定且性能好,缺點就是輸入格式固定,定制難度大。此外 Agent 與開源壓測引擎之間通常是不同進程,進程通信也存在比較大的問題,不容易控制。

          自研壓測引擎,優點是和 Agent 通常運行在單進程內,比較容易控制;缺點可能就是性能稍微差一些。但是 Golang 天然支持高并發,因此自研和開源之間的性能差距并不明顯。

          • HTTP 協議:默認 Gatling ,單機發壓性能非常好,遠超于 Jmeter。對于智能壓測,或動態調節的情況,會切換到自研壓測引擎上。
          • RPC 協議:自研引擎,主要利用 Golang 協程+RPC 連接池,來完成高并發壓測。
          • GoPlugin 協議:自研引擎,利用 Golang Plugin 可動態裝載的特性,自動裝載自定義壓測插件,來完成壓測。

          3.8 壓測監控

          客戶端監控

          由于公司監控系統,最小時間粒度是 30s,30s 內的數據會聚合成一個點。這個時間粒度對于壓測來說是比較難以接受的。因此,Rhino 平臺自己搭建了一套客戶端監控系統。

          • 每個 Request 都會以請求開始時間為基準打一個點。
          • 單個 Agent 內,會將相同任務相同接口,1s 內的打點數據在本地做一次匯總,上報到 Kafka 中。
          • 監控服務會消費 Kafka 中的打點數據,將多個 Agent 上報的數據進行再次匯總,然后寫入數據庫中。
          • 前端監控報表會實時拉取數據庫中監控匯總數據,繪制實時監控曲線
          • 在監控數據匯總流程中,對于請求響應時間的 PCT99 計算,是比較難處理的:目前 Rhino 平臺采用的 T-Digest 算法來計算 1 秒內的 PCT99整個時間段內的 PCT99 的計算,則是以 PCT & AGV 的方式聚合。即單位時間內通過 T-Digest 計算 PCT99;整個時間段內的 PCT99,則是對所有點的 PCT99 取平均值。整體計算方案已與公司服務端監控算法對齊,目的是減少客戶端監控與服務端監控之間的 Gap,減少壓測結果分析的干擾因素。

          服務端監控

          服務端監控,直接接入了公司 Metric 系統。

          • 在壓測過程中,Rhino 平臺會提供整條鏈路上所有節點核心指標的監控大盤,并高亮顯示可能存在風險的節點,來提供實時預警。
          • 對于每個節點也都提供了實時的,詳細的監控曲線圖。
          • 對于每個節點默認提供 CPU、Memory、QPS 和 Error_Rate 等核心監控指標,用戶可以在 Rhino 平臺上修改監控配置,增加其他自定義監控指標。

          性能 Profile

          在壓測過程中,Rhino 平臺還可以實時采集目標服務進程的性能 Profile,并通過火焰圖的方式展示出來,方便用戶進行性能問題分析和優化,如圖。

          4. 壓測實踐

          Rhino 壓測平臺是一個面向全字節跳動公司的,為了所有研發同學提供的一站式全鏈路壓測的平臺。Rhino 平臺的研發團隊,不僅負責 Rhino 平臺的研發任務,還會配合 QA&RD 來完成公司大型項目,重點業務的性能壓測工作。

          4.1 重大項目支撐

          公司內重大項目的壓測,Rhino 平臺都會積極參與,全力支撐的。其中,比較典型的項目有抖音春晚,西瓜百萬英雄,春節紅包雨等活動。

          其中字節春節紅包雨活動,完成是由 Rhino 團隊來負責和完成的。字節春節紅包雨活動是在春節期間,所有字節客戶端發起的,諸如抽卡分現金,紅包錦鯉,紅包雨等一系列的超大規模的紅包引流活動。其流量規模巨大,流量突發性強,業務邏輯和網絡架構復雜度高等等,都對 Rhino 平臺提出不小的挑戰。

          在春節紅包雨活動中,所有用戶流量都經過運營商專線接入到網絡邊緣的匯聚機房,然后經過過濾和驗證后,再轉發到核心機房。其中各個 IDC 互為備份,其具體流量路線如圖。在這里,不僅要驗證后端各服務是否能承載預期流程,還要驗證各個專線帶寬,各個網關帶寬及轉發能力,各 IDC 承載能力以及之間帶寬等等。

          為此,我們將整個壓測拆分成多個階段,來簡化壓測復雜性,也降低壓測問題定位的難度:

          • 通過撥測/CDN 壓測來分別驗證各個匯聚機房的承載能力,帶寬,以及網關性能。
          • 在各個匯聚機房部署壓測 Agent,來模擬用戶流量分布,來壓測部署在核心機房的后端服務性能。
          • 單接口單實例壓測,單接口單機房壓測,場景化全鏈路單機房壓測,場景化全鏈路全資源壓測,分階段來驗證后端服務性能。
          • 最后會通過全網撥測,來模擬真實春節紅包雨高峰期流量,整體驗證全系統性能。

          在這些大型項目的支撐中,Rhino 團隊不僅學到了大量的業務和架構設計知識,還了解到業務研發同學如何看待壓測,如何使用平臺,幫助我們發現更多平臺的問題,促進平臺不斷迭代優化。

          4.2 日常壓測任務支撐

          日常壓測支撐,也是 Rhino 平臺非常重要的一項任務。對于日常壓測中遇到的各種問題,我們采用了各種方案來解決:

          • 專人 Oncall 值周,一對一指導。
          • 詳細完善的壓測知識庫,不僅介紹了平臺如何使用,還包括壓測如何改造,壓測方案如何制定,壓測問題如何定位。
          • 完善的性能培訓體系:定期開展性能測試相關分享,并對于 QA&RD 團隊,也會開展專業的壓測培訓。

          4.3 線上流量調度

          Rhino 平臺還實現了線上流量的定期調度,以達到線上實例自動壓測的目的[8]:

          • 將線上流量逐步調度到目標實例上,來測試服務實例性能極限,并給出實例性能 Profile,分析出實例性能瓶頸。
          • 通過長期的流量調度,來觀察服務實例性能變化,以監控服務性能的變化趨勢。
          • 通過不同資源水位下的實例性能,來預估出整個集群容量。完成對服務容量預估,以及線上風險評估。
          • 基于泳道化的流量調度,可以精確的預估服務集群容量。

          其具體實現方案如下:

          • 修改負載均衡中目標實例的權重 Weight 值,逐步調大該 Weight 值,將更多流量集中打到目標實例,直到達到設置的停止閾值。

          目前已經有 500+微服務接入,每天定時執行流量調度,來監控線上服務性能變化趨勢,如下圖。

          4.4 常態化壓測

          Rhino 平臺目前還在公司內推行常態化壓測,通過周期定時化的自動化全鏈路壓測,來實現以下目標:

          • 實時監控線上服務集群容量,防止服務性能劣化。
          • 實時監控線上鏈路容量,防止鏈路性能劣化。

          目前 Rhino 平臺上的常態化壓測,會周期定時,以無人值守的方式,自動執行壓測任務,并推送壓測結果。在壓測執行過程中,會根據調用鏈自動完成壓測開關開啟,發起壓測流量。實時監控服務性能指標,并根據 Metric 及告警監控,自動完成壓測熔斷,以保證壓測安全。

          目前已經有多個業務方接入常態化壓測,以此保證線上服務的穩定性。

          4.5 DevOps 流水線中的壓測

          服務在上線時,都會經過預發布,線上小流量灰度,線上全量發布。在這個過程中,我們可以通過線上測試 Case 以及灰度發布,來攔截服務線上功能缺陷。但是對于性能缺陷的攔截,卻不夠有效。

          從線上故障跟蹤系統里就可以發現,由于上線前沒有做好性能壓測,很多性能缺陷都逃逸到了線上。

          為了攔截各種性能缺陷,Rhino 平臺完成了 DevOps 平臺的打通。將壓測服務在 DevOps 平臺上注冊成一個原子服務 ,研發人員可以將壓測節點編排在任意流水線的任意位置,實現上線前的例行壓測。DevOps 流水線中的壓測,不僅可以幫助 RD 發現代碼中的性能問題,還能與性能基線進行 Diff,來發現代碼性能變壞的味道。

          5. 總結與展望

          5.1 總結

          Rhino 壓測平臺從立項到現在,不到兩年的時間內,其發展已經初具規模,如圖(每月壓測執行統計)。這個期間,非常非常感謝公司內所有合作團隊,尤其是架構團隊,中臺團隊對壓測平臺的支撐,沒有他們的支撐,全鏈路壓測建設是難以完成的。

          5.2 未來發展

          業務深層次定制化

          通用壓測平臺已經初步搭建完成,基本上能滿足業務線日常壓測需求。但在日常壓測支撐過程中,發現不同業務線在壓測時,但是仍然有大量的前置和后繼工作需要人工來完成。

          如何更進一步降低業務方壓測改造的成本,如何減少壓測環境數據預置成本,如何快速完成壓測數據清理,如何快速定位出性能問題等等,Rhino 壓測平臺后續將更進一步深入業務,與各大業務方開展更深入的合作,提供更深度的業務定制,為研發提效,助力業務線發展。

          壓測與容量規劃

          業務目前資源是否充足,其具體容量是多少;按照目前業務增長,其機器資源還能支撐多久?

          目前服務資源利用如何,是否可以優化,如何更進一步提升資源利用率,降低機器資源成本?

          某大型活動,需要申請多少資源?是否不需要壓測,或者自動化利用線上流量數據,或者利用日常壓測數據,就可以給出上述問題的結論?

          壓測與 SRE

          如何保證服務穩定性,如何監控服務性能劣化并及時預警,限流、超時、重試以及熔斷等服務治理措施配置是否合理?以及如何配合混沌測試進行容災演練,保證服務穩定性等等,這些 Rhino 平臺都會做更進一步探索。

          6. 招聘

          目前 Rhino 團隊還非常小,非常缺少性能測試以及后端開發相關的研發工程師,歡迎感興趣的同學來加入。簡歷投遞郵箱: tech@bytedance.com ;郵件標題: 姓名 - 工作年限 - Rhino 。

          參考文獻

          [1] http://jm.taobao.org/2017/03/30/20170330/

          [2] https://testerhome.com/topics/19493

          [3] https://tech.meituan.com/2018/09/27/quake-introduction.html

          [4] https://tech.meituan.com/2019/02/14/full-link-pressure-test-automation.html

          [5] https://www.open-open.com/lib/view/open1484317425690.html

          [6] https://www.infoq.cn/article/NvfJekpvU154pwlsCTLW

          [7] https://tech.bytedance.net/articles/3199

          [8] https://www.usenix.org/conference/osdi16/technical-sessions/presentation/veeraraghavan

          更多分享

          Fastbot:行進中的智能 Monkey

          品質優化 - 圖文詳情頁秒開實踐

          Android Camera 內存問題剖析

          字節跳動自研線上引流回放系統的架構演進


          歡迎關注「字節跳動技術團隊」


          主站蜘蛛池模板: 春暖花开亚洲性无区一区二区| 无码精品视频一区二区三区| 精品福利一区3d动漫| 内射一区二区精品视频在线观看| 色婷婷AV一区二区三区浪潮| 亚洲综合色一区二区三区小说| 国产成人一区二区三区电影网站 | 亚洲一区二区三区国产精品| 久久国产视频一区| 一本色道久久综合一区| 97精品国产一区二区三区| 免费无码一区二区三区蜜桃| 无码视频免费一区二三区| 一区二区三区国产精品| 一区二区三区免费看| 国产精品资源一区二区| 伊人久久精品无码麻豆一区| 无码少妇一区二区性色AV| 国产成人精品无码一区二区老年人| 亚洲福利一区二区三区| 国产精品揄拍一区二区久久| 国产成人一区二区三区在线观看| 久久精品国产一区二区三区不卡| 国产精品一区二区不卡| 在线精品国产一区二区| 人妻av无码一区二区三区| 久久久久一区二区三区| 3d动漫精品啪啪一区二区免费| 日本一区二区三区在线观看 | 午夜视频一区二区| 久久青青草原一区二区| 精品一区二区三区视频在线观看 | 日本一区免费电影| 波多野结衣在线观看一区二区三区| 濑亚美莉在线视频一区| 精品少妇人妻AV一区二区| 最新中文字幕一区二区乱码| 一区二区三区在线播放| 亚洲欧洲∨国产一区二区三区| 亚洲一区免费观看| 国产综合精品一区二区|