欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
Spring Cloud Feign設計原理

什么是Feign?

Feign 的英文表意為“假裝,偽裝,變形”, 是一個(gè)http請求調用的輕量級框架,可以以Java接口注解的方式調用Http請求,而不用像Java中通過(guò)封裝HTTP請求報文的方式直接調用。Feign通過(guò)處理注解,將請求模板化,當實(shí)際調用的時(shí)候,傳入參數,根據參數再應用到請求上,進(jìn)而轉化成真正的請求,這種請求相對而言比較直觀(guān)。
Feign被廣泛應用在Spring Cloud 的解決方案中,是學(xué)習基于Spring Cloud 微服務(wù)架構不可或缺的重要組件。
開(kāi)源項目地址:
https://github.com/OpenFeign/feign

Feign解決了什么問(wèn)題?

封裝了Http調用流程,更適合面向接口化的變成習慣
在服務(wù)調用的場(chǎng)景中,我們經(jīng)常調用基于Http協(xié)議的服務(wù),而我們經(jīng)常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,這些框架在基于自身的專(zhuān)注點(diǎn)提供了自身特性。而從角色劃分上來(lái)看,他們的職能是一致的提供Http調用服務(wù)。具體流程如下:

Feign是如何設計的?

PHASE 1. 基于面向接口的動(dòng)態(tài)代理方式生成實(shí)現類(lèi)

在使用feign 時(shí),會(huì )定義對應的接口類(lèi),在接口類(lèi)上使用Http相關(guān)的注解,標識HTTP請求參數信息,如下所示:

interface GitHub {  @RequestLine("GET /repos/{owner}/{repo}/contributors")  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);}public static class Contributor {  String login;  int contributions;}public class MyApp {  public static void main(String... args) {    GitHub github = Feign.builder()                         .decoder(new GsonDecoder())                         .target(GitHub.class, "https://api.github.com");      // Fetch and print a list of the contributors to this library.    List<Contributor> contributors = github.contributors("OpenFeign", "feign");    for (Contributor contributor : contributors) {      System.out.println(contributor.login + " (" + contributor.contributions + ")");    }  }}

在Feign 底層,通過(guò)基于面向接口的動(dòng)態(tài)代理方式生成實(shí)現類(lèi),將請求調用委托到動(dòng)態(tài)代理實(shí)現類(lèi),基本原理如下所示:

 public class ReflectiveFeign extends Feign{  ///省略部分代碼  @Override  public <T> T newInstance(Target<T> target) {    //根據接口類(lèi)和Contract協(xié)議解析方式,解析接口類(lèi)上的方法和注解,轉換成內部的MethodHandler處理方式    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();    for (Method method : target.type().getMethods()) {      if (method.getDeclaringClass() == Object.class) {        continue;      } else if(Util.isDefault(method)) {        DefaultMethodHandler handler = new DefaultMethodHandler(method);        defaultMethodHandlers.add(handler);        methodToHandler.put(method, handler);      } else {        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));      }    }    InvocationHandler handler = factory.create(target, methodToHandler);    // 基于Proxy.newProxyInstance 為接口類(lèi)創(chuàng  )建動(dòng)態(tài)實(shí)現,將所有的請求轉換給InvocationHandler 處理。    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {      defaultMethodHandler.bindTo(proxy);    }    return proxy;  }  //省略部分代碼

PHASE 2. 根據Contract協(xié)議規則,解析接口類(lèi)的注解信息,解析成內部表現:

Feign 定義了轉換協(xié)議,定義如下:

/** * Defines what annotations and values are valid on interfaces. */public interface Contract {  /**   * Called to parse the methods in the class that are linked to HTTP requests.   * 傳入接口定義,解析成相應的方法內部元數據表示   * @param targetType {@link feign.Target#type() type} of the Feign interface.   */  // TODO: break this and correct spelling at some point  List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);}
默認Contract 實(shí)現

Feign 默認有一套自己的協(xié)議規范,規定了一些注解,可以映射成對應的Http請求,如官方的一個(gè)例子:

public interface GitHub {    @RequestLine("GET /repos/{owner}/{repo}/contributors")  List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);    class Contributor {    String login;    int contributions;  }}

上述的例子中,嘗試調用GitHub.getContributors(“foo”,“myrepo”)的的時(shí)候,會(huì )轉換成如下的HTTP請求:

GET /repos/foo/myrepo/contributorsHOST XXXX.XXX.XXX

Feign 默認的協(xié)議規范

注解接口Target使用說(shuō)明
@RequestLine方法上定義HttpMethod 和 UriTemplate. UriTemplate 中使用{} 包裹的表達式,可以通過(guò)在方法參數上使用@Param 自動(dòng)注入
@Param方法參數定義模板變量,模板變量的值可以使用名稱(chēng)的方式使用模板注入解析
@Headers類(lèi)上或者方法上定義頭部模板變量,使用@Param 注解提供參數值的注入。如果該注解添加在接口類(lèi)上,則所有的請求都會(huì )攜帶對應的Header信息;如果在方法上,則只會(huì )添加到對應的方法請求上
@QueryMap方法上定義一個(gè)鍵值對或者 pojo,參數值將會(huì )被轉換成URL上的 query 字符串上
@HeaderMap方法上定義一個(gè)HeaderMap, 與 UrlTemplate 和HeaderTemplate 類(lèi)型,可以使用@Param 注解提供參數值

具體FeignContract 是如何解析的,不在本文的介紹范圍內,詳情請參考代碼:
https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Contract.java

基于Spring MVC的協(xié)議規范SpringMvcContract:

當前Spring Cloud 微服務(wù)解決方案中,為了降低學(xué)習成本,采用了Spring MVC的部分注解來(lái)完成 請求協(xié)議解析,也就是說(shuō) ,寫(xiě)客戶(hù)端請求接口和像寫(xiě)服務(wù)端代碼一樣:客戶(hù)端和服務(wù)端可以通過(guò)SDK的方式進(jìn)行約定,客戶(hù)端只需要引入服務(wù)端發(fā)布的SDK API,就可以使用面向接口的編碼方式對接服務(wù):

我們團隊內部就是按照這種思路,結合Spring Boot Starter 的特性,定義了服務(wù)端starter,
服務(wù)消費者在使用的時(shí)候,只需要引入Starter,就可以調用服務(wù)。這個(gè)比較適合平臺無(wú)關(guān)性,接口抽象出來(lái)的好處就是可以根據服務(wù)調用實(shí)現方式自有切換:

  1. 可以基于簡(jiǎn)單的Http服務(wù)調用;
  2. 可以基于Spring Cloud 微服務(wù)架構調用;
  3. 可以基于Dubbo SOA服務(wù)治理

這種模式比較適合在SaSS混合軟件服務(wù)的模式下自有切換,根據客戶(hù)的硬件能力選擇合適的方式部署,也可以基于自身的服務(wù)集群部署微服務(wù)

至于Spring Cloud 是如何實(shí)現 協(xié)議解析的,可參考代碼:
https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java

當然,目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping,@PutMapping 等,僅支持使用@RequestMapping 等,另外注解繼承性方面也有些問(wèn)題;具體限制細節,每個(gè)版本能會(huì )有些出入,可以參考上述的代碼實(shí)現,比較簡(jiǎn)單。

Spring Cloud 沒(méi)有基于Spring MVC 全部注解來(lái)做Feign 客戶(hù)端注解協(xié)議解析,個(gè)人認為這個(gè)是一個(gè)不小的坑。在剛入手Spring Cloud 的時(shí)候,就碰到這個(gè)問(wèn)題。后來(lái)是深入代碼才解決的… 這個(gè)應該有人寫(xiě)了增強類(lèi)來(lái)處理,暫且不表,先MARK一下,是一個(gè)開(kāi)源代碼練手的好機會(huì )。

PHASE 3. 基于 RequestBean,動(dòng)態(tài)生成Request

根據傳入的Bean對象和注解信息,從中提取出相應的值,來(lái)構造Http Request 對象:

PHASE 4. 使用Encoder 將Bean轉換成 Http報文正文(消息解析和轉碼邏輯)

Feign 最終會(huì )將請求轉換成Http 消息發(fā)送出去,傳入的請求對象最終會(huì )解析成消息體,如下所示:

在接口定義上Feign做的比較簡(jiǎn)單,抽象出了Encoder 和decoder 接口:

public interface Encoder {  /** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */  Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;  /**   * Converts objects to an appropriate representation in the template.   *  將實(shí)體對象轉換成Http請求的消息正文中   * @param object   what to encode as the request body.   * @param bodyType the type the object should be encoded as. {@link #MAP_STRING_WILDCARD}   *                 indicates form encoding.   * @param template the request template to populate.   * @throws EncodeException when encoding failed due to a checked exception.   */  void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;  /**   * Default implementation of {@code Encoder}.   */  class Default implements Encoder {    @Override    public void encode(Object object, Type bodyType, RequestTemplate template) {      if (bodyType == String.class) {        template.body(object.toString());      } else if (bodyType == byte[].class) {        template.body((byte[]) object, null);      } else if (object != null) {        throw new EncodeException(            format("%s is not a type supported by this encoder.", object.getClass()));      }    }  }}
public interface Decoder {  /**   * Decodes an http response into an object corresponding to its {@link   * java.lang.reflect.Method#getGenericReturnType() generic return type}. If you need to wrap   * exceptions, please do so via {@link DecodeException}.   *  從Response 中提取Http消息正文,通過(guò)接口類(lèi)聲明的返回類(lèi)型,消息自動(dòng)裝配   * @param response the response to decode    * @param type     {@link java.lang.reflect.Method#getGenericReturnType() generic return type} of   *                 the method corresponding to this {@code response}.   * @return instance of {@code type}   * @throws IOException     will be propagated safely to the caller.   * @throws DecodeException when decoding failed due to a checked exception besides IOException.   * @throws FeignException  when decoding succeeds, but conveys the operation failed.   */  Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;  /** Default implementation of {@code Decoder}. */  public class Default extends StringDecoder {    @Override    public Object decode(Response response, Type type) throws IOException {      if (response.status() == 404) return Util.emptyValueOf(type);      if (response.body() == null) return null;      if (byte[].class.equals(type)) {        return Util.toByteArray(response.body().asInputStream());      }      return super.decode(response, type);    }  }}

目前Feign 有以下實(shí)現:

Encoder/ Decoder 實(shí)現說(shuō)明
JacksonEncoder,JacksonDecoder基于 Jackson 格式的持久化轉換協(xié)議
GsonEncoder,GsonDecoder基于Google GSON 格式的持久化轉換協(xié)議
SaxEncoder,SaxDecoder基于XML 格式的Sax 庫持久化轉換協(xié)議
JAXBEncoder,JAXBDecoder基于XML 格式的JAXB 庫持久化轉換協(xié)議
ResponseEntityEncoder,ResponseEntityDecoderSpring MVC 基于 ResponseEntity< T > 返回格式的轉換協(xié)議
SpringEncoder,SpringDecoder基于Spring MVC HttpMessageConverters 一套機制實(shí)現的轉換協(xié)議 ,應用于Spring Cloud 體系中
PHASE 5. 攔截器負責對請求和返回進(jìn)行裝飾處理

在請求轉換的過(guò)程中,Feign 抽象出來(lái)了攔截器接口,用于用戶(hù)自定義對請求的操作:

public interface RequestInterceptor {  /**   * 可以在構造RequestTemplate 請求時(shí),增加或者修改Header, Method, Body 等信息   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.   */  void apply(RequestTemplate template);}

比如,如果希望Http消息傳遞過(guò)程中被壓縮,可以定義一個(gè)請求攔截器:

public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {	/**	 * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.	 *	 * @param properties the encoding properties	 */	protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {		super(properties);	}	/**	 * {@inheritDoc}	 */	@Override	public void apply(RequestTemplate template) {		//  在Header 頭部添加相應的數據信息		addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,				HttpEncoding.DEFLATE_ENCODING);	}}
PHASE 6. 日志記錄

在發(fā)送和接收請求的時(shí)候,Feign定義了統一的日志門(mén)面來(lái)輸出日志信息 , 并且將日志的輸出定義了四個(gè)等級:

級別說(shuō)明
NONE不做任何記錄
BASIC只記錄輸出Http 方法名稱(chēng)、請求URL、返回狀態(tài)碼和執行時(shí)間
HEADERS記錄輸出Http 方法名稱(chēng)、請求URL、返回狀態(tài)碼和執行時(shí)間 和 Header 信息
FULL記錄Request 和Response的Header,Body和一些請求元數據
public abstract class Logger {  protected static String methodTag(String configKey) {    return new StringBuilder().append('[').append(configKey.substring(0, configKey.indexOf('(')))        .append("] ").toString();  }  /**   * Override to log requests and responses using your own implementation. Messages will be http   * request and response text.   *   * @param configKey value of {@link Feign#configKey(Class, java.lang.reflect.Method)}   * @param format    {@link java.util.Formatter format string}   * @param args      arguments applied to {@code format}   */  protected abstract void log(String configKey, String format, Object... args);  protected void logRequest(String configKey, Level logLevel, Request request) {    log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());    if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {      for (String field : request.headers().keySet()) {        for (String value : valuesOrEmpty(request.headers(), field)) {          log(configKey, "%s: %s", field, value);        }      }      int bodyLength = 0;      if (request.body() != null) {        bodyLength = request.body().length;        if (logLevel.ordinal() >= Level.FULL.ordinal()) {          String              bodyText =              request.charset() != null ? new String(request.body(), request.charset()) : null;          log(configKey, ""); // CRLF          log(configKey, "%s", bodyText != null ? bodyText : "Binary data");        }      }      log(configKey, "---> END HTTP (%s-byte body)", bodyLength);    }  }  protected void logRetry(String configKey, Level logLevel) {    log(configKey, "---> RETRYING");  }  protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response,                                            long elapsedTime) throws IOException {    String reason = response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ?        " " + response.reason() : "";    int status = response.status();    log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);    if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {      for (String field : response.headers().keySet()) {        for (String value : valuesOrEmpty(response.headers(), field)) {          log(configKey, "%s: %s", field, value);        }      }      int bodyLength = 0;      if (response.body() != null && !(status == 204 || status == 205)) {        // HTTP 204 No Content "...response MUST NOT include a message-body"        // HTTP 205 Reset Content "...response MUST NOT include an entity"        if (logLevel.ordinal() >= Level.FULL.ordinal()) {          log(configKey, ""); // CRLF        }        byte[] bodyData = Util.toByteArray(response.body().asInputStream());        bodyLength = bodyData.length;        if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) {          log(configKey, "%s", decodeOrDefault(bodyData, UTF_8, "Binary data"));        }        log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);        return response.toBuilder().body(bodyData).build();      } else {        log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);      }    }    return response;  }  protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {    log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(),        elapsedTime);    if (logLevel.ordinal() >= Level.FULL.ordinal()) {      StringWriter sw = new StringWriter();      ioe.printStackTrace(new PrintWriter(sw));      log(configKey, sw.toString());      log(configKey, "<--- END ERROR");    }    return ioe;  }
PHASE 7 . 基于重試器發(fā)送HTTP請求

Feign 內置了一個(gè)重試器,當HTTP請求出現IO異常時(shí),Feign會(huì )有一個(gè)最大嘗試次數發(fā)送請求,以下是Feign核心
代碼邏輯:

final class SynchronousMethodHandler implements MethodHandler {  // 省略部分代碼  @Override  public Object invoke(Object[] argv) throws Throwable {   //根據輸入參數,構造Http 請求。    RequestTemplate template = buildTemplateFromArgs.create(argv);    // 克隆出一份重試器    Retryer retryer = this.retryer.clone();    // 嘗試最大次數,如果中間有結果,直接返回    while (true) {      try {        return executeAndDecode(template);      } catch (RetryableException e) {        retryer.continueOrPropagate(e);        if (logLevel != Logger.Level.NONE) {          logger.logRetry(metadata.configKey(), logLevel);        }        continue;      }    }  }

重試器有如下幾個(gè)控制參數:

重試參數說(shuō)明默認值
period初始重試時(shí)間間隔,當請求失敗后,重試器將會(huì )暫停 初始時(shí)間間隔(線(xiàn)程 sleep 的方式)后再開(kāi)始,避免強刷請求,浪費性能100ms
maxPeriod當請求連續失敗時(shí),重試的時(shí)間間隔將按照:long interval = (long) (period * Math.pow(1.5, attempt - 1)); 計算,按照等比例方式延長(cháng),但是最大間隔時(shí)間為 maxPeriod, 設置此值能夠避免 重試次數過(guò)多的情況下執行周期太長(cháng)1000ms
maxAttempts最大重試次數5

具體的代碼實(shí)現可參考:
https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Retryer.java

PHASE 8. 發(fā)送Http請求

Feign 真正發(fā)送HTTP請求是委托給 feign.Client 來(lái)做的:

public interface Client {  /**   * Executes a request against its {@link Request#url() url} and returns a response.   *  執行Http請求,并返回Response   * @param request safe to replay.   * @param options options to apply to this request.   * @return connected response, {@link Response.Body} is absent or unread.   * @throws IOException on a network error connecting to {@link Request#url()}.   */  Response execute(Request request, Options options) throws IOException;  }

Feign 默認底層通過(guò)JDK 的 java.net.HttpURLConnection 實(shí)現了feign.Client接口類(lèi),在每次發(fā)送請求的時(shí)候,都會(huì )創(chuàng )建新的HttpURLConnection 鏈接,這也就是為什么默認情況下Feign的性能很差的原因??梢酝ㄟ^(guò)拓展該接口,使用Apache HttpClient 或者OkHttp3等基于連接池的高性能Http客戶(hù)端,我們項目?jì)炔渴褂玫木褪荗kHttp3作為Http 客戶(hù)端。

如下是Feign 的默認實(shí)現,供參考:

public static class Default implements Client {    private final SSLSocketFactory sslContextFactory;    private final HostnameVerifier hostnameVerifier;    /**     * Null parameters imply platform defaults.     */    public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {      this.sslContextFactory = sslContextFactory;      this.hostnameVerifier = hostnameVerifier;    }    @Override    public Response execute(Request request, Options options) throws IOException {      HttpURLConnection connection = convertAndSend(request, options);      return convertResponse(connection).toBuilder().request(request).build();    }    HttpURLConnection convertAndSend(Request request, Options options) throws IOException {      final HttpURLConnection          connection =          (HttpURLConnection) new URL(request.url()).openConnection();      if (connection instanceof HttpsURLConnection) {        HttpsURLConnection sslCon = (HttpsURLConnection) connection;        if (sslContextFactory != null) {          sslCon.setSSLSocketFactory(sslContextFactory);        }        if (hostnameVerifier != null) {          sslCon.setHostnameVerifier(hostnameVerifier);        }      }      connection.setConnectTimeout(options.connectTimeoutMillis());      connection.setReadTimeout(options.readTimeoutMillis());      connection.setAllowUserInteraction(false);      connection.setInstanceFollowRedirects(true);      connection.setRequestMethod(request.method());      Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);      boolean          gzipEncodedRequest =          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);      boolean          deflateEncodedRequest =          contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);      boolean hasAcceptHeader = false;      Integer contentLength = null;      for (String field : request.headers().keySet()) {        if (field.equalsIgnoreCase("Accept")) {          hasAcceptHeader = true;        }        for (String value : request.headers().get(field)) {          if (field.equals(CONTENT_LENGTH)) {            if (!gzipEncodedRequest && !deflateEncodedRequest) {              contentLength = Integer.valueOf(value);              connection.addRequestProperty(field, value);            }          } else {            connection.addRequestProperty(field, value);          }        }      }      // Some servers choke on the default accept string.      if (!hasAcceptHeader) {        connection.addRequestProperty("Accept", "*/*");      }      if (request.body() != null) {        if (contentLength != null) {          connection.setFixedLengthStreamingMode(contentLength);        } else {          connection.setChunkedStreamingMode(8196);        }        connection.setDoOutput(true);        OutputStream out = connection.getOutputStream();        if (gzipEncodedRequest) {          out = new GZIPOutputStream(out);        } else if (deflateEncodedRequest) {          out = new DeflaterOutputStream(out);        }        try {          out.write(request.body());        } finally {          try {            out.close();          } catch (IOException suppressed) { // NOPMD          }        }      }      return connection;    }    Response convertResponse(HttpURLConnection connection) throws IOException {      int status = connection.getResponseCode();      String reason = connection.getResponseMessage();      if (status < 0) {        throw new IOException(format("Invalid status(%s) executing %s %s", status,            connection.getRequestMethod(), connection.getURL()));      }      Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();      for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {        // response message        if (field.getKey() != null) {          headers.put(field.getKey(), field.getValue());        }      }      Integer length = connection.getContentLength();      if (length == -1) {        length = null;      }      InputStream stream;      if (status >= 400) {        stream = connection.getErrorStream();      } else {        stream = connection.getInputStream();      }      return Response.builder()              .status(status)              .reason(reason)              .headers(headers)              .body(stream, length)              .build();    }  }

Feign 的性能怎么樣?

Feign 整體框架非常小巧,在處理請求轉換和消息解析的過(guò)程中,基本上沒(méi)什么時(shí)間消耗。真正影響性能的,是處理Http請求的環(huán)節。
如上所述,由于默認情況下,Feign采用的是JDK的HttpURLConnection,所以整體性能并不高,剛開(kāi)始接觸Spring Cloud 的同學(xué),如果沒(méi)注意這些細節,可能會(huì )對Spring Cloud 有很大的偏見(jiàn)。
我們項目?jì)炔渴褂玫氖荗kHttp3 作為連接客戶(hù)端。
系統的壓測方案后續在貼出來(lái),有興趣的同學(xué)可以持續關(guān)注~

另外作者已開(kāi)通微信訂閱號,精品文章同步更新,歡迎關(guān)注~

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
查看新聞/公告
CookieUtils —— Cookie 工具類(lèi)
.net 完美操作cookies
struts2代碼分析
[Java教程]spring mvc 實(shí)現網(wǎng)站登錄與非登錄的控制
【C#】工具類(lèi)-FTP操作封裝類(lèi)FTPHelper
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久