当前位置: 首页 > news >正文

从零开始搭建AI网站(6):如何使用响应式编程

响应式编程(Reactive Programming)是一种编程范式,旨在处理异步数据流和事件流。它通过使用观察者模式和函数式编程的概念,将数据流和事件流抽象为可观察的序列,然后通过操作这些序列来实现各种功能。

在响应式编程中,数据流和事件流被视为连续的时间序列,可以通过操作符来转换、过滤和组合它们。这种编程范式的主要优势是它可以简化异步编程,并提供一种声明式的方式来处理数据流和事件流。它还可以提高代码的可读性和可维护性,因为它将复杂的异步逻辑封装在操作符中,使得代码更易于理解和修改。

响应式编程可以应用于各种领域,包括前端开发、后端开发、移动开发等。在前端开发中,响应式编程可以用于处理用户界面的事件流和数据流,使得界面能够动态地响应用户的操作。在后端开发中,响应式编程可以用于处理大量的异步请求和数据流,提高系统的吞吐量和响应速度。

常见的响应式编程框架包括RxJava、RxJS、ReactiveX等。这些框架提供了一系列的操作符和工具,用于处理数据流和事件流,并提供了一种简洁而强大的方式来处理异步编程。

当前响应式编程的典型例子莫过于最近炙手可热的ChatGPT的流式输出了。因为ChatGPT请求响应时间较长,如果采用传统的一直等待全部数据就绪,用户恐怕早就跑光了,而响应式方式则不需要等待所有数据就绪,而只需要有部分数据就绪即可输出,从而极大地提升了用户体验。下面以此为例,来说明实现这种效果的原理(开发语言Java)。

先来看看上文中提到的的三个响应式编程框架:RxJava、RxJS和ReactiveX。它们是三个相关的概念,同时也是不同平台上的实现。

  1. RxJava:RxJava是ReactiveX在Java平台上的实现,它提供了一套丰富的API和操作符,用于处理异步和事件驱动的编程。RxJava是基于观察者模式和迭代器模式的,可以用于处理数据流、事件流和异步任务等。
  2. RxJS:RxJS是ReactiveX在JavaScript平台上的实现,它提供了类似于RxJava的API和操作符,用于处理异步和事件驱动的编程。RxJS可以在浏览器端和Node.js环境中使用,可以处理DOM事件、AJAX请求、定时器等。
  3. ReactiveX:ReactiveX是一个跨平台的响应式编程库,它提供了一套统一的API和操作符,用于处理异步和事件驱动的编程。ReactiveX的目标是提供一种通用的编程模型,使得开发者可以在不同的平台和语言中共享代码和思想。

在Springboot中,另有WebFlux模块可供使用,同时它也可以跟上面的模块一起使用。说起Flux,这里也会涉及到另一个概念:Flowable。其实Flowable和Flux都是响应式流的实现,它们有以下关系:

  1. Flowable是RxJava的一部分,而Flux是Reactor的一部分。RxJava是一个用于Java的响应式编程库,而Reactor是一个用于Java的响应式编程框架。
  2. Flowable是RxJava中的一个类,它实现了Reactive-Streams规范,提供了对背压(backpressure)的支持。Flowable可以处理异步和并发的数据流,并且可以控制数据流的速率,以避免生产者和消费者之间的不匹配。
  3. Flux是Reactor中的一个类,它也实现了Reactive-Streams规范,提供了类似的功能。Flux可以处理异步和并发的数据流,并且可以控制数据流的速率。
  4. Flowable和Flux都提供了一系列的操作符,可以对数据流进行转换、过滤、映射等操作。这些操作符可以帮助开发者处理和操作数据流,使代码更加简洁和可读。

跟tRxJava和Reactor密切相关的开发库之一是WebClien。WebClient是一个用于发送HTTP请求的非阻塞的响应式客户端,它是Reactor项目的一部分。

WebClient提供了一种简洁、灵活和可组合的方式来发送HTTP请求,并处理响应。它可以与RxJava和Reactor的异步和响应式编程模型无缝集成,使得在响应式应用程序中处理HTTP请求变得更加方便和高效。

WebClient可以与RxJava的Flowable一起使用,通过toFlowable()方法将响应转换为Flowable流,从而实现对响应的处理和操作。

WebClient webClient = WebClient.create();
Flowable<String> response = webClient.get().uri("https://example.com").retrieve().bodyToFlux(String.class).toFlowable();

同样,WebClient也可以与Reactor的Flux一起使用,通过bodyToFlux()方法将响应转换为Flux流,从而实现对响应的处理和操作。

WebClient webClient = WebClient.create();
Flux<String> response = webClient.get().uri("https://example.com").retrieve().bodyToFlux(String.class);

下面我们将关注点放在Reactor框架中,在Reactor中,不得不提另一个跟Flux相对的概念:Mono。Flux和Mono是Reactor框架中的两个关键类,它们都是用于处理响应式流的。

  1. Flux是一个表示0到N个元素的响应式流。它可以发出多个元素,并以异步的方式产生这些元素。Flux可以用于处理多个值的数据流,例如从数据库查询结果、文件读取等。
  2. Mono是一个表示0或1个元素的响应式流。它要么发出一个元素,要么不发出任何元素。Mono可以用于处理单个值的数据流,例如从缓存中获取数据、获取单个实体等。
  3. Flux和Mono之间有以下关系:
  • Flux可以被转换成Mono。
Flux<Integer> flux = Flux.just(1, 2, 3);
Mono<Integer> mono = flux.single();
    • Mono可以被转换成Flux。
Mono<Integer> mono = Mono.just(1);
Flux<Integer> flux = mono.flux();

Flux和Mono可以通过一系列的操作符进行转换、过滤、映射等操作,使得对响应式流的处理变得更加灵活和方便。它们是Reactor框架中的核心类,用于构建响应式应用程序。

webClient可以实现复杂的处理逻辑,比如异常处理:

webClient.get().uri(url).retrieve().onStatus(HttpStatus::is4xxClientError, response -> Mono.error(new CustomException("客户端错误"))).onStatus(HttpStatus::is5xxServerError, response -> Mono.error(new CustomException("服务器错误"))).bodyToMono(String.class).onErrorResume(throwable -> {if (throwable instanceof WebClientResponseException) {WebClientResponseException ex = (WebClientResponseException) throwable;// 处理响应异常} else {// 处理其他异常}});

在使用 Spring Boot 的 WebClient 时,bodyToMono 和 bodyToFlux 方法都可以用于将响应体转换为 Mono 或 Flux 对象。

bodyToMono 方法用于将响应体转换为 Mono 对象,适用于响应体只有一个元素的情况,例如返回一个 JSON 对象或者一个字符串。

bodyToFlux 方法用于将响应体转换为 Flux 对象,适用于响应体有多个元素的情况,例如返回一个 JSON 数组或者一个流式数据。

因此,当我们需要处理的响应体只有一个元素时,应该使用 bodyToMono 方法;当我们需要处理的响应体有多个元素时,应该使用 bodyToFlux 方法。

在 Reactor 中,Flux 流结束的实现原理是通过发送一个 onComplete 信号来通知订阅者流已经结束。当 Flux 流中的所有元素都被消费完毕时,会自动发送一个 onComplete 信号。

例如,当我们使用 Flux.range(1, 10) 创建一个包含 1 到 10 的整数序列的 Flux 流时,当订阅者订阅该流并消费完所有元素后,会自动发送一个 onComplete 信号来通知订阅者流已经结束。

在使用 Spring Boot 的 WebClient 时,当我们使用 bodyToFlux 方法将响应体转换为 Flux 对象时,如果响应体是一个流式数据,那么当流式数据传输完毕后,会自动发送一个 onComplete 信号来通知订阅者流已经结束。

webClient.get().uri(url).retrieve().bodyToFlux(String.class).doFinally(signalType -> {if (signalType == SignalType.ON_COMPLETE) {System.out.println("流已结束");}}).subscribe();

有了这些基础知识的准备,我们再来看看ChatGPT的响应结果样例。OpenAI的聊天接口是:

http://api.openai.com/v1/chat/completitions。

该接口接受这样的一个请求数据结构:ChatCompletionRequest。其中有个属性stream 可以设定是否采用流输出。默认false。

这个例子是非stream输出,输出格式为:ChatCompletionResponse

$ curl https://api.openai.com/v1/chat/completions -H 'Content-Type: application/json' -H "Authorization: Bearer sk-zDxkX0Na0e63B18c9c6bT3BlBkFJf3De387b398749c5bD1d" -d '{"model": "gpt-3.5-turbo","stream":"false","messages": [{"role": "user", "content": "Hello!"}]}'
{"id":"chatcmpl-7tywVQ4vSPzs8yuZy5FqvL0CX07W0","object":"chat.completion","created":1693576659,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"message":{"role":"assistant","content":"Hello

这个例子是stream输出,输出结构体为:字符串格式的ChatCompletionResponse:

curl https://api.openai.com/v1/chat/completions -H 'Content-Type: application/json' -H "Authorization: Bearer sk-zDxkX0Na0e63B18c9c6bT3BlBkFJf3De387b398749c5bD1d" -d '{"model": "gpt-3.5-turbo","stream":"true","messages": [{"role": "user", "content": "Hello!"}]}'

比较stream和非stream的输出区别,有一下几点:

1.非stream 输出只有一条记录;stream 有若干条,取决于响应内容大小;

2. 非stream 输出包含消耗的tokens数量,stream 没有;

3. 非stream 输出结果是json格式的ChatCompletionResponse结构,stream 输出j格式类似:data:str(ChatCompletionResponse),同时以data:[NONE]结尾;

结合上面的知识,我们就能实现上述功能:

public Publisher<String> generateChatCompletion(ChatCompletionRequest chatCompletionRequest) {WebClient.ResponseSpec responseSpec = webClient.post().uri(this.apiUrl + "/chat/completions").header("Authorization", "Bearer " + this.apiKey)//                .accept(MediaType.TEXT_EVENT_STREAM)                .bodyValue(chatCompletionRequest).retrieve();if (chatCompletionRequest.getStream())return                    responseSpec.bodyToFlux(ChatCompletionResponse.class).onErrorResume(error -> {// 异常处理逻辑 logger.error("bodyToFlux error: {}", error);return Flux.empty();}).filter(response -> {ChatMessage message = response.getChoices().get(0).getMessage();if (message != null) {String content = message.getContent();return StringUtils.isNotEmpty(StringUtils.trim(content));}return false;}).mapNotNull(response -> {try {return objectMapper.writeValueAsString(response);} catch (JsonProcessingException e) {logger.error(e);return null;}}).concatWithValues("[DONE]");else            return     responseSpec.bodyToMono(ChatCompletionResponse.class).onErrorResume(error -> {// 异常处理逻辑   logger.error("bodyToMono error: {}", error);return Mono.empty();}).mapNotNull(response -> {try {return objectMapper.writeValueAsString(response);} catch (JsonProcessingException e) {logger.error(e);return null;}});}

Publisher是一个通用的概念,它代表一个发布者,可以发布数据或事件。在Spring WebFlux中,Flux和Mono都是Publisher的实现类。

试用地址:https://chatgpt-discount.zeabur.app

相关文章:

从零开始搭建AI网站(6):如何使用响应式编程

响应式编程&#xff08;Reactive Programming&#xff09;是一种编程范式&#xff0c;旨在处理异步数据流和事件流。它通过使用观察者模式和函数式编程的概念&#xff0c;将数据流和事件流抽象为可观察的序列&#xff0c;然后通过操作这些序列来实现各种功能。 在响应式编程中…...

MPI之虚拟进程拓扑

什么是虚拟进程拓扑 在很多并行应用进程中&#xff0c;进程的线性排列不能充分的反映进程间在逻辑上的通信模型&#xff0c;通常由问题几何和所用的算法决定&#xff0c;进程经常被排列成二维或者三维网络形式的拓扑模型而通常用一个图来描述逻辑进程排列&#xff0c;此种逻辑…...

Three.js相机参数及Z-Fighting问题的解决方案

本主题讨论透视相机以及如何为远距离环境设置合适的视锥体。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 透视相机是一种投影模式&#xff0c;旨在模仿人类在现实世界中看待事物的方式。 这是渲染 3D 场景最常用的投影模式。 - three.js 如果你看一下 Three.js 文档…...

微信小程序食疗微信小程序的设计与实现

摘要 现在人们的生活水平高了&#xff0c;大家都想在多活个几十年&#xff0c;要想实现这个想法&#xff0c;有很多事情都必须考虑到&#xff0c;第一个就是适当运动&#xff0c;第二个就是心情好&#xff0c;第三个就是要注意饮食。民以食为天&#xff0c;科学合理的饮食结构是…...

mac环境使用pkgbuild命令打pkg包的几个小细节

mac环境使用pkgbuild命令打pkg包的几个小细节 最近&#xff0c;研发提出要使用jenkins来自动生成mac环境下的pkg包&#xff0c;研究了一下&#xff0c;可以使用pkgbuild来打包。但是有几个小细节需要注意一下&#xff1a; 1 如果有pre-install和post-install脚本&#xff0c;…...

在 Spring Boot 中集成 MinIO 对象存储

MinIO 是一个开源的对象存储服务器&#xff0c;专注于高性能、分布式和兼容S3 API的存储解决方案。本文将介绍如何在 Spring Boot 应用程序中集成 MinIO&#xff0c;以便您可以轻松地将对象存储集成到您的应用中。 安装minio 拉取 minio Docker镜像 docker pull minio/minio创…...

seq2seq与引入注意力机制的seq2seq

1、什么是 seq2seq&#xff1f; 就是字面意思&#xff0c;“句子 到 句子”。比如翻译。 2、seq2seq 有一些特点 seq2seq 的整体架构是 “编码器-解码器”。 其中&#xff0c;编码器是 RNN&#xff0c;并将 最后一个hidden state&#xff08;隐藏状态&#xff09;【即&…...

【zookeeper】zookeeper介绍

分布式协调技术 在学习ZooKeeper之前需要先了解一种技术——分布式协调技术。那么什么是分布式协调技术&#xff1f;其实分布式协调技术主要用来解决分布式环境当中多个进程之间的同步控制&#xff0c;让他们有序的去访问某种临界资源&#xff0c;防止造成"脏数据"的…...

2023高教社杯数学建模思路 - 案例:ID3-决策树分类算法

文章目录 0 赛题思路1 算法介绍2 FP树表示法3 构建FP树4 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模…...

springboot docker

在Spring Boot中使用Docker可以帮助你将应用程序与其依赖的容器化&#xff0c;并简化部署和管理过程。 当你在Spring Boot中使用Docker时&#xff0c;你的代码不需要特殊的更改。你可以按照通常的方式编写Spring Boot应用程序。 java示例代码&#xff0c;展示了如何编写一个基…...

docker-compose 部署nacos 整合 postgresql 为DB

标题docker-compose 部署nacos 整合 postgresql 为DB 前提&#xff1a; 已经安装好postgresql数据库 先创建好一个数据库 nacos&#xff0c;执行以下sql: /** Copyright 1999-2018 Alibaba Group Holding Ltd.** Licensed under the Apache License, Version 2.0 (the "…...

详解 ElasticSearch Kibana 配置部署

默认安装部署所在机器允许外网 SSH工具 Putty 链接&#xff1a;https://pan.baidu.com/s/1b6gumtsjL_L64rEsOdhd4A 提取码&#xff1a;lxs9 Winscp 链接&#xff1a;https://pan.baidu.com/s/1tD8_2knvv0EJ5OYvXP6VTg 提取码&#xff1a;lxs9 WinSCP安装直接下一步到完成…...

SourceTree 使用技巧

参考资料 SourceTree使用教程&#xff08;一&#xff09;—克隆、提交、推送SourceTree的软合并、混合合并、强合并区别SourceTree 合并分支上的多个提交&#xff0c;一次性合并分支的多次提交至另一分支&#xff0c;主分支前进时的合并冲突解决 目录 一. 基础设置1.1 用户信息…...

VIRTIO-BLK代码分析(0)概述

也无风雨也无晴。- 苏轼&#xff08;宋&#xff09; 接下来介绍VIRTIO相关内容。首先从VIRTIO-BLK开始分析&#xff0c;VIRTIO-BLK各部分交互图如下所示&#xff1a; 这里包含以下几个部分&#xff1a; Guest UserSpace&#xff1a;虚拟机用户空间&#xff0c;如虚拟机中运行f…...

【2023年11月第四版教材】第10章《进度管理》(第一部分)

第10章《进度管理》&#xff08;第一部分&#xff09; 1 章节说明2 管理基础3 管理过程3.1 管理的过程★★★3.2 管理ITTO汇总★★★ 1 章节说明 【本章分值预测】大部分内容不变&#xff0c;细节有一些变化&#xff0c;预计选择题考3-4分&#xff0c;案例和论文 都有可能考&a…...

【多线程案例】生产者消费者模型(堵塞队列)

文章目录 1. 什么是堵塞队列&#xff1f;2. 堵塞队列的方法3. 生产者消费者模型4. 自己实现堵塞队列 1. 什么是堵塞队列&#xff1f; 堵塞队列也是队列&#xff0c;故遵循先进先出的原则。但堵塞队列是一种线程安全的数据结构&#xff0c;可以避免线程安全问题&#xff0c;当队…...

数据结构与算法基础-学习-30-插入排序之直接插入排序、二分插入排序、希尔排序

一、排序概念 将一组杂乱无章的数据按一定规律顺次排列起来。 将无序序列排成一个有序序列&#xff08;由小到大或由大到小&#xff09;的运算。 二、排序方法分类 1、按数据存储介质 名称描述内部排序数据量不大、数据在内存&#xff0c;无需内外交换存交换存储。外部排序…...

Qt+C++桌面计算器源码

程序示例精选 QtC桌面计算器源码 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<QtC桌面计算器源码>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 学习与…...

kubesphere安装Maven+JDK17 流水线打包

kubesphere 3.4.0版本&#xff0c;默认支持的jav版本是8和11&#xff0c;不支持17 。需要我们自己定义JenKins Agent 。方法如下&#xff1a; 一、构建镜像 1、我们需要从Jenkins Agent的github仓库拉取master最新源码&#xff0c;最新源码里已经支持jdk17了。 git clone ht…...

百度搜索清理大量低质量网站

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 据部分站长爆料&#xff1a;百度大规模删低质量网站的百度资源站长平台权限&#xff0c;很多网站都被删除了百度站长资源平台后台权限&#xff0c;以前在百度后台添加的网站大量被删除&#xff01;…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...