SpringCloud與Docker微服務架構實戰筆記


一  微服務架構概述

      1. 單體應用架構存在的問題

          結合:https://www.cnblogs.com/jialanshun/p/10637454.html一起看,在該篇博客中搜索“單塊架構的優缺點

        (1)復雜性高

                        以筆者經手的一個百萬行級別的單體應用為例,整個項目包含的模塊非常多、模塊的邊界模糊、依賴關系不

                 清晰、代碼質量參差不齊、混亂地堆砌在一起整個項目非常復雜。每次修改代碼都心驚膽戰,甚至添加一個簡單

                 的功能,或者修改一個Bug都會帶來隱含的缺陷。

        (2)技術債務

                        隨着時間推移、需求變更和人員更迭,會逐漸形成應用程序的技術債務,並且越積越多。“不壞不修

               (Notbroken,don'tfix)'',這在軟件開發中非常常見,在單體應用中這種思想更甚。已使用的系統設計或代碼難以

                 被修改,因為應用程序中的其他模塊可能會以意料之外的方式使用它。

        (3)部署頻率低

                        隨着代碼的增多,構建和部署的時間也會增加。而在單體應用中,每次功能的變更或缺陷的修復都會導致需

                 要重新部署整個應用。全量部署的方式耗時長、影響范圍大、風險高,這使得單體應用項目上線部署的頻率較低。

                 而部署頻率低又導致兩次發布之間會有大量的功能變更和缺陷修復,出錯概率比較高。

        (3)可靠性差

                        某個應用Bug,例如死循環、OOM等,可能會導致整個應用的崩潰。

        (4)新人培養周期長

      2. 什么是微服務架構

          什么是微服務的架構和微服務架構的一些思想去博客https://www.cnblogs.com/jialanshun/p/10637454.html中看

二  Spring Cloud概述

      1. Spring Cloud的版本號               

                 如下圖所示,Spring Cloud是以英文單詞加SRX(X代表數字)來命名版本號的,其中英文單詞 Brixton、Angel、

          Camden表示的是倫敦地鐵站的名稱,按照字母順序發行。SR表示“Service Release”,一般表示BUG修復,在SR版

          本發行前,會先發行一個Release版本,例如“Camden Release”。

                 這樣定義版本的原因是因為SpringCloud是一個綜合項目,它包含很多的子項目。由於子項目也維護着自己的版

          本號,springcloud采用了這種版本命名方式,從而避免與子項目的版本混淆。

                

      3. Spring Cloud的前世今生

          https://my.oschina.net/polly/blog/1790057

      2. Spring Cloud和Spring Boot的兼容性

Spring Cloud

Spring Boot

Finchley

兼容Spring Boot 2.0.x,不兼容Spring Boot 1.5.x

Dalston和Edgware

兼容Spring Boot 1.5.x,不兼容Spring Boot 2.0.x

Camden

兼容Spring Boot 1.4.x,也兼容Spring Boot 1.5.x

Brixton

兼容Spring Boot 1.3.x,也兼容Spring Boot 1.4.x

Angel

兼容Spring Boot 1.2.x

        可以去https://spring.io/projects/spring-cloud查看版本兼容性,這本書比較老,上面表格可能不是最新的

三  Eureka

          SpringCloud支持多種注冊中心,如Eureka、Consul、Zookeeper,但是本文只講Eureka

      1.  Eureka簡介

         (1)Eureka的基本原理

                  關於Eureka的原理參看:

                  https://blog.csdn.net/forezp/article/details/73017664

 

             

             

              由圖可知,Eureka包含兩個組件,Eureka Server和Eureka Client

              Eureka Server:提供服務注冊服務,各個節點啟動后會在Eureka Server進行注冊。Eureka Server之間通過復

                                         制的方式完成數據的同步。在應用啟動后,將會向Eureka Server發送心跳,默認周期為30秒,

                                         如果Eureka Server在多個心跳周期內沒有接收到某個節點的心跳,將會重服務器注冊表中把

                                         這個服務節點移除(開啟自我保護除外)。

              Eureka Client:是一個Java客戶端,用於簡化與Eureka Server的交互,該客戶端具備一個內置的使用輪詢負

                                       載均算法的負載均衡器。Eureka提供客戶端緩存機制,即使所有的Eureka Server都掛掉,客

                                       戶端依然可以利用緩存中的信息消費其它服務中的Api。

                                       客戶端緩存的實現原理:

                                              Eureka Client緩存機制很簡單,設置了一個每30秒執行一次的定時任務,定時去服務端獲

                                       取注冊信息。獲取之后,存入本地內存。

              綜上,Eureka通過心跳檢測、健康檢查、客戶端緩存等機制確保了系統的高可用性、靈活性和可伸縮性。

        (2)Eureka包含的核心功能

               (a)Register:服務注冊

                        當Eureka客戶端向Eureka Server注冊時,它提供自身的元數據,比如IP地址、端口,運行狀況指示符

                        URL,主頁等。

               (b)Renew:服務續約

                        Eureka客戶會每隔30秒發送一次心跳來續約。 通過續約來告知Eureka Server該Eureka客戶仍然存在,沒

                        有出現問題。 正常情況下,如果Eureka Server在90秒沒有收到Eureka客戶的續約,它會將實例從其注冊

                        表中刪除。 建議不要更改續約間隔。

               (c)Fetch Registries:獲取注冊列表信息

                        Eureka客戶端從服務器獲取注冊表信息,並將其緩存在本地。客戶端會使用該信息查找其他服務,從而進

                        行遠程調用。該注冊列表信息定期(每30秒鍾)更新一次。每次返回注冊列表信息可能與Eureka客戶端的

                        緩存信息不同, Eureka客戶端自動處理。如果由於某種原因導致注冊列表信息不能及時匹配,Eureka客戶

                        端則會重新獲取整個注冊表信息。 Eureka服務器緩存注冊列表信息,整個注冊表以及每個應用程序的信息

                        進行了壓縮,壓縮內容和沒有壓縮的內容完全相同。Eureka客戶端和Eureka 服務器可以使用JSON / XML

                        格式進行通訊。在默認的情況下Eureka客戶端使用壓縮JSON格式來獲取注冊列表的信息。

               (d)Cancel:服務下線

                        Eureka客戶端在程序關閉時向Eureka服務器發送取消請求。 發送請求后,該客戶端實例信息將從服務器的

                        實例注冊表中刪除。該下線請求不會自動完成,它需要調用以下內容:

                        DiscoveryManager.getInstance().shutdownComponent(); 

      2. 學習Eureka需要掌握的知識點  

        (1)Eureka集群配置

                 Eureka服務器端application.yml配置(以兩台為例):

server:
  port: 8761
spring:
  application:
    name: eureka-servier
  #Spring提供了profiles的功能,可以配置多套配置,用於區分不同的環境(開發、測試、生產)在運行時 
  #指定使用那套,這樣代碼只要一套,運行時加入不同參數就可以了。比如在UnitTest中,加入: 
  #@ActiveProfiles("dev"),即可使用dev的配置。也可以在運行jar的時候
  #加入:-Dspring.profiles.active=release。
  profiles: slave1
eureka:
  client:
    serviceUrl:
      #服務注冊中心的配置內容,指定服務注冊中心的位置
      defaultZone: http://localhost:8762/eureka
---
server:
  port: 8762
spring:
  application:
    name: eureka-servier
  profiles: slave2
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka

                Eureka客戶端配置:

spring:
  application:
    name: consumer-demo
server:
  port: 9000
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka

 

                集群成功示例圖:

                ​

        (2)健康檢查:

                 如果Eureka Server在一定時間內(默認90秒)沒有接收到某個微服務實例的心跳,Eureka Server將會移除該

                 實例。

                 健康檢查配置(下面代碼是一個eureka客戶端而不是eureka server):

spring:
  application:
    name: consume-demo-hertbeat
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
      #eureka客戶端發送給eureka服務器心跳的頻率
      lease-renewal-interval-in-seconds: 5
      #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超時時間,在這個時間內若沒收到下一次心跳,則將移除該instance。
      lease-expiration-duration-in-seconds: 10

                  同時應該在eureka server中關閉自我保護,示例代碼如下(下例是一個eureka server而不是eureka client):

spring:
  application:
    name: eurker-servier-heartbeat
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  server:
    #自我保護配置(true 開啟,false 關閉)
    enable-self-preservation: false
    #(清理服務列表的時間間隔)

                 上面代碼中為了測試方便還增加了清理服務列表的時間,即使滿足“10秒沒收到請求但不滿足到了清理服務列

                  表的時間,一樣不會剔除該服務”。

        (3)自我保護:

                 首先闡明存在的一個問題:  

                        默認情況下,如果Eureka Server在一定時間內(默認90秒)沒有接收到某個微服務實例的心跳,

                        Eureka Server將會移除該實例。但是當網絡分區故障發生時,微服務與Eureka Server之間無法正常通

                        信,而微服務本身是正常運行的,此時不應該移除這個微服務,Eureka通過自我保護機制來解決該問題

                        的。

                 自我保護機制

                        當Eureka Server節點在短時間內丟失過多客戶端時(15分鍾內超過85%的客戶端節點都沒有正常的心

                        跳),那么這個節點就會進入自我保護模式。一旦進入該模式,Eureka Server就會保護服務注冊表中的

                        信息,不再刪除服務注冊表中的數據(也就是不會注銷任何微服務)。當網絡故障恢復后,該

                        Eureka Server節點會自動退出自我保護模式。

                 自我保護配置:

spring:
  application:
    name: eurker-servier-heartbeat
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
  server:
    #自我保護配置(true 開啟,false 關閉)
    enable-self-preservation: false
    #(清理服務列表的時間間隔)
    eviction-interval-timer-in-ms: 10000

 

        (4)健康監控

                        默認情況下注冊到eureka server的服務是通過心跳來告知自己是UP還是DOWN,並不是通過

                 spring-boot-actuator模塊的/health端點來實現的,這樣其實不是很合理。因為默認的心跳實現方式可以有效

                 的檢查eureka客戶端進程是否正常運作,但是無法保證客戶端應用能夠正常提供服務(大多數微服務應用都

                 會有一些其他的外部資源依賴,比如數據庫,REDIS緩存等,如果我們的應用與這些外部資源無法連通的時

                 候,實際上已經不能提供正常的對外服務了,但因為客戶端心跳依然在運行,所以它還是會被服務消費者調

                 用)。

                        健康狀態有UP和DOWN兩種,如果Eureka中的是UP,則該服務可以正常調用;如果Eureka中的健康狀

                 態是DOWN則該服務不可調用。

                 問題場景:如果一個服務並沒有死掉,但是其本身是有問題的,例如訪問數據庫的服務無法連接

                 到數據庫,這個時候需要使用健康監控。

                 注意:健康監控監控的是客戶端,所以健康指示器和健康處理器的代碼只能寫在需要健康的客戶端。

               (a)引入jar包

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>1.5.4.RELEASE</version>
       </dependency>

               (b)編寫健康指示器

package app;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;

@Component
public class MyHealthIndicator implements HealthIndicator {
    public Health health() {
        if(ProviderMonitorController.canVisitDB)
        {
            return new Health.Builder(Status.UP).build();
        }else
        {
            return new Health.Builder(Status.DOWN).build();
        }
    }
}

               (c)模擬數據庫無法訪問

package app;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderMonitorController {
    public static boolean canVisitDB=true;
    @RequestMapping(value = "/setDB/{can}",method = RequestMethod.GET)
    public void setDB(@PathVariable boolean can)
    {
        canVisitDB=can;
    }
}

               (d)測試

                      (i)查看健康狀態

                              在瀏覽器里輸入http://localhost:8080/health,返回狀態為UP

                     (ii)執行模擬數據庫無法訪問並查看健康狀態

                              瀏覽器中輸入:http://localhost:8080/setDB/false 設置數據庫無法訪問

                              瀏覽器中輸入:http://localhost:8080/health 查看健康狀態為 DOWN

                              查看Eureka中的健康狀態仍然為UP,這時需要使用健康檢查處理器來改變Eureka中的狀態(只有改

                              變了Eureka中的狀態,那么有問題的服務才不能被其它被訪問)。

               (e)健康處理器代碼(默認情況下這個處理器30秒執行一次):

package app;

import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import jdk.net.SocketFlow;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.boot.actuate.health.Status;
@Component
public class MyHealthCheckHandle implements HealthCheckHandler {
    @Autowired
    private MyHealthIndicator myHealthIndicator;
    public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus instanceStatus) {
       Status status= myHealthIndicator.health().getStatus();
       if(status.equals(Status.UP))
       {
            return  InstanceInfo.InstanceStatus.UP;
       }else
       {
           return  InstanceInfo.InstanceStatus.DOWN;
       }
    }
}

             application.yml(主要關注健康處理器的執行頻率)

spring:
  application:
    name: my-health-provider
endpoints:
  sensitive: false
eureka:
  client:
    #健康處理器執行頻率。默認30秒執行一次,這里改成10秒執行一次
    instanceInfoReplicationIntervalSeconds: 10
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

 

               (f)測試健康處理器

                       瀏覽器中執行:http://localhost:8080/setDB/false,可以看到health端口和Eureka 中的狀態全部變成

                       DOWN

        (5)Eureka元數據

        (6)REST端點

        (7)多網卡環境下的IP選擇

                 上面代碼中為了測試方便還增加了清理服務列表的時間,即使滿足“10秒沒收到請求但不滿足到了清理服務列

                 表的時間,一樣不會剔除該服務”。

二  Ribbon

         1.客戶端負載均衡框架,支持可插拔式的負載均衡規則

         2.支持多中協議,如HTTP、UDP

         3.Ribbon支持的負載均衡Rule

          (1)RoundRobinRule:輪詢

          (2)AvailabilityFilteringRule:會過濾掉兩類服務器。(1)短路狀態(連續3次超時)(2)並發數過高的服務

                                                            器。

          (3)WeightedResponseTimeRule:為每個服務器設置一個權重值,服務器的響應時間越長權重值越小。這里

                                                                     是根據權重值隨機選擇服務器。

          (4)ZoneAvoidanceRule:復合判斷server所在區域的性能和server的可用性選擇server,使用區域對服務器進

                                                       行選擇。

          (5)BestAvailableRule:忽略短路的服務器並選擇並發數最小的一個服務器

          (6)RandomRule:隨機數

          (7)RetryRule:含有重試機制的選擇邏輯。

         4. Ribbon可配置的類

             NFLoadBalancerPingClassName:用於配置查看服務器是否存活。配置IPing的實現類。

             NFLoadBalancerRuleClassName:指定負載均衡器的實現類。當然,可以設置自己實現的負載均衡器。配置

                                                                      IRule的實現類。IRule代表的是負載均衡算法。

             NFLoadBalancerClassName:配置ILoadBalancer的實現類

             NIWSServerListClassName:是服務器列表的處理類,用來維護服務器列表的。Ribbon已經實現了動態服務器

                                                             列表。配置ServerList的實現類。

             NIWSServerListFilterClassName:是服務器的攔截類。配置ServerListFilter的實現類。

三  feign

                 Feign是Netflix開發的聲明式、模板化的HTTP客戶端,Feign可幫助我們更加便捷、優雅地調用HTTPAPI。

          在springCloud中,使用Feign非常簡單一創建一個接口,並在接口上添加一些注解,代碼就完成了。Feign支持多

          種注解,例如Feign自帶的注解或者JAX-RS注解等。

                 SpringCloud對Feign進行了增強,使Feign支持了SpringMVC注解,並整合了Ribbon和Eureka,從而讓

          Feign的使用更加方便。

                 feign是可以配置的,默認配置類是FeignClientsConfiguration,該類定義了feign的編碼器、解碼器、使用契約

        (SpringCloud的默認契約使用的是SpringMVCContract,因此它可以使用SpringMVC的注解)等。SpringCloud允

          許通過注解@FeignCIient的configuration屬性自定義Feign的配置,自定義配置的優先級要高於

          FeignClientsConfiguration。

                 feign中已經使用了Ribbon的Api,默認負載均衡配置是輪詢,可以通過設置Ribbon的負載均衡策略來定義

          Feign客戶端的負載均衡策略

          1. SpriingCloud整合feign

            (1)添加feign的依賴

                     

            (2)創建一個Feign接口,並添加@FeignClient注解    

                     

                       @FeignC1ient注解中的microservice-provider-user是一個任意的客戶端名稱,用於創建Ribbon負載均衡

                       器。在本例中,由於使用了Eureka,所以Ribbon會把microservice-provider-user解析成EurekaServer服

                       務注冊表中的服務。當然,如果不想使用Eureka,可使用service.ribbon.listofservers屬性配置服務器列

                       表(詳見5·5節)。

                              還可使用url屬性指定請求的URL(URL可以是完整的URL或者主機名),例如:

                       

            (4)調用feign接口的Controller代碼如下:

                    

             (5)啟動類添加@EnableFeignClients使feign生效

                      

 

               這樣就可以通過feign去調用微服務的microservice-provider-user了

           2. 自定義feign的配置

                     SpringCloud允許通過注解@FeignCIient的configuration屬性自定義Feign的配置,自定義配置的優先級比

               FeignClientsConfiguration要高。

             (1)自定義契約

                             SpringCloud中,默認使用的契約是SpriingMVCContract,因此,我們在使用feign時,使用的是

                      SprinigMVC注解。

                             本例示范自定義使用feign的自己的注解。

                           (a)創建feign的配置類

                                    

                                    擴展,為了看懂該例子,了解下@Bean

                                    代碼:

                     @Configuration
                     public class AppConfig {
                         @Bean
                         public TransferService transferService() {
                         return new TransferServiceImpl();
                        }

                     }

 

                                    等同於:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

 

                            (b)Feign接口修改為如下,使用@FeignC1ient的configuration屬性指定配置類,同時,將findById

                                     上的SpringMVC注解修改為Feign自帶的注解。

                                      

             (2)為接口添加HTTP Baseic

                      配置類寫法:

                      

                     其它略。

             (3)類似地,還可自定義Feign的編碼器、解碼器、日志打印,甚至為Feign添加攔截器。具體demo網上找,

                      書上沒寫。

                      Feign的常用配置有:

                      解碼器(Decoder):bean名稱為feignDecoder,ResponseEntityDecoder類。

                      編碼器(Encoder):bean名稱為feignEecoder,SpringEncoder類。

                      日志(Logger): bean名稱為feignLogger,Slf4jLogger類。

                      注解翻譯器(Contract): bean名稱為feignContract,SpringMvcContract類。

                      Feign實例的創建者(Feign.Builder):bean名稱為feignBuilder,HystrixFeign.Builder類。Hystrix框架

                                                                                     將在后面章節中講述。

                      Feign客戶端(Client):bean名稱為feignClient,LoadBalancerFeignClient類。

                      Logger.Level:接口日志的記錄級別,相當於調用了Fiegn.Builder的logLevel方法,請見5.2.9章節(視頻)。

                      Retryer:重試處理器,相當於調用了Fiegn.Builder的retryer方法。

                      ErrorDecoder:異常解碼器,相當於調用了Fiegn.Builder的errorDecoder方法。

                      Request.Options:設置請求的配置項,相當於調用了Fiegn.Builder的options方法。

                      Collection<RequestInterceptor>:設置請求攔截器,相當於調用了Fiegn.Builder的requestInterceptors

                                                                            方法。下面是配置多個攔截器的樣例,並不全,只是為了便於理解,

                                                                            只是補充需要百度。

             2. feign對壓縮的支持

                 壓縮配置:

                feign.compression.request.enabled:

                設置為true時,表示開啟“請求”壓縮。

                feign.compression.response.enabled:

                設置為true時,表示開啟響應壓縮。

                feign.compression.request.mime-types:

                數據類型列表,默認值為text/xml,application/xml,application/json。

                feign.compression.request.min-request-size:

                設置請求內容的最小閥值,默認值為2048。若大於2048則開啟壓縮。

               3. feign構造多參數請求

                   看書,挺重要但是不帶要摘錄了

四  Hystrix

          1.雪崩效應

                    服務雪崩效應是一種因,服務提供者的不可用導致服務調用者的不可用,並將不可用逐漸放大的過程。

             要想防止雪崩效應,必須有一個強大的容錯機制。該容錯機制需實現以下兩點:

           (1)為網絡設置超時時間

                           必須為網絡請求設置超時。正常情況下,一個遠程調用一般在幾十毫秒內就能得到響應了。如果

                    依賴的服務不可用或者網絡有問題,那么響應時間就會變得很長(幾十秒)通常情況下,一次遠程調

                    用對應着一個線程/進程。如果響應太慢,這個線程/進程就得不到釋放。而線程/進程又對應着系統資

                    源,如果得不到釋放的線程/進程越積越多,資源就會逐漸被耗盡,最終導致服務的不可用。因此必須

                    為每個網絡請求設置超時,讓資源盡快釋放。

           (2)使用斷路器

                           如果對某個微服務的請求有大量超時(常常說明該微服務不可用),再去讓新的請求訪問該服務

                    已經沒有任何意義,只會無謂消耗資源。例如,設置了超時時間為1秒,如果短時間內有大量的請求無

                    法在1秒內得到響應,就沒有必要再去請求依賴的服務了。

                           斷路器可以實現快速失敗,如果它在一段時間內檢測到許多類似的錯誤(例如超時),就會在之

                    后的一段時間內,強迫對該服務的調用快速失敗,即不再請求所依賴的服務。這樣,應用程序就無須

                    再浪費CPU時間去等待長時間的超時。

                           斷路器也可自動診斷依賴的服務是否已經恢復正常。如果發現依賴的服務已經恢復正常,那么就

                   會恢復請求該服務。使用這種方式,就可以實現微服務的“自我修復"一當依賴的服務不正常時打開斷路

                   器時快速失敗,從而防止雪崩效應;當發現依賴的服務恢復正常時,又會恢復請求。

                   斷路器的執行邏輯:

                          --  正常情況下,斷路器關閉,可正常請求依賴的服務。

                          --  當一段時間內,請求失敗率達到一定閾值(例如錯誤率達到50%,或100次/分鍾等),斷路器

                              就會打開。此時,不會再去請求依賴的服務。

                          -- 斷路器打開一段時間后,會自動進入“半開"狀態。此時,斷路器可允許一個請求訪問依賴的服務。

                             如果該請求能夠調用成功,則關閉斷路器;否則繼續保持打開狀態。

          2. 使用Hystrix實現容錯

               翻以前博客和看書

          3. Hystrix的監控

            (1)使用/hystrix.stream端點進行監控(有用)

                     看書

            (2)使用Hystrix Dashboard可視化監控(有用)

                     看書,書上的例子並沒有把Hystrix Dashboard注冊到Eureka,生產環境可以把Hystrix Dashboard注冊

                     到Eureka上。

            (3)使用Turbine進行監控(重點)

                             由於/hystrix.stream只能看單個服務,如需要查看其它服務就需要在Hystrix Dashboard上切換要

                     監控的地址,這樣做很不方便,於是我們使用Turbine,它可將所有./hystrix.stream聚合到一個組合的

                     ./turbine.stream中,讓集群監控更加方便。

                     

                      具體使用示例看書,太繁瑣,不好摘錄

 

                    

             

 

                      

 

 

 

 

 

 

 

 

 

 

 

 

                             


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM