一 从使用开始

0.依赖引入

implementation ("com.squareup.okhttp3:okhttp:3.14.7")

1.创建OkHttpClient实例

  • 方式一:直接使用默认配置的Builder

//从源码可以看出,当我们直接new创建OkHttpClient实例时,会默认给我们配置好一个Builder
OkHttpClient okHttpClient = new OkHttpClient();
​
//源码
public OkHttpClient() {this(new Builder());}public Builder() {dispatcher = new Dispatcher();protocols = DEFAULT_PROTOCOLS;connectionSpecs = DEFAULT_CONNECTION_SPECS;eventListenerFactory = EventListener.factory(EventListener.NONE);proxySelector = ProxySelector.getDefault();if (proxySelector == null) {proxySelector = new NullProxySelector();}cookieJar = CookieJar.NO_COOKIES;socketFactory = SocketFactory.getDefault();hostnameVerifier = OkHostnameVerifier.INSTANCE;certificatePinner = CertificatePinner.DEFAULT;proxyAuthenticator = Authenticator.NONE;authenticator = Authenticator.NONE;connectionPool = new ConnectionPool();dns = Dns.SYSTEM;followSslRedirects = true;followRedirects = true;retryOnConnectionFailure = true;callTimeout = 0;connectTimeout = 10_000;readTimeout = 10_000;writeTimeout = 10_000;pingInterval = 0;}
  • 方式二:自定义配置Builder

OkHttpClient client = new OkHttpClient().newBuilder().addInterceptor(new MyCookieInterceptor()).addNetworkInterceptor(new MyNetWorkInterceptor()).readTimeout(3000, TimeUnit.SECONDS).build();

2.创建Request实例

Request request = new Request.Builder().url("https://www.xxx.com").build();

3.创建Call请求,发起同步或异步网络请求

 //同步请求Response execute = client.newCall(request).execute();//异步请求
client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {//请求响应失败回调}
​@Overridepublic void onResponse(Call call, Response response) throws IOException {//请求响应成功回调}});

二 责任链的构建剖析

1.责任链模式介绍

什么是责任链模式?

一个请求沿着一条“链”传递,直到该“链”上的某个处理者处理它为止。

OkHttp中的责任链是如何体现的?

将一个网络请求Request沿着一条由多个拦截器组成的“链”顺序向后传递,若拦截器无法完全处理请求则将请求继续向后传递,直到某个拦截器完全处理它并返回Respose结果(不一定是成功的),再依据顺序层层向前传递。

2.OkHttp中的责任链模式剖析

前面我们已经了解了OkHttp的使用,接下来我们就从OkHttp进行同步请求的代码入手,来剖析其责任链在怎么构建起来的。

//同步请求Response execute = client.newCall(request).execute();

同步请求部分的代码如上,ctrl+鼠标左键点击newCall进入OkHttp的源码,向下追溯,最终发现调用到了RealCall的静态方法,将OkHttpClient对象和Request对象作为参数传递给了RealCall构造方法,构建了一个RealCall对象并返回。

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {// Safely publish the Call instance to the EventListener.RealCall call = new RealCall(client, originalRequest, forWebSocket);call.transmitter = new Transmitter(client, call);return call;
}

再回到同步请求的代码

//同步请求Response execute = client.newCall(request).execute();

这次我们ctrl+鼠标左键点击execute查看同步请求的代码逻辑,此时发现走到了Call接口,execute()方法没有方法体,没关系,我们在Call接口中ctrl+h,就可以看到Call接口的继承树啦

//Call
Response execute() throws IOException;

而后你就会发现Call下只有一个子类也就是我们刚刚看过的RealCall,在RealCall中搜索execute()方法,就可以看到实际执行的到的execute()方法如下

@Override public Response execute() throws IOException {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}transmitter.timeoutEnter();transmitter.callStart();try {client.dispatcher().executed(this);return getResponseWithInterceptorChain();} finally {client.dispatcher().finished(this);}
}

此方法的作用主要有三个:(transmitter我们暂不关注,它的作用主要是管理Call的生命周期,关心的宝子自行了解哈)

  • 检查同步请求是否已经被执行过

  • client.dispatcher().executed(this),将同步请求存入ArrayDeque中,以实现发起多个同步请求时,按顺序进行同步请求

  • getResponseWithInterceptorChain(),创建责任链并执行,获取同步请求返回结果

我们进入client.dispatcher().executed(this)的executed(this)方法,可以看到如下代码:

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
​
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {runningSyncCalls.add(call);
}

可以看到在同步请求中,此方法的作用仅为记录同步请求

接下来我们进入getResponseWithInterceptorChain()方法,OkHttp责任链构建和开启就开始于这个方法:

//RealCall.class
​
Response getResponseWithInterceptorChain() throws IOException {// Build a full stack of interceptors.//创建一个拦截器集合List<Interceptor> interceptors = new ArrayList<>();/**将用户即我们自己定义的拦截器加入到此集合中,即我们刚刚在自定义配置Builder中调用.addInterceptor(new         MyCookieInterceptor())传递进来的拦截器**/interceptors.addAll(client.interceptors());//加入一些默认的内置拦截器//负责失败重试以及重定向的拦截器interceptors.add(new RetryAndFollowUpInterceptor(client));//负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的拦截器interceptors.add(new BridgeInterceptor(client.cookieJar()));//负责读取缓存直接返回、更新缓存的拦截器interceptors.add(new CacheInterceptor(client.internalCache()));//负责和服务器建立连接的拦截器interceptors.add(new ConnectInterceptor(client));//判断当前请求是 WebSocket 握手请求 还是HTTP或HTTPS请求if (!forWebSocket) {/**是HTTP或HTTPS请求才添加用户在自定义配置Builder中调用addNetworkInterceptor(new MyNetWorkInterceptor())配置进来的网络拦截器,因为网络拦截器是专门为处理 HTTP 网络请求而生的,为WebSocket类型的请求设置此拦截器是无意义的,甚至可能因拦截 HTTP 帧而干扰 WebSocket 协议的正常交互**/interceptors.addAll(client.networkInterceptors());}//负责向服务器发送请求数据、从服务器读取响应数据的拦截器(此为最后一个执行的拦截器)interceptors.add(new CallServerInterceptor(forWebSocket));/**构建责任链chain,将我们刚刚的拦截器集合interceptors,原始的请求originalRequest(也就是我们创建的Request对象),此类自身的  实例传入(此处我们只关心这三个参数就好啦)**/Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,originalRequest, this, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());boolean calledNoMoreExchanges = false;try {//调用proceed()方法将原始请求传递进去开启责任链Response response = chain.proceed(originalRequest);if (transmitter.isCanceled()) {closeQuietly(response);throw new IOException("Canceled");}return response;} catch (IOException e) {calledNoMoreExchanges = true;throw transmitter.noMoreExchanges(e);} finally {if (!calledNoMoreExchanges) {transmitter.noMoreExchanges(null);}}
}

上面我们分析了RealCall的getResponseWithInterceptorChain()方法,最后走到了Interceptor.Chain的proceed()方法,让我们来分析这部分源码吧

ctrl+鼠标左键点击proceed()方法,可以查看到如下源码:

/*** Observes, modifies, and potentially short-circuits requests going out and the corresponding* responses coming back in. Typically interceptors add, remove, or transform headers on the request* or response.*/
public interface Interceptor {Response intercept(Chain chain) throws IOException;
​interface Chain {Request request();
​Response proceed(Request request) throws IOException;
​/*** Returns the connection the request will be executed on. This is only available in the chains* of network interceptors; for application interceptors this is always null.*/@Nullable Connection connection();
​Call call();
​int connectTimeoutMillis();
​Chain withConnectTimeout(int timeout, TimeUnit unit);
​int readTimeoutMillis();
​Chain withReadTimeout(int timeout, TimeUnit unit);
​int writeTimeoutMillis();
​Chain withWriteTimeout(int timeout, TimeUnit unit);}
}

可以看到,proceed()方法在Interceptor接口中的Chain接口中,没有具体实现,所以我们依旧在Chain接口中ctrl+h,查看Chain接口的继承树,而后我们就又可以发现,它又是只有一个子类 RealInterceptorChain,那proceed()方法的具体实现就必然在 RealInterceptorChain中啦,如下:

//RealInterceptorChain.class
//此方法负责驱动拦截器链的执行(OkHttp 的拦截器链是核心设计,用于处理请求、响应的各种中间逻辑,如重试、缓存、网络连接等)
//每个拦截器(除最后一个)处理完毕后(但未完全处理完毕)都会回调回这个方法,在此方法中将请求交由下一个拦截器处理
@Override public Response proceed(Request request) throws IOException {//可以看到这里调用了RealInterceptorChain类中自己实现的同名proceed方法return proceed(request, transmitter, exchange);
}
​
//也就是说,这里就是proceed方法的具体实现了
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)throws IOException {
//index变量的作用是控制拦截器的顺序执行,OkHttp的责任链就是依靠此变量来实现顺序执行的,index表示拦截器集合interceptors的索引//此处进行拦截器索引合法性校验if (index >= interceptors.size()) throw new AssertionError();/**通过记录代码调用执行到此处的次数,以统计当前拦截器链的调用次数,因为我们上面说了,每个拦截器(除最后一个)处理完毕后(但未完全处理完毕)都会回调回这个方法,故可以这样来统计**/calls++;//验证网络拦截器(Network Interceptor)是否修改了请求的 Host(主机)或 Port(端口)/**因为网络拦截器的职责是在网络请求建立之前和之后处理逻辑(例如,处理重定向、重试、缓存等)。一个已经建立的 TCP 连接是和特定的主机与端口绑定的。如果拦截器修改了这些信息,那么这个连接就无法被重用,这会导致不必要的连接开销,并且破坏了拦截器链的逻辑完整性。**/// If we already have a stream, confirm that the incoming request will use it.if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)+ " must retain the same host and port");}//确保每个网络拦截器只调用一次 chain.proceed() 方法// If we already have a stream, confirm that this is the only call to chain.proceed().if (this.exchange != null && calls > 1) {throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)+ " must call proceed() exactly once");}//创建下一个(index + 1)拦截器 链对象// Call the next interceptor in the chain.RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,index + 1, request, call, connectTimeout, readTimeout, writeTimeout);//通过index获取到当前拦截器对象Interceptor interceptor = interceptors.get(index);/**用当前拦截器对象调用intercept(next),将下一个拦截器 链对象传递进去,此方法为每个拦截器都有的,拦截器对请求的处理都在这个方法中进行,好了接下来就可以跳到下面看这个方法的源码了**/Response response = interceptor.intercept(next);
​// Confirm that the next interceptor made its required call to chain.proceed().if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {throw new IllegalStateException("network interceptor " + interceptor+ " must call proceed() exactly once");}
​// Confirm that the intercepted response isn't null.if (response == null) {throw new NullPointerException("interceptor " + interceptor + " returned null");}
​if (response.body() == null) {throw new IllegalStateException("interceptor " + interceptor + " returned a response with no body");}
​return response;
}

下面,我们以BridgeInterceptor拦截器中的intercept方法为例,来捋一下intercept方法的执行流程

//BridgeInterceptor.class  
​
@Override public Response intercept(Chain chain) throws IOException {//通过拦截器链对象获取到请求对象Request userRequest = chain.request();Request.Builder requestBuilder = userRequest.newBuilder();/**下面就是,对请求对象进行此拦截器的负责的一系列请求前处理,由于本章我们只关注责任链模式的整体构建,故下面的拦截器处理逻辑,我们暂不做过多的解析**/RequestBody body = userRequest.body();if (body != null) {MediaType contentType = body.contentType();if (contentType != null) {requestBuilder.header("Content-Type", contentType.toString());}
​long contentLength = body.contentLength();if (contentLength != -1) {requestBuilder.header("Content-Length", Long.toString(contentLength));requestBuilder.removeHeader("Transfer-Encoding");} else {requestBuilder.header("Transfer-Encoding", "chunked");requestBuilder.removeHeader("Content-Length");}}
​if (userRequest.header("Host") == null) {requestBuilder.header("Host", hostHeader(userRequest.url(), false));}
​if (userRequest.header("Connection") == null) {requestBuilder.header("Connection", "Keep-Alive");}
​// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing// the transfer stream.boolean transparentGzip = false;if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {transparentGzip = true;requestBuilder.header("Accept-Encoding", "gzip");}
​List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());if (!cookies.isEmpty()) {requestBuilder.header("Cookie", cookieHeader(cookies));}
​if (userRequest.header("User-Agent") == null) {requestBuilder.header("User-Agent", Version.userAgent());}//到此处,此拦截器负责的,请求前的处理已经完成/**继续通过传进来的责任链对象chain调用proceed()方法,将处理后的请求传回此方法,如此形成一个链路使请求能够通过责任链上的拦截器能有条不紊的向后执行:除最后一个拦截器的每个拦截器在其intercept(Chain chain)方法中进行请求的前置处理完毕后都会将处理完的请求传回到RealInterceptorChain的proceed()方法,在proceed()方法中通过index来获取到下一个要执行的拦截器对象,调用到对应的intercept(Chain chain)方法,直到到调用到最后一个拦截器对象的intercept(Chain chain)方法时,也就是CallServerInterceptor的intercept(Chain chain)方法,才会真正发起网络请求,并最终返回一个Response对象,而后返回其调用处,一层层回调回去,直至最初的调用的proceed()方法。**/Response networkResponse = chain.proceed(requestBuilder.build());
​HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
​Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);
​if (transparentGzip&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))&& HttpHeaders.hasBody(networkResponse)) {GzipSource responseBody = new GzipSource(networkResponse.body().source());Headers strippedHeaders = networkResponse.headers().newBuilder().removeAll("Content-Encoding").removeAll("Content-Length").build();responseBuilder.headers(strippedHeaders);String contentType = networkResponse.header("Content-Type");responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));}
​return responseBuilder.build();}

接下来我们来看最后一个拦截器的intercept(Chain chain)方法逻辑

//CallServerInterceptor.class  
@Override public Response intercept(Chain chain) throws IOException {//与其他拦截器一样通过拦截器链对象获取到请求对象RealInterceptorChain realChain = (RealInterceptorChain) chain;Exchange exchange = realChain.exchange();Request request = realChain.request();
​long sentRequestMillis = System.currentTimeMillis();
​exchange.writeRequestHeaders(request);//而后就是进行一些前置处理后直接发起网络请求了boolean responseHeadersStarted = false;Response.Builder responseBuilder = null;if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100// Continue" response before transmitting the request body. If we don't get that, return// what we did get (such as a 4xx response) without ever transmitting the request body.if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {exchange.flushRequest();responseHeadersStarted = true;exchange.responseHeadersStart();responseBuilder = exchange.readResponseHeaders(true);}
​if (responseBuilder == null) {if (request.body().isDuplex()) {// Prepare a duplex body so that the application can send a request body later.exchange.flushRequest();BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, true));request.body().writeTo(bufferedRequestBody);} else {// Write the request body if the "Expect: 100-continue" expectation was met.BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, false));request.body().writeTo(bufferedRequestBody);bufferedRequestBody.close();}} else {exchange.noRequestBody();if (!exchange.connection().isMultiplexed()) {// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection// from being reused. Otherwise we're still obligated to transmit the request body to// leave the connection in a consistent state.exchange.noNewExchangesOnConnection();}}} else {exchange.noRequestBody();}
​if (request.body() == null || !request.body().isDuplex()) {exchange.finishRequest();}
​if (!responseHeadersStarted) {exchange.responseHeadersStart();}
​if (responseBuilder == null) {responseBuilder = exchange.readResponseHeaders(false);}
​Response response = responseBuilder.request(request).handshake(exchange.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();
​int code = response.code();if (code == 100) {// server sent a 100-continue even though we did not request one.// try again to read the actual responseresponse = exchange.readResponseHeaders(false).request(request).handshake(exchange.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();
​code = response.code();}
​exchange.responseHeadersEnd(response);
​if (forWebSocket && code == 101) {// Connection is upgrading, but we need to ensure interceptors see a non-null response body.response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();} else {response = response.newBuilder().body(exchange.openResponseBody(response)).build();}
​if ("close".equalsIgnoreCase(response.request().header("Connection"))|| "close".equalsIgnoreCase(response.header("Connection"))) {exchange.noNewExchangesOnConnection();}
​if ((code == 204 || code == 205) && response.body().contentLength() > 0) {throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());}//网络请求完毕将处理完毕的结果返回return response;}

在经过最后一个拦截器的intercept(Chain chain)方法后,就会一层层将结果返回,直到回到最初的proceed()方法了,至此OkHttp的责任链模式就分析完毕啦~

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装3DS MAX 2026后,无法运行,提示缺少.net core的解决方案

今天安装了3DS MAX 2026&#xff08;俗称3DMAX&#xff09;&#xff0c;安装完毕后死活运行不了。提示如下&#xff1a; 大意是找不到所需的.NET Core 8库文件。后来搜索了下&#xff0c;各种文章说.NET CORE和.NET FRAMEWORK不是一个东西。需要单独下载安装。然后根据提示&…

FastAPI + LangChain 和 Spring AI + LangChain4j

FastAPI+LangChain和Spring AI+LangChain4j这两个技术组合进行详细对比。 核心区别: 特性维度 FastAPI + LangChain (Python栈) Spring AI + LangChain4j (Java栈) 技术栈 Python生态 (FastAPI, LangChain) Java生态 (Spring Boot, Spring AI, LangChain4j) 核心设计哲学 灵活…

Apache 2.0 开源协议详解:自由、责任与商业化的完美平衡-优雅草卓伊凡

Apache 2.0 开源协议详解&#xff1a;自由、责任与商业化的完美平衡-优雅草卓伊凡引言由于我们优雅草要推出收银系统&#xff0c;因此要采用开源代码&#xff0c;卓伊凡目前看好了一个产品是apache 2.0协议&#xff0c;因此我们有必要深刻理解apache 2.0协议避免触犯版权问题。…

自学嵌入式第37天:MQTT协议

一、MQTT&#xff08;消息队列遥测传输协议Message Queuing Telemetry Transport&#xff09;1.MQTT是应用层的协议&#xff0c;是一种基于发布/订阅模式的“轻量级”通讯协议&#xff0c;建构于TCP/IP协议上&#xff0c;可以以极少的代码和有限的带宽为连接远程设备提供实时可…

RabbitMQ--延时队列总结

一、延迟队列概念 延迟队列&#xff08;Delay Queue&#xff09;是一种特殊类型的队列&#xff0c;队列中的元素需要在指定的时间点被取出和处理。简单来说&#xff0c;延时队列就是存放需要在某个特定时间被处理的消息。它的核心特性在于“延迟”——消息在队列中停留一段时间…

Java 提取 PDF 文件内容:告别手动复制粘贴,拥抱自动化解析!

在日常工作中&#xff0c;我们经常需要处理大量的 PDF 文档&#xff0c;无论是提取报告中的关键数据&#xff0c;还是解析合同中的重要条款&#xff0c;手动复制粘贴不仅效率低下&#xff0c;还极易出错。当面对海量的 PDF 文件时&#xff0c;这种传统方式更是让人望而却步。那…

关键字 const

Flutter 是一个使用 Dart 语言构建的 UI 工具包&#xff0c;因此它完全遵循 Dart 的语法和规则。Dart 中的 const 是语言层面的特性&#xff0c;而 Flutter 因其声明式 UI 和频繁重建的特性&#xff0c;将 const 的效能发挥到了极致。Dart 中的 const&#xff08;语言层面&…

Ubuntu22.04中使用cmake安装abseil-cpp库

Ubuntu22.04中使用cmake安装abseil-cpp库 关于Abseil库 Abseil 由 Google 的基础 C 和 Python 代码库组成&#xff0c;包括一些正支撑着如 gRPC、Protobuf 和 TensorFlow 等开源项目并一起 “成长” 的库。目前已开源 C 部分&#xff0c;Python 部分将在后续开放。 Abseil …

FreeRTOS项目(序)目录

这章是整个专栏的目录&#xff0c;负责记录这个小项目的开发日志和目录。附带总流程图。 目录 项目简介 专栏目录 开发日志 总流程图 项目简介 本项目基于STM32C8T6核心板和FreeRTOS&#xff0c;实现一些简单的功能。以下为目前已实现的功能。 &#xff08;1&#xff09…

Python 多任务编程:进程、线程与协程全面解析

目录 一、多任务基础&#xff1a;并发与并行 1. 什么是多任务 2. 两种表现形式 二、进程&#xff1a;操作系统资源分配的最小单位 1. 进程的概念 2. 多进程实现多任务 2.1 基础示例&#xff1a;边听音乐边敲代码 2.2 带参数的进程任务 2.3 进程编号与应用注意点 2.3.…

ADSL技术

<摘要> ADSL&#xff08;非对称数字用户线路&#xff09;是一种利用传统电话线实现宽带上网的技术。其核心原理是频率分割&#xff1a;将一根电话线的频带划分为语音、上行数据&#xff08;慢&#xff09;和下行数据&#xff08;快&#xff09;三个独立频道&#xff0c;从…

信号衰减中的分贝到底是怎么回事

问题&#xff1a;在一个低通滤波中&#xff0c;经常会看到一个值-3dB&#xff08;-3分贝&#xff09;&#xff0c;到底是个什么含义&#xff1f; 今天我就来粗浅的讲解这个问题。 在低通滤波器中&#xff0c;我们说的 “截止频率”&#xff08;或叫 - 3dB 点&#xff09;&…

工具分享--IP与域名提取工具2.0

基于原版的基础上新增了一个功能点:IP-A段过滤&#xff0c;可以快速把内网192、170、10或者其它你想要过滤掉的IP-A段轻松去掉&#xff0c;提高你的干活效率&#xff01;&#xff01;&#xff01; 界面样式预览&#xff1a;<!DOCTYPE html> <html lang"zh-CN&quo…

如何通过日志先行原则保障数据持久化:Redis AOF 和 MySQL redo log 的对比

在分布式系统或数据库管理系统中&#xff0c;日志先行原则&#xff08;Write-Ahead Logging&#xff0c;WAL&#xff09; 是确保数据一致性、持久性和恢复能力的重要机制。通过 WAL&#xff0c;系统能够在发生故障时恢复数据&#xff0c;保证数据的可靠性。在这篇博客中&#x…

临床研究三千问——临床研究体系的3个维度(8)

在上周的文章中&#xff0c;我们共同探讨了1345-10战策的“临床研究的起点——如何提出一个犀利的临床与科学问题”。问题固然是灵魂&#xff0c;但若没有坚实的骨架与血肉&#xff0c;灵魂便无所依归。今天&#xff0c;我们将深入“1345-10战策”中的“3”&#xff0c;即支撑起…

AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年9月7日第172弹

从今天开始&#xff0c;咱们还是暂时基于旧的模型进行预测&#xff0c;好了&#xff0c;废话不多说&#xff0c;按照老办法&#xff0c;重点8-9码定位&#xff0c;配合三胆下1或下2&#xff0c;杀1-2个和尾&#xff0c;再杀4-5个和值&#xff0c;可以做到100-300注左右。(1)定位…

万字详解网络编程之socket

一&#xff0c;socket简介1.什么是socketsocket通常也称作"套接字"&#xff0c;⽤于描述IP地址和端⼝&#xff0c;是⼀个通信链的句柄&#xff0c;应用程序通常通过"套接字"向⽹络发出请求或者应答⽹络请求。⽹络通信就是两个进程间的通信&#xff0c;这两…

维度跃迁:当万物皆成电路,智能将从“拥有”变为“存在”

我们习以为常的电子世界&#xff0c;其本质是一个由电路构成的精密宇宙。而一场从二维到三维的终极变革&#xff0c;正在悄然酝酿&#xff0c;它将彻底颠覆我们创造和交互的方式。一、电子世界的本质&#xff1a;一切都是电路 在深入未来之前&#xff0c;我们首先要理解当下。电…

大语言模型预训练数据采集与清洗技术实践:从语料到知识库的全流程优化

大语言模型(LLM)的性能上限由 “数据质量 数据规模 数据多样性” 共同决定 —— 预训练阶段的海量语料决定模型的泛化能力与语言理解基础,而知识库数据则决定模型的知识准确性与领域专业性。当前 LLM 落地面临的核心痛点之一,便是 “数据脏、处理难、知识杂”:预训练语料…

模拟音频采集设备的制作

模拟音频程序与设备的制作 需要设备 esp32s3 pcm1808 pcm5102(非必须) 程序界面 程序代码 代码链接