SpringCloud + Python 混合微服务架构,打造AI分布式应用的技术底层

SpringCloud + Python 混合微服务的使用场景

一般来说,后端软件系统采用的也是基于Spring Cloud框架为主的微服务架构,服务的注册发现是基于Nacos,而服务的调用基于OpenFeign调用,负载均衡Robbin实现的,所以整体架构大概就是这样的一个标准Spring Cloud微服务架构。

如图所示:

图片

Java 微服务直接的RPC,大概是下面的调用关系

大部分场景,基于以上微服务架构是比较好扩展的。

例如有一个新的微服务,只需要基于Spring Boot编写一个微服务项目,然后通过Spring Cloud提供的注解将其快速地注入Nacos的服务注册&发现机制,然后就可以很快地对内或对外提供服务了。

然后,如果是 在AI+业务的场景呢?

AI应用,比如图形识别,比如翻译,比如AIGC(文生文、文生图)等等。 这些应用,用Java开发的话很不方便,更佳的方式是使用Python开发,因为: Python 有一个完整的AI开发生态体系。

那么,就意味着Python 微服务需要 混入 Spring Cloud 微服务体系,否则,需要对于Python服务做单独的部署及负载设计、服务治理设计等等大量重复性的工作。

SpringCloud + Python 混合微服务,如何架构?

SpringCloud + Python 混合微服务,如何架构?

一种切实可行的方式是:

  • 将Python写的异构服务也融入Spring Cloud体系,能够通过Nacos实现注册发现

  • 能够通过OpenFeign/gRpc进行远程调用,能够使用Ribbo进行负载均衡

  • 如下图所示:

    SpringCloud + Python 混合微服务架构实操

    实操场景介绍

    这里,通过Python 图像识别与文本提取(OCR) 业务为例,实现一个 SpringCloud + Python 混合微服务架构实操。

    在日常的开发中,图像识别与文本提取(OCR)技术扮演着至关重要的角色,它们极大地促进了业务流程的自动化,提升了用户体验。特别是在互联网和金融行业,这一需求尤为显著。

    一般来说,图像识别与文本提取(OCR)都是通过python实现。

    并且在Python生态中,有一个OCR的明星库——PaddleOCR。

    PaddleOCR以其在OCR领域的高性能和高精度著称,似乎是解决这一挑战的理想工具。

    PaddleOCR是由百度飞桨(PaddlePaddle)团队开发的开源OCR(Optical Character Recognition,光学字符识别)工具。

    它基于深度学习技术,提供了一整套高效、易用的OCR解决方案,能够识别多种语言和复杂场景下的文本。

    PaddleOCR在处理文字检测和文字识别方面具有高性能和高准确性,被广泛应用于图像处理、文本提取等领域。

    PaddleOCR的关键特性

    高精度

  • 基于深度学习模型,PaddleOCR在各种复杂场景下均能提供高精度的文字识别,包括自然场景文本、印刷文本、手写文本等。

  • 多语言支持

  • PaddleOCR支持多种语言的文字识别,适用于全球范围内的多语言文本处理需求。

  • 丰富的功能

  • PaddleOCR不仅提供了文字检测(识别文本区域)和文字识别(识别文本内容),还包括文字方向分类、表格识别、版面分析等高级功能。

  • 易用性

  • 提供了丰富的API接口和详细的文档,方便开发者快速集成和使用。

  • 高效性

  • 利用飞桨(PaddlePaddle)深度学习框架的高效计算能力,PaddleOCR能够在保证精度的前提下提供快速的文字识别服务。

  • 然而,随着项目的深入实施,一系列预料之外的难题逐渐浮现,揭示了直接在Java微服务中集成AI模型面临的困境。

    传统的 SpringCloud + Python 本地直接调用的不足

    在使用微服务架构之前,传统的 SpringCloud + Python 互相调用,使用的是 本地直接调用的方式,原始本地直接调用的架构设计如下:

    本地直接调用流程: 

  • 用户通过前端上传图片到Java微服务。

  • Java微服务接收到图片后,通过本地进程调用Python脚本来处理图片。

  • Python脚本利用PaddleOCR库进行OCR处理,将结果返回给Java微服务。

  • Java微服务接收OCR结果,并将其返回给用户或存入数据库。

  • 本地直接具体实现: ‍‍

  • 在Java代码中,使用ProcessBuilderRuntime.exec执行Python脚本。

  • Python脚本通过命令行参数或读取临时文件的方式获取图像数据。

  • OCR结果通过标准输出返回给Java微服务,Java读取标准输出获取结果。

  • 本地直接设计存在以下几个主要问题:

    语言跨界调用复杂性: 

  • Java微服务直接调用本地Python脚本执行PaddleOCR,这要求在Java环境中嵌入Python解释器或通过进程间通信(如使用Jython或子进程调用),增加了系统的维护成本和运行时的不稳定因素。

  • 硬件依赖限制: 

  • PaddleOCR为了达到最佳性能,推荐使用GPU进行模型推理。这意味着部署环境必须配备NVIDIA GPU,限制了服务的部署灵活性和成本控制。

  • 耦合度高与扩展性差: ‍‍

  • Java服务直接绑定到本地Python脚本,这种紧耦合设计不仅难以适应未来技术栈的变化,也影响了服务的独立升级和横向扩展能力。

  • 配置与管理难题: ‍‍

  • 随着服务规模的扩大,对服务实例的监控、配置更新和故障转移变得愈发困难,原始设计缺乏有效的服务治理机制。

  • 针对以上问题,我们对此进行了改进:SpringCloud + Python 远程调用的架构方案

    先进的 SpringCloud + Python 远程调用的架构方案

    为了解决上述问题,我们提出了一个新的架构设计方案,SpringCloud + Python 统一微服务治理+RPC远程调用的架构方案,主要有以下3步

  • 第一步,AI的微服务化:利用FastAPI构建轻量级Python后端服务,专门负责OCR处理

  • 第二步,通过Nacos注册发现:并结合Python-Nacos客户端SDK实现服务的注册发现及动态配置管理。

  • 第三步,通过RPC进行远程调用:通过OpenFein/gPRC进行远程调用

  • SpringCloud + Python混合微服务架构设计及步骤

    第一步:AI的微服务化

    FastAPI服务封装 OCR处理,暴露成为rest服务, 以服务的形式进行能力的暴露,而不是以 函数的形式进行能力的暴露。

    创建FastAPI应用:

    图片

    FastAPI是一个现代的,快速(高性能)python web框架。基于标准的python类型提示,使用python3.6+构建API的Web框架。

    FastAPI的主要特点如下:

  • 快速:非常高的性能,与NodeJS和Go相当(这个要感谢Starlette和Pydantic),是最快的Python框架之一。

  • 快速编码:将开发速度提高约200%到300%。

  • 更少的bug:减少大约40%的开发人员人为引起的错误。

  • 直观:强大的编辑器支持,调试时间更短。

  • 简单:易于使用和学习。减少阅读文档的时间。

  • 代码简洁:尽量减少代码重复。每个参数可以声明多个功能,减少程序的bug。

  • 健壮:生产代码会自动生成交互式文档。

  • 基于标准:基于并完全兼容API的开放标准:OpenAPI和JSON模式。

  • 总之,FastAPI是一个现代、快速(高性能)的Web框架,用于基于Python 3.7+构建API,借助标准Python类型提示,FastAPI可以提供自动文档生成、高性能和易于使用的开发体验。

    FastAPI以极简的代码实现高效的API接口,非常适合构建RESTful API和微服务。

    首先,利用FastAPI快速搭建一个RESTful API服务,该服务对外提供API接口,接收HTTP请求,其中包含待处理的图片数据路径。

    集成PaddleOCR:在FastAPI应用中,定义路由处理函数,调用PaddleOCR库处理接收到的图片数据,将识别结果以JSON格式返回。

    第二步:通过Nacos服务注册与发现:

    引入Python-Nacos客户端:Nacos作为阿里巴巴开源的服务发现与配置管理平台,提供了Python客户端。在FastAPI应用中集成该客户端,实现服务的自动注册与发现。

    配置管理:利用Nacos的配置中心,集中管理FastAPI服务的配置信息,如OCR模型参数等,实现配置的动态刷新,无需重启服务即可生效。

    第三步:通过RPC进行远程调用

    调整Java服务逻辑,改为通过Nacos注册中心发现在线AI服务,通过 OpenFeign 发起HTTP请求调用FastAPI提供的OCR服务,彻底解耦Java微服务与AI模型环境,增强系统的灵活性和可维护性。

    改进后的架构优点如下:
  • 松耦合架构:Java微服务与OCR服务通过API接口交互,降低了组件间的直接依赖,便于独立开发、测试和部署。

  • 部署灵活性:FastAPI服务可部署在任何支持Python的环境中,包括云服务器,不再受限于GPU,可通过水平扩展提高处理能力。

  • 服务治理能力增强:借助Nacos,实现了服务的自动化注册与发现,简化了微服务架构下的服务管理和配置管理,提高了系统的稳定性和可扩展性。

  • 资源优化与成本控制:通过分离OCR处理逻辑,可以根据实际需求灵活分配计算资源,有效控制成本。

  • 代码实现

    接下来我们将深入实践,通过细致的代码示例与步骤解析,展现如何巧妙地构建JAVA微服务与AI模型服务之间的无缝集成。 不再是简单的理论探讨,而是动手操作,从零开始,直到完成一个功能完备、高度解耦的系统集成方案。

    环境安装

    在开始代码编写前,我们需要准备好运行环境,java和maven的安装就不在此介绍了,我们简单介绍下如何准备python环境以及nacos。

    Python虚拟环境

    本文采用anaonda配置虚拟环境

  • Anaconda是一个开源的Python和R编程语言的发行版,用于数据科学、机器学习、深度学习、科学计算以及大数据处理。

  • Anaconda包含了众多流行的数据科学包和工具,以及一个包管理系统和环境管理系统。

  • Anaconda可以帮助开发者轻松地管理和部署不同的项目环境,并解决包依赖问题。

  • Anaconda是一种广泛使用的开源Python和R编程语言的发行版,主要用于数据科学、机器学习和大数据处理。Anaconda包含了众多常用的数据科学包和库,并提供了一个包管理器和环境管理器(Conda),这使得用户可以轻松地安装、更新和管理Python包及其依赖项。

    安装Anaconda

    下载Anaconda

    访问 Anaconda官网 并下载适合你操作系统的安装程序(Windows、macOS或Linux)。

    ananconda的安装和配置建议参考清华源:

    anaconda | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

    图片

    安装Anaconda

    根据下载的安装程序运行安装,按照提示完成安装过程。

    可以选择将Anaconda添加到系统PATH中,这样可以直接在命令行中使用conda命令。

    WIN+R键调出运行窗口,输入cmd回车

    图片

    输入conda -V命令,可以看到版本信息,代表安装成功。

    图片

    接着,输入python命令查看python是否安装成功。

    图片

    到此安装完成。

    使用Anaconda

    创建和管理环境

    Anaconda通过Conda提供了强大的环境管理功能,使得用户可以为不同的项目创建隔离的环境,以避免包版本冲突。

    1. 创建新环境

      conda create –name myenv python=3.8

      这将创建一个名为myenv的环境,并安装Python 3.8。

    2. 激活环境

      conda activate myenv

      激活后,所有的包管理操作都会在这个环境中进行。

    3. 安装依赖包

      conda install numpy pandas

      这将在当前激活的环境中安装numpypandas包。

    4. 列出环境

      conda env list

      这将列出所有创建的Conda环境。

    5. 删除环境

      conda remove –name myenv –all

      这将删除名为myenv的环境。

    使用Jupyter Notebook

    Anaconda默认包含Jupyter Notebook,一个广泛使用的交互式笔记本工具,特别适合于数据科学和机器学习项目。

    1. 安装Jupyter Notebook(如果未安装):

      conda install jupyter

    2. 启动Jupyter Notebook

      jupyter notebook

      这将启动Jupyter Notebook服务器,并在浏览器中打开一个新的标签页。

    管理包

    除了Conda,Anaconda也支持使用pip来安装Python包。

    以下是一些常见的包管理操作:

    1. 使用Conda安装包

      conda install scipy

    2. 使用pip安装包

      pip install requests

    3. 更新包

      conda update numpy

    4. 卸载包

      conda remove pandas

    第一步:使用Anaconda进行Python微服务的开发

    数据科学项目
    1. 创建项目环境

      conda create –name myproject python=3.8
      conda activate myproject

    2. 安装数据科学包

      conda install numpy pandas scikit-learn matplotlib seaborn

    3. 启动Jupyter Notebook进行数据分析

      jupyter notebook

    机器学习项目
    1. 创建项目环境并安装机器学习包

      conda create –name mlproject python=3.8
      conda activate mlproject
      conda install scikit-learn tensorflow keras

    2. 开始开发机器学习模型

      可以在Jupyter Notebook中编写和运行代码,或者使用任意Python IDE进行开发。

    Anaconda 安装PaddleOCR

    Anaconda通过其强大的包管理和环境管理功能,为数据科学和机器学习项目提供了一个便捷的开发平台。

    无论是安装包、管理环境,还是使用Jupyter Notebook进行交互式编程,Anaconda都能大大简化工作流程,提高工作效率。

    安装完成后,就可以创建虚拟环境及安装依赖库了。

    创建项目环境 paddle_env:

    conda create –name paddle_env python=3.8 –channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/

    激活项目环境 paddle_env:
    conda activate paddle_env

    安装依赖包

    pip install paddlepaddle paddleocr fastapi uvicorn python-nacos

    Nacos

    Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的一个服务发现、配置管理和服务治理平台。它能够帮助开发者更高效地构建、管理和维护微服务架构。

    Nacos提供了一整套简便易用的工具和API,用于服务注册与发现、动态配置管理、服务健康监测以及DNS服务。 安装及配置建议参考官方文档, Nacos 快速开始

    下载安装包后,解压缩,在bin目录下下运行以下命令:

    startup.cmd -m standalone

    启动后,既可打开nacos管理页面

    图片

    示例:图文识别的AI服务

    我们要创建一个名为ocr-service 的AI服务,下面是整体的目录结构:

    图片

    首先编写基础模块

    settings.py: 初始化相关配置参数及更新方法:

    # settings.py
    
    import socket
    
    def get_local_ip():
    """
    获取当前计算机的IP地址
    """
    return socket.gethostbyname(socket.gethostname())
    
    # 服务端口
    SERVICE_PORT = 8000
    SERVICE_IP = get_local_ip()
    # 服务名
    SERVICE_NAME = "ocr-service"
    
    # Nacos 配置
    NACOS_SERVER_ADDRESS = "127.0.0.1:8848"
    NACOS_NAMESPACE = "public"
    NACOS_DATA_ID = "ocr-service"
    NACOS_USERNAME = "nacos"
    NACOS_PASSWORD = "nacos"
    NACOS_GROUP_NAME = "DEFAULT_GROUP"
    
    # 语言属性
    LANG = "ch" # 例如:中文
    
    def set_lang(new_lang):
    """
    设置OCR模型语言属性
    :param new_lang: 新的语言属性
    """
    print("设置OCR模型语言属性为:", new_lang)
    global LANG
    LANG = new_lang

    nacos_client.py: 初始化nacos client ,以及相关的注册、心跳及监听配置等方法。

    import nacos
    import asyncio
    import yaml
    import app.settings as settings
    
    # 初始化 Nacos 客户端
    # 使用settings中的NACOS_SERVER_ADDRESS, NACOS_NAMESPACE, NACOS_USERNAME, NACOS_PASSWORD来初始化Nacos客户端
    client = nacos.NacosClient(settings.NACOS_SERVER_ADDRESS, namespace=settings.NACOS_NAMESPACE,
    username=settings.NACOS_USERNAME, password=settings.NACOS_PASSWORD)
    
    def register_service():
    # 向Nacos注册服务实例
    client.add_naming_instance(
    settings.SERVICE_NAME, settings.SERVICE_IP, settings.SERVICE_PORT)
    
    async def send_heartbeat():
    # 异步发送心跳,每10秒发送一次心跳
    while True:
    try:
    client.send_heartbeat(settings.SERVICE_NAME,
    settings.SERVICE_IP, settings.SERVICE_PORT)
    except Exception as e:
    print(f"Failed to send heartbeat: {e}")
    await asyncio.sleep(10) # 每10秒发送一次心跳
    
    def load_config(content):
    # 加载配置文件,解析yaml格式,设置语言
    yaml_config = yaml.full_load(content)
    print("yaml_config:", yaml_config)
    lang = yaml_config['lang']
    settings.set_lang(lang)
    
    def nacos_config_callback(args):
    # Nacos配置回调函数,处理配置更新
    content = args['raw_content']
    load_config(content)
    
    def watch_config():
    # 启动时,强制同步一次配置
    config = client.get_config(settings.NACOS_DATA_ID,
    settings.NACOS_GROUP_NAME)
    print("config:", config)
    load_config(config)
    # 启动监听器,监控配置变化
    client.add_config_watcher(settings.NACOS_DATA_ID,
    settings.NACOS_GROUP_NAME, nacos_config_callback)

    pocr.py : 提供根据图片地址,提取对应图片的文本信息方法。

    import os
    import sys
    from paddleocr import PaddleOCR
    import app.settings as settings
    
    def recognize_image_text(image_path):
    # 初始化PaddleOCR对象,加载预训练模型
    ocr = PaddleOCR(use_angle_cls=True, lang=settings.LANG) # 更改lang参数以支持不同语言
    
    # 加载图片并进行识别
    result = ocr.ocr(image_path)
    print(result)
    
    # 提取并格式化识别出的文本
    recognized_text = ''
    for line in result:
    # 每个line是一个包含坐标信息和文字识别结果的元组列表
    for item in line:
    recognized_text += item[1][0] + '\n' # 提取文字内容
    
    # 去除末尾的换行符(如果存在)
    if recognized_text.endswith('\n'):
    recognized_text = recognized_text[:-1]
    
    return recognized_text
    
    
    if __name__ == "__main__":
    # 输入图片路径
    image_path = sys.argv[1]
    
    # 提取图片中的文字
    output_text = recognize_image_text(image_path)
    print("Recognized text:", output_text)

    routes.py : 创建API路由对象,提供接口访问ocr方法。

    from fastapi import APIRouter, HTTPException
    import os
    from pydantic import BaseModel
    from pocr.pocr import recognize_image_text
    
    # 定义请求体模型
    # 创建一个请求体模型,用于接收文件路径
    
    
    class FilePathModel(BaseModel):
    filePath: str
    
    
    # 创建路由
    # 创建一个API路由对象
    router = APIRouter()
    
    # 定义一个异步函数,用于处理POST请求,接收文件路径并识别图片中的文本信息
    @router.post("/ocr/")
    async def print_file_path(file_path_model: FilePathModel):
    filePath = file_path_model.filePath
    if not os.path.exists(filePath):
    raise HTTPException(status_code=400, detail="File path does not exist")
    print(f"Received file path: {filePath}")
    text = recognize_image_text(filePath)
    return {"message": text}

    main.py

    from fastapi import FastAPI # 导入 FastAPI 框架
    import uvicorn # 导入 uvicorn 用于运行 ASGI 应用
    import asyncio # 异步编程库
    from app.nacos_client import register_service, send_heartbeat, watch_config # 从 app.nacos_client 模块导入注册服务、发送心跳和监听配置的函数
    from app.routes import router # 从 app.routes 模块导入路由
    import app.settings as settings # 导入 app.settings 模块,并重命名为 settings
    
    # 创建 FastAPI 应用
    app = FastAPI()
    
    # 注册路由
    app.include_router(router)
    
    # 当应用启动时执行的事件
    @app.on_event("startup")
    async def startup_event():
    register_service() # 调用注册服务函数
    
    watch_config() # 调用监听配置函数
    
    # 启动心跳任务
    asyncio.create_task(send_heartbeat()) # 创建异步任务发送心跳
    
    if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=settings.SERVICE_PORT) # 运行应用,监听指定端口

    代码编写完成后,我们还需要在nacos配置中心,创建配置文件:

    图片

    启动项目:

    (paddle_env) PS D:\doc\crazy\nacos\ocr-servic& D:/portable/anaconda/envs/paddle_env/python.exe d:/doc/crazy/nacos/ocr-service/main.py
    INFO: Started server process [20328]
    INFO: Waiting for application startup.
    config: lang: ch
    yaml_config: {'lang': 'ch'}
    设置OCR模型语言属性为: ch
    INFO: Application startup complete.
    INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

    从启动日志可以看到,服务启动时会从nacos读取配置,并更新对应的lang值。

    启动后,在nacos管理页面可以看到服务已经注册成功:

    图片

    准备一张包含文字的图片demo.jpg,放在D盘下,在终端使用curl命令调用服务的接口:

    图片

    可以看到接口返回了图片的文本信息。

    SpringCloud 微服务RPC 远程调用 Python 微服务

    现在,我们可以创建一个简单的spring cloud 微服务项目,名为 consumer-service,

    项目结构如下:

    图片

    OcrClient.java :

    使用Feign构建OCRClient接口, 代理ocr-service服务的ocr接口

    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import com.crazy.consumerservice.model.ImagePathRequest;
    
    @FeignClient(name = "ocr-service")
    public interface OcrClient {
    
    @PostMapping("/ocr/")
    String recognizeImage(@RequestBody ImagePathRequest imagePathRequest);
    }

    OcrController.java

    import com.crazy.consumerservice.feign.OcrClient;
    import com.crazy.consumerservice.model.ImagePathRequest;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("/api")
    public class OcrController {
    
    @Autowired
    private OcrClient ocrClient;
    
    @PostMapping("/recognize")
    public String recognizeImage(@RequestParam String filePath) {
    ImagePathRequest request = new ImagePathRequest();
    request.setFilePath(filePath);
    return ocrClient.recognizeImage(request);
    }
    }

    启动服务后,在nacos可以看到服务已注册

    图片

    启动SpringBoot微服务

    图片

    访问swagger界面

    图片

    现在,记住咱们要 识别的图片,路径 d:\03.png

    图片

    然后,在Java微服务的swagger界面填写这个路径 ,发现java到python 的RPC调用成功

    图片

    看以看到该接口成功的调用了ocr-service服务对应的接口,并返回了相应的信息。

    结语

    通过重构原有的Java微服务与AI服务集成方式,采用FastAPI与Python-Nacos相结合的方案,我们不仅解决了原有架构的种种痛点,还为系统带来了更高的灵活性、可扩展性和运维便利性。

    作者:lichunericli

    物联沃分享整理
    物联沃-IOTWORD物联网 » SpringCloud + Python 混合微服务架构,打造AI分布式应用的技术底层

    发表回复