RestTemplate使用HttpClient连接池

news2024/11/26 0:37:56

文章目录

  • RestTemplate使用HttpClient连接池
    • ClientHttpRequestFactory
    • SimpleClientHttpRequestFactory
      • SimpleClientHttpRequestFactory 设置超时时间
      • HttpURLConnection的缺点
    • HttpComponentsClientHttpRequestFactory
      • PoolingHttpClientConnectionManager配置连接池
    • HttpClient总结图
    • 参考

RestTemplate使用HttpClient连接池

ClientHttpRequestFactory

@FunctionalInterface
public interface ClientHttpRequestFactory {

	/**
	 * Create a new {@link ClientHttpRequest} for the specified URI and HTTP method.
	 * <p>The returned request can be written to, and then executed by calling
	 * {@link ClientHttpRequest#execute()}.
	 * @param uri the URI to create a request for
	 * @param httpMethod the HTTP method to execute
	 * @return the created request
	 * @throws IOException in case of I/O errors
	 */
	ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;

}

ClientHttpRequestFactory是个函数式接口,用于根据URI和HttpMethod创建出一个ClientHttpRequest来发送请求。

/**
 * Represents a client-side HTTP request.
 * Created via an implementation of the {@link ClientHttpRequestFactory}.
 *
 * <p>A {@code ClientHttpRequest} can be {@linkplain #execute() executed},
 * receiving a {@link ClientHttpResponse} which can be read from.
 *
 * @author Arjen Poutsma
 * @since 3.0
 * @see ClientHttpRequestFactory#createRequest(java.net.URI, HttpMethod)
 */
public interface ClientHttpRequest extends HttpRequest, HttpOutputMessage {

	/**
	 * Execute this request, resulting in a {@link ClientHttpResponse} that can be read.
	 * @return the response result of the execution
	 * @throws IOException in case of I/O errors
	 */
	ClientHttpResponse execute() throws IOException;

}

ClientHttpRequest则代表客户端的HTTP请求

在这里插入图片描述

ClientHttpRequest底下的实现有HttpClient,OkHttp3,以及Java jdk内置的HttpUrlConnection

SimpleClientHttpRequestFactory

RestTemplate默认使用SimpleClientHttpRequestFactory,是Spring内置默认的实现

在这里插入图片描述

SimpleClientHttpRequestFactory创建出的ClientHttpRequest是使用Java jdk内置的HttpUrlConnection实现的。

在这里插入图片描述

SimpleClientHttpRequestFactory 设置超时时间

特别需要注意的是当我们直接new RestTemplate的时候,底层默认使用的SimpleClientHttpRequestFactory是没有设置超时时间的,而Java jdk内置的HttpUrlConnection,若readTimeout和connectTimeout没有设置,那请求是没有超时时间的,会导致请求一直pending住。

在这里插入图片描述

在这里插入图片描述

所以我们使用RestTemplate的时候务必设置上超时时间

@Bean
  public RestTemplate restTemplate(){
    RestTemplate restTemplate = new RestTemplate();
    SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
    simpleClientHttpRequestFactory.setConnectTimeout(10000);
    simpleClientHttpRequestFactory.setReadTimeout(30000);
    restTemplate.setRequestFactory(simpleClientHttpRequestFactory);
    return restTemplate;
  }

HttpURLConnection的缺点

HttpURLConnection是JDK内置的,所以它除了封装的比较简单之外还存在性能上的问题。

因为他在每一次创建请求的时候都会建立一个新的连接,所以没办法复用连接。而且如果通信异常会导致连接不被回收,进而导致创建的连接越来越多,最终导致服务卡死

在这里插入图片描述

在这里插入图片描述

HttpComponentsClientHttpRequestFactory

上面的HttpURLConnection的缺点就是我们为什么是需要使用HttpClient连接池。为了就是更好复用连接。

为什么要用连接池?
因为使用它可以有效降低延迟和系统开销。如果不采用连接池,每当我们发起http请求时,都需要重新发起Tcp三次握手建立链接,请求结束时还需要四次挥手释放链接。而Tcp链接的建立和释放是有时间和系统开销的。另外每次发起请求时,需要分配一个端口号,请求完毕后在进行回收。使用链接池则可以复用已经建立好的链接,一定程度的避免了建立和释放链接的时间开销。

在HttpClient 4.3以后增加了PoolingHttpClientConnectionManager连接池来管理持有连接,同一条TCP链路上,连接是可以复用的。HttpClient通过连接池的方式进行连接持久化(所以它这个连接池其实是tcp的连接池。它里面有一个很重要的概念:Route的概念,代表一条线路。比如baidu.com是一个route,163.com是一个route…)。
连接池:可能是http请求,也可能是https请求
加入池话技术,就不用每次发起请求都新建一个连接(每次连接握手三次,效率太低)
参考:https://blog.51cto.com/u_3631118/3121677

使用HttpClient我们首先需要引入依赖

<dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.13</version>
</dependency>

然后我们只需要把之前的SimpleClientHttpRequestFactory改成HttpComponentsClientHttpRequestFactory

@Bean
  public RestTemplate restTemplate(){
    RestTemplate restTemplate = new RestTemplate();
    HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
    restTemplate.setRequestFactory(clientHttpRequestFactory);
    return restTemplate;
  }

PoolingHttpClientConnectionManager配置连接池

配置连接池,我们就需要用到PoolingHttpClientConnectionManager

PoolingHttpClientConnectionManager的作用如下:

ClientConnectionPoolManager maintains a pool of HttpClientConnections and is able to service connection requests from multiple execution threads. Connections are pooled on a per route basis. A request for a route which already the manager has persistent connections for available in the pool will be services by leasing a connection from the pool rather than creating a brand new connection.
参考: https://hc.apache.org/httpcomponents-client-4.5.x/current/httpclient/apidocs/

大概意思就是PoolingHttpClientConnectionManager使用来维护一个连接池,能够为来自多个执行线程的连接请求提供服务。连接池是基于每个路由的(比如baidu.com是一个路由,163.com是一个路由)。对路由的请求,如果连接池中有可用的持久连接,则将通过将复用连接池中的连接,而不是创建全新的连接。

那我们在如何给RestTemplate设置HttpClient的连接池呢?

  @Bean
  public RestTemplate restTemplate(){
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(httpComponentsClientHttpRequestFactory());
    return restTemplate;
  }
  
    private HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
    HttpComponentsClientHttpRequestFactory requestFactory =
        new HttpComponentsClientHttpRequestFactory(httpClientBuilder().build());
    return requestFactory;
  }
  
  private HttpClientBuilder httpClientBuilder() {
    return HttpClients.custom()
        .setConnectionManager(poolingConnectionManager());
  }

  private PoolingHttpClientConnectionManager poolingConnectionManager() {
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
    return connectionManager;
  }

我们首先需要创建一个HttpClientBuilder,然后我们需要在创建我们的PoolingHttpClientConnectionManager,然后为HttpClientBuilder调用setConnectionManager设置连接管理器。最后我们再用这个HttpClientBuilder来初始化我们的HttpComponentsClientHttpRequestFactory

接下来我们的核心重点还是在PoolingHttpClientConnectionManager

我们可以看看PoolingHttpClientConnectionManager的默认构造方法

在这里插入图片描述

有两个很显眼的默认参数值分别是2和20,且这两个值分别对应HttpClientBuilder的两个配置参数。官方给出了解释

ClientConnectionPoolManager maintains a maximum limit of connection on a per route basis and in total. Per default this implementation will create no more than than 2 concurrent connections per given route and no more 20 connections in total.

大概意思就是PoolingHttpClientConnectionManager在默认情况下,每个路由的并发连接最大是2个,全部路由总共最大是20个。

默认配置限制的太小了,所以我们一般需要根据自己需求进行配置,如下:

connectionManager.setMaxTotal(1000); //最大连接数
connectionManager.setDefaultMaxPerRoute(500); //每个路由(域名)最大连接数

顺便一提的是,在HttpClientBuilder中也可以设置这两个参数,分别是setMaxConnPerRoutesetMaxConnTotal。但是他们都会被PoolingHttpClientConnectionManager中设置的覆盖

在这里插入图片描述

接下来我们来看看超时的配置

我们可以通过HttpComponentsClientHttpRequestFactory中的三个参数来设置超时

requestFactory.setConnectTimeout(CONNECT_TIMEOUT);
requestFactory.setConnectionRequestTimeout(CONNECT_TIMEOUT);
requestFactory.setReadTimeout(TIMEOUT);

这三个参数跟我们设置RequestConfig中是等价的。

我们可以在HttpClientBuilder中利用setDefaultRequestConfig方法设置RequestConfig。在RequestConfig一样有三个参数来配置超时。

public RequestConfig requestConfig() {
    return RequestConfig
        .custom().setConnectionRequestTimeout(CONNECT_TIMEOUT)
        .setConnectTimeout(CONNECT_TIMEOUT)
        .setSocketTimeout(TIMEOUT)
        .build();
  }

我们先来介绍RequestConfig中的这三个配置

  • setConnectionRequestTimeout: 从连接管理器请求连接时使用的超时时间(以毫秒为单位)
  • setConnectTimeout:确定连接建立之前的超时时间(以毫秒为单位)。也就是客户端发起TCP连接请求的超时时间,一般也就是TCP三次握手的时间
  • setSocketTimeout:客户端等待服务端返回数据的超时时间

这三个值默认都是-1,也就是没有超时的限制。

HttpComponentsClientHttpRequestFactory中的三个超时配置其实内部也是在配置RequestConfig的超时配置。

/**
	 * Set the connection timeout for the underlying {@link RequestConfig}.
	 * A timeout value of 0 specifies an infinite timeout.
	 * <p>Additional properties can be configured by specifying a
	 * {@link RequestConfig} instance on a custom {@link HttpClient}.
	 * <p>This options does not affect connection timeouts for SSL
	 * handshakes or CONNECT requests; for that, it is required to
	 * use the {@link org.apache.http.config.SocketConfig} on the
	 * {@link HttpClient} itself.
	 * @param timeout the timeout value in milliseconds
	 * @see RequestConfig#getConnectTimeout()
	 * @see org.apache.http.config.SocketConfig#getSoTimeout
	 */
	public void setConnectTimeout(int timeout) {
		Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
		this.requestConfig = requestConfigBuilder().setConnectTimeout(timeout).build();
	}

	/**
	 * Set the timeout in milliseconds used when requesting a connection
	 * from the connection manager using the underlying {@link RequestConfig}.
	 * A timeout value of 0 specifies an infinite timeout.
	 * <p>Additional properties can be configured by specifying a
	 * {@link RequestConfig} instance on a custom {@link HttpClient}.
	 * @param connectionRequestTimeout the timeout value to request a connection in milliseconds
	 * @see RequestConfig#getConnectionRequestTimeout()
	 */
	public void setConnectionRequestTimeout(int connectionRequestTimeout) {
		this.requestConfig = requestConfigBuilder()
				.setConnectionRequestTimeout(connectionRequestTimeout).build();
	}

	/**
	 * Set the socket read timeout for the underlying {@link RequestConfig}.
	 * A timeout value of 0 specifies an infinite timeout.
	 * <p>Additional properties can be configured by specifying a
	 * {@link RequestConfig} instance on a custom {@link HttpClient}.
	 * @param timeout the timeout value in milliseconds
	 * @see RequestConfig#getSocketTimeout()
	 */
	public void setReadTimeout(int timeout) {
		Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
		this.requestConfig = requestConfigBuilder().setSocketTimeout(timeout).build();
	}

从查看源码可以看出来HttpComponentsClientHttpRequestFactorysetConnectTimeout等价于RequestConfig中的setConnectTimeout,HttpComponentsClientHttpRequestFactorysetConnectionRequestTimeout等价于RequestConfig中的setConnectionRequestTimeout,HttpComponentsClientHttpRequestFactorysetReadTimeout等价于RequestConfig中的setSocketTimeout

但从源码上看,他们本质上设置的不是同一个RequestConfig,而是在createRequest操作的时候会进行一个merge的操作。

/**
	 * Merge the given {@link HttpClient}-level {@link RequestConfig} with
	 * the factory-level {@link RequestConfig}, if necessary.
	 * @param clientConfig the config held by the current
	 * @return the merged request config
	 * @since 4.2
	 */
	protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) {
		if (this.requestConfig == null) {  // nothing to merge
			return clientConfig;
		}

		RequestConfig.Builder builder = RequestConfig.copy(clientConfig);
		int connectTimeout = this.requestConfig.getConnectTimeout();
		if (connectTimeout >= 0) {
			builder.setConnectTimeout(connectTimeout);
		}
		int connectionRequestTimeout = this.requestConfig.getConnectionRequestTimeout();
		if (connectionRequestTimeout >= 0) {
			builder.setConnectionRequestTimeout(connectionRequestTimeout);
		}
		int socketTimeout = this.requestConfig.getSocketTimeout();
		if (socketTimeout >= 0) {
			builder.setSocketTimeout(socketTimeout);
		}
		return builder.build();
	}

从源码的注释也可以看出来,他会把HttpComponentsClientHttpRequestFactory中的RequestConfig和我们在HttpClientBuilder中设置的RequestConfig进行一个合并。

那我们继续来关注PoolingHttpClientConnectionManager, 我们可以发现PoolingHttpClientConnectionManager有如下的构造方法

public PoolingHttpClientConnectionManager(long timeToLive, TimeUnit timeUnit)

那这个timeToLive,也就是我们常说的TTL是什么意思呢?
我们可以看官网的解释如下:

Total time to live (TTL) set at construction time defines maximum life span of persistent connections regardless of their expiration setting. No persistent connection will be re-used past its TTL value.

大概意思就是在构造时设置的持久链接的存活时间(TTL),它定义了持久连接的最大使用时间。超过其TTL值的连接不会再被复用。

   /**
     * Creates new {@code PoolEntry} instance.
     *
     * @param id unique identifier of the pool entry. May be {@code null}.
     * @param route route to the opposite endpoint.
     * @param conn the connection.
     * @param timeToLive maximum time to live. May be zero if the connection
     *   does not have an expiry deadline.
     * @param timeUnit time unit.
     */
    public PoolEntry(final String id, final T route, final C conn,
            final long timeToLive, final TimeUnit timeUnit) {
        super();
        Args.notNull(route, "Route");
        Args.notNull(conn, "Connection");
        Args.notNull(timeUnit, "Time unit");
        this.id = id;
        this.route = route;
        this.conn = conn;
        this.created = System.currentTimeMillis();
        this.updated = this.created;
        if (timeToLive > 0) {
            final long deadline = this.created + timeUnit.toMillis(timeToLive);
            // If the above overflows then default to Long.MAX_VALUE
            this.validityDeadline = deadline > 0 ? deadline : Long.MAX_VALUE;
        } else {
            this.validityDeadline = Long.MAX_VALUE;
        }
        this.expiry = this.validityDeadline;
    }

从上面代码我们可以看出来当我们设置了TTL,创建PoolEntry的时候就会设置一个expiry过期时间。超过过期时间的连接就会标志为过期的。

在这里插入图片描述

所以我们设置了TTL,就相当于设置了连接最大的可用时间,超过了这个可用时间的连接,就会从池中剔除,变为不可重用。

除此之外HttpClientBuilder中也能设置TTL

   /**
     * Sets maximum time to live for persistent connections
     * <p>
     * Please note this value can be overridden by the {@link #setConnectionManager(
     *   org.apache.http.conn.HttpClientConnectionManager)} method.
     * </p>
     *
     * @since 4.4
     */
    public final HttpClientBuilder setConnectionTimeToLive(final long connTimeToLive, final TimeUnit connTimeToLiveTimeUnit) {
        this.connTimeToLive = connTimeToLive;
        this.connTimeToLiveTimeUnit = connTimeToLiveTimeUnit;
        return this;
    }

方法的注释上也说明了,这个设置会被PoolingHttpClientConnectionManager中设置的TTL覆盖。

同时官网还提到了

The handling of stale connections was changed in version 4.4. Previously, the code would check every connection by default before re-using it. The code now only checks the connection if the elapsed time since the last use of the connection exceeds the timeout that has been set. The default timeout is set to 2000ms

大概意思就是在4.4版中更改了对不可重用连接的处理。4.4之前在重用每个连接之前默认检查每个连接是否已经可重用。4.4之后是自上次使用连接以来所经过的时间超过已设置的连接不活动时间(默认连接不活动时间设置为2000ms),才检查连接。如果发现连接不可用,则从连接池剔除,在重新获取新的链接。

上面getPoolEntryBlocking的代码中,我们不是已经判断了连接是否过期,还有连接是否关闭了吗?为什么还需要判断连接是否可用?

在这里插入图片描述

我的想法是,连接过期或者是连接是否关闭并不代表连接还是能重用的,有可能连接是打开状态的,但是连接的时候存在一些问题(这种概率可能很小),所以需要作进一步的可重用检测

在这里插入图片描述

在这里插入图片描述

setValidateAfterInactivity使用来定义以毫秒为单位的连接不活动时间,在此之后,在将持久连接租给使用者之前必须重新验证。如果ValidateAfterInactivity的值小于0则表示禁用连接验证。

从上面的源码我们可以看出来,默认配置的检查时间为2s。然后我们从代码上可以看出来,再每次去创建连接的时候,会从连接池中进行连接的租赁,在去连接池获取连接的时候,会判validateAfterInactivity + 当前获取的连接上次最后的使用时间是否小于当前时间,如果小于则需要检查连接是否可用。如果检查到连接不可用,则会把当前连接从连接池中剔除,然后重新获取新的连接。

我们可以看到校验方法最后调用isStale方法

在这里插入图片描述

 /**
     * Checks whether this connection has gone down.
     * Network connections may get closed during some time of inactivity
     * for several reasons. The next time a read is attempted on such a
     * connection it will throw an IOException.
     * This method tries to alleviate this inconvenience by trying to
     * find out if a connection is still usable. Implementations may do
     * that by attempting a read with a very small timeout. Thus this
     * method may block for a small amount of time before returning a result.
     * It is therefore an <i>expensive</i> operation.
     *
     * @return  {@code true} if attempts to use this connection are
     *          likely to succeed, or {@code false} if they are likely
     *          to fail and this connection should be closed
     */
    boolean isStale();

isStale()方法是会阻塞一小段时间的,所以为什么在4.4版本之后不会每次都检查,而是超过连接不活动时间之后才会进行检查。

最后我们再来看两个配置,这两个配置能够帮助我们定期清理连接。

这两个配置都是在HttpClientBuilder中进行配置。

  • evictExpiredConnections:定时清理过期连接的开关,默认关闭,建议打开

  • evictIdleConnections:定时清理闲置连接的开关,默认关闭, 需要指定时间,建议打开

这两个分别是什么意思呢?

定时清理过期连接

其实就是清理超过TTL时间的链接,跟上面getPoolEntryBlocking代码中获取连接中会检查过期连接是一样的。我个人想法就是一个是主动清理,一个是获取连接的时候才会清理。而且一个是主动清理池中全部过期的,而另一个只是获取到池中的连接才进行清理,并不是清理全部。

定时清理闲置连接

先说 keep-alive 机制。每个 TCP 连接都要经过三次握手建立连接后才能发送数据,要经过四次挥手才能断开连接,如果每个 TCP 连接在服务端返回后都立马断开,则发起多个 HTTP 请求就要多次创建和断开 TCP,这在请求很多的情况下无疑是很耗性能的。如果在服务端返回后不立即断开 TCP 链接,而是复用这条连接进行下一次的 Http 请求,则可以省略了很多创建 断开 TCP 的开销,性能上无疑会有很大提升。虽然 keep-alive 省去了很多不必要的握手/挥手操作,但由于连接长期存活,如果没有请求的话也会浪费系统资源。所以定时清理闲置连接就是主动去清理超过指定时间都没有被使用过的连接。

我们直接上源代码来看看

在这里插入图片描述

当我们设置了evictExpiredConnections或者设置了evictIdleConnections, 就会构造一个IdleConnectionEvictor空闲连接清除器。如果没有指定maxIdleTime的话,但是有设置evictExpiredConnections的话,默认是10秒。

在这里插入图片描述

IdleConnectionEvictor中会启动一个线程,然后在指定的maxIdleTime时间之后调用connectionManager.closeExpiredConnections();connectionManager.closeIdleConnections(maxIdleTimeMs, TimeUnit.MILLISECONDS);进行连接清理。

我们先来看connectionManager.closeExpiredConnections();方法

 /**
     * Closes all expired connections in the pool.
     * <p>
     * Open connections in the pool that have not been used for
     * the timespan defined when the connection was released will be closed.
     * Currently allocated connections are not subject to this method.
     * Times will be checked with milliseconds precision.
     * </p>
     */
    void closeExpiredConnections();
@Override
    public void closeExpiredConnections() {
        this.log.debug("Closing expired connections");
        this.pool.closeExpired();
    }
 /**
     * Closes expired connections and evicts them from the pool.
     */
    public void closeExpired() {
        final long now = System.currentTimeMillis();
        enumAvailable(new PoolEntryCallback<T, C>() {

            @Override
            public void process(final PoolEntry<T, C> entry) {
                if (entry.isExpired(now)) { //超过TTL时间的会标记为过期,对于过期的连接则会进行清理
                    entry.close();
                }
            }

        });
    }

从上面的代码可以看出来,closeExpiredConnections方法会清理池中全部的过期连接,判断过期则会依据我们设置的TTL

然后我们来看connectionManager.closeIdleConnections(maxIdleTimeMs, TimeUnit.MILLISECONDS);方法

/**
     * Closes idle connections in the pool.
     * <p>
     * Open connections in the pool that have not been used for the
     * timespan given by the argument will be closed.
     * Currently allocated connections are not subject to this method.
     * Times will be checked with milliseconds precision
     * </p>
     * <p>
     * All expired connections will also be closed.
     * </p>
     *
     * @param idletime  the idle time of connections to be closed
     * @param timeUnit     the unit for the {@code idletime}
     *
     * @see #closeExpiredConnections()
     */
    void closeIdleConnections(long idletime, TimeUnit timeUnit);
@Override
   public void closeIdleConnections(final long idleTimeout, final TimeUnit timeUnit) {
       if (this.log.isDebugEnabled()) {
           this.log.debug("Closing connections idle longer than " + idleTimeout + " " + timeUnit);
       }
       this.pool.closeIdle(idleTimeout, timeUnit);
   }
/**
     * Closes connections that have been idle longer than the given period
     * of time and evicts them from the pool.
     *
     * @param idletime maximum idle time.
     * @param timeUnit time unit.
     */
    public void closeIdle(final long idletime, final TimeUnit timeUnit) {
        Args.notNull(timeUnit, "Time unit");
        long time = timeUnit.toMillis(idletime);
        if (time < 0) {
            time = 0;
        }
        final long deadline = System.currentTimeMillis() - time;
        enumAvailable(new PoolEntryCallback<T, C>() {

            @Override
            public void process(final PoolEntry<T, C> entry) {
                if (entry.getUpdated() <= deadline) {
                    entry.close();
                }
            }

        });
    }

从源码看出来,closeIdleConnections会清理池中所有的空闲连接。只要连接的上次使用时间超过了我们设置的maxIdleTime则属于空闲连接,需要清除掉。

HttpClient总结图

在这里插入图片描述

参考

RestTemplate组件:ClientHttpRequestFactory、ClientHttpRequestInterceptor、ResponseExtractor

HttpClient使用连接池

使用HttpClient的正确姿势

RestTemplate未使用线程池问题

傻傻分不清的TCP keepalive和HTTP keepalive

【348期】高并发场景下的 httpClient 使用优化

HttpClient 在vivo内销浏览器的高并发实践优化

httpClient连接池管理,你用对了?

简单粗暴的RestTemplate

Spring RestTemplate 设置每次请求的 Timeout

可视化的分析Keep-Alive长连接

restTemplate超时时间引发的生产事故

HttpClient官网

Http 持久连接与 HttpClient 连接池

httpclient参数配置

HttpClient 专题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/385085.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【教学典型案例】26.nacos的命名空间名字和id不一致

一&#xff1a;背景介绍 由于dev和localhost使用的同一个命名空间&#xff0c;我们多个服务相互调用的时候&#xff0c;由于开发人员本地启动把本地服务注册到和dev同一个命令空间下&#xff0c;导致dev环境部分功能不能使用&#xff08;在线人员列表无法加载出数据&#xff0…

DatenLord前沿技术分享 No.19

达坦科技专注于打造新一代开源跨云存储平台DatenLord&#xff0c;致力于解决多云架构、多数据中心场景下异构存储、数据统一管理需求等问题&#xff0c;以满足不同行业客户对海量数据跨云、跨数据中心高性能访问的需求。GPU编程可以大幅提升计算速度和效率&#xff0c;从而使得…

门诊患者正在流失?如何打造差异化服务,成功破局,留住患者?

无论是诊所还是医院&#xff0c;长久经营发展始终是围绕品牌、医生、技术、服务这些核心要素来进行运营。 尤其是民营医疗机构&#xff0c;要让患者满意认可&#xff0c;除了医生与诊疗技术&#xff0c;医疗服务也尤为重要。 但是很多民营医疗机构往往更注重前者&#xff0c;而…

新号涨粉22w,搞笑博主再次爆火,小红书近期创作趋势是什么?

2月借势元宵、情人节&#xff0c;小红书平台又涌现出哪些黑马博主&#xff1f;品牌在投放种草方面有何亮眼表现&#xff1f;为洞察小红书平台的内容创作趋势及品牌营销策略&#xff0c;新红推出2月月度榜单&#xff0c;从创作者及品牌两方面入手&#xff0c;解析月榜数据&#…

maven镜像源及代理配置

在公司使用网络一般需要设置代理&#xff0c; 我在idea中创建springboot工程时&#xff0c;发现依赖下载不了&#xff0c;原以为只要浏览器设置代理&#xff0c;其他的网络访问都会走代理&#xff0c;经过查资料设置了以下几个地方后工程创建正常&#xff0c;在此记录给大家参考…

分享5款用了一段时间,个人觉得非常nice的软件

大家在使用Windows办公、学习的时候&#xff0c;有没有觉得自己的电脑差了点意思&#xff1f;比如&#xff1a;电脑桌面上太杂乱、装满了各类五花八门的软件、桌面壁纸不美观等。今天&#xff0c;给大家分享五款个人用了段时间后&#xff0c;觉得非常nice的软件。 1.鼠标可视化…

【PPT】《我去!还有这种网站?》-知识点目录

《我去&#xff01;还有这种网站&#xff1f;》 1. Vega AI 输入提示&#xff1a; girl&#xff0c;粉头发2. 物理画线&#xff1a;休闲小游戏 3. Dialogue&#xff1a;影视台词搜索 4. Can you run it&#xff1a;游戏设备要求查询 5. Deviceshots&#xff1a;使用设备边…

Linux:ip addr命令分析

运行命令 [rootlocalhost ~]# ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreveri…

关于【网格结构】岛屿类问题的通用解法DFS(深度遍历)遍历框架+回溯+剪枝总结

最近在刷力扣时遇见的问题&#xff0c;自己总结加上看了力扣大佬的知识总结写下本篇文章&#xff0c;我们所熟悉的 DFS&#xff08;深度优先搜索&#xff09;问题通常是在树或者图结构上进行的。而我们今天要讨论的 DFS 问题&#xff0c;是在一种「网格」结构中进行的。岛屿问题…

FIFO IP Core

FIFO IP Core 先进先出的缓存器常常被用于数据的缓存&#xff0c;或者高速异步数据交互&#xff08;跨时钟信号传递&#xff09;和RAM和ROM的区别是没有地址线&#xff0c;无法指定地址 写时钟(Write Clock Domain)&#xff0c;读时钟写复位&#xff08;wr_rst)&#xff0c;读…

JAVA开发(史上最完整追本溯源JAVA历史、发展和学习)

(第二次世界大战1931-1945) 世界上最先进的技术往往是由于战争催生&#xff0c;在第二次世界大战中除了飞机&#xff0c;坦克和大炮的武器较量外&#xff0c;在隐秘战线的情报工作其实更为重要&#xff0c;在军队将领来往的电报中&#xff0c;为了防止军事情报的泄漏&#xff…

k8s编程operator实战之云编码平台——⑤项目完成、部署

文章目录1、效果展示2、保存用户状态和访问用户服务实现方案2.1 如何保存用户的状态2.1.1 解决保留安装的插件问题2.2 如何访问到用户在工作空间中启动的http服务2.2.1 code-server如何帮我们实现了用户程序的代理3、Operator功能实现3.1 使用KubeBuilder创建项目3.1.1 完善kin…

牛逼了!这是什么神仙面试宝典?半月看完25大专题,居然斩获阿里P7offer

这是什么神仙面试宝典&#xff1f;半月看完25大专题&#xff0c;居然斩获阿里P7offer&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;容我小小的嘚瑟一下下啦~~这份神仙面试宝典总共有25大专题&#xff1a;专题一&#xff1a;JavaOOP面…

QT基础入门目录

&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;&#x1f4a2;目录总览&#x1f4a2;&#x1f4a2;&#x1f4a2;&…

CSGO服务器插件配置教程SourceModMetaMod插件深度解析

CSGO服务器插件配置教程SourceMod&MetaMod插件深度解析 我是艾西&#xff0c;再上一篇中我详细的说了csgo社区/私人服务器的搭建过程以及需要注意的一些事项&#xff0c;那么很多小伙伴对于插件可能还是会有些疑惑&#xff0c;主流的现在就是SourceMod&MetaMod插件&…

基于Ant DesignPro Vue实现通过SpringBoot后台加载自定义菜单- 前后端分离

基于Ant DesignPro Vue实现通过SpringBoot后台加载自定义菜单- 前后端分离 本文想基于Ant DesignPro Vue构建的前端SpringBoot实现的后端接口服务&#xff0c;实现前后端分离开发和独立运行&#xff0c;业务场景是登录认证&#xff0c;认证成功后返回该用户相应权限范围内可见的…

C语言杂记(指针篇)

指针篇 指针就是地址&#xff0c;地址就是指针 指针变量就是存放地址的变量 *号只有定义的时候表示定义指针变量&#xff0c;其他表示从地址里面取内容 通过指针的方法使main函数中的data1和data2发生数据交换。 #include <stdio.h> void chang_data(int *data1,int *da…

Flutter知识点(二)处理Json

flutter不支持反射&#xff0c;所以本来很简单的事情&#xff0c;一下子变复杂了。当然官方也提供了一些工具来方便开发者。 由于Dart的map和array的数据结构和json一样&#xff0c;所以在flutter中&#xff0c;变成了json string与Map&#xff0c;array之间的砖换。 &#x…

MYSQL数据库学习

数据库的基本概念 什么是数据库&#xff1f; 数据库&#xff08;database&#xff09;是用来组织 存储和管理数据的仓库。 当今世界是一个充满着数据的互联网世界&#xff0c;充斥着大量的数据。数据的来源有很多&#xff0c;比如出行记录 消费记录 浏览的网页 发送的信息等等…

《C++ Primer》 第十一章 关联容器

《C Primer》 第十一章 关联容器 11.1 使用关联容器 使用map: //统计每个单词在输入中出现的次数 map<string, size_t> word_count;//string到size_t的空map string word; while(cin>>word)word_count[word];//提取word的计数器并将其加1 for(const auto &w:…