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

微服務(wù)架構(gòu)下的熔斷框架:Hystrix-Go

開發(fā) 架構(gòu)
伴隨著微服務(wù)架構(gòu)被宣傳得如火如茶,一些概念也被推到了我們的面前。一提到微服務(wù),就離不開這幾個(gè)字:高內(nèi)聚低耦合;微服務(wù)的架構(gòu)設(shè)計(jì)最終目的也就是實(shí)現(xiàn)這幾個(gè)字。

[[421890]]

本文轉(zhuǎn)載自微信公眾號(hào)「Golang夢工廠」,作者AsongGo 。轉(zhuǎn)載本文請(qǐng)聯(lián)系Golang夢工廠公眾號(hào)。

背景

伴隨著微服務(wù)架構(gòu)被宣傳得如火如茶,一些概念也被推到了我們的面前。一提到微服務(wù),就離不開這幾個(gè)字:高內(nèi)聚低耦合;微服務(wù)的架構(gòu)設(shè)計(jì)最終目的也就是實(shí)現(xiàn)這幾個(gè)字。在微服務(wù)架構(gòu)中,微服務(wù)就是完成一個(gè)單一的業(yè)務(wù)功能,每個(gè)微服務(wù)可以獨(dú)立演進(jìn),一個(gè)應(yīng)用可能會(huì)有多個(gè)微服務(wù)組成,微服務(wù)之間的數(shù)據(jù)交可以通過遠(yuǎn)程調(diào)用來完成,這樣在一個(gè)微服務(wù)架構(gòu)下就會(huì)形成這樣的依賴關(guān)系:

微服務(wù)A調(diào)用微服務(wù)C、D,微服務(wù)B又依賴微服務(wù)B、E,微服務(wù)D依賴于服務(wù)F,這只是一個(gè)簡單的小例子,實(shí)際業(yè)務(wù)中服務(wù)之間的依賴關(guān)系比這還復(fù)雜,這樣在調(diào)用鏈路上如果某個(gè)微服務(wù)的調(diào)用響應(yīng)時(shí)間過長或者不可用,那么對(duì)上游服務(wù)(按調(diào)用關(guān)系命名)的調(diào)用就會(huì)占用越來越多的系統(tǒng)資源,進(jìn)而引起系統(tǒng)崩潰,這就是微服務(wù)的雪蹦效應(yīng)。

為了解決微服務(wù)的雪蹦效應(yīng),提出來使用熔斷機(jī)制為微服務(wù)鏈路提供保護(hù)機(jī)制。熔斷機(jī)制大家應(yīng)該都不陌生,電路的中保險(xiǎn)絲就是一種熔斷機(jī)制,在微服務(wù)中的熔斷機(jī)制是什么樣的呢?

當(dāng)鏈路中的某個(gè)微服務(wù)不可用或者響應(yīng)的時(shí)間太長時(shí),會(huì)進(jìn)行服務(wù)的降級(jí),進(jìn)而熔斷該節(jié)點(diǎn)微服務(wù)的調(diào)用,快速返回錯(cuò)誤的響應(yīng)信息,當(dāng)檢測到該節(jié)點(diǎn)微服務(wù)調(diào)用響應(yīng)正常后,恢復(fù)調(diào)用鏈路。

本文我們就介紹一個(gè)開源熔斷框架:hystrix-go。

熔斷框架(hystrix-go)

Hystrix是一個(gè)延遲和容錯(cuò)庫,旨在隔離對(duì)遠(yuǎn)程系統(tǒng)、服務(wù)和第三方服務(wù)的訪問點(diǎn),停止級(jí)聯(lián)故障并在故障不可避免的復(fù)雜分布式系統(tǒng)中實(shí)現(xiàn)彈性。hystrix-go 旨在允許 Go 程序員輕松構(gòu)建具有與基于 Java 的 Hystrix 庫類似的執(zhí)行語義的應(yīng)用程序。所以本文就從使用開始到源碼分析一下hystrix-go。

快速安裝

  1. go get -u github.com/afex/hystrix-go/hystrix 

快速使用

hystrix-go真的是開箱即用,使用還是比較簡單的,主要分為兩個(gè)步驟:

  • 配置熔斷規(guī)則,否則將使用默認(rèn)配置。可以調(diào)用的方法
  1. func Configure(cmds map[string]CommandConfig)  
  2. func ConfigureCommand(name string, config CommandConfig) 

Configure方法內(nèi)部也是調(diào)用的ConfigureCommand方法,就是傳參數(shù)不一樣,根據(jù)自己的代碼風(fēng)格選擇。

  • 定義依賴于外部系統(tǒng)的應(yīng)用程序邏輯 - runFunc 和服務(wù)中斷期間執(zhí)行的邏輯代碼 - fallbackFunc,可以調(diào)用的方法:
  1. func Go(name string, run runFunc, fallback fallbackFunc) // 內(nèi)部調(diào)用Goc方法 
  2. func GoC(ctx context.Context, name string, run runFuncC, fallback fallbackFuncC)  
  3. func Do(name string, run runFunc, fallback fallbackFunc) // 內(nèi)部調(diào)用的是Doc方法 
  4. func DoC(ctx context.Context, name string, run runFuncC, fallback fallbackFuncC) // 內(nèi)部調(diào)用Goc方法,處理了異步過程 

Go和Do的區(qū)別在于異步還是同步,Do方法在調(diào)用Doc方法內(nèi)處理了異步過程,他們最終都是調(diào)用的Goc方法。后面我們進(jìn)行分析。

舉一個(gè)例子:我們在Gin框架上加一個(gè)接口級(jí)的熔斷中間件

  1. // 代碼已上傳github: 文末查看地址 
  2. var CircuitBreakerName = "api_%s_circuit_breaker" 
  3. func CircuitBreakerWrapper(ctx *gin.Context){ 
  4.  name := fmt.Sprintf(CircuitBreakerName,ctx.Request.URL) 
  5.  hystrix.Do(name, func() error { 
  6.   ctx.Next() 
  7.   code := ctx.Writer.Status() 
  8.   if code != http.StatusOK{ 
  9.    return errors.New(fmt.Sprintf("status code %d", code)) 
  10.   } 
  11.   return nil 
  12.  
  13.  }, func(err error) error { 
  14.   if err != nil{ 
  15.    // 監(jiān)控上報(bào)(未實(shí)現(xiàn)) 
  16.    _, _ = io.WriteString(f, fmt.Sprintf("circuitBreaker and err is %s\n",err.Error())) //寫入文件(字符串) 
  17.    fmt.Printf("circuitBreaker and err is %s\n",err.Error()) 
  18.    // 返回熔斷錯(cuò)誤 
  19.    ctx.JSON(http.StatusServiceUnavailable,gin.H{ 
  20.     "msg": err.Error(), 
  21.    }) 
  22.   } 
  23.   return nil 
  24.  }) 
  25.  
  26. func init()  { 
  27.  hystrix.ConfigureCommand(CircuitBreakerName,hystrix.CommandConfig{ 
  28.   Timeout:                int(3*time.Second), // 執(zhí)行command的超時(shí)時(shí)間為3s 
  29.   MaxConcurrentRequests:  10, // command的最大并發(fā)量 
  30.   RequestVolumeThreshold: 100, // 統(tǒng)計(jì)窗口10s內(nèi)的請(qǐng)求數(shù)量,達(dá)到這個(gè)請(qǐng)求數(shù)量后才去判斷是否要開啟熔斷 
  31.   SleepWindow:            int(2 * time.Second), // 當(dāng)熔斷器被打開后,SleepWindow的時(shí)間就是控制過多久后去嘗試服務(wù)是否可用了 
  32.   ErrorPercentThreshold:  20, // 錯(cuò)誤百分比,請(qǐng)求數(shù)量大于等于RequestVolumeThreshold并且錯(cuò)誤率到達(dá)這個(gè)百分比后就會(huì)啟動(dòng)熔斷 
  33.  }) 
  34.  if checkFileIsExist(filename) { //如果文件存在 
  35.   f, errfile = os.OpenFile(filename, os.O_APPEND, 0666) //打開文件 
  36.  } else { 
  37.   f, errfile = os.Create(filename) //創(chuàng)建文件 
  38.  } 
  39.  
  40.  
  41. func main()  { 
  42.  defer f.Close() 
  43.  hystrixStreamHandler := hystrix.NewStreamHandler() 
  44.  hystrixStreamHandler.Start() 
  45.  go http.ListenAndServe(net.JoinHostPort("""81"), hystrixStreamHandler) 
  46.  r := gin.Default() 
  47.  r.GET("/api/ping/baidu", func(c *gin.Context) { 
  48.   _, err := http.Get("https://www.baidu.com"
  49.   if err != nil { 
  50.    c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()}) 
  51.    return 
  52.   } 
  53.   c.JSON(http.StatusOK, gin.H{"msg""success"}) 
  54.  }, CircuitBreakerWrapper) 
  55.  r.Run()  // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080"
  56.  
  57. func checkFileIsExist(filename string) bool { 
  58.  if _, err := os.Stat(filename); os.IsNotExist(err) { 
  59.   return false 
  60.  } 
  61.  return true 

指令:wrk -t100 -c100 -d1s http://127.0.0.1:8080/api/ping/baidu

運(yùn)行結(jié)果:

  1. circuitBreaker and err is status code 500 
  2. circuitBreaker and err is status code 500 
  3. .....  
  4. circuitBreaker and err is hystrix: max concurrency 
  5. circuitBreaker and err is hystrix: max concurrency 
  6. ..... 
  7. circuitBreaker and err is hystrix: circuit open 
  8. circuitBreaker and err is hystrix: circuit open 
  9. ..... 

對(duì)錯(cuò)誤進(jìn)行分析:

  • circuitBreaker and err is status code 500:因?yàn)槲覀冴P(guān)閉了網(wǎng)絡(luò),所以請(qǐng)求是沒有響應(yīng)的
  • circuitBreaker and err is hystrix: max concurrency:我們設(shè)置的最大并發(fā)量MaxConcurrentRequests是10,我們的壓測工具使用的是100并發(fā),所有會(huì)觸發(fā)這個(gè)熔斷
  • circuitBreaker and err is hystrix: circuit open:我們設(shè)置熔斷開啟的請(qǐng)求數(shù)量RequestVolumeThreshold是100,所以當(dāng)10s內(nèi)的請(qǐng)求數(shù)量大于100時(shí)就會(huì)觸發(fā)熔斷。

簡單對(duì)上面的例子做一個(gè)解析:

  • 添加接口級(jí)的熔斷中間件
  • 初始化熔斷相關(guān)配置
  • 開啟dashboard 可視化hystrix的上報(bào)信息,瀏覽器打開http://localhost:81,可以看到如下結(jié)果:

hystrix-go流程分析

本來想對(duì)源碼進(jìn)行分析,代碼量有點(diǎn)大,所以就針對(duì)流程來分析,順便看一些核心代碼。

配置熔斷規(guī)則

既然是熔斷,就要有熔斷規(guī)則,我們可以調(diào)用兩個(gè)方法配置熔斷規(guī)則,不會(huì)最終調(diào)用的都是ConfigureCommand,這里沒有特別的邏輯,如果我們沒有配置,系統(tǒng)將使用默認(rèn)熔斷規(guī)則:

  1. var ( 
  2.  // DefaultTimeout is how long to wait for command to complete, in milliseconds 
  3.  DefaultTimeout = 1000 
  4.  // DefaultMaxConcurrent is how many commands of the same type can run at the same time 
  5.  DefaultMaxConcurrent = 10 
  6.  // DefaultVolumeThreshold is the minimum number of requests needed before a circuit can be tripped due to health 
  7.  DefaultVolumeThreshold = 20 
  8.  // DefaultSleepWindow is how long, in milliseconds, to wait after a circuit opens before testing for recovery 
  9.  DefaultSleepWindow = 5000 
  10.  // DefaultErrorPercentThreshold causes circuits to open once the rolling measure of errors exceeds this percent of requests 
  11.  DefaultErrorPercentThreshold = 50 
  12.  // DefaultLogger is the default logger that will be used in the Hystrix package. By default prints nothing. 
  13.  DefaultLogger = NoopLogger{} 

配置規(guī)則如下:

  • Timeout:定義執(zhí)行command的超時(shí)時(shí)間,時(shí)間單位是ms,默認(rèn)時(shí)間是1000ms;
  • MaxConcurrnetRequests:定義command的最大并發(fā)量,默認(rèn)值是10并發(fā)量;
  • SleepWindow:熔斷器被打開后使用,在熔斷器被打開后,根據(jù)SleepWindow設(shè)置的時(shí)間控制多久后嘗試服務(wù)是否可用,默認(rèn)時(shí)間為5000ms;
  • RequestVolumeThreshold:判斷熔斷開關(guān)的條件之一,統(tǒng)計(jì)10s(代碼中寫死了)內(nèi)請(qǐng)求數(shù)量,達(dá)到這個(gè)請(qǐng)求數(shù)量后再根據(jù)錯(cuò)誤率判斷是否要開啟熔斷;
  • ErrorPercentThreshold:判斷熔斷開關(guān)的條件之一,統(tǒng)計(jì)錯(cuò)誤百分比,請(qǐng)求數(shù)量大于等于RequestVolumeThreshold并且錯(cuò)誤率到達(dá)這個(gè)百分比后就會(huì)啟動(dòng)熔斷 默認(rèn)值是50;

這些規(guī)則根據(jù)command的name進(jìn)行區(qū)分存放到一個(gè)map中。

執(zhí)行command

執(zhí)行command主要可以調(diào)用四個(gè)方法,分別是:

  1. func Go(name string, run runFunc, fallback fallbackFunc) 
  2. func GoC(ctx context.Context, name string, run runFuncC, fallback fallbackFuncC)  
  3. func Do(name string, run runFunc, fallback fallbackFunc) 
  4. func DoC(ctx context.Context, name string, run runFuncC, fallback fallbackFuncC) 

Do內(nèi)部調(diào)用的Doc方法,Go內(nèi)部調(diào)用的是Goc方法,在Doc方法內(nèi)部最終調(diào)用的還是Goc方法,只是在Doc方法內(nèi)做了同步邏輯:

  1. func DoC(ctx context.Context, name string, run runFuncC, fallback fallbackFuncC) error { 
  2.   ..... 省略部分封裝代碼 
  3.   var errChan chan error 
  4.  if fallback == nil { 
  5.   errChan = GoC(ctx, name, r, nil) 
  6.  } else { 
  7.   errChan = GoC(ctx, name, r, f) 
  8.  } 
  9.  
  10.  select { 
  11.  case <-done: 
  12.   return nil 
  13.  case err := <-errChan: 
  14.   return err 
  15.  } 

因?yàn)樗麄冏罱K都是調(diào)用的Goc方法,所以我們執(zhí)行分析Goc方法的內(nèi)部邏輯;代碼有點(diǎn)長,我們分邏輯來分析:

創(chuàng)建command對(duì)象

  1. cmd := &command{ 
  2.  run:      run, 
  3.  fallback: fallback, 
  4.  start:    time.Now(), 
  5.  errChan:  make(chan error, 1), 
  6.  finished: make(chan bool, 1), 
  7. // 獲取熔斷器 
  8. circuit, _, err := GetCircuit(name
  9. if err != nil { 
  10.  cmd.errChan <- err 
  11.  return cmd.errChan 

介紹一下command的數(shù)據(jù)結(jié)構(gòu):

  1. type command struct { 
  2.  sync.Mutex 
  3.  
  4.  ticket      *struct{} 
  5.  start       time.Time 
  6.  errChan     chan error 
  7.  finished    chan bool 
  8.  circuit     *CircuitBreaker 
  9.  run         runFuncC 
  10.  fallback    fallbackFuncC 
  11.  runDuration time.Duration 
  12.  events      []string 

字段介紹:

  • ticket:用來做最大并發(fā)量控制,這個(gè)就是一個(gè)令牌
  • start:記錄command執(zhí)行的開始時(shí)間
  • errChan:記錄command執(zhí)行錯(cuò)誤
  • finished:標(biāo)志command執(zhí)行結(jié)束,用來做協(xié)程同步
  • circuit:存儲(chǔ)熔斷器相關(guān)信息
  • run:應(yīng)用程序
  • fallback:應(yīng)用程序執(zhí)行失敗后要執(zhí)行的函數(shù)
  • runDuration:記錄command執(zhí)行消耗時(shí)間
  • events:events主要是存儲(chǔ)事件類型信息,比如執(zhí)行成功的success,或者失敗的timeout、context_canceled等

上段代碼重點(diǎn)是GetCircuit方法,這一步的目的就是獲取熔斷器,使用動(dòng)態(tài)加載的方式,如果沒有就創(chuàng)建一個(gè)熔斷器,熔斷器結(jié)構(gòu)如下:

  1. type CircuitBreaker struct { 
  2.  Name                   string 
  3.  open                   bool 
  4.  forceOpen              bool 
  5.  mutex                  *sync.RWMutex 
  6.  openedOrLastTestedTime int64 
  7.  
  8.  executorPool *executorPool 
  9.  metrics      *metricExchange 

解釋一下這幾個(gè)字段:

  • name:熔斷器的名字,其實(shí)就是創(chuàng)建的command名字
  • open:判斷熔斷器是否打開的標(biāo)志
  • forceopen:手動(dòng)觸發(fā)熔斷器的開關(guān),單元測試使用
  • mutex:使用讀寫鎖保證并發(fā)安全
  • openedOrLastTestedTime:記錄上一次打開熔斷器的時(shí)間,因?yàn)橐鶕?jù)這個(gè)時(shí)間和SleepWindow時(shí)間來做恢復(fù)嘗試
  • executorPool:用來做流量控制,因?yàn)槲覀冇幸粋€(gè)最大并發(fā)量控制,就是根據(jù)這個(gè)來做的流量控制,每次請(qǐng)求都要獲取令牌

metrics:用來上報(bào)執(zhí)行狀態(tài)的事件,通過它把執(zhí)行狀態(tài)信息存儲(chǔ)到實(shí)際熔斷器執(zhí)行各個(gè)維度狀態(tài) (成功次數(shù),失敗次數(shù),超時(shí)……) 的數(shù)據(jù)集合中。

后面會(huì)單獨(dú)分析executorPool、metrics的實(shí)現(xiàn)邏輯。

定義令牌相關(guān)的方法和變量

因?yàn)槲覀冇幸粋€(gè)條件是最大并發(fā)控制,采用的是令牌的方式進(jìn)行流量控制,每一個(gè)請(qǐng)求都要獲取一個(gè)令牌,使用完畢要把令牌還回去,先看一下這段代碼:

  1. ticketCond := sync.NewCond(cmd) 
  2. ticketChecked := false 
  3. // When the caller extracts error from returned errChan, it's assumed that 
  4. // the ticket's been returned to executorPool. Therefore, returnTicket() can 
  5. // not run after cmd.errorWithFallback(). 
  6. returnTicket := func() { 
  7.  cmd.Lock() 
  8.  // Avoid releasing before a ticket is acquired. 
  9.  for !ticketChecked { 
  10.   ticketCond.Wait() 
  11.  } 
  12.  cmd.circuit.executorPool.Return(cmd.ticket) 
  13.  cmd.Unlock() 

使用sync.NewCond創(chuàng)建一個(gè)條件變量,用來協(xié)調(diào)通知你可以歸還令牌了。

然后定義一個(gè)返回令牌的方法,調(diào)用Return方法歸還令牌。

定義上報(bào)執(zhí)行事件的方法

前面我們也提到了,我們的熔斷器會(huì)上報(bào)執(zhí)行狀態(tài)的事件,通過它把執(zhí)行狀態(tài)信息存儲(chǔ)到實(shí)際熔斷器執(zhí)行各個(gè)維度狀態(tài) (成功次數(shù),失敗次數(shù),超時(shí)……) 的數(shù)據(jù)集合中。所以要定義一個(gè)上報(bào)的方法:

  1. reportAllEvent := func() { 
  2.  err := cmd.circuit.ReportEvent(cmd.events, cmd.start, cmd.runDuration) 
  3.  if err != nil { 
  4.   log.Printf(err.Error()) 
  5.  } 

開啟協(xié)程一:執(zhí)行應(yīng)用程序邏輯 - runFunc

協(xié)程一的主要目的就是執(zhí)行應(yīng)用程序邏輯:

  1. go func() { 
  2.   defer func() { cmd.finished <- true }() // 標(biāo)志協(xié)程一的command執(zhí)行結(jié)束,同步到協(xié)程二 
  3.  
  4.   // 當(dāng)最近執(zhí)行的并發(fā)數(shù)量超過閾值并且錯(cuò)誤率很高時(shí),就會(huì)打開熔斷器。  
  5.    // 如果熔斷器打開,直接拒絕拒絕請(qǐng)求并返回令牌,當(dāng)感覺健康狀態(tài)恢復(fù)時(shí),熔斷器將允許新的流量。 
  6.   if !cmd.circuit.AllowRequest() { 
  7.    cmd.Lock() 
  8.    // It's safe for another goroutine to go ahead releasing a nil ticket. 
  9.    ticketChecked = true 
  10.    ticketCond.Signal() // 通知釋放ticket信號(hào) 
  11.    cmd.Unlock() 
  12.       // 使用sync.Onece保證只執(zhí)行一次。 
  13.    returnOnce.Do(func() { 
  14.         // 返還令牌 
  15.     returnTicket() 
  16.         // 執(zhí)行fallback邏輯 
  17.     cmd.errorWithFallback(ctx, ErrCircuitOpen) 
  18.         // 上報(bào)狀態(tài)事件 
  19.     reportAllEvent() 
  20.    }) 
  21.    return 
  22.   } 
  23.    // 控制并發(fā) 
  24.   cmd.Lock() 
  25.   select { 
  26.     // 獲取到令牌 
  27.   case cmd.ticket = <-circuit.executorPool.Tickets: 
  28.       // 發(fā)送釋放令牌信號(hào) 
  29.    ticketChecked = true 
  30.    ticketCond.Signal() 
  31.    cmd.Unlock() 
  32.   default
  33.       // 沒有令牌可用了, 也就是達(dá)到最大并發(fā)數(shù)量則直接處理fallback邏輯 
  34.    ticketChecked = true 
  35.    ticketCond.Signal() 
  36.    cmd.Unlock() 
  37.    returnOnce.Do(func() { 
  38.     returnTicket() 
  39.     cmd.errorWithFallback(ctx, ErrMaxConcurrency) 
  40.     reportAllEvent() 
  41.    }) 
  42.    return 
  43.   } 
  44.   // 執(zhí)行應(yīng)用程序邏輯 
  45.   runStart := time.Now() 
  46.   runErr := run(ctx) 
  47.   returnOnce.Do(func() { 
  48.    defer reportAllEvent() // 狀態(tài)事件上報(bào) 
  49.       // 統(tǒng)計(jì)應(yīng)用程序執(zhí)行時(shí)長 
  50.    cmd.runDuration = time.Since(runStart) 
  51.       // 返還令牌 
  52.    returnTicket() 
  53.       // 如果應(yīng)用程序執(zhí)行失敗執(zhí)行fallback函數(shù) 
  54.    if runErr != nil { 
  55.     cmd.errorWithFallback(ctx, runErr) 
  56.     return 
  57.    } 
  58.    cmd.reportEvent("success"
  59.   }) 
  60.  }() 

總結(jié)一下這個(gè)協(xié)程:

  • 判斷熔斷器是否打開,如果打開了熔斷器直接進(jìn)行熔斷,不在進(jìn)行后面的請(qǐng)求
  • 運(yùn)行應(yīng)用程序邏輯

開啟協(xié)程二:同步協(xié)程一并監(jiān)聽錯(cuò)誤

先看代碼:

  1. go func() { 
  2.     //  使用定時(shí)器來做超時(shí)控制,這個(gè)超時(shí)時(shí)間就是我們配置的,默認(rèn)1000ms 
  3.   timer := time.NewTimer(getSettings(name).Timeout) 
  4.   defer timer.Stop() 
  5.  
  6.   select { 
  7.       // 同步協(xié)程一 
  8.   case <-cmd.finished: 
  9.    // returnOnce has been executed in another goroutine 
  10.        
  11.     // 是否收到context取消信號(hào) 
  12.   case <-ctx.Done(): 
  13.    returnOnce.Do(func() { 
  14.     returnTicket() 
  15.     cmd.errorWithFallback(ctx, ctx.Err()) 
  16.     reportAllEvent() 
  17.    }) 
  18.    return 
  19.     // command執(zhí)行超時(shí)了 
  20.   case <-timer.C: 
  21.    returnOnce.Do(func() { 
  22.     returnTicket() 
  23.     cmd.errorWithFallback(ctx, ErrTimeout) 
  24.     reportAllEvent() 
  25.    }) 
  26.    return 
  27.   } 
  28.  }() 

這個(gè)協(xié)程的邏輯比較清晰明了,目的就是監(jiān)聽業(yè)務(wù)執(zhí)行被取消以及超時(shí)。

畫圖總結(jié)command執(zhí)行流程

上面我們都是通過代碼來進(jìn)行分析的,看起來還是有點(diǎn)亂,最后畫個(gè)圖總結(jié)一下:

上面我們分析了整個(gè)具體流程,接下來我們針對(duì)一些核心點(diǎn)就行分析

上報(bào)狀態(tài)事件

hystrix-go為每一個(gè)Command設(shè)置了一個(gè)默認(rèn)統(tǒng)計(jì)控制器,用來保存熔斷器的所有狀態(tài),包括調(diào)用次數(shù)、失敗次數(shù)、被拒絕次數(shù)等,存儲(chǔ)指標(biāo)結(jié)構(gòu)如下:

  1. type DefaultMetricCollector struct { 
  2.  mutex *sync.RWMutex 
  3.  
  4.  numRequests *rolling.Number 
  5.  errors      *rolling.Number 
  6.  
  7.  successes               *rolling.Number 
  8.  failures                *rolling.Number 
  9.  rejects                 *rolling.Number 
  10.  shortCircuits           *rolling.Number 
  11.  timeouts                *rolling.Number 
  12.  contextCanceled         *rolling.Number 
  13.  contextDeadlineExceeded *rolling.Number 
  14.  
  15.  fallbackSuccesses *rolling.Number 
  16.  fallbackFailures  *rolling.Number 
  17.  totalDuration     *rolling.Timing 
  18.  runDuration       *rolling.Timing 

使用rolling.Number結(jié)構(gòu)保存狀態(tài)指標(biāo),使用rolling.Timing保存時(shí)間指標(biāo)。

最終監(jiān)控上報(bào)都依靠metricExchange來實(shí)現(xiàn),數(shù)據(jù)結(jié)構(gòu)如下:

  1. type metricExchange struct { 
  2.  Name    string 
  3.  Updates chan *commandExecution 
  4.  Mutex   *sync.RWMutex 
  5.  
  6.  metricCollectors []metricCollector.MetricCollector 

上報(bào)command的信息結(jié)構(gòu):

  1. type commandExecution struct { 
  2.  Types            []string      `json:"types"` // 區(qū)分事件類型,比如success、failure.... 
  3.  Start            time.Time     `json:"start_time"` // command開始時(shí)間 
  4.  RunDuration      time.Duration `json:"run_duration"` // command結(jié)束時(shí)間 
  5.  ConcurrencyInUse float64       `json:"concurrency_inuse"` // command 線程池使用率 

說了這么多,大家還是有點(diǎn)懵,其實(shí)用一個(gè)類圖就能表明他們之間的關(guān)系:

我們可以看到類mertricExchange提供了一個(gè)Monitor方法,這個(gè)方法主要邏輯就是監(jiān)聽狀態(tài)事件,然后寫入指標(biāo),所以整個(gè)上報(bào)流程就是這個(gè)樣子:

流量控制

hystrix-go對(duì)流量控制采用的是令牌算法,能得到令牌的就可以執(zhí)行后繼的工作,執(zhí)行完后要返還令牌。結(jié)構(gòu)體executorPool就是hystrix-go 流量控制的具體實(shí)現(xiàn)。字段Max就是每秒最大的并發(fā)值。

  1. type executorPool struct { 
  2.  Name    string 
  3.  Metrics *poolMetrics // 上報(bào)執(zhí)行數(shù)量指標(biāo) 
  4.  Max     int // 最大并發(fā)數(shù)量 
  5.  Tickets chan *struct{} // 代表令牌 

這里還有一個(gè)上報(bào)指標(biāo),這個(gè)又單獨(dú)實(shí)現(xiàn)一套方法用來統(tǒng)計(jì)執(zhí)行數(shù)量,比如執(zhí)行的總數(shù)量、最大并發(fā)數(shù)等,我們依賴畫一個(gè)類圖來表示:

上報(bào)執(zhí)行數(shù)量邏輯與上報(bào)狀態(tài)事件的邏輯是一樣的,使用channel進(jìn)行數(shù)據(jù)通信的,上報(bào)與返還令牌都在Return方法中:

  1. func (p *executorPool) Return(ticket *struct{}) { 
  2.  if ticket == nil { 
  3.   return 
  4.  } 
  5.  
  6.  p.Metrics.Updates <- poolMetricsUpdate{ 
  7.   activeCount: p.ActiveCount(), 
  8.  } 
  9.  p.Tickets <- ticket 

主要邏輯兩步:

  • 上報(bào)當(dāng)前可用的令牌數(shù)
  • 返回令牌

熔斷器

我們最后來分析熔斷器中一個(gè)比較重要的方法:AllowRequest,我們在執(zhí)行Command是會(huì)根據(jù)這個(gè)方法來判斷是否可以執(zhí)行command,接下來我們就來看一下這個(gè)判斷的主要邏輯:

  1. func (circuit *CircuitBreaker) AllowRequest() bool { 
  2.  return !circuit.IsOpen() || circuit.allowSingleTest() 

內(nèi)部就是調(diào)用IsOpen()、allowSingleTest這兩個(gè)方法:

  • IsOpen()
  1. func (circuit *CircuitBreaker) IsOpen() bool { 
  2.  circuit.mutex.RLock() 
  3.  o := circuit.forceOpen || circuit.open 
  4.  circuit.mutex.RUnlock() 
  5.  // 熔斷已經(jīng)開啟 
  6.  if o { 
  7.   return true 
  8.  } 
  9.  // 判斷10s內(nèi)的并發(fā)數(shù)是否超過設(shè)置的最大并發(fā)數(shù),沒有超過時(shí),不需要開啟熔斷器 
  10.  if uint64(circuit.metrics.Requests().Sum(time.Now())) < getSettings(circuit.Name).RequestVolumeThreshold { 
  11.   return false 
  12.  } 
  13.  // 此時(shí)10s內(nèi)的并發(fā)數(shù)已經(jīng)超過設(shè)置的最大并發(fā)數(shù)了,如果此時(shí)系統(tǒng)錯(cuò)誤率超過了預(yù)設(shè)值,那就開啟熔斷器 
  14.  if !circuit.metrics.IsHealthy(time.Now()) { 
  15.   //  
  16.   circuit.setOpen() 
  17.   return true 
  18.  } 
  19.  
  20.  return false 
  • allowSingleTest()

先解釋一下為什么要有這個(gè)方法,還記得我們之前設(shè)置了一個(gè)熔斷規(guī)則中的SleepWindow嗎,如果在開啟熔斷的情況下,在SleepWindow時(shí)間后進(jìn)行嘗試,這個(gè)方法的目的就是干這個(gè)的:

  1. func (circuit *CircuitBreaker) allowSingleTest() bool { 
  2.  circuit.mutex.RLock() 
  3.  defer circuit.mutex.RUnlock() 
  4.   
  5.   // 獲取當(dāng)前時(shí)間戳 
  6.  now := time.Now().UnixNano() 
  7.  openedOrLastTestedTime := atomic.LoadInt64(&circuit.openedOrLastTestedTime) 
  8.   // 當(dāng)前熔斷器是開啟狀態(tài),當(dāng)前的時(shí)間已經(jīng)大于 (上次開啟熔斷器的時(shí)間 +SleepWindow 的時(shí)間) 
  9.  if circuit.open && now > openedOrLastTestedTime+getSettings(circuit.Name).SleepWindow.Nanoseconds() { 
  10.     // 替換openedOrLastTestedTime 
  11.   swapped := atomic.CompareAndSwapInt64(&circuit.openedOrLastTestedTime, openedOrLastTestedTime, now) 
  12.   if swapped { 
  13.    log.Printf("hystrix-go: allowing single test to possibly close circuit %v", circuit.Name
  14.   } 
  15.   return swapped 
  16.  } 

這里只看到了熔斷器被開啟的設(shè)置了,但是沒有關(guān)閉熔斷器的邏輯,因?yàn)殛P(guān)閉熔斷器的邏輯是在上報(bào)狀態(tài)指標(biāo)的方法ReportEvent內(nèi)實(shí)現(xiàn),我們最后再看一下ReportEvent的實(shí)現(xiàn):

  1. func (circuit *CircuitBreaker) ReportEvent(eventTypes []string, start time.Time, runDuration time.Duration) error { 
  2.  if len(eventTypes) == 0 { 
  3.   return fmt.Errorf("no event types sent for metrics"
  4.  } 
  5.   
  6.  circuit.mutex.RLock() 
  7.  o := circuit.open 
  8.  circuit.mutex.RUnlock() 
  9.   // 上報(bào)的狀態(tài)事件是success 并且當(dāng)前熔斷器是開啟狀態(tài),則說明下游服務(wù)正常了,可以關(guān)閉熔斷器了 
  10.  if eventTypes[0] == "success" && o { 
  11.   circuit.setClose() 
  12.  } 
  13.  
  14.  var concurrencyInUse float64 
  15.  if circuit.executorPool.Max > 0 { 
  16.   concurrencyInUse = float64(circuit.executorPool.ActiveCount()) / float64(circuit.executorPool.Max
  17.  } 
  18.  
  19.  select { 
  20.     // 上報(bào)狀態(tài)指標(biāo),與上文的monitor呼應(yīng) 
  21.  case circuit.metrics.Updates <- &commandExecution{ 
  22.   Types:            eventTypes, 
  23.   Start:            start, 
  24.   RunDuration:      runDuration, 
  25.   ConcurrencyInUse: concurrencyInUse, 
  26.  }: 
  27.  default
  28.   return CircuitError{Message: fmt.Sprintf("metrics channel (%v) is at capacity", circuit.Name)} 
  29.  } 
  30.  
  31.  return nil 

可視化hystrix的上報(bào)信息

通過上面的分析我們知道hystrix-go上報(bào)了狀態(tài)事件、執(zhí)行數(shù)量事件,那么這些指標(biāo)我們可以怎么查看呢?

設(shè)計(jì)者早就想到了這個(gè)問題,所以他們做了一個(gè)dashborad,可以查看hystrix的上報(bào)信息,使用方法只需在服務(wù)啟動(dòng)時(shí)添加如下代碼:

  1. hystrixStreamHandler := hystrix.NewStreamHandler() 
  2. hystrixStreamHandler.Start() 
  3. go http.ListenAndServe(net.JoinHostPort("""81"), hystrixStreamHandler) 

然后打開瀏覽器:http://127.0.0.1:81/hystrix-dashboard,進(jìn)行觀測吧。

總結(jié)

故事終于接近尾聲了,一個(gè)熔斷機(jī)制的實(shí)現(xiàn)確實(shí)不簡單,要考慮的因素也是方方面面,尤其在微服務(wù)架構(gòu)下,熔斷機(jī)制是必不可少的,不僅要在框架層面實(shí)現(xiàn)熔斷機(jī)制,還要根據(jù)具體業(yè)務(wù)場景使用熔斷機(jī)制,這些都是值得我們深思熟慮的。本文介紹的熔斷框架實(shí)現(xiàn)的還是比較完美的,這種優(yōu)秀的設(shè)計(jì)思路值得我們學(xué)習(xí)。

 

文中代碼已上傳github:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/hystrix_demo,歡迎star。

 

責(zé)任編輯:武曉燕 來源: Golang夢工廠
相關(guān)推薦

2020-09-26 10:56:33

服務(wù)器熔斷服務(wù)隔離

2020-07-28 08:32:57

微服務(wù)API網(wǎng)關(guān)熔斷

2022-01-17 10:55:50

微服務(wù)API網(wǎng)關(guān)

2025-03-13 00:55:00

微服務(wù)架構(gòu)系統(tǒng)

2017-07-03 09:50:07

Spring Clou微服務(wù)架構(gòu)

2018-12-06 14:56:46

微服務(wù)隔離熔斷

2024-06-05 06:43:20

2020-11-27 10:50:06

微服務(wù)架構(gòu)框架

2017-07-04 17:35:46

微服務(wù)架構(gòu)Spring Clou

2025-08-04 01:22:00

Go 語言微服務(wù)Kratos

2021-03-05 11:09:46

Go框架微服務(wù)

2025-01-20 00:10:00

Go語言Kratos

2023-12-13 07:19:01

微服務(wù)架構(gòu)Golang

2024-06-27 10:50:01

2017-07-17 15:50:17

微服務(wù)Docker架構(gòu)

2021-06-22 18:00:09

微服務(wù)架構(gòu)系統(tǒng)

2025-01-13 00:00:07

Go語言微服務(wù)

2024-04-09 07:27:06

微服務(wù)架構(gòu)YAML

2024-12-23 00:22:55

2022-05-13 09:05:49

Hystrix熔斷器
點(diǎn)贊
收藏

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

制服丝袜综合网| 亚洲高清乱码| 欧美黄色一级大片| 欧美亚洲国产一区| 欧美一区永久视频免费观看| 久久人人爽人人爽人人av| 天堂av中文在线资源库| 美女性感视频久久| 欧美激情啊啊啊| 99在线视频免费| 麻豆国产一区二区三区四区| 精品久久久久人成| 国产精品夜夜夜爽张柏芝| 内射后入在线观看一区| 久久国产综合精品| 91高清视频在线免费观看| 91ts人妖另类精品系列| 欧美a一欧美| 欧美放荡的少妇| 免费无码国产v片在线观看| 色网站免费在线观看| 99久久久国产精品免费蜜臀| 91日韩在线视频| 国产免费一区二区三区四区五区 | 欧美一区二区三区男人的天堂 | 久久亚洲影院| 欧美激情综合色| 免费成人深夜夜行网站| 亚洲资源网你懂的| 亚洲成色777777女色窝| 激情文学亚洲色图| 日韩经典一区| 色综合色狠狠综合色| av在线播放天堂| 中文字幕资源网在线观看| 欧美国产1区2区| 蜜桃视频在线观看91| 手机看片一区二区| 国产999精品久久久久久| 亚洲自拍偷拍色片视频| 亚洲香蕉在线视频| 日韩黄色在线观看| 欧美一级片一区| 日韩av大片在线观看| 韩国av一区| 中文字幕日韩欧美在线视频| 欧美黄色一级生活片| 天堂网av成人| 精品视频在线播放| 国产精品久久AV无码| 成人台湾亚洲精品一区二区 | 国产调教打屁股xxxx网站| vam成人资源在线观看| 欧美三级电影一区| 一区二区免费av| 成人豆花视频| 日韩一区二区免费在线电影| 人妻精品久久久久中文字幕69| 日韩美女在线| 欧美群妇大交群中文字幕| 亚洲一级免费观看| 中文字幕综合| 日韩欧美一级在线播放| 亚洲乱妇老熟女爽到高潮的片 | 欧美日韩精品免费观看| 污污网站在线免费观看| 91麻豆免费观看| 女同一区二区| 国产黄在线看| 国产精品久线在线观看| 99精品视频网站| 欧美人动性xxxxz0oz| 香蕉加勒比综合久久| 亚洲欧洲日产国码无码久久99 | 亚洲影院高清在线| 亚洲卡一卡二卡三| 97精品久久久久中文字幕| 免费成人av网站| 97视频在线观看网站| 1区2区3区国产精品| 视色,视色影院,视色影库,视色网 日韩精品福利片午夜免费观看 | 精品一区在线视频| 噜噜噜躁狠狠躁狠狠精品视频| 国产精品精品视频一区二区三区| 国产剧情久久久| proumb性欧美在线观看| 日本一区免费观看| 超碰个人在线| 欧美日韩亚洲一区二区三区| 亚洲成人av免费看| 亚洲国产中文在线二区三区免| 亚洲精品国产美女| 毛片视频免费播放| 亚洲第一在线| 国产人妖伪娘一区91| 亚洲产国偷v产偷v自拍涩爱| 久久亚区不卡日本| 国产免费xxx| 黑人精品一区| 日韩你懂的在线播放| 亚洲黄色在线网站| 亚洲第一偷拍| 欧美最猛性xxxxx免费| 国产理论片在线观看| 99视频超级精品| 中文字幕欧美人与畜| 在线最新版中文在线| 91精品蜜臀在线一区尤物| 国产人妻人伦精品1国产丝袜| 欧美激情黄色片| 欧美在线视频免费观看| 午夜精品无码一区二区三区| 中文文精品字幕一区二区| 韩日视频在线观看| 国产成年精品| 这里精品视频免费| 日韩在线视频免费播放| 国产美女视频一区| 任我爽在线视频精品一| 欧美男男激情videos| 欧美一区二区播放| eeuss中文字幕| 亚洲制服av| 国产精品我不卡| 最新黄网在线观看| 制服丝袜亚洲播放| 丁香花五月婷婷| 免费亚洲网站| 91免费精品视频| 三级做a全过程在线观看| 亚洲综合区在线| 四虎1515hh.com| 99九九热只有国产精品| 国产成人亚洲综合91精品| 亚洲色图 校园春色| 亚洲国产精品一区二区www在线 | 99久久国产综合精品色伊 | 手机在线免费观看av| 欧美久久一二区| 美女福利视频网| 美女视频黄 久久| 丝袜美腿玉足3d专区一区| 波多视频一区| 亚洲欧美国产精品| www亚洲视频| 91亚洲精华国产精华精华液| 日韩亚洲欧美视频| 精品国产导航| …久久精品99久久香蕉国产| 污污视频在线观看网站| 精品国产户外野外| 国产制服丝袜在线| 午夜一区不卡| 欧美在线视频二区| 欧美123区| 日韩亚洲欧美成人| 国产精品主播一区二区| 伊人一区二区三区| 韩国黄色一级片| 1024日韩| 日韩欧美第二区在线观看| 国产激情欧美| 欧美xxxx18国产| 懂色av蜜臀av粉嫩av分享吧| 亚洲 欧美综合在线网络| 性欧美18—19sex性高清| 亚洲国产一区二区三区高清| 欧美极品一区二区| 成人免费在线观看视频| 不卡av在线网站| 天天操天天插天天射| 色婷婷一区二区| 久久国产高清视频| 成人激情免费电影网址| 国产免费黄色av| 残酷重口调教一区二区| 91香蕉亚洲精品| 999精品网| 国产一区二区三区三区在线观看 | 韩国精品主播一区二区在线观看| 国产一区二区日韩精品欧美精品| 国产黄色一区二区| 精品人伦一区二区三区蜜桃网站| 少妇无套高潮一二三区| 国产一区91精品张津瑜| 成人免费在线小视频| 91久久久精品国产| 精品国产乱码久久久久软件| 成人精品国产| 久久久久久国产免费| 国产三级视频在线播放线观看| 欧美一区二区播放| 成人毛片18女人毛片| 中文字幕一区二区三区四区不卡| 亚洲图片欧美另类| 麻豆久久久久久| 婷婷五月综合缴情在线视频| 日韩欧美精品一区| 精品久久中出| 精品国产亚洲一区二区在线观看| 欧美一区二粉嫩精品国产一线天| 麻豆传媒免费在线观看| 精品视频久久久久久久| 亚洲第一免费视频| 欧美视频你懂的| 日韩av女优在线观看| 亚洲人精品午夜| 日韩丰满少妇无码内射| www.在线成人| 制服下的诱惑暮生| 免费av成人在线| 日韩欧美在线播放视频| 欧美国产精品| 在线观看日韩羞羞视频| 免费av一区二区三区四区| 国产成人精品一区二区三区福利| 福利精品一区| 国产精欧美一区二区三区| av资源在线| 久久综合久久美利坚合众国| av黄色在线观看| 亚洲欧美在线一区| 亚洲色欧美另类| 亚洲成人精品久久| 精品人妻无码一区二区三区蜜桃一| 欧美自拍偷拍一区| 一二三区免费视频| 欧美日韩日本国产| 国产精品99精品无码视| 一区二区三区日韩欧美| 波多野结衣亚洲一区二区| 亚洲国产成人在线| 欧美激情久久久久久久| 久久久久国产精品厨房| 香蕉网在线播放| 91热门视频在线观看| 手机在线成人av| 成人h精品动漫一区二区三区| 日本一区二区三区在线免费观看| 国产一区二区三区不卡在线观看 | 国产高清av在线播放| 欧美国产三级| 国产美女作爱全过程免费视频| 一区二区三区四区电影| 黄色网zhan| 亚洲成av人片一区二区密柚| 在线视频一区观看| 色97色成人| 国产系列第一页| 欧美aⅴ99久久黑人专区| 五月天在线免费视频| 国产精品x453.com| 国产日韩视频在线播放| 国产精品7m凸凹视频分类| 日本不卡一区二区三区四区| 五月精品视频| 日韩欧美猛交xxxxx无码| 亚洲高清资源| 黄色国产精品视频| 日韩av在线播放中文字幕| 小泽玛利亚视频在线观看| 久草热8精品视频在线观看| 亚洲男人天堂2021| 成熟亚洲日本毛茸茸凸凹| 欧美精品欧美极品欧美激情| 91欧美激情一区二区三区成人| 黄色aaa视频| 国产精品护士白丝一区av| 成人自拍小视频| 亚洲国产精品人人做人人爽| 一级做a爰片久久毛片| 欧美午夜精品理论片a级按摩| 国产一区二区在线不卡| 精品国产免费人成在线观看| 日韩欧美在线观看一区二区| 深夜福利一区二区| 国产精品69xx| 国产精品日韩在线一区| 成人在线视频国产| 精品欧美日韩在线| 日韩电影免费网站| 男人天堂av片| 奇米影视一区二区三区小说| 国产一级二级av| 久久久久国产精品厨房| 私库av在线播放| 欧美性高跟鞋xxxxhd| 国产露脸无套对白在线播放| 日韩av在线网址| 老司机在线视频二区| 国内精品视频久久| 日韩综合av| 国产美女精品在线观看| 国产在视频线精品视频www666| 8x8x华人在线| 首页欧美精品中文字幕| 亚洲精品激情视频| 国产精品―色哟哟| 全部毛片永久免费看| 欧美一区二区三区日韩| 九九热视频在线观看| 久久久久久久影院| 色诱色偷偷久久综合| 免费在线一区二区| 韩日精品视频| 激情黄色小视频| 久久婷婷色综合| 国产精品2020| 欧美一级国产精品| 777电影在线观看| 欧美综合激情网| 国产劲爆久久| 300部国产真实乱| 老司机精品视频导航| 国产呦小j女精品视频| 亚洲伊人色欲综合网| 91资源在线视频| 在线观看视频亚洲| 黑人巨大亚洲一区二区久| 国内一区二区在线视频观看| 欧美日韩91| 天天看片天天操| 中文在线一区二区| 波多野结衣在线观看视频| 日韩av网址在线观看| 色av手机在线| 亚洲sss综合天堂久久| 日韩免费久久| www.com操| 国产精品视频你懂的| 天天综合久久综合| 亚洲视频在线观看| 韩日精品一区二区| 欧美在线3区| 日韩中文字幕麻豆| av男人的天堂av| 色诱亚洲精品久久久久久| 日韩三级电影网| 日本一区二区三区四区视频| 亚洲永久精品唐人导航网址| 国产福利视频在线播放| 91免费国产在线| 国产午夜精品久久久久| 亚洲精品自拍视频| 最新中文字幕在线播放| 老牛影视免费一区二区| 久久综合婷婷| 国产成人免费观看网站| 欧美午夜精品免费| 免费网站成人| 91九色偷拍| 国产一区二区三区四区三区四| 精品久久久久一区二区| 午夜不卡在线视频| 日本a一级在线免费播放| 国产成人精品视频在线观看| 成人3d动漫在线观看| 波多野结衣xxxx| 亚洲日本一区二区| 精品国精品国产自在久不卡| 国自产精品手机在线观看视频| 欧美中文一区| 黄色国产小视频| 中文字幕日韩av资源站| 99国产精品99| 97免费中文视频在线观看| 女人丝袜激情亚洲| 日本黄色的视频| 亚洲第一搞黄网站| 可以直接在线观看的av| 成人午夜在线视频一区| 在线国产欧美| 69精品无码成人久久久久久| 欧美理论片在线| 两个人看的在线视频www| 视频在线99| 国产精品一区不卡| 一级成人黄色片| 久久视频在线视频| 西野翔中文久久精品国产| 粉色视频免费看| 精品福利樱桃av导航| 18免费在线视频| 国产精品视频福利| 久久精品久久久精品美女| 国产真人真事毛片| 国产一区二区三区四区福利| 日韩精品视频在线看| 情侣黄网站免费看| 亚洲图片欧美激情| 免费福利在线观看| 亚洲自拍小视频免费观看| 久久精品官网| 青娱乐国产在线视频| 亚洲欧洲国产伦综合| 亚洲1区在线| 久久久精品三级| 亚洲成av人综合在线观看| av在线电影播放| 久久国产手机看片|