云音乐Android Cronet接入实践
背景
网易云音乐产品线终端类型广泛,除了移动端(IOS/安卓)之外,还有PC、MAC、Iot多终端等等。移动端由于上线时间早,用户基数大,沉淀了一些端侧相对比较稳定的网络策略和网络基础能力。然而由于各端在基础能力上存在不对齐的现状:移动端双端在这些能力细节上有差异,同时PC、MAC这方面能力相较于移动端又略微滞后。为了避免各端在网络侧反复投入人力进行能力维护和定位解决问题,同时统一网络基础设置,将端侧稳定网络策略进行沉淀复用,经过调研,我们计划采用 Google chromium项目的 Cronet 作为跨端通用网络库。Cronet 在chrome 中经过多年的打磨,稳定性得到了验证,同时 Cronet 支持 QUIC 协议,可以支持后期对弱网场景进行专项优化。安卓端作为 Cronet 的首先落地一端,已经全量在线上运行了一年多的时间,本文主要介绍接入方案和过程中解决的问题。
介绍
Cronet 网络库
Cronet是 google chromium 的网络组件,可单独编译成库提供给 Android/Ios 应用使用。Cronet在性能方面表现出色,目前已经有 Youtube、Goolge 全家桶等大量应用使用 Cronet 作为网络模块。
它有以下功能:
- 支持 HTTP2/QUIC/websocket 协议
- 支持对请求设置优先级标签
- 可以使用内存缓存或磁盘缓存来存储资源
- 支持 Brotlin 压缩(有研究表明,对于文本文件,相同的压缩质量下,brotlin 通常比 gzip 高出了20%的压缩率)
接入方案
目前项目中使用 Okhttp 作为网络基础库,Cronet 的对外接口和 Okhttp 无法兼容,在接入上主要有两个方向:
- 网络业务接口根据新的 Cronet 接口重新封装接口层,逐步替换老接口;
- 业务侧不做改动的情况下,底层入口统一切换到 Cronet 侧,通过中间的胶水层来抹平差异。
方向一需要对项目中的网络请求接口做改造,但由于项目中广泛使用了 Okhttp 的特性,例如 Interceptor、cookiestore、cache、eventlistener 等等,直接使用 Cronet 接口意味着这些特性全部需要重新实现,改造成本巨大;
方向二的实现思路是在 Cronet 的最底层通过创建 CronetInterceptor 来实现 Cronet 请求,并且将它放到 Okhttp Interceptors 的最末尾保证原有 interceptors 全部执行,同时通过适配层将 Okhttp 原有能力无缝桥接到Cronet实现,不对上层有任何侵入和改动,做到业务调用侧无感知。
P.S google 官方后来推出的 Cronet 接入库 github.com/google/cron…
结合我们的项目现状,我们决定使用方向二的思路来接入 Cronet。
Android 网络库整体架构
作为一个通用的网络模块,我们将整体抽象出了四层来展示,从底层到业务层方向分别为:协议层、通用能力、适配层、业务支撑层。
协议层
这部分主要是从 chromuim 中抽离出的 Cronet 源码部分,主要是 Cronet 的基础能力,包括了不同网络协议的实现以及 Cronet 内部的优化,是 Cronet 的最核心实现。这部分除了一些向上接口之外,通常不会对源码做过多的改动。
通用能力层
这一层主要包括我们从 java 侧沉淀到 C++ 层的一些通用网络策略和网络组件(APM、Httpdns等),这一层通过拆分出不同组件的方式相互隔离,共同依赖于协议层。
适配层
适配层定义为胶水层,主要目的是保证在上层接口无须做任何改动的情况下将底层实现在 Okhttp 和 Cronet 间进行切换。
业务支撑层
支撑端侧各种业务的能力,这层无须做改动。
适配方案
okhttp接口适配
1、interceptor 适配
前文提到,通过创建 CronetInterceptor 且放到 okhttp addInterceptor 的最末尾保证 interceptor 全部执行,但是仍有部分 interceptors 是覆盖不到的,那就是 Okhttp 内置的 interceptor。内置的主要有:
RetryAndFollowUpInterceptor
BridgeInterceptor
CacheInterceptor
ConnectInterceptor
CallServerInterceptor
其中
RetryAndFollowUpInterceptor
BridgeInterceptor
CacheInterceptor
这三个主要负责重定向、鉴权、cookie、缓存等逻辑,和 Okhttp 的接口息息相关,这部分逻辑是我们主要适配的内容。适配也非常简单,只需要把这些 interceptor 的核心逻辑移植到我们创建的 CronetInterceptor 即可,这样就能保证上层业务使用到的 cookiestore、cache 等 okhttp api 不受影响。
public abstract void onRedirectReceived(UrlRequest var1, UrlResponseInfo var2, String var3)
public abstract void onResponseStarted(UrlRequest var1, UrlResponseInfo var2)
public abstract void onReadCompleted(UrlRequest var1, UrlResponseInfo var2, ByteBuffer var3)
public abstract void onSucceeded(UrlRequest var1, UrlResponseInfo var2);
public abstract void onFailed(UrlRequest var1, UrlResponseInfo var2, CronetException var3);
这两个 interceptor 主要负责的是核心的网络请求的全部后续细节,Cronet 有自己来接管自然无需适配。
2、eventlistener适配
由于 okhttp eventlistener 依赖的一些回调例如 connectEnd、dnsEnd 等是在这两个拦截器中调用的,虽然Cronet 有自己的是 Callback:
public abstract void onRedirectReceived(UrlRequest var1, UrlResponseInfo var2, String var3)
public abstract void onResponseStarted(UrlRequest var1, UrlResponseInfo var2)
public abstract void onReadCompleted(UrlRequest var1, UrlResponseInfo var2, ByteBuffer var3)
public abstract void onSucceeded(UrlRequest var1, UrlResponseInfo var2);
public abstract void onFailed(UrlRequest var1, UrlResponseInfo var2, CronetException var3);
但是没有 okhttp eventlistener 提供的全面,如果需要完整的实现 okhttp eventlistener,需要对 Cronet 的核心关键请求点做改造来透出给 java 层,考虑到成本和使用场景,我们没有对这部分做改造,而是直接采用 Cronet 的 callback 做桥接来实现了部分的核心 eventlistener 的 callback。
3、超时逻辑适配
业务侧指定请求的超时时间来做一些策略也是常见的操作,而 Cronet 并未提供超时相关的 api,于是我们基于Cronet 源码开发了建链超时和读流超时等能力
void CronetURLRequest::SetOriginRequestID(uint32_t origin_request_id)
void CronetURLRequest::SetConnectTimeoutDuration(uint32_t connect_timeout_ms)
并通过 jni 暴露给 java 层,java 层通过适配层桥接到 Okhttp 接口:
CronetUrlRequest.java类
mRequestContext.onRequestStarted();
if (mInitialMethod != null) {
if (!nativeSetHttpMethod(mUrlRequestAdapter, mInitialMethod)) {
throw new IllegalArgumentException("Invalid http method " + mInitialMethod);
}
}
if (mRequestId > 0) {
nativeSetOriginRequestID(mUrlRequestAdapter, mRequestId);
}
// 将业务侧设置的超时时间传递到Cronet
if (connectTime > 0) {
nativeSetConnectTimeoutDuration(mUrlRequestAdapter, (int) connectTime);
}
// 将业务侧设置的超时时间传递到Cronet
if (readTime > 0) {
nativeSetReadTimeoutDuration(mUrlRequestAdapter, (int) readTime);
}
这样上层业务侧无需任何改动既可继续使用 Okhttp 原有能力。
网络请求适配
1、请求维度适配
发起请求时,由原先的通过 Okhttp 内置 interceptor 发起请求切换到使用 Cronet 发起请求后,需要在 Okhttp 接口到 Cronet 接口间做一下请求和响应的适配转换。
网络请求切换示意图
同时由于将之前的一些 java 层网络策略下沉到 C++ 实现,之前的一些 java 层的直接调用和传参我们通过基于CronetUrlRequest 进行扩展打通了向 Cronet 的 jni 调用
2、全局调用适配
下沉到 C++ 的网络策略,为尽可能做到和 Cronet 原有代码的解耦,在 C++ 以一个个独立插件形式存在。java 侧通过 CronetRequestContext 设置到 C++ 侧,然后向对应注册的组件进行分发,这个链路上涉及到 java、jni 和C++ 的代码改动,为了降低后续网络策略的开发维护成本,采用了类 JsBridge 的方法,开发了’CppBridge’,将java 和 C++ 之间的方法调用协议化,通过 json 传递数据,这样避免了后续对插件做更新带来的 java 到 C++ 请求链路上繁琐的开发工作,且 C++ 策略可以通过java层的配置中心能力进行动态配置。
解决问题
1、线程优化
众所周知,网络请求需要在子线程中发起,在 Cronet 的官方文档介绍中,推荐通过传入 Executor 来负责执行网络请求:
然后在 okhttp interceptor 中已经是子线程的执行环境,如果仍然传入独立对 executor,会造成不必要的线程切换和时间消耗。通过查看 Cronet 源码,发现其 CronetHttpURLConnection 使用的 MessageLoop 类实现是在当前线程,使用 MessageLoop 即可减少不必要的多余线程引入。
通过 MessageLoop 请求生命周期
2、兼容性解决
不同网络库之间切换,兼容性问题在所难免。虽然同样遵循 http 协议,但是对于一些边界条件的处理不一致或处理严格程度不同也会引起兼容性偏差。篇幅所限,这里仅介绍几个兼容点:
-
Cronet 库对于http链接数设置为了6个,如果有对于 http 请求的不当使用,例如不正常持有未释放,一旦达到了6个,后续的请求将会 block 直到前序连接资源释放,这在 http1.1 下更容易触发;
-
Cronet 对请求做了检测,如请求 body 未设置 Content-Type,将会直接抛出异常,
if (!hasContentType) {throw new IllegalArgumentException("Requests with upload data must have a Content-Type.");
}
在某些特殊设置情况下,存在有 request body 未设置 Content-Type 的情况将会直接导致请求抛异常;
- Cronet 请求返回4xx时,会直接抛出异常,而 okhttp 是通过将结果连带 code 返回到上层,交由使用者自己去处理。
兼容性优化没有统一的解决办法,只能见招拆招,通常是向前保证兼容性或推动优化不合理代码来解决。
3、重定向问题解决
Http 请求发生重定向时,请求 header 中的 Host 字段需要更新为新的目标主机地址,否则服务端校验Host字段和实际请求的 host 不一致时会拒绝请求。首先看一下 Okhttp 是如何实现的这个功能:
okhttp 在 RetryAndFollowUpInterceptor 类中,302会触发重新构建请求对象:
之后在 BridgeInterceptor 中,重新设置 Host:
而 Cronet 在 android 侧的默认实现中,并未对此进行更新,查看cronet代码:
类:cronet_url_request.cc
可以看到,cronet 下层接口是支持对重定向时传入修改的 header 的,但是默认传入了空,也没有提供暴露给 java 侧的接口来进行设置。
解决方案:对 cronet 重定向时更新 header 的能力进行打通,新增设置接口:
void CronetURLRequest::NetworkTasks::SetRedirectHeader(const std::string& key,const std::string& value) {DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);DCHECK(url_request_.get());if (redirect_request_headers_ == base::nullopt) {redirect_request_headers_ = base::make_optional<net::HttpRequestHeaders>();}redirect_request_headers_->SetHeader(key, value);
}
在重定向时将从 java 侧设置下来的 header 传入:
@Overrideprotected void handleRedirectReceived(UrlRequest request, UrlResponseInfo info, String newLocationUrl) {try {Uri newUri = Uri.parse(newLocationUrl);String host = newUri.getHost();// 更新Hostrequest.setRedirectHeader("Host", host);request.followRedirect();} catch (Exception e) {e.printStackTrace();}}
cronet 执行 FollowDeferredRedirect (真正重定向的方法)时,将原有方法替换为传入重定向 header 的方法:
void CronetURLRequest::NetworkTasks::FollowDeferredRedirect() {DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
#if defined(WOW_BUILD)url_request_->FollowDeferredRedirect(this->redirect_request_headers_ /* modified_request_headers */);
#elseurl_request_->FollowDeferredRedirect(base::nullopt /* modified_request_headers */);
#endif
}
灰度&上线
网络库切换牵扯业务的方方面面,影响面较大,上线需要格外谨慎:
-
在上线前的开发阶段,在开发环境提前切换到 Cronet,如果有问题可以尽早暴露;
-
灰度阶段反复分流验证,结合稳定性平台和舆情信息反馈观察,确保 Cronet sdk 的稳定性;
-
技术上,为了防止有其他异常情况引起的网络不可用,对非网络抖动引起的网络请求异常自动降级到 Okhttp,达到一定次数后开始彻底降低回 Okhttp,并上报日志进行分析;对网络组件以最小粒度进行动态配置,保证根据任意的组件都可以按需更新/开闭以进行线上ab效果观测;对网络请求各阶段的进行全面端到端数据埋点。
-
上线后,拉长观测周期,分阶段放量。反复从各个维度比对网络性能数据,发现异常数据及时分析定位解决,确保数据是完全正向的。分析维度包括:
- 首包时长/请求时长
- 错误率
- 长尾数据分析
- 业务体感数据
这个阶段相对较为漫长,通常是从数据侧发现问题后,结合对应的业务场景去进一步定位问题,在针对不同具体错误类型的数据分析过程中,我们也发现了一些上层非正常使用带来的错误率问题,并一起促进优化降低了部分场景的错误率。
目前 android cronet 已经线上全量稳定运行了一年多时间,从统计数据来看,主站api请求时长有16%的优化,错误率有4%的优化,cdn请求不同域名也有不同程度的优化。
后续规划
弱网场景的特殊优化是业务开发中经常遇到的,云音乐基于 Cronet 的 nqe 模块做二次开发,对外提供弱网检测通知能力(正在进行中);
Cronet 的一个核心功能便是支持 quic 协议,作为下一代的网络通信协议,quic 协议具有一系列的协议层面优化:
1、 更少的建链 RTT
2、链接迁移
不同于 tcp 的四元组标识,quic 使用 cid 作为标识,cid 不变即可维持 quic 连接不中断
3、解决tcp队头阻塞(head of line blocking)问题
4、拥塞控制算法实现
将固化在操作系统实现的 tcp 拥塞控制等算法在应用层实现,无需升级操作系统即可实现对算法的升级
5、更好的安全性
2022年6月6日,IETF 正式发布了 http3 协议,云音乐在线上也小范围进行了 quic 协议的测试,在部分场景下quic 表现了更优秀的网络性能。当然在线上想充分利用 quic 的全部特性例如:连接恢复时的0RTT、链接迁移等特性,还需要对服务端前端机集群进行相应的改造。后续云音乐也会对这业界方面进展持续保持关注。
Android 学习笔录
Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap
相关文章:

云音乐Android Cronet接入实践
背景 网易云音乐产品线终端类型广泛,除了移动端(IOS/安卓)之外,还有PC、MAC、Iot多终端等等。移动端由于上线时间早,用户基数大,沉淀了一些端侧相对比较稳定的网络策略和网络基础能力。然而由于各端在基础…...
Linux dup和dup2
Linux dup和dup2函数,他们有什么区别,什么场景下会用到,使用它们有什么注意事项 dup和dup2都是Linux系统中的系统调用,用于复制文件描述符。它们的主要区别在于如何指定新的文件描述符以及处理新文件描述符的方式。 dup函数 #i…...

Spring Boot实战 | 如何整合高性能数据库连接池HikariCP
专栏集锦,大佬们可以收藏以备不时之需 Spring Cloud实战专栏:https://blog.csdn.net/superdangbo/category_9270827.html Python 实战专栏:https://blog.csdn.net/superdangbo/category_9271194.html Logback 详解专栏:https:/…...
Spring依赖注入
依赖注入底层原理流程图: https://www.processon.com/view/link/5f899fa5f346fb06e1d8f570 Spring中有两种依赖注入的方式 首先分两种: 手动注入自动注入 手动注入 在XML中定义Bean时,就是手动注入,因为是程序员手动给某个属…...

Linux下Jenkins自动化部署SpringBoot应用
Linux下Jenkins自动化部署SpringBoot应用 1、 Jenkins介绍 官方网址:https://www.jenkins.io/ 2、安装Jenkins 2.1 centos下命令行安装 访问官方,点击文档: 点击 Installing Jenkins: 点击 Linux: 选择 Red Hat/…...
【git 学习】--- ubuntu18.04 搭建本地git服务器
在Ubuntu18.04 上简单创建自己的git服务器~ 环境配置 Ubuntu: 18.04git服务器搭建步骤: ##1.安装git sudo apt-get install git##2.添加用户 sudo adduser test_git //test_git -- git用户名##3. 在Git用户的home目录下创建文件夹,作为裸仓库 sudo…...

JAVA电商平台免费搭建 B2B2C商城系统 多用户商城系统 直播带货 新零售商城 o2o商城 电子商务 拼团商城 分销商城
涉及平台 平台管理、商家端(PC端、手机端)、买家平台(H5/公众号、小程序、APP端(IOS/Android)、微服务平台(业务服务) 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis …...
Android 13 Framework 裁剪
裁剪应用 1. 修改 build/core/product.mk 添加PRODUCT_DEL_PACKAGES变量的声明 新增一行_product_single_value_vars PRODUCT_DEL_PACKAGES # The first API level this product shipped with _product_single_value_vars PRODUCT_SHIPPING_API_LEVEL _product_single_val…...
【Axios封装示例Vue2】
文章目录 为什么要封装axios?如何封装axios在Vue组件中使用封装的axios 为什么要封装axios? 在Vue 2项目中,直接在组件中使用axios可能会导致以下问题: 代码重复:每个组件都需要导入axios并编写相似的请求代码&#…...
k8s-----20、持久化存储--PV/PVC
PV/PVC 1、概念1.1 基本定义1.2 生命周期1.3 PV 卷阶段状态 2、 示例2.1 创建pod和PVC 与PV2.2 绑定PV2.3 强制删除pv,pvc2.4 测试 1、概念 1.1 基本定义 PersistentVolume(PV)是集群中由管理员配置的一段网络存储。 它是集群中的资源,就像…...
python matplotlib 生成矢量图
import matplotlib.pyplot as plt plt.savefig(r"xxx.svg", format"svg")注意: plt.savefig(r"xxx.svg", format"svg") 需要放在 plt.show()前面 原因:如果在 plt.show()调用后, 实际上已经创建了一…...

机器学习中常见的特征工程处理
一、特征工程 特征工程(Feature Engineering)对特征进行进一步分析,并对数据进行处理。 常见的特征工程包括:异常值处理、缺失值处理、数据分桶、特征处理、特征构造、特征筛选及降维等。 1、异常值处理 具体实现 from scipy.s…...

Spring IOC 和 AOP
核心概念 咱们这节就讲完了,在这节中我们讲了两个大概念,一个叫做IOC,一个叫做DI IOC是什么?是用对象的时候不要自己用new而是由外部提供,而spring在进行实现的时候是谁提供,就是IOC容器给你提供。 DI是什…...

echarts插件-liquidFill(水球图)
echarts插件-liquidFill(水球图) 1.下载2.引入:3.使用 1.下载 echarts.js下载:https://cdnjs.com/libraries/echarts echarts-liquidfill.js下载:https://github.com/ecomfe/echarts-liquidfill 2.引入: …...
c++ vscode cmake debug for mac
1. 下载vscode 2. 安装c插件 参考:C programming with Visual Studio Code 3. 安装llvm,可以使用brew安装 4. 配置llvm到系统环境变量中 5. 编写c代码 6. 编写CMakeLists.txt文件(前提安装cmake) cmake_minimum_required(V…...

17 结构型模式-享元模式
1 享元模式介绍 2 享元模式原理 3 享元模式实现 抽象享元类可以是一个接口也可以是一个抽象类,作为所有享元类的公共父类, 主要作用是提高系统的可扩展性. //* 抽象享元类 public abstract class Flyweight {public abstract void operation(String extrinsicState); }具体享…...
创建Secret(手动)
和创建其他类型的 API 对象(Pod、Deployment、StatefulSet、ConfigMap 等)一样,您也可以先在 yaml 文件中定义好 Secret,然后通过 kubectl apply -f 命令创建。此时,您可以通过如下两种方式在 yaml 文件中定义 Secret&…...

基于PHP的线上购物商城,MySQL数据库,PHPstudy,原生PHP,前台用户+后台管理,完美运行,有一万五千字论文。
目录 演示视频 基本介绍 论文截图 功能结构 系统截图 演示视频 基本介绍 基于PHP的线上购物商城,MySQL数据库,PHPstudy,原生PHP,前台用户后台管理,完美运行,有一万五千字论文。 现如今,购物网站是商业…...
Lua 事件触发机制(注册,触发)
日常工作中经常会用到触发机制,这里就提供一个注册触发机制,在代码中在也不用专门去调用各个模块的接口;只需要触发即可,触发后会自动调用接口 直接上代码 local _EventHandle {}; _EventHandle.listenerHandleIndex 0 _EventH…...
c++ 并发与多线程(12)线程安全的单例模式-1
一、什么是线程安全 在拥有共享数据的多条数据并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。 二、如何保证线程安全 法1、给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用; 法2、让线…...
CSP信奥赛C++常用系统函数汇总
# CSP信奥赛C常用系统函数汇总## 一、输入输出函数### 1. cin / cout(<iostream>) cpp int x; cin >> x; // 输入 cout << x << endl;// 输出 优化:ios::sync_with_stdio(false); 可提升速度 2. scanf() /…...

大陆4D毫米波雷达ARS548调试
本文介绍了大陆ARS548毫米波雷达的调试与测试流程,主要包括以下内容: 设备参数:最大检测距离301m(可调93-1514m),支持gPTP时间同步。 接线调试: Windows需使用USB-RJ45转换器 Linux可直接连接网…...

FMC STM32H7 SDRAM
如何无痛使用片外SDRAM? stm32 已经成功初始化了 STM32H7 上的外部 SDRAM(32MB) 如何在开发中无痛使用SDRAM 使它像普通 RAM 一样“自然地”使用? [todo] 重要 MMT(Memory Management Tool) of STM32CubeMx The Memory Management Tool (MMT) disp…...
达梦使用存储过程实现删除重复记录、判断并添加主键和自增列的逻辑
在达梦数据库中,要确保主键的唯一性约束,可以在存储过程的最前面添加删除重复记录的逻辑。以下是一个完整的存储过程,包含删除重复记录、判断并添加主键和自增列的逻辑: 存储过程示例 -- 切换到指定模式;schema_name 是目标模…...

Redis实战-消息队列篇
前言: 讲讲做消息队列遇到的问题。 今日所学: 异步优化消息队列基于stream实现异步下单 1. 异步优化 1.1 需求分析 1.1.1 现有下单流程: 1.查询优惠劵 2.判断是否是秒杀时间,库存是否充足 3.实现一人一单 在这个功能中&…...

CAD多面体密堆积3D插件
插件介绍 CAD多面体密堆积3D插件可在AutoCAD内建立三维随机多面体密堆积模型。 插件内置物理动力学模拟算法,通过模拟重力、碰撞等现象,使多面体在虚拟环境中发生自然堆积,进而实现真实的堆积效果。多面体堆积模拟中存在的局部穿模问题可通…...

Maven入门(够用)
1、Maven是什么? 这个问题非常不重要,或者说不应该上来就问maven是什么,而是直接学习maven怎么用能干什么,学完之后自然就知道了maven是个什么玩意儿,很多技术都是如此。 2、Maven下载 先准备Java环境,安…...

【计算机网络】数据链路层-滑动窗口协议
数据链路层滑动窗口协议 1. 三种协议对比表 特性停止-等待协议GBN协议SR协议窗口大小发送 1,接收 1发送 W (1<W≤2ⁿ-1),接收 1发送 C,接收 R确认方式单个确认累积确认选择性确认重传策略超时重传回退N帧重传选择性重传接收缓冲区…...
算法训练第十一天
150. 逆波兰表达式求值 代码: class Solution(object):def evalRPN(self, tokens):""":type tokens: List[str]:rtype: int"""stack []for i in tokens:if i:b int(stack.pop())a int(stack.pop())stack.append(ab)elif i-:b i…...

pgsql batch insert optimization (reWriteBatchedInserts )
reWriteBatchedInserts 是 PostgreSQL JDBC 驱动 提供的一个优化选项,它可以 重写批量插入语句,从而提高插入性能。 作用 当 reWriteBatchedInsertstrue 时,PostgreSQL JDBC 驱动会将 多个单独的 INSERT 语句 转换为 一个多行 INSERT 语句&a…...