<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>

          .NET Core 使用 Consul 服務(wù)注冊(cè)發(fā)現(xiàn)

          共 14273字,需瀏覽 29分鐘

           ·

          2020-10-05 18:39

          Consul是一個(gè)用來(lái)實(shí)現(xiàn)分布式系統(tǒng)服務(wù)發(fā)現(xiàn)與配置的開(kāi)源工具。它內(nèi)置了服務(wù)注冊(cè)與發(fā)現(xiàn)框架、分布一致性協(xié)議實(shí)現(xiàn)、健康檢查、Key/Value存儲(chǔ)、多數(shù)據(jù)中心方案,不再需要依賴(lài)其他工具,使用起來(lái)也較為簡(jiǎn)單。

          • Consul官網(wǎng):https://www.consul.io
          • 開(kāi)源地址:https://github.com/hashicorp/consul、https://github.com/G-Research/consuldotnet

          安裝

          Consul支持各種平臺(tái)的安裝,安裝文檔:https://www.consul.io/downloads,為了快速使用,我這里選擇用docker方式安裝。

          version:?"3"

          services:
          ??service_1:
          ????image:?consul
          ????command:?agent?-server?-client=0.0.0.0?-bootstrap-expect=3?-node=service_1
          ????volumes:
          ??????-?/usr/local/docker/consul/data/service_1:/data
          ??service_2:
          ????image:?consul
          ????command:?agent?-server?-client=0.0.0.0?-retry-join=service_1?-node=service_2
          ????volumes:
          ??????-?/usr/local/docker/consul/data/service_2:/data
          ????depends_on:
          ??????-?service_1
          ??service_3:
          ????image:?consul
          ????command:?agent?-server?-client=0.0.0.0?-retry-join=service_1?-node=service_3
          ????volumes:
          ??????-?/usr/local/docker/consul/data/service_3:/data
          ????depends_on:
          ??????-?service_1
          ??client_1:
          ????image:?consul
          ????command:?agent?-client=0.0.0.0?-retry-join=service_1?-ui?-node=client_1
          ????ports:
          ??????-?8500:8500
          ????volumes:
          ??????-?/usr/local/docker/consul/data/client_1:/data
          ????depends_on:
          ??????-?service_2
          ??????-?service_3

          提供一個(gè)docker-compose.yaml,使用docker-compose up編排腳本啟動(dòng)Consul,如果你不熟悉,可以選擇其它方式能運(yùn)行Consul即可。

          這里使用 Docker 搭建 3個(gè) server 節(jié)點(diǎn) + 1 個(gè) client 節(jié)點(diǎn),API 服務(wù)通過(guò) client 節(jié)點(diǎn)進(jìn)行服務(wù)注冊(cè)和發(fā)現(xiàn)。

          安裝完成啟動(dòng)Consul,打開(kāi)默認(rèn)地址 http://localhost:8500 可以看到Consului界面。

          快速使用

          添加兩個(gè)webapi服務(wù),ServiceA和ServiceB,一個(gè)webapi客戶(hù)端Client來(lái)調(diào)用服務(wù)。

          dotnet?new?sln?-n?consul_demo

          dotnet?new?webapi?-n?ServiceA
          dotnet?sln?add?ServiceA/ServiceA.csproj

          dotnet?new?webapi?-n?ServiceB
          dotnet?sln?add?ServiceB/ServiceB.csproj

          dotnet?new?webapi?-n?Client
          dotnet?sln?add?Client/Client.csproj

          在項(xiàng)目中添加Consul組件包

          Install-Package Consul

          服務(wù)注冊(cè)

          接下來(lái)在兩個(gè)服務(wù)中添加必要的代碼來(lái)實(shí)現(xiàn)將服務(wù)注冊(cè)到Consul中。

          首先將Consul配置信息添加到appsettings.json

          {
          ????"Consul":?{
          ????????"Address":?"http://host.docker.internal:8500",
          ????????"HealthCheck":?"/healthcheck",
          ????????"Name":?"ServiceA",
          ????????"Ip":?"host.docker.internal"
          ????}
          }

          因?yàn)槲覀円獙㈨?xiàng)目都運(yùn)行在docker中,所以這里的地址要用 host.docker.internal 代替,使用 localhost 無(wú)法正常啟動(dòng),如果不在 docker 中運(yùn)行,這里就配置層 localhost。

          添加一個(gè)擴(kuò)展方法UseConul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime)。

          using?System;
          using?Consul;
          using?Microsoft.AspNetCore.Builder;
          using?Microsoft.Extensions.Configuration;
          using?Microsoft.Extensions.Hosting;

          namespace?ServiceA
          {
          ????public?static?class?Extensions
          ????{
          ????????public?static?IApplicationBuilder?UseConul(this?IApplicationBuilder?app,?IConfiguration?configuration,?IHostApplicationLifetime?lifetime)
          ????????{
          ????????????var?client?=?new?ConsulClient(options?=>
          ????????????{
          ????????????????options.Address?=?new?Uri(configuration["Consul:Address"]);?//?Consul客戶(hù)端地址
          ????????????});

          ????????????var?registration?=?new?AgentServiceRegistration
          ????????????{
          ????????????????ID?=?Guid.NewGuid().ToString(),?//?唯一Id
          ????????????????Name?=?configuration["Consul:Name"],?//?服務(wù)名
          ????????????????Address?=?configuration["Consul:Ip"],?//?服務(wù)綁定IP
          ????????????????Port?=?Convert.ToInt32(configuration["Consul:Port"]),?//?服務(wù)綁定端口
          ????????????????Check?=?new?AgentServiceCheck
          ????????????????{
          ????????????????????DeregisterCriticalServiceAfter?=?TimeSpan.FromSeconds(5),?//?服務(wù)啟動(dòng)多久后注冊(cè)
          ????????????????????Interval?=?TimeSpan.FromSeconds(10),?//?健康檢查時(shí)間間隔
          ????????????????????HTTP?=?$"http://{configuration["Consul:Ip"]}:{configuration["Consul:Port"]}{configuration["Consul:HealthCheck"]}",?//?健康檢查地址
          ????????????????????Timeout?=?TimeSpan.FromSeconds(5)?//?超時(shí)時(shí)間
          ????????????????}
          ????????????};

          ????????????//?注冊(cè)服務(wù)
          ????????????client.Agent.ServiceRegister(registration).Wait();

          ????????????//?應(yīng)用程序終止時(shí),取消服務(wù)注冊(cè)
          ????????????lifetime.ApplicationStopping.Register(()?=>
          ????????????{
          ????????????????client.Agent.ServiceDeregister(registration.ID).Wait();
          ????????????});

          ????????????return?app;
          ????????}
          ????}
          }

          然后在Startup.cs中使用擴(kuò)展方法即可。

          public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env,?IHostApplicationLifetime?lifetime)
          {
          ????...
          ????app.UseConul(Configuration,?lifetime);
          }

          注意,這里將IConfigurationIHostApplicationLifetime作為參數(shù)傳進(jìn)來(lái)的,根據(jù)實(shí)際開(kāi)發(fā)做對(duì)應(yīng)的修改就可以了。

          分別在ServiceA和ServiceB都完成一遍上述操作,因?yàn)椴皇菍?shí)際項(xiàng)目,這里就產(chǎn)生的許多重復(fù)代碼,在真正的項(xiàng)目開(kāi)發(fā)過(guò)程中可以考慮放在一個(gè)單獨(dú)的項(xiàng)目中,ServiceA和ServiceB分別引用,調(diào)用。

          接著去實(shí)現(xiàn)健康檢查接口。

          //?ServiceA
          using?Microsoft.AspNetCore.Mvc;

          namespace?ServiceA.Controllers
          {
          ????[Route("[controller]")]
          ????[ApiController]
          ????public?class?HealthCheckController?:?ControllerBase
          ????{
          ????????///?
          ????????///?健康檢查
          ????????///?

          ????????///?
          ????????[HttpGet]
          ????????public?IActionResult?api()
          ????????{
          ????????????return?Ok();
          ????????}
          ????}
          }
          //?ServiceB
          using?Microsoft.AspNetCore.Mvc;

          namespace?ServiceB.Controllers
          {
          ????[Route("[controller]")]
          ????[ApiController]
          ????public?class?HealthCheckController?:?ControllerBase
          ????{
          ????????///?
          ????????///?健康檢查
          ????????///?

          ????????///?
          ????????[HttpGet]
          ????????public?IActionResult?Get()
          ????????{
          ????????????return?Ok();
          ????????}
          ????}
          }

          最后在ServiceA和ServiceB中都添加一個(gè)接口。

          //?ServiceA
          using?System;
          using?Microsoft.AspNetCore.Mvc;
          using?Microsoft.Extensions.Configuration;

          namespace?ServiceA.Controllers
          {
          ????[Route("api/[controller]")]
          ????[ApiController]
          ????public?class?ServiceAController?:?ControllerBase
          ????{
          ????????[HttpGet]
          ????????public?IActionResult?Get([FromServices]?IConfiguration?configuration)
          ????????{
          ????????????var?result?=?new
          ????????????{
          ????????????????msg?=?$"我是{nameof(ServiceA)},當(dāng)前時(shí)間:{DateTime.Now:G}",
          ????????????????ip?=?Request.HttpContext.Connection.LocalIpAddress.ToString(),
          ????????????????port?=?configuration["Consul:Port"]
          ????????????};

          ????????????return?Ok(result);
          ????????}
          ????}
          }
          //?ServiceB
          using?System;
          using?Microsoft.AspNetCore.Mvc;
          using?Microsoft.Extensions.Configuration;

          namespace?ServiceB.Controllers
          {
          ????[Route("api/[controller]")]
          ????[ApiController]
          ????public?class?ServiceBController?:?ControllerBase
          ????{
          ????????[HttpGet]
          ????????public?IActionResult?Get([FromServices]?IConfiguration?configuration)
          ????????{
          ????????????var?result?=?new
          ????????????{
          ????????????????msg?=?$"我是{nameof(ServiceB)},當(dāng)前時(shí)間:{DateTime.Now:G}",
          ????????????????ip?=?Request.HttpContext.Connection.LocalIpAddress.ToString(),
          ????????????????port?=?configuration["Consul:Port"]
          ????????????};

          ????????????return?Ok(result);
          ????????}
          ????}
          }

          這樣我們寫(xiě)了兩個(gè)服務(wù),ServiceA和ServiceB。都添加了健康檢查接口和一個(gè)自己的服務(wù)接口,返回一段json。

          我們現(xiàn)在來(lái)運(yùn)行看看效果,可以使用任何方式,只要能啟動(dòng)即可,我這里選擇在docker中運(yùn)行,直接在 Visual Studio中對(duì)著兩個(gè)解決方案右鍵添加,選擇Docker支持,默認(rèn)會(huì)幫我們自動(dòng)創(chuàng)建好Dockfile,非常方便。

          生成的Dockfile文件內(nèi)容如下:

          #?ServiceA
          FROM?mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim?AS?base
          WORKDIR?/app
          EXPOSE?80
          EXPOSE?443

          FROM?mcr.microsoft.com/dotnet/core/sdk:3.1-buster?AS?build
          WORKDIR?/src
          COPY?["ServiceA/ServiceA.csproj",?"ServiceA/"]
          RUN?dotnet?restore?"ServiceA/ServiceA.csproj"
          COPY?.?.
          WORKDIR?"/src/ServiceA"
          RUN?dotnet?build?"ServiceA.csproj"?-c?Release?-o?/app/build

          FROM?build?AS?publish
          RUN?dotnet?publish?"ServiceA.csproj"?-c?Release?-o?/app/publish

          FROM?base?AS?final
          WORKDIR?/app
          COPY?--from=publish?/app/publish?.
          ENTRYPOINT?["dotnet",?"ServiceA.dll"]
          #?ServiceB
          FROM?mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim?AS?base
          WORKDIR?/app
          EXPOSE?80
          EXPOSE?443

          FROM?mcr.microsoft.com/dotnet/core/sdk:3.1-buster?AS?build
          WORKDIR?/src
          COPY?["ServiceB/ServiceB.csproj",?"ServiceB/"]
          RUN?dotnet?restore?"ServiceB/ServiceB.csproj"
          COPY?.?.
          WORKDIR?"/src/ServiceB"
          RUN?dotnet?build?"ServiceB.csproj"?-c?Release?-o?/app/build

          FROM?build?AS?publish
          RUN?dotnet?publish?"ServiceB.csproj"?-c?Release?-o?/app/publish

          FROM?base?AS?final
          WORKDIR?/app
          COPY?--from=publish?/app/publish?.
          ENTRYPOINT?["dotnet",?"ServiceB.dll"]

          然后定位到項(xiàng)目根目錄,使用命令去編譯兩個(gè)鏡像,service_a和service_b

          docker?build?-t?service_a:dev?-f?./ServiceA/Dockerfile?.

          docker?build?-t?service_b:dev?-f?./ServiceB/Dockerfile?.

          看到 Successfully 就成功了,通過(guò)docker image ls可以看到我們打包的兩個(gè)鏡像。

          這里順便提一句,已經(jīng)可以看到我們編譯的鏡像,service_a和service_b了,但是還有許多名稱(chēng)為的鏡像,這些鏡像可以不用管它,這種叫做虛懸鏡像,既沒(méi)有倉(cāng)庫(kù)名,也沒(méi)有標(biāo)簽。是因?yàn)?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(71, 193, 168);">docker build導(dǎo)致的這種現(xiàn)象。由于新舊鏡像同名,舊鏡像名稱(chēng)被取消,從而出現(xiàn)倉(cāng)庫(kù)名、標(biāo)簽均為??的鏡像。

          一般來(lái)說(shuō),虛懸鏡像已經(jīng)失去了存在的價(jià)值,是可以隨意刪除的,可以docker image prune命令刪除,這樣鏡像列表就干凈多了。

          最后將兩個(gè)鏡像service_a和service_b,分別運(yùn)行三個(gè)實(shí)例。

          docker run -d -p 5050:80 --name service_a1 service_a:dev --Consul:Port="5050"
          docker run -d -p 5051:80 --name service_a2 service_a:dev --Consul:Port="5051"
          docker run -d -p 5052:80 --name service_a3 service_a:dev --Consul:Port="5052"

          docker run -d -p 5060:80 --name service_b1 service_b:dev --Consul:Port="5060"
          docker run -d -p 5061:80 --name service_b2 service_b:dev --Consul:Port="5061"
          docker run -d -p 5062:80 --name service_b3 service_b:dev --Consul:Port="5062"

          運(yùn)行成功,接下來(lái)就是見(jiàn)證奇跡的時(shí)刻,去到Consul看看。

          成功將兩個(gè)服務(wù)注冊(cè)到Consul,并且每個(gè)服務(wù)都有多個(gè)實(shí)例。

          訪(fǎng)問(wèn)一下接口試試吧,看看能不能成功出現(xiàn)結(jié)果。

          因?yàn)榻K端編碼問(wèn)題,導(dǎo)致顯示亂碼,這個(gè)不影響,ok,至此服務(wù)注冊(cè)大功告成。

          服務(wù)發(fā)現(xiàn)

          搞定了服務(wù)注冊(cè),接下來(lái)演示一下如何服務(wù)發(fā)現(xiàn),在Client項(xiàng)目中先將Consul地址配置到appsettings.json中。

          {
          ????"Consul":?{
          ????????"Address":?"http://host.docker.internal:8500"
          ????}
          }

          然后添加一個(gè)接口,IService.cs,添加三個(gè)方法,分別獲取兩個(gè)服務(wù)的返回結(jié)果以及初始化服務(wù)的方法。

          using?System.Threading.Tasks;

          namespace?Client
          {
          ????public?interface?IService
          ????{
          ????????///?
          ????????///?獲取?ServiceA?返回?cái)?shù)據(jù)
          ????????///?

          ????????///?
          ????????Task<string>?GetServiceA();

          ????????///?
          ????????///?獲取?ServiceB?返回?cái)?shù)據(jù)
          ????????///?

          ????????///?
          ????????Task<string>?GetServiceB();

          ????????///?
          ????????///?初始化服務(wù)
          ????????///?

          ????????void?InitServices();
          ????}
          }

          實(shí)現(xiàn)類(lèi):Service.cs

          using?System;
          using?System.Collections.Concurrent;
          using?System.Linq;
          using?System.Net.Http;
          using?System.Threading.Tasks;
          using?Consul;
          using?Microsoft.Extensions.Configuration;

          namespace?Client
          {
          ????public?class?Service?:?IService
          ????{
          ????????private?readonly?IConfiguration?_configuration;
          ????????private?readonly?ConsulClient?_consulClient;

          ????????private?ConcurrentBag<string>?_serviceAUrls;
          ????????private?ConcurrentBag<string>?_serviceBUrls;

          ????????private?IHttpClientFactory?_httpClient;

          ????????public?Service(IConfiguration?configuration,?IHttpClientFactory?httpClient)
          ????????{
          ????????????_configuration?=?configuration;

          ????????????_consulClient?=?new?ConsulClient(options?=>
          ????????????{
          ????????????????options.Address?=?new?Uri(_configuration["Consul:Address"]);
          ????????????});

          ????????????_httpClient?=?httpClient;
          ????????}

          ????????public?async?Task<string>?GetServiceA()
          ????????{
          ????????????if?(_serviceAUrls?==?null)
          ????????????????return?await?Task.FromResult("ServiceA正在初始化...");

          ????????????using?var?httpClient?=?_httpClient.CreateClient();

          ????????????var?serviceUrl?=?_serviceAUrls.ElementAt(new?Random().Next(_serviceAUrls.Count()));

          ????????????Console.WriteLine("ServiceA:"?+?serviceUrl);

          ????????????var?result?=?await?httpClient.GetStringAsync($"{serviceUrl}/api/servicea");

          ????????????return?result;
          ????????}

          ????????public?async?Task<string>?GetServiceB()
          ????????{
          ????????????if?(_serviceBUrls?==?null)
          ????????????????return?await?Task.FromResult("ServiceB正在初始化...");

          ????????????using?var?httpClient?=?_httpClient.CreateClient();

          ????????????var?serviceUrl?=?_serviceBUrls.ElementAt(new?Random().Next(_serviceBUrls.Count()));

          ????????????Console.WriteLine("ServiceB:"?+?serviceUrl);

          ????????????var?result?=?await?httpClient.GetStringAsync($"{serviceUrl}/api/serviceb");

          ????????????return?result;
          ????????}

          ????????public?void?InitServices()
          ????????{
          ????????????var?serviceNames?=?new?string[]?{?"ServiceA",?"ServiceB"?};

          ????????????foreach?(var?item?in?serviceNames)
          ????????????{
          ????????????????Task.Run(async?()?=>
          ????????????????{
          ????????????????????var?queryOptions?=?new?QueryOptions
          ????????????????????{
          ????????????????????????WaitTime?=?TimeSpan.FromMinutes(5)
          ????????????????????};
          ????????????????????while?(true)
          ????????????????????{
          ????????????????????????await?InitServicesAsync(queryOptions,?item);
          ????????????????????}
          ????????????????});
          ????????????}

          ????????????async?Task?InitServicesAsync(QueryOptions?queryOptions,?string?serviceName)
          ????????????{
          ????????????????var?result?=?await?_consulClient.Health.Service(serviceName,?null,?true,?queryOptions);

          ????????????????if?(queryOptions.WaitIndex?!=?result.LastIndex)
          ????????????????{
          ????????????????????queryOptions.WaitIndex?=?result.LastIndex;

          ????????????????????var?services?=?result.Response.Select(x?=>?$"http://{x.Service.Address}:{x.Service.Port}");

          ????????????????????if?(serviceName?==?"ServiceA")
          ????????????????????{
          ????????????????????????_serviceAUrls?=?new?ConcurrentBag<string>(services);
          ????????????????????}
          ????????????????????else?if?(serviceName?==?"ServiceB")
          ????????????????????{
          ????????????????????????_serviceBUrls?=?new?ConcurrentBag<string>(services);
          ????????????????????}
          ????????????????}
          ????????????}
          ????????}
          ????}
          }

          代碼就不解釋了,相信都可以看懂,使用了Random類(lèi)隨機(jī)獲取一個(gè)服務(wù),關(guān)于這點(diǎn)可以選擇更合適的負(fù)載均衡方式。

          Startup.cs中添加接口依賴(lài)注入、使用初始化服務(wù)等代碼。

          using?Microsoft.AspNetCore.Builder;
          using?Microsoft.AspNetCore.Hosting;
          using?Microsoft.Extensions.Configuration;
          using?Microsoft.Extensions.DependencyInjection;
          using?Microsoft.Extensions.Hosting;

          namespace?Client
          {
          ????public?class?Startup
          ????{
          ????????public?Startup(IConfiguration?configuration)
          ????????{
          ????????????Configuration?=?configuration;
          ????????}

          ????????public?IConfiguration?Configuration?{?get;?}

          ????????public?void?ConfigureServices(IServiceCollection?services)
          ????????{

          ????????????services.AddControllers();

          ????????????services.AddHttpClient();

          ????????????services.AddSingleton();
          ????????}

          ????????public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env,?IService?service)
          ????????{
          ????????????if?(env.IsDevelopment())
          ????????????{
          ????????????????app.UseDeveloperExceptionPage();
          ????????????}

          ????????????app.UseHttpsRedirection();

          ????????????app.UseRouting();

          ????????????app.UseAuthorization();

          ????????????app.UseEndpoints(endpoints?=>
          ????????????{
          ????????????????endpoints.MapControllers();
          ????????????});

          ????????????service.InitServices();
          ????????}
          ????}
          }

          一切就緒,添加api訪(fǎng)問(wèn)我們的兩個(gè)服務(wù)。

          using?System.Threading.Tasks;
          using?Microsoft.AspNetCore.Mvc;

          namespace?Client.Controllers
          {
          ????[Route("api")]
          ????[ApiController]
          ????public?class?HomeController?:?ControllerBase
          ????{
          ????????[HttpGet]
          ????????[Route("service_result")]
          ????????public?async?Task?GetService([FromServices]?IService?service)
          ????????{
          ????????????return?Ok(new
          ????????????{
          ????????????????serviceA?=?await?service.GetServiceA(),
          ????????????????serviceB?=?await?service.GetServiceB()
          ????????????});
          ????????}
          ????}
          }

          直接在Visual Studio中運(yùn)行Client項(xiàng)目,在瀏覽器訪(fǎng)問(wèn)api。

          大功告成,服務(wù)注冊(cè)與發(fā)現(xiàn),現(xiàn)在就算之中的某個(gè)節(jié)點(diǎn)掛掉,服務(wù)也可以照常運(yùn)行。



          往期精彩回顧




          【推薦】.NET Core開(kāi)發(fā)實(shí)戰(zhàn)視頻課程?★★★

          .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門(mén)篇-開(kāi)篇及總體規(guī)劃

          【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開(kāi)篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴(lài)注入與動(dòng)態(tài)選擇看這篇就夠了

          10個(gè)小技巧助您寫(xiě)出高性能的ASP.NET Core代碼

          用abp vNext快速開(kāi)發(fā)Quartz.NET定時(shí)任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說(shuō)法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢(xún)優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

          C#異步編程看這篇就夠了


          瀏覽 83
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲成人篇在线观看无码 | 成人伊人久久 | 激情小说激情视频 | 北条麻妃成人视频 | 成人免费性爱视频 |