15|检索增强生成:通过RAG助力鲜花运营

15|检索增强生成:通过RAG助力鲜花运营

什么是 RAG?其全称为 Retrieval-Augmented Generation,即检索增强生成,它结合了检索和生成的能力,为文本序列生成任务引入外部知识。RAG 将传统的语言生成模型与大规模的外部知识库相结合,使模型在生成响应或文本时可以动态地从这些知识库中检索相关信息。这种结合方法旨在增强模型的生成能力,使其能够产生更为丰富、准确和有根据的内容,特别是在需要具体细节或外部事实支持的场合。

RAG 的工作原理可以概括为几个步骤。

  1. **检索:**对于给定的输入(问题),模型首先使用检索系统从大型文档集合中查找相关的文档或段落。这个检索系统通常基于密集向量搜索,例如 ChromaDB、Faiss 这样的向量数据库。
  2. **上下文编码:**找到相关的文档或段落后,模型将它们与原始输入(问题)一起编码。
  3. **生成:**使用编码的上下文信息,模型生成输出(答案)。这通常当然是通过大模型完成的。

RAG 的一个关键特点是,它不仅仅依赖于训练数据中的信息,还可以从大型外部知识库中检索信息。这使得 RAG 模型特别适合处理在训练数据中未出现的问题。

img

RAG 的 Pipeline

RAG 类的任务,目前企业实际应用场景中的需求量相当大,也是 LangChain 所关注的一个重点内容。在这节课中,我会对 LangChain 中所有与之相关的工具进行一个梳理,便于你把握 LangChain 在这个领域中都能够做到些什么。

文档加载

RAG 的第一步是文档加载。LangChain 提供了多种类型的文档加载器,以加载各种类型的文档(HTML、PDF、代码),并与该领域的其他主要提供商如 Airbyte 和 Unstructured.IO 进行了集成。

下面给出常用的文档加载器列表。

img

文本转换

加载文档后,下一个步骤是对文本进行转换,而最常见的文本转换就是把长文档分割成更小的块(或者是片,或者是节点),以适合模型的上下文窗口。LangChain 有许多内置的文档转换器,可以轻松地拆分、组合、过滤和以其他方式操作文档。

文本分割器

把长文本分割成块听起来很简单,其实也存在一些细节。文本分割的质量会影响检索的结果质量。理想情况下,我们希望将语义相关的文本片段保留在一起。

LangChain 中,文本分割器的工作原理如下:

  1. 将文本分成小的、具有语义意义的块(通常是句子)。
  2. 开始将这些小块组合成一个更大的块,直到达到一定的大小。
  3. 一旦达到该大小,一个块就形成了,可以开始创建新文本块。这个新文本块和刚刚生成的块要有一些重叠,以保持块之间的上下文。

因此,LangChain 提供的各种文本拆分器可以帮助你从下面几个角度设定你的分割策略和参数:

  1. 文本如何分割
  2. 块的大小
  3. 块之间重叠文本的长度

这些文本分割器的说明和示例如下:

img

你可能会关心,文本分割在实践,有哪些具体的考量因素,我总结了下面几点。

**首先,就是 LLM 的具体限制。**GPT-3.5-turbo 支持的上下文窗口为 4096 个令牌,这意味着输入令牌和生成的输出令牌的总和不能超过 4096,否则会出错。为了保证不超过这个限制,我们可以预留约 2000 个令牌作为输入提示,留下约 2000 个令牌作为返回的消息。这样,如果你提取出了五个相关信息块,那么每个片的大小不应超过 400 个令牌。

此外,文本分割策略的选择和任务类型相关。

  • 需要细致查看文本的任务,最好使用较小的分块。例如,拼写检查、语法检查和文本分析可能需要识别文本中的单个单词或字符。垃圾邮件识别、查找剽窃和情感分析类任务,以及搜索引擎优化、主题建模中常用的关键字提取任务也属于这类细致任务。
  • 需要全面了解文本的任务,则使用较大的分块。例如,机器翻译、文本摘要和问答任务需要理解文本的整体含义。而自然语言推理、问答和机器翻译需要识别文本中不同部分之间的关系。还有创意写作,都属于这种粗放型的任务。

**最后,你也要考虑所分割的文本的性质。**例如,如果文本结构很强,如代码或 HTML,你可能想使用较大的块,如果文本结构较弱,如小说或新闻文章,你可能想使用较小的块。

你可以反复试验不同大小的块和块与块之间重叠窗口的大小,找到最适合你特定问题的解决方案。

其他形式的文本转换

除拆分文本之外,LangChain 中还集成了各种工具对文档执行的其他类型的转换。下面让我们对其进行逐点分析。

  1. 过滤冗余的文档:使用 EmbeddingsRedundantFilter 工具可以识别相似的文档并过滤掉冗余信息。这意味着如果你有多份高度相似或几乎相同的文档,这个功能可以帮助识别并删除这些多余的副本,从而节省存储空间并提高检索效率。
  2. 翻译文档:通过与工具 doctran 进行集成,可以将文档从一种语言翻译成另一种语言。
  3. 提取元数据:通过与工具 doctran 进行集成,可以从文档内容中提取关键信息(如日期、作者、关键字等),并将其存储为元数据。元数据是描述文档属性或内容的数据,这有助于更有效地管理、分类和检索文档。
  4. 转换对话格式:通过与工具 doctran 进行集成,可以将对话式的文档内容转化为问答(Q/A)格式,从而更容易地提取和查询特定的信息或回答。这在处理如访谈、对话或其他交互式内容时非常有用。

所以说,文档转换不仅限于简单的文本拆分,还可以包含附加的操作,这些操作的目的都是更好地准备和优化文档,以供后续生成更好的索引和检索功能。

文本嵌入

文本块形成之后,我们就通过 LLM 来做嵌入(Embeddings),将文本转换为数值表示,使得计算机可以更容易地处理和比较文本。OpenAI、Cohere、Hugging Face 中都有能做文本嵌入的模型。

Embeddings 会创建一段文本的向量表示,让我们可以在向量空间中思考文本,并执行语义搜索之类的操作,在向量空间中查找最相似的文本片段。

img

图片来源网络

LangChain 中的 Embeddings 类是设计用于与文本嵌入模型交互的类。这个类为所有这些提供者提供标准接口。

# 初始化Embedding类
from langchain.embeddings import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()

它提供两种方法:

  1. 第一种是 embed_documents 方法,为文档创建嵌入。这个方法接收多个文本作为输入,意味着你可以一次性将多个文档转换为它们的向量表示。
  2. 第二种是 embed_query 方法,为查询创建嵌入。这个方法只接收一个文本作为输入,通常是用户的搜索查询。

**为什么需要两种方法?**虽然看起来这两种方法都是为了文本嵌入,但是 LangChain 将它们分开了。原因是一些嵌入提供者对于文档和查询使用的是不同的嵌入方法。文档是要被搜索的内容,而查询是实际的搜索请求。这两者可能因为其性质和目的,而需要不同的处理或优化。

embed_documents 方法的示例代码如下:

embeddings = embeddings_model.embed_documents(
    [
        "您好,有什么需要帮忙的吗?",
        "哦,你好!昨天我订的花几天送达",
        "请您提供一些订单号?",
        "12345678",
    ]
)
len(embeddings), len(embeddings[0])

输出:

(4, 1536)

embed_documents 方法的示例代码如下:

embedded_query = embeddings_model.embed_query("刚才对话中的订单号是多少?")
embedded_query[:3]

输出:

[-0.0029746221837547455, -0.007710168602107487, 0.00923260021751183]

存储嵌入

计算嵌入可能是一个时间消耗大的过程。为了加速这一过程,我们可以将计算出的嵌入存储或临时缓存,这样在下次需要它们时,就可以直接读取,无需重新计算。

缓存存储

CacheBackedEmbeddings 是一个支持缓存的嵌入式包装器,它可以将嵌入缓存在键值存储中。具体操作是:对文本进行哈希处理,并将此哈希值用作缓存的键。

要初始化一个 CacheBackedEmbeddings,主要的方式是使用 from_bytes_store。其需要以下参数:

  • underlying_embedder:实际计算嵌入的嵌入器。
  • document_embedding_cache:用于存储文档嵌入的缓存。
  • namespace(可选):用于文档缓存的命名空间,避免与其他缓存发生冲突。

不同的缓存策略如下:

  1. InMemoryStore:在内存中缓存嵌入。主要用于单元测试或原型设计。如果需要长期存储嵌入,请勿使用此缓存。
  2. LocalFileStore:在本地文件系统中存储嵌入。适用于那些不想依赖外部数据库或存储解决方案的情况。
  3. RedisStore:在 Redis 数据库中缓存嵌入。当需要一个高速且可扩展的缓存解决方案时,这是一个很好的选择。

在内存中缓存嵌入的示例代码如下:

# 导入内存存储库,该库允许我们在RAM中临时存储数据
from langchain.storage import InMemoryStore

# 创建一个InMemoryStore的实例
store = InMemoryStore()

# 导入与嵌入相关的库。OpenAIEmbeddings是用于生成嵌入的工具,而CacheBackedEmbeddings允许我们缓存这些嵌入
from langchain.embeddings import OpenAIEmbeddings, CacheBackedEmbeddings

# 创建一个OpenAIEmbeddings的实例,这将用于实际计算文档的嵌入
underlying_embeddings = OpenAIEmbeddings()

# 创建一个CacheBackedEmbeddings的实例。
# 这将为underlying_embeddings提供缓存功能,嵌入会被存储在上面创建的InMemoryStore中。
# 我们还为缓存指定了一个命名空间,以确保不同的嵌入模型之间不会出现冲突。
embedder = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings,  # 实际生成嵌入的工具
    store,  # 嵌入的缓存位置
    namespace=underlying_embeddings.model  # 嵌入缓存的命名空间
)

# 使用embedder为两段文本生成嵌入。
# 结果,即嵌入向量,将被存储在上面定义的内存存储中。
embeddings = embedder.embed_documents(["你好", "智能鲜花客服"])

解释下这段代码。首先我们在内存中设置了一个存储空间,然后初始化了一个嵌入工具,该工具将实际生成嵌入。之后,这个嵌入工具被包装在一个缓存工具中,用于为两段文本生成嵌入。

至于其他两种缓存器,嵌入的使用方式也不复杂,你可以参考 LangChain 文档自行学习。

向量数据库(向量存储)

更常见的存储向量的方式是通过向量数据库(Vector Store)来保存它们。LangChain 支持非常多种向量数据库,其中有很多是开源的,也有很多是商用的。比如 Elasticsearch、Faiss、Chroma 和 Qdrant 等等。

因为选择实在是太多了,我也给你列出来了一个表。

img

那么问题来了,面对这么多种类的向量数据库,应该如何选择呢?

这就涉及到许多技术和业务层面的考量,你应该根据具体需求进行选型

  1. 数据规模和速度需求:考虑你的数据量大小以及查询速度的要求。一些向量数据库在处理大规模数据时更加出色,而另一些在低延迟查询中表现更好。
  2. 持久性和可靠性:根据你的应用场景,确定你是否需要数据的高可用性、备份和故障转移功能。
  3. 易用性和社区支持:考虑向量数据库的学习曲线、文档的完整性以及社区的活跃度。
  4. 成本:考虑总体拥有成本,包括许可、硬件、运营和维护成本。
  5. 特性:考虑你是否需要特定的功能,例如多模态搜索等。
  6. 安全性:确保向量数据库符合你的安全和合规要求。

在进行向量数据库的评测时,进行性能基准测试是了解向量数据库实际表现的关键。这可以帮助你评估查询速度、写入速度、并发性能等。

没有“最好”的向量数据库,只有“最适合”的向量数据库。在你的需求上做些研究和测试,确保你选择的向量数据库满足你的业务和技术要求就好。

数据检索

在 LangChain 中,Retriever,也就是检索器,是数据检索模块的核心入口,它通过非结构化查询返回相关的文档。

向量存储检索器

向量存储检索器是最常见的,它主要支持向量检索。当然 LangChain 也有支持其他类型存储格式的检索器。

下面实现一个端到端的数据检索功能,我们通过 VectorstoreIndexCreator 来创建索引,并在索引的 query 方法中,通过 vectorstore 类的 as_retriever 方法,把向量数据库(Vector Store)直接作为检索器,来完成检索任务。

# 设置OpenAI的API密钥
import os
os.environ["OPENAI_API_KEY"] = 'Your OpenAI Key'

# 导入文档加载器模块,并使用TextLoader来加载文本文件
from langchain.document_loaders import TextLoader
loader = TextLoader('LangChainSamples/OneFlower/易速鲜花花语大全.txt', encoding='utf8')

# 使用VectorstoreIndexCreator来从加载器创建索引
from langchain.indexes import VectorstoreIndexCreator
index = VectorstoreIndexCreator().from_loaders([loader])

# 定义查询字符串, 使用创建的索引执行查询
query = "玫瑰花的花语是什么?"
result = index.query(query)
print(result) # 打印查询结果

输出:

玫瑰花的花语是爱情、热情、美丽。

你可能会觉得,这个数据检索过程太简单了。这就要归功于 LangChain 的强大封装能力。如果我们审视一下位于 vectorstore.py 中的 VectorstoreIndexCreator 类的代码,你就会发现,它其中封装了 vectorstore、embedding 以及 text_splitter,甚至 document loader(如果你使用 from_documents 方法的话)。

class VectorstoreIndexCreator(BaseModel):
    """Logic for creating indexes."""

    vectorstore_cls: Type[VectorStore] = Chroma
    embedding: Embeddings = Field(default_factory=OpenAIEmbeddings)
    text_splitter: TextSplitter = Field(default_factory=_get_default_text_splitter)
    vectorstore_kwargs: dict = Field(default_factory=dict)

    class Config:
        """Configuration for this pydantic object."""

        extra = Extra.forbid
        arbitrary_types_allowed = True

    def from_loaders(self, loaders: List[BaseLoader]) -> VectorStoreIndexWrapper:
        """Create a vectorstore index from loaders."""
        docs = []
        for loader in loaders:
            docs.extend(loader.load())
        return self.from_documents(docs)

    def from_documents(self, documents: List[Document]) -> VectorStoreIndexWrapper:
        """Create a vectorstore index from documents."""
        sub_docs = self.text_splitter.split_documents(documents)
        vectorstore = self.vectorstore_cls.from_documents(
            sub_docs, self.embedding, **self.vectorstore_kwargs
        )
        return VectorStoreIndexWrapper(vectorstore=vectorstore)

因此,上面的检索功能就相当于我们第 2 课中讲过的一系列工具的整合。而我们也可以用下面的代码,来显式地指定索引创建器的 vectorstore、embedding 以及 text_splitter,并把它们替换成你所需要的工具,比如另外一种向量数据库或者别的 Embedding 模型。

from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
index_creator = VectorstoreIndexCreator(
    vectorstore_cls=Chroma,
    embedding=OpenAIEmbeddings(),
    text_splitter=CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
)

那么,下一个问题是 index.query(query),又是如何完成具体的检索及文本生成任务的呢?我们此处既没有看到大模型,又没有看到 LangChain 的文档检索工具(比如我们在第 2 课中见过的 QARetrival 链)。

秘密仍然存在于源码中,在 VectorStoreIndexWrapper 类的 query 方法中,可以看到,在调用方法的同时,RetrievalQA 链被启动,以完成检索功能。

class VectorStoreIndexWrapper(BaseModel):
    """Wrapper around a vectorstore for easy access."""

    vectorstore: VectorStore

    class Config:
        """Configuration for this pydantic object."""

        extra = Extra.forbid
        arbitrary_types_allowed = True

    def query(
        self,
        question: str,
        llm: Optional[BaseLanguageModel] = None,
        retriever_kwargs: Optional[Dict[str, Any]] = None,
        **kwargs: Any
    ) -> str:
        """Query the vectorstore."""
        llm = llm or OpenAI(temperature=0)
        retriever_kwargs = retriever_kwargs or {}
        chain = RetrievalQA.from_chain_type(
            llm, retriever=self.vectorstore.as_retriever(**retriever_kwargs), **kwargs
        )
        return chain.run(question)

上面我们用到的向量存储检索器,是向量存储类的轻量级包装器,使其符合检索器接口。它使用向量存储中的搜索方法(例如相似性搜索和 MMR)来查询向量存储中的文本。

各种类型的检索器

除向量存储检索器之外,LangChain 中还提供很多种其他的检索工具。

img

这些检索工具,各有其功能特点,你可以查找它们的文档说明,并尝试使用。

索引

在本节课的最后,我们来看看 LangChain 中的索引(Index)。简单的说,索引是一种高效地管理和定位文档信息的方法,确保每个文档具有唯一标识并便于检索。

尽管在第 2 课的示例中,我们并没有显式的使用到索引就完成了一个 RAG 任务,但在复杂的信息检索任务中,有效地管理和索引文档是关键的一步。LangChain 提供的索引 API 为开发者带来了一个高效且直观的解决方案。具体来说,它的优势包括:

  • 避免重复内容:确保你的向量存储中不会有冗余数据。
  • 只更新更改的内容:能检测哪些内容已更新,避免不必要的重写。
  • 省时省钱:不对未更改的内容重新计算嵌入,从而减少了计算资源的消耗。
  • 优化搜索结果:减少重复和不相关的数据,从而提高搜索的准确性。

LangChain 利用了记录管理器(RecordManager)来跟踪哪些文档已经被写入向量存储。

在进行索引时,API 会对每个文档进行哈希处理,确保每个文档都有一个唯一的标识。这个哈希值不仅仅基于文档的内容,还考虑了文档的元数据。

一旦哈希完成,以下信息会被保存在记录管理器中:

  • 文档哈希:基于文档内容和元数据计算出的唯一标识。
  • 写入时间:记录文档何时被添加到向量存储中。
  • 源 ID:这是一个元数据字段,表示文档的原始来源。

这种方法确保了即使文档经历了多次转换或处理,也能够精确地跟踪它的状态和来源,确保文档数据被正确管理和索引。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pygame和Cocos2d

Pygame和Cocos2d都是 Python 中常用的游戏引擎,但它们的设计目标、特点和使用场景略有不同。 Pygame与Cocos2d,目前是使用人数最多的两个Python游戏库。根据某知名产品点评网站的数据显示,排名前五的Python 2D游戏库如下图所示。其中&#x…

聚观早报 |小米汽车SU7官图发布;优酷上线“AI搜片”功能

聚观早报每日整理最值得关注的行业重点事件,帮助大家及时了解最新行业动态,每日读报,就读聚观365资讯简报。 整理丨Cutie 12月29日消息 小米汽车SU7官图发布 优酷上线“AI搜片”功能 小米汽车智能驾驶技术公布 百度投资AIGC公司必优科技…

硬链接和软链接以及inode的简述【Linux】

硬链接和软链接 inode是什么?面试题 硬链接软链接 inode是什么? 认识inode之前,先来看一下一个文件在磁盘里面是怎么存储的。   首先一个物理的圆盘形状且多层的一个磁盘会被逻辑化成为一个数组,找到一个文件在这个数组里面叫做…

JavaScript新加入的**运算符,哪里有些不一样呢?

JavaScript语法(四):新加入的**运算符,哪里有些不一样呢? 上一节课我们已经给你介绍了表达式的一些结构,其中关于赋值表达式,我们讲完了它的左边部分,而留下了它右边部分,那么,我们…

HarmonyOS4.0系统性深入开发14AbilityStage组件容器

AbilityStage组件容器 AbilityStage是一个Module级别的组件容器,应用的HAP在首次加载时会创建一个AbilityStage实例,可以对该Module进行初始化等操作。 AbilityStage与Module一一对应,即一个Module拥有一个AbilityStage。 DevEco Studio默…

1-并发编程线程基础

什么是线程 在讨论什么是线程前有必要先说下什么是进程,因为线程是进程中的一个实体,线程本身是不会独立存在的。 进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径&#…

创意与技术的结晶:AI魔法绘图与中文描述的完美结合

在人类文明的长河中,创意与技术一直是推动发展的重要动力。随着科技的日新月异,人工智能(AI)在创意领域的应用逐渐崭露头角,而AI魔法绘图与中文描述的结合,更是将这一趋势推向了新的高度。AI魔法绘图是一种…

各类Java对象

相关概念的混淆 在某一时间段,人们对某种编程困境感到烦恼,不少人脑中产生了一种新开发方式的概念 一些代表人物提出了他们的意见,而同一时期可能又不少人对同一问题,用自己的不同语言提出不同概念 如果又官方组织维护概念&#x…

外包干了3个多月,技术退步明显。。。。。

先说一下自己的情况,本科生生,19年通过校招进入广州某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

初探ElasticSearch

1.什么是ElasticSearch? ElasticSearch简称ES,也成为弹性搜索,是基于Apache Lucene构建的开源搜索引擎。其实Lucene本身就是一款性能很好的开源搜索引擎工具包,但是Lucene的API相对复杂,而且掌握它需要很深厚的“内功…

【Linux Shell】7. printf 命令

文章目录 【 1. printf 命令的使用方法 】【 2. 实例 】 【 1. printf 命令的使用方法 】 printf 命令模仿 C 程序库(library)里的 printf() 程序,printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。prin…

kubeSphere集群部署nacos

kubeSphere部署nacos 个人环境说明执行nacos数据脚本kubeSphere添加配置创建有状态副本集修改集群配置文件 创建外部访问服务访问 个人环境说明 由于我之前这个项目就是dockerjenkins部署的,只是现在升级到k8skubeSphere所有下面有些操作我可能不同,例如我的nacos配置文件就是d…

x-cmd pkg | gh - GitHub 官方 CLI

目录 简介首次用户功能特点与 x-cmd gh 模块的关系相关作品进一步探索 简介 gh,是由 GitHub 官方使用 Go 语言开发和维护的命令行工具,旨在脚本或是命令行中便捷管理和操作 GitHub 的工作流程。 注意: 由于 x-cmd 提供了同名模块,因此使用官…

虚幻UE 增强输入-触发器

上一篇增强输入基础:虚幻UE 增强输入-第三人称模板增强输入分析与扩展 主要对第三人称模板的增强输入进行分析、复刻和扩展 本篇将会对增强输入中的触发器中的各参数进行讲解 文章目录 前言触发器参数1、下移TriggerDown2、已按下TriggerPressed3、已松开TriggerRel…

系列一、如何正确的获取Spring Cloud Alibaba Spring Cloud Spring Boot之间的版本对应关系

一、正确的获取Spring Cloud Alibaba & Spring Cloud & Spring Boot之间的版本对应关系 1.1、概述 Java发展日新月异,Spring Cloud Alibaba 、 Spring Cloud 、 Spring Boot在GitHub上的迭代也是异常的频繁,这也说明其社区很活跃,通…

【ChatGPT+】创新与教育的交汇点:中国训练工程师的崛起

人工智能总价值超15.7万亿美元 根据国际数据公司(IDC)的预测,到2030年,全球人工智能市场总价值将超过15.7万亿美元,这表明人工智能技术将在未来几十年内得到广泛应用并取得长足发展。 人工智能的快速发展将对各个领域…

【案例】HOOPS Web Platform助力Eurostep简化全球制造流程!

行业:制造业 公司:Eurostep 软件:ShareAspace软件开发包:Hoops Web Platform 挑战: 为制造商打造协同设计产品的云服务平台。结合本地3D功能以增加现有的2D数据功能。在供应链日益全球化的情况下,保证数…

【深度学习:Self-supervised learning (SSL) 】自我监督学习解释

【深度学习:SSL Self-supervised learning 】自我监督学习解释 什么是自我监督学习?比较自我监督学习与监督学习和无监督学习 为什么计算机视觉模型需要自监督学习?自我监督学习的好处自监督学习的局限性 自我监督学习如何运作?对…

UOS下通过SSH隧道访问云端内网windows桌面

1 用户痛点 随着时代的发展,众多企业的服务器慢慢走向云端。大量云端服务器节省企业成本的同时,也带来了安全性问题。例如:管理云端的服务器,特别是windows桌面服务器,往往需要给这个服务器分配一个公网IP地址&#x…

计算机网络面试题

参考:小林Coding-图解计网 必备基础知识点 (一)TCP/IP网络模型和标准ISO网络模型 1,TCP/IP详解-各层使用到的协议 对应的数据包的结构 : 2,ISO七层模型 3,TCP/IP和ISO的对比 主要特定是&…