深入浅出RPC框架 | 青训营

news2025/4/6 1:57:38

Powered by:NEFU AB-IN

文章目录

  • 深入浅出RPC框架 | 青训营
    • RPC 框架分层设计
      • 远程函数调用(RPC)介绍
      • 名词解释
      • 一次RPC过程
      • RPC好处和弊端
      • 分层设计
        • 编解码层
        • 协议层
        • 网络通信层
    • RPC 关键指标分析与企业实践
      • 稳定性
        • 保障策略
        • 请求成功率
        • 长尾请求
        • 注册中间件(middleware、拦截器...)
      • 易用性
      • 拓展性
      • 观测性
      • 高性能
    • RPC企业实践
      • 架构
      • 自研网络库-Netpoll
      • 拓展性设计
      • 性能优化-网络库优化
      • 性能优化-编解码优化
      • 合并部署
      • 既有HTTP,为什么用RPC?
        • RPC 只是一种设计而已
        • RPC框架功能更齐全

深入浅出RPC框架 | 青训营

  • RPC 框架分层设计

  • RPC 关键指标分析与企业实践

RPC 框架分层设计

远程函数调用(RPC)介绍

RPC框架是一种用于实现远程过程调用(Remote Procedure Call,简称RPC)的软件工具或框架。它提供了一种方式,使不同计算机上的程序能够通过网络相互调用函数或方法,就像调用本地函数一样。RPC框架的主要目的是简化分布式系统中的通信和远程调用过程,使开发人员能够更轻松地构建分布式应用程序。

以下是RPC框架的一些常见用途和使用方法:

1. 分布式服务调用: RPC框架允许在不同的服务器上运行的程序相互调用函数或方法。这对于构建分布式应用程序和微服务体系结构非常有用,因为它可以将不同服务之间的通信抽象化。

2. 跨语言通信: RPC框架通常支持多种编程语言,这意味着您可以使用不同编程语言编写的程序之间进行通信。这在构建多语言系统时非常有用。

3. 代码生成: 大多数RPC框架会生成客户端和服务器端的代码,这样开发人员就不需要手动编写网络通信代码。这减少了错误和提高了开发效率。

4. 序列化和反序列化: RPC框架处理数据的序列化(将数据转换为可在网络上传输的格式)和反序列化(将接收到的数据转换回本地格式),从而使远程调用变得更加透明。

5. 异常处理: RPC框架通常具有异常处理机制,允许在分布式系统中处理各种错误情况。

6. 安全性: 许多RPC框架提供安全性功能,如身份验证和数据加密,以保护通信数据的安全性。

下面是使用RPC框架的一般步骤

1. 定义接口: 首先,您需要定义远程过程调用的接口,包括要调用的函数或方法名称、参数和返回值。

2. 生成代码: 使用RPC框架工具,您可以生成客户端和服务器端的代码。这通常涉及在接口定义上运行代码生成器,以生成与您选择的编程语言相对应的代码。

3. 实现服务器: 在服务器端,您需要实现定义的接口,以处理来自客户端的远程调用请求。

4. 实现客户端: 在客户端,您可以使用生成的客户端代码来远程调用服务器上的函数或方法。

5. 部署和运行: 部署服务器和客户端应用程序,然后通过网络进行通信,进行远程调用。

常见的RPC框架包括gRPC、Apache Thrift、Java RMI(远程方法调用)、JSON-RPC、XML-RPC等。不同的框架具有不同的特点和适用场景,您可以根据项目的需求选择合适的RPC框架。


名词解释

当涉及到RPC框架和分布式系统时,以下是与您提到的术语相关的解释:

  1. IDL (Interface Description Language) 文件: IDL是一种特定语法的语言,用于定义接口和数据结构。它提供了一种独立于编程语言的方式来描述接口的方法、参数和返回值,以及数据的结构。IDL文件定义了客户端和服务器之间的通信协议的规范。

  2. 生成代码: RPC框架通常能够根据IDL文件生成客户端和服务器端的代码。这些代码用于处理序列化、反序列化、网络通信和远程过程调用。生成的代码使开发人员无需手动编写底层通信代码,从而提高了开发效率。

  3. 编解码: 在RPC中,数据在网络上传输之前需要被序列化(编码)为一种可传输的格式,然后在接收方被反序列化(解码)回本地格式。编解码是为了在网络上传输数据,并确保数据能够正确地在不同机器上传递和解释。

  4. 通信协议: 通信协议是定义在网络上传输数据时使用的规则和约定。它决定了数据的结构、序列化方法、错误处理等。在RPC中,通信协议用于确保客户端和服务器之间的正确通信。

  5. 网络传输: RPC框架使用网络传输来在不同计算机之间传递数据。网络传输包括将数据从一个计算机发送到另一个计算机的过程,通常涉及数据分割、传输控制、数据校验等。

image.png

在一个典型的RPC框架中,这些步骤按以下方式运作:

  1. 使用IDL文件定义接口、方法、参数和数据结构。
  2. 使用RPC框架的工具生成客户端和服务器端的代码,这些代码包括编解码和通信协议的实现。
  3. 在服务器端实现IDL文件中定义的接口方法,用于处理远程调用请求。
  4. 在客户端使用生成的客户端代码,通过远程调用请求调用服务器上的方法。
  5. 数据在客户端和服务器之间经过编码和解码,使用通信协议在网络上传输。
  6. 网络传输将编码后的数据从客户端发送到服务器,然后将结果返回给客户端。

整个过程的目标是使远程调用和分布式通信尽可能地透明,使开发人员能够将注意力集中在业务逻辑而不是底层通信细节上。


一次RPC过程

image.png


RPC好处和弊端

好处

  1. 单一职责,有利于分工协作和运维开发。开发(使用的语言)、部署以及运维(上线时间)都是独立的。
  2. 可扩展性强,能够扩缩资源,复用资源,使资源使用率更优。
  3. 故障隔离,服务整体可靠性更高。

弊端

  1. 服务宕机,对方应如何处理?
  2. 调用过程中发生网络异常,如何保证消息的可达性?
  3. 请求突增导致服务无法及时处理,有哪些应对措施?

分层设计

Apache Thrift(简称Thrift)是一个开源的跨语言的远程过程调用(RPC)框架和服务化的软件堆栈,最初由Facebook开发并于2007年开源。它旨在解决不同编程语言之间进行高效通信的问题,特别是在分布式系统中。

分层主要是三层

  • 编解码层
  • 协议层
  • 网络通信层

image.png

编解码层

image.png

生成代码也可看做编解码层的一部分,因为内部包含了编解码的逻辑。用户和服务器依赖同一份IDL生成codegen(可以是不同语言的)

IDL的数据格式如何选择?

  • 语言特定的格式:许多编程语言都内建了将内存对象编码为字节序列的支持,例如JavaJava.io.Serializable。好处是非常方便,可以用很少的额外代码实现内存对象的保存与恢复。但是这类编码通常与特定的编程语言深度绑定,其他语言很难读取这种数据。此外还有安全性和兼容性的问题。
  • 文本格式:JSON、XML、CSV等文本格式具有人类可读性。但数字编码多有歧义之处,如XML和CSV不能区分数字和字符串;JSON不能区分整数和浮点数,且不能指定精度。处理大数据时存在问题。没有强制模型约束使得实际操作中往往以文档方式进行约定,可能会给调试带来不便。由于JSON在一些语言中的序列化和反序列化需要采用反射机制,所以性能也比较差。
  • 二进制编码:有TLV、Varint等多种编码方式,具备跨语言和高性能等优点,常见的有Thrift的BinaryProtocol,Protobuf等。

image.png

选择依据

  1. 兼容性(支持新增字段):支持自动增加新的字段,而不影响老的服务,这将提高系统的灵活度
  2. 通用性(编码大小解码时长):支持跨平台、跨语言
  3. 性能(平台语言):从空间和时间两个维度来考虑,也就是编码后数据大小和编码耗费时长

协议层

现在把请求的数据转换成字节流了,需要通信协议和元数据,传给另一边

协议是双方确定的交流语义

常见的两种协议

image.png

多路复用:同一个连接内可以有多个请求流

image.png

image.png

网络通信层

Sockets API (套接字应用程序接口)介于应用层和传输层中间,是一组函数和数据结构,用于在网络上进行进程间通信。它提供了一种编程接口,使得开发者可以创建网络连接、发送和接收数据,并处理网络通信的各种细节,使用套接字,应用程序可以建立网络连接并进行双向通信。

套接字在网络通信中扮演重要的角色,它提供了以下功能:

  1. 建立连接:套接字允许应用程序建立与远程主机的连接。通过指定主机地址和端口,应用程序可以使用套接字来发起连接请求。

  2. 数据传输:一旦建立了连接,套接字可用于在应用程序之间传输数据。发送方可以将数据写入套接字,而接收方可以从套接字中读取数据。

  3. 协议支持:套接字库支持多种协议,如TCP(传输控制协议)和UDP(用户数据报协议)。这使得开发人员可以选择适合特定需求的协议,并根据需要进行配置。

下面是一个简单的示例,使用Python的socket模块来创建一个基本的客户端-服务器应用程序:

# 服务器端代码
import socket

# 创建套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定地 址和端口
server_address = ('localhost', 8080)
server_socket.bind(server_address)

# 监听连接
server_socket.listen(1)

# 等待客户端连接
print('等待客户端连接...')
client_socket, client_address = server_socket.accept()
print('客户端已连接:', client_address)

# 接收数据
data = client_socket.recv(1024)
print('接收到的数据:', data.decode())

# 发送响应
response = 'Hello, client!'
client_socket.sendall(response.encode())

# 关闭连接
client_socket.close()
server_socket.close()
# 客户端代码
import socket

# 创建套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接服务器
server_address = ('localhost', 8080)
client_socket.connect(server_address)

# 发送数据
message = 'Hello, server!'
client_socket.sendall(message.encode())

# 接收响应
response = client_socket.recv(1024)
print('收到的响应:', response.decode())

# 关闭连接
client_socket.close()

在这个例子中,通过使用socket模块创建了一个服务器和一个客户端。服务器绑定在本地主机的8080端口上,并监听来自客户端的连接请求。客户端连接到服务器的指定地址和端口。

一旦建立连接,服务器接收到来自客户端的消息,并发送一个简单的响应。客户端发送一个消息给服务器,并等待响应。最后,他们关闭连接。

这只是一个简单的示例,展示了套接字在建立连接、发送和接收数据方面的作用。套接字API提供了更多功能和选项,以满足不同类型的网络通信需求。

image.png

服务器会监听连接。由于服务器不止负责连接,而且要负责处理其它事物,于是会先将连接放入消息队列。队列大小(back log)一般设为128。

如果队列未满,客户端直接将状态设为已连接,若队列已满,则客户端阻塞,等待队列更新。

客户端与服务器连接完成后将进行数据读写,直到一方关闭连接。

采用封装好的网络库,作为通信层

  • 提供易用API:封装底层Socket API。连接管理和事件分发
  • 功能:支持tcp、udp和uds等协议。能够优雅的退出、进行异常处理等
  • 性能:应用层buffer减少copy,高性能定时器、对象池等

RPC 关键指标分析与企业实践

稳定性

保障策略

  • 熔断: 一个服务 A 调用服务 B 时,服务 B 的业务逻辑又调用了服务 C,而这时服务 C 响应超时了,由于服务 B 依赖服务 C,C 超时直接导致 B 的业务逻辑一直等待,而这个时候服务 A 继续频繁地调用服务 B,服务 B 就可能会因为堆积大量的请求而导致服务宕机,由此就导致了服务雪崩的问题。

  • 限流: 当调用端发送请求过来时,服务端在执行业务逻辑之前先执行检查限流逻辑,如果发现访问量过大并且超出了限流条件,就让服务端直接降级处理或者返回给调用方一个限流异常。

  • 超时: 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,避免浪费资源

三种保障策略都算是降级手段(服务器的降级手段是在服务器面临压力或故障时采取的策略,旨在减轻负载、保障稳定性。常见方法包括请求限流、关闭不必要的服务、降低服务质量、分流流量至备用服务器等。这有助于防止服务器过载,确保系统仍然可用,尽管在较低的性能水平下。)

请求成功率

提高方法:

  • 负载均衡:若A调用B,可多个A调用,多个B被调用,减少压力
  • 重试

长尾请求

长尾请求的一般标准为用时超过99%其它请求的请求。长尾请求总是会存在,业界关于延迟有一个常用的P99标准,也就是99%的请求延迟要满足在一定耗时以内,1%的请求会大于这个耗时,而这1%就可以认为是长尾请求。造成长尾请求的原因包括网络抖动、GC、系统调度等。

解决:backup request(备份请求):我们先预设一个阈值t3(比超时时间小,通常建议是RPC请求实验的pct99),当req1发出后超过t3没有返回,我们就直接发起重试请求req2。此时相当于有两个请求同时运行,任何一个成功返回后就可以立即结束这次请求。这样整体耗时就是t4,它表示从第一个请求出发到第一个结果返回的时间。相比与超时后再请求,大大减少了时延。

注册中间件(middleware、拦截器…)

框架通过注册中间件的方式使用上述功能。用户或服务器在创建时可选是否加入上述功能,这种灵活的方式保障了整体的稳定性。

image.png


易用性

  • 开箱即用:合理的默认参数选项,丰富的文档
  • 周边工具:框架提供一系列工具辅助用户来更好的使用框架,如生成代码工具,脚手架工具等。尽可能支持多个编解码协议

拓展性

image.png


观测性

  • Log
  • Metric(指标)是在监控和评估系统性能时使用的度量标准
    • QPS(每秒查询率,Queries Per Second)
    • 延迟(请求处理时间)
  • Tracing(链路跟踪)
  • 内置状态观测性服务

image.png

线程(Thread)和协程(Coroutine)都是用于并发执行任务的编程概念,但它们有一些区别和联系。

区别:

  1. 并发模型: 线程是操作系统层面的并发执行单元,每个线程都有自己的上下文,可以由操作系统进行调度。而协程是程序层面的并发模型,由程序员显式地控制执行的切换,不需要操作系统的干预。
  2. 开销: 线程通常有较高的创建和切换开销,因为涉及操作系统的资源分配和上下文切换。协程的切换开销较低,因为切换是在用户空间内进行的。
  3. 并发性: 线程可以在多个处理器核心上同时运行,因此适合CPU密集型任务。协程通常运行在单个线程内,适合I/O密集型任务。

联系:

  1. 并发性目标: 线程和协程都旨在实现并发执行,提高程序的效率和响应性。
  2. 多任务处理: 两者都可以用于处理多个任务,但线程可能需要更多的系统资源。
  3. 状态管理: 线程和协程都需要考虑状态同步和共享资源的问题,以避免竞争条件和数据不一致。

总之,线程适用于需要并行处理和多核利用的场景,而协程适用于处理大量I/O操作和轻量级并发的场景,通过避免昂贵的线程切换开销来提供更好的性能。


高性能

连接池多路复用:减少连接的复用率,减少重复创建和销毁的开销

多路复用:调用端向服务端的一个节点发送请求,并发场景下,如果是非连接多路复用,每个请求都会持有一个连接,直到请求结束连接才会被关闭或者放入连接池复用,并发量与连接数是对等的关系。而使用连接多路复用,所有请求都可以在一个连接上完成,大家可以明显看到连接资源利用上的差异

image.png


RPC企业实践

Kitex 企业内部大范围使用 go 语言进行开发,而 kitex 是内部多年最佳实践沉淀出来的一个高性能高可扩展性的 go RPC 框架。

架构

kitex分为三个部分

  • 核心组件core
  • 与字节公司内部基础设施集成的部分Byted
  • 代码生成工具tool

核心部分除了用户端和服务端之外,还包括注册中心(register)、服务发现(discovery)、负载均衡(loadbalance)、熔断(circuitbreak)、重试(retry)、限流(limit)等。关键的数据结构包括结点(endpoint)、rpc元数据(rpcinfo)等。remote是与远端交互的一层,transport可以和网络库交互,codec负责编解码。

代码生成工具提供命令行工具(cmd)、解析器(parser)、插件机制(plugin)、生成器(generator)、自更新(selfupdate)。

image.png


自研网络库-Netpoll

为什么要自研网络库?

  1. 原生库(go net)(如net/http 用于处理 HTTP 请求和响应,net/tcp 用于处理 TCP 连接,net/udp 用于处理 UDP 数据等) 无法感知连接状态,使用连接池时,池中存在失效连接,影响连接池的复用。
  2. 原生库存在goroutine暴涨的风险:使用一个连接对应一个goroutine的模式,利用率低下,存在大量goroutine占用调度开销,影响性能。

Netpoll 解决了什么问题:

  • 解决无法感知状态连接问题:引入epoll主动监听机制,感知连接状态
  • 解决goroutine池:建立goroutine池,复用goroutine
  • 提升性能:引入Nocopy Buffer,向上层提供NoCopy接口,编解码层面零拷贝

拓展性设计

支持多协议,自定义协议拓展

image.png


性能优化-网络库优化

调度优化

  • epoll wait在调度上的控制
  • gopool重用goroutine降低同时运行协程数

LinkBuffer

  • 读写并行无锁,支持nocopy地流式读写
  • 高效扩缩容
  • Nocopy Buffer池化,减少GC

Pool

  • 引入内存池和对象池,减少GC开销

性能优化-编解码优化

编解码逻辑往往在生成代码里面

Codegen(代码生成):

  • 预计算并预分配内存,减少内存操作次数,包括内存分配和拷贝
  • inline减少函数调用次数和避免不必要得反射操作等
  • 自研基于go的thrift IDL解析和代码生成器,支持完善的Thrift IDL语法和语义检查,支持插件机制-Thriftgo

JIT(just in time,即时编译,当某段代码即将第一次被执行时编译):

  • 使用JIT编译技术改善用户体验的同时带来更强的编解码性能,减轻用户维护生成代码的负担
  • 基于JIT编译技术的高性能动态Thrift编解码器-frugal。

合并部署

但是不同微服务不能直接合并

image.png

image.png


既有HTTP,为什么用RPC?

RPC 只是一种设计而已

RPC 只是一种概念、一种设计,就是为了解决 不同服务之间的调用问题, 它一般会包含有 传输协议 和 序列化协议 这两个。

但是,HTTP 是一种协议,RPC框架可以使用 HTTP协议作为传输协议或者直接使用TCP作为传输协议,使用不同的协议一般也是为了适应不同的场景。

RPC框架功能更齐全

成熟的 RPC框架还提供好了“服务自动注册与发现”、“智能负载均衡”、“可视化的服务治理和运维”、“运行期流量调度”等等功能,这些也算是选择 RPC 进行服务注册和发现的一方面原因吧!

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

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

相关文章

期权交易权限开通:需满足哪些要求,开通流程是怎样的?

现在期权开通方式多种多样,一种是50万元券商开通的正规渠道,一种是民间第三方期权分仓平台0门槛开通的方式,期权的买方风险有限收益无限,卖方风险无限收益有限,下文介绍期权交易权限开通:需满足哪些要求&am…

Android中级——消息机制

消息机制 概念ThreadLocalMessageQueueLooperHandlerrunOnUiThread() 概念 MessageQueue:采用单链表的方法存储消息列表Looper:查询MessageQueue是否有新消息,有则处理,无则等待ThreadLocal:用于Handler获取当前线程的…

基于鹰栖息算法优化的BP神经网络(预测应用) - 附代码

基于鹰栖息算法优化的BP神经网络(预测应用) - 附代码 文章目录 基于鹰栖息算法优化的BP神经网络(预测应用) - 附代码1.数据介绍2.鹰栖息优化BP神经网络2.1 BP神经网络参数设置2.2 鹰栖息算法应用 4.测试结果:5.Matlab代…

cs231n_1_IntroToConv

参考的视频来自如下链接https://www.bilibili.com/video/BV1Ed4y1b7bm/ 参考笔记如下https://blog.csdn.net/TeFuirnever/article/details/89059673 x.1 CV历史 生物快速发展于5.4亿年前,那时的化石显示生物进化出了视觉,视觉使得生物多样性大爆炸。 …

探索回文链表:识别中轴,判断对称性

如何识别回文链表呢?大家可以先思考一下,看看能不能做出力扣234. 回文链表这道题目。 识别以head为头结点的链表是否为回文链表分为三个步骤,分别是: 找出链表的中间结点mid。如果链表有偶数个结点,则找出偏右边的结点…

怎么把pdf图片转换成jpg?pdf转jpg的方法分享

pdf文件在我们的日常工作中非常的常见,因为这种文件安全性高,不会轻易的乱码,所以受到了我们的欢迎,但是它不容易被编辑,而且占用内存会比较大,所以我们需要将pdf文件进行转换,接下来小编会为大…

什么是synchronized的重量级锁

今天我们继续学习synchronized的升级过程,目前只剩下最后一步了:轻量级锁->重量级锁。 通过今天的内容,希望能帮助大家解答synchronized都问啥?中除锁粗化,锁消除以及Java 8对synchronized的优化外全部的问题。 获…

(笔记五)利用opencv进行图像几何转换

参考网站:https://docs.opencv.org/4.1.1/da/d6e/tutorial_py_geometric_transformations.html (1)读取原始图像和标记图像 import cv2 as cv import numpy as np from matplotlib import pyplot as pltpath r"D:\data\flower.jpg&qu…

电工-照明电路施工图

照明电路施工图 上面介绍的电气照明基本电路用作施工的依据是不够的,这是因为图上并没有注明电气元件的规格、型号、安装要求、线路敷设方式以及其他一些特征。作为实际电路安装的依据,必须是根据国家颁布的有关电器技术标准和统一符号绘制的施工图。照…

2022年下半年系统架构设计师真题(下午带答案)

试题一 (25分) 某电子商务公司拟升级其会员与促销管理系统,向用户提供个性化服务,提高用户的粘性。在项目立项之初,公司领导层一致认为本次升级的主要目标是提升会员管理方式的灵活性,由于当前用户规模不大,业务也相对…

QT的介绍和优点,以及使用QT初步完成一个登录界面

QT介绍 QT主要用于图形化界面的开发,QT是基于C编写的一套界面相关的类库,进程线程库,网络编程的库,数据库操作的库,文件操作的库…QT是一个跨平台的GUI图形化界面开发工具 QT的优点 跨平台,具有较为完备…

MySQL存储引擎MyISAM和InnoDB特点全解

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年6月CSDN上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师…

Flutter开发- iOS 问题CocoaPods not installed or not in valid state

解决问题方案: 1、先检查本机CocoaPods是否安装,通过gem list 查看是否安装 打开终端,执行gem list,出现图中的数据即为已安装。未安装看第4 步 2、已经安装了CocoaPods,还出现了图中的提示,你可能已经猜…

快速制作餐厅签到抽奖营销活动,吸引更多顾客

在如今竞争激烈的市场中,吸引用户参与活动是企业获取关注和提升转化率的重要手段。而签到抽奖活动无疑是一种简单而又有效的方式。本文将教你如何利用乔拓云平台后台制作一个快速而有效的签到抽奖活动。 首先,登录乔拓云平台后台,进入【营销活…

【自学开发之旅】基于Flask的web开发(一)

web开发项目设计: 立项-需求分析-设计(原型图、数据库、api设计)-技术选型-写代码-测试-上线 web开发的本质上就是生成超文本。 前端负责展示,后端负责逻辑处理:后逻辑请求(接收请求、响应请求&#xff0…

CSA研讨会|聚焦云原生安全,探讨技术与应用策略

为产业数字化保驾护航, 云原生安全体系如何有效抵御网络威胁? 网络安全的下一个十年, 云原生安全是网络安全创新之路吗? CNAPP部署现状,你了解多少? 9月6日(周三)下午14&#xff1a…

教你实现一个深浅拷贝!

浅拷贝的原理与实现 对于浅拷贝的定义我们可以初步理解为: 自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的…

每日一题(移除链表元素)

每日一题(移除链表元素) 203. 移除链表元素 - 力扣(LeetCode) 思路一: 可以创建一个新的链表头节点newhead,只要是原链表中值不为val的节点、都通过尾插操作插到newhead所指向的链表中,原链表中…

swiper插件使用

swiper插件使用 1.进入官网 官网地址 2.下载文件保存到自己电脑上 3.解压文件夹,找到如图所示的两个文件夹,复制并引入到自己的项目中 4.使用 1.继续打开官网地址,寻找在线演示里面的轮播图案例,挑一个自己需要的,到新窗口打开 2.打开之后,右键,检查网页源代码 3.复制里面…

优秀案例 | 数字人+文化浪漫,开启城市数字文化沉浸式体验

数字人作为城市宣传的新载体、新介质, 可带来多元化、数字化的城市文旅发展模式, 通过打破虚实次元空间, 展现出传统文化与现代生活的相碰撞的魅力。 数字人文化浪漫 赋能城市文化新体验 南京首个以文化元宇宙主题体验中心及高校、文博…