1. 在Web应用中,流式输出是优化大数据量传输和内存使用的关键技术。传统阻塞式Servlet架构需将全部数据加载到内存后再响应,易引发内存溢出。而响应式编程(如WebFlux)和流式API(如StreamingResponseBody)支持逐条处理数据,边查询边输出,显著降低资源占用。本文通过四种场景对比:

  伪流式(阻塞式+Flux包装List)

  内存流式(阻塞式+StreamingResponseBody)

  真流式(阻塞式+数据库流查询)

  响应式流式(WebFlux+R2DBC)

  2. 实战案例

  2.1 基于Servlet技术(阻塞式)

  我们引入了以下两个依赖:其中 spring-boot-starter-webflux 用于支持在 Controller 接口中返回 Flux 类型的响应式流数据。

<dependency>  
<groupId>org.springframework.boot</groupId>  
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>  
<groupId>org.springframework.boot</groupId>  
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
@Service("blockUserService") 
public  class  UserService 
{  
    private  final  UserRepository ur;  
    public  List < User > queryUsers()
    {   
        return  this.ur.findAll(); 
    }
}
@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) 
public  Flux < User > flux(HttpServletResponse response)
{ 
    response.setCharacterEncoding("UTF-8");  
    return  Flux.fromIterable(this.userService.queryUsers()).delayElements(Duration.ofSeconds(1));
}

  该接口返回值为Flux,以实现流式输出。

  运行结果

640

   2.2 使用StreamingResponseBody

   在 UserService#queryUsers 方法中,当前直接返回 List 集合。我们可改用 StreamingResponseBody 实现流式输出。如下示例:

@GetMapping(path =  "/stream") 
public  StreamingResponseBody  stream(HttpServletResponse  response)
    { 
        response.setContentType("text/event-stream;charset=utf-8");  
        return  outputStream - >
            {  
                userService.queryUsers().forEach(user - >
                        {    
                            try 
                            {     
                                String  json = objectMapper.writeValueAsString(user) +  "\n" ;    
                                outputStream.write(json.getBytes());    
                                outputStream.flush();      
                                  TimeUnit.SECONDS.sleep(1) ;     
        } catch (Exception e) {        
          e.printStackTrace();      
        }    
    }) ;  
} ;
}

  运行结果

640 (1)

   2.3 流式查询输出

  在上面的示例中,我们是先将所有数据查询到内存中再通过流的方式输出数据,这样会带来内存溢出的风险,接下来的示例我们将通过流的方式查询数据,然后在通过StreamingResponseBody 流式输出到客户端。

  Repository

@Query("SELECT u FROM User u") 
Stream < User >  queryUsers() ;

  Service

@Transactionalpublic  
void  streamQuery(Consumer < User > consumer)
{  
    try (Stream < User > stream =  this.ur.queryUsers())
    {  
        stream.forEach(user - >
        {   
            consumer.accept(user);  
        }); 
    }
}

  Controller接口

@GetMapping(path =  "/real/stream") 
public  StreamingResponseBody  realStream(HttpServletResponse  response)
{ 
    response.setContentType("text/event-stream;charset=utf-8");  
    return  outputStream - >
    {  
        userService.streamQuery(user - >
        {    
            try 
            {     
                String  json = objectMapper.writeValueAsString(user) +  "\n" ;    
                outputStream.write(json.getBytes());    
                outputStream.flush();     
                TimeUnit.SECONDS.sleep(1);   
            } 
            catch (Exception  e)
            {    
                e.printStackTrace();   
            }  
        }); 
    };
}

  运行结果

640 (2)

   2.4 基于WebFlux技术(响应式)

  引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>io.asyncer</groupId>
    <artifactId>r2dbc-mysql</artifactId>
</dependency>

  实体对象

@Table("x_user") 
public  class  User 
{  @Id   
    private  Integer  id;  
    private  String  name;  
    private  String  sex;  
    private  String  phone;  
    private  Integer  age;
}

  Repository & Service

public  interface  UserRepository  extends  ReactiveCrudRepository < User, Integer >
{}
@Service
public  class  UserService 
{  
    private  final  UserRepository ur;  
    public  Flux < User > queryUsers()
    {   
        return  this.ur.findAll(); 
    }
}

  Controller接口

@GetMapping(produces =  "text/event-stream") 
public  Flux < User > queryByName(ServerHttpResponse response)
    {   
      // 通过delayElement模拟逐条输出效果  
      return this.userService.queryUsers().delayElements(Duration.ofSeconds(1)) ;
    }

  运行结果

640 (3)

   原文地址:https://mp.weixin.qq.com/s/WZde8gX7cJ1r56yFWrgh-w

posted on 2026-02-09 15:07  homle  阅读(133)  评论(0)    收藏  举报