RestTemplate使用HttpClient连接池
文章目录
- 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的时候务必设置上超时时间
@Beanpublic 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
@Beanpublic 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
的连接池呢?
@Beanpublic 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
中也可以设置这两个参数,分别是setMaxConnPerRoute
和setMaxConnTotal
。但是他们都会被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();}
从查看源码可以看出来HttpComponentsClientHttpRequestFactory
中setConnectTimeout
等价于RequestConfig
中的setConnectTimeout
,HttpComponentsClientHttpRequestFactory
中setConnectionRequestTimeout
等价于RequestConfig
中的setConnectionRequestTimeout
,HttpComponentsClientHttpRequestFactory
中setReadTimeout
等价于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 mergereturn 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_VALUEthis.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();
@Overridepublic 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>() {@Overridepublic 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);
@Overridepublic 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>() {@Overridepublic 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 专题
相关文章:

RestTemplate使用HttpClient连接池
文章目录RestTemplate使用HttpClient连接池ClientHttpRequestFactorySimpleClientHttpRequestFactorySimpleClientHttpRequestFactory 设置超时时间HttpURLConnection的缺点HttpComponentsClientHttpRequestFactoryPoolingHttpClientConnectionManager配置连接池HttpClient总结…...

Python 操作Redis
在 Python中我们使用 redis库来操作 Redis数据库。Redis数据库的使用命令这里就不介绍了。 需要安装 redis库。检查是否安装redis: pip redis 如果未安装,使用 pip命令安装 redis。 pip install redis #安装最新版本 一、Redis连接 Redis提供两个类 Re…...

CEC2020:鱼鹰优化算法(Osprey optimization algorithm,OOA)求解CEC2020(提供MATLAB代码
一、鱼鹰优化算法简介 鱼鹰优化算法(Osprey optimization algorithm,OOA)由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出,其模拟鱼鹰的捕食行为。 鱼鹰是鹰形目、鹗科、鹗属的仅有的一种中型猛禽。雌雄相似。体长51-64厘米…...
词对齐 - MGIZA++
文章目录关于 MGIZAgiza-py安装 MGIZA命令说明mkclsd4normhmmnormplain2sntsnt2coocsnt2coocrmpsnt2plainsymalmgizageneral parameters:No. of iterations:parameter for various heuristics in GIZA for efficient training:parameters for describing the type and amount o…...

GUI 之 Tkinter编程
GUI 图形界面,Tkinter 是 Python 内置的 GUI 库,IDLE 就是 Tkinter 设计的。 1. Tkinter 之初体验 import tkinter as tkroot tk.Tk() # 创建一个窗口root.title(窗口标题)# 添加 label 组件 theLabel tk.Label(root, text文本内容) theLabel.p…...

【软件测试】性能测试面试题都问什么?面试官想要什么?回答惊险避坑......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 1、你认为不同角色关…...
后端开发基础能力以及就Java的主流开发框架介绍
前言:java语言开发转后端,必须了解后端主流的一些东西,共勉。 后端开发需要具备以下基础能力: 1.编程语言:熟练掌握至少一门编程语言,如Java、Python、Ruby、PHP、C#等。 2.数据结构和算法:具…...

H2数据库连接时用户密码错误:Wrong user name or password [28000-214] 28000/28000 (Help)
H2数据库连接时用户密码错误: 2023-03-03 08:25:07 database: wrong user or password; user: "SA" org.h2.message.DbException: Wrong user name or password [28000-214]出现的问题配置信息原因解决办法org.h2.message.DbException: Wrong user name or password …...

青岛诺凯达机械盛装亮相2023济南生物发酵展,3月与您相约
BIO CHINA生物发酵展,作为生物发酵产业一年一度行业盛会,由中国生物发酵产业协会主办,上海信世展览服务有限公司承办,2023第10届国际生物发酵展(济南)于2023年3月30-4月1日在山东国际会展中心(济…...

【JAVA程序设计】【C00111】基于SSM的网上图书商城管理系统——有文档
基于SSM的网上图书商城管理系统——有文档项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架开发的网上在线图书售卖商城项目,本项目分为三种权限:系统管理员、卖家、买家 管理员角色包含以下功能: 用户信息管理、权限管理、订…...

基于卷积神经网络CNN的三相故障识别
目录 背影 卷积神经网络CNN的原理 卷积神经网络CNN的定义 卷积神经网络CNN的神经元 卷积神经网络CNN的激活函数 卷积神经网络CNN的传递函数 卷积神经网络CNN手写体识别 基本结构 主要参数 MATALB代码 结果图 展望 背影 现在生活,为节能减排,减少电能损…...

Java工厂设计模式详解,大厂的Java抽象工厂模式分享!
我是好程序员-小源!本期文章主要给大家分享:Java工厂设计模式。文中使用通俗易懂的案例,使你快速学习和轻松上手!一、什么是Java抽象工厂模式1. Java抽象工厂是23种设计模式中创建型模式的一种,Java抽象工厂是由多个工…...

Git 企业级分支提交流程
Git 企业级分支提交流程 首先在本地分支hfdev上进行开发,开发后要经过测试。 如果测试通过了,那么久可以合并到本地分支develop,合并之后hfdev和development应该完全一样。 git add 文件 git commit -m ‘注释’ git checkout develop //切换…...

C/C++每日一练(20230303)
目录 1. 字符串相乘 2. 单词拆分 II 3. 串联所有单词的子串 1. 字符串相乘 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 示例 1: 输入: num1 "2", num2 "3"…...

Python3-条件控制
Python3 条件控制 Python 条件语句是通过一条或多条语句的执行结果(True 或者 False)来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程: 代码执行过程: if 语句 Python中if语句的一般形式如下所示: if condi…...

KDZD地埋电缆故障测试仪
一、产品特性 ★电缆故障测试仪(闪测仪) (1)使用范围广:用于测量各种不同截面、不同介质的各种电力电缆、高频同轴电缆,市话电缆及两根以上均匀铺设的地埋电线等电缆高低阻、短路、开路、断线以及高阻泄漏…...

爆款升级!新系列南卡Neo最强旗舰杀到,业内首款无线充骨传导耳机!
中国专业骨传导耳机品牌NANK南卡于近日发布了全新南卡Neo骨传导运动耳机,打造一款佩戴最舒适、音质体验最好的骨传导耳机。推出第2代声学響科技技术,提供更优质的开放式骨传导听音体验,透过不一样的音质体验,打造更好的骨传导耳机…...
基于Spring Boot+Thymeleaf的在线投票系统
文章目录 项目介绍主要功能截图:后台登录注册个人信息展示投票数据显示首页展示对战匹配分数排行榜部分代码展示设计总结项目获取方式🍅 作者主页:Java韩立 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅…...
【每日一题Day135】LC1487保证文件名唯一 | 哈希表
保证文件名唯一【LC1487】 给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。 由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用࿰…...

计算机系统的基本组成 第一节
一、计算机系统 计算机系统是指:电子数字通用、计算机系统 由硬件和软件两个子系统组成 硬件是保存和运行软件的物质基础 软件是指挥硬件完成预期功能的智力部分 重点: 计算机系统部件 五个 1、数据运算部件:完成对数据的运算处理功能…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...