数据访问进阶----------------Project Reactor

一些核心的概念

Operators - Publisher / Subscriber
Publisher / Subscriber 类似于生产者和消费者的关系,在这里作为消息的发布者和订阅者,在Java中类似于集合中的迭代器iterator,对于iterator来说,是我们主动的获取集合中的下一个元素,是我们拉数据的过程,对于Publisher / Subscriber来说,是一种推的方式,是Publisher将下一个序列的元素推送给Subscriber

  • Nothing Happens Until You subscribe()
    在调用subscribe之前,不会发生任何事情(简单的把所有的操作都包涵在subscribe里面,所以只有当subscribe调用之后,其他的代码才能执行)
  • Flux [ 0…N ] - onNext()、onComplete()、onError()
    N个元素的序列,onNext()方法:遇到下一个元素该执行什么 ;onComplete():当整个序列完成时,执行什么;onError() :遇到错误时,该执行什么。这些方法中的每一个都可以传入lambda表达式去处理。
  • Mono [ 0…1 ] - onNext()、onComplete()、onError()
    0或者1个元素的序列,onNext()方法:遇到下一个元素该执行什么 ;onComplete():当整个序列完成时,执行什么;onError() :遇到错误时,该执行什么。这些方法中的每一个都可以传入lambda表达式去处理。

Backpressure
指的一种反压力,就是说,在上游Publisher生产速度非常快,而下游Subscriber跟不上上游的速度的时候,我可以做到,既然上游生产了200多个元素,但是订阅者一次只拿几个

  • Subscription
  • onRequest()、onCancel()、onDispose()
    onRequest():每次请求多少个元素
    onDispose() :终止订阅的过程

线程调度 Schedulers

  • immediate() / single() / newSingle()
    immediate():我现在在哪一个线程上,我在当前线程上做后续的动作
    single():使用一个可复用的线程
    newSingle():新启一个线程去做
  • elastic() / parallel() / newParallel()
    线程池上的操作
    elastic(): 在一个缓存的线程池上面做一个后续的操作,可以做Publisher ,也可以做Subscriber。在elastic线程池在线程空闲60s之后,就会被回收。
    parallel() :固定线程池,会创建出和cpu核数相同的线程,这里的线程不会被回收。

错误处理

  • onError / onErrorReturn / onErrorResume
    onError:相当于trycatch
    onErrorReturn :在遇到异常时,返回一个默认值
    onErrorResume :用一段特定的lambda处理来做一个异常处理
  • doOnError / doFinally
    doFinally :不管程序是正常执行完还是异常执行完,都需要执行doFinally的代码。

不指定任何的线程信息,在主线程main上面发生

package geektime.spring.reactor.simple;

import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import javax.security.auth.Subject;
import java.io.Serializable;

/**
 * @Author: 13679
 * @CreateTime: 2019-12-01 13:15
 */
@Slf4j
@SpringBootApplication
public class ReactorTest implements ApplicationRunner {
    public static void main(String[] args) {
        SpringApplication.run(ReactorTest.class,args);
    }
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Flux.range(1,6)            //注意顺序造成的区别
                .doOnRequest(n->log.info("Request {} number",n)) //打印出请求多少个数的日志
                .doOnComplete(()->log.info("Publisher COMPLETE 1"))//1~6publish完了之后,打印个1
 //             .publishOn(Schedulers.elastic())
                .map(i -> {   //map实现了元素的转换,这里主要演示map的执行在哪个线程上发生的  Thread.currentThread()会打印出当前线程的信息和一个数字
                    log.info("Publish {},{}",Thread.currentThread(),i);
                    //return 10/(i-3);
                    return i;
                })
                .doOnComplete(()->log.info("Publisher COMPLETE 2")) //打印完之后 做一个Complete
                //.subscribeOn(Schedulers.single())
                //.onErrorResume(e->{
                //    log.error("Exception {}",e.toString());
                //    return Mono.just(-1);
                //})
                //.onErrorReturn(-1)
                .subscribe(i->log.info("Subscribe {}; {}",Thread.currentThread(),i),
                        e->log.error("error {}",e.toString()),
                        ()->log.info("Subscriber COMPLETE")
                        //,s->s.request(4)
                );
        Thread.sleep(2000);
    }
}

结果如下:

在这里插入图片描述
我们可以看到,一开始请求了一个long的最大值,因为不管生产多少,都需要被消费,使用都会request过来,然后就在main上面不停的Publish Subscribe,每次生产一个,订阅一个,执行完毕之后,打印出COMPLETE 1 和COMPLETE 2。

elastic做Publish和Schedulers.single()让一个单独的线程进行订阅

package geektime.spring.reactor.simple;

import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import javax.security.auth.Subject;
import java.io.Serializable;

/**
 * @Author: 13679
 * @CreateTime: 2019-12-01 13:15
 */
@Slf4j
@SpringBootApplication
public class ReactorTest implements ApplicationRunner {
    public static void main(String[] args) {
        SpringApplication.run(ReactorTest.class,args);
    }
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Flux.range(1,6)            //注意顺序造成的区别
                .doOnRequest(n->log.info("Request {} number",n)) //打印出请求多少个数的日志
                .doOnComplete(()->log.info("Publisher COMPLETE 1"))//1~6publish完了之后,打印个1
                .publishOn(Schedulers.elastic())  //publishOn这一行后面的代码,都会执行在elastic线程池里面  elastic做Publish
                .map(i -> {   //map实现了元素的转换,这里主要演示map的执行在哪个线程上发生的  Thread.currentThread()会打印出当前线程的信息和一个数字
                    log.info("Publish {},{}",Thread.currentThread(),i);
                    //return 10/(i-3);
                    return i;
                })
                .doOnComplete(()->log.info("Publisher COMPLETE 2")) //打印完之后 做一个Complete
                .subscribeOn(Schedulers.single()) //让一个单独的线程进行订阅
                //.onErrorResume(e->{
                //    log.error("Exception {}",e.toString());
                //    return Mono.just(-1);
                //})
                //.onErrorReturn(-1)
                .subscribe(i->log.info("Subscribe {}; {}",Thread.currentThread(),i),
                        e->log.error("error {}",e.toString()),
                        ()->log.info("Subscriber COMPLETE")
                        //,s->s.request(4)
                );
        Thread.sleep(2000);
    }
}

结果如下:
在这里插入图片描述

现在,request在single上,同时,long的最大值现在变成了256,先取256个元素,然后放到elastic线程池里面来,在elastic池的一个线程里面,进行订阅。

有关错误异常的处理

 public void run(ApplicationArguments args) throws Exception {
        Flux.range(1,6)            //注意顺序造成的区别
                .doOnRequest(n->log.info("Request {} number",n)) //打印出请求多少个数的日志
                .doOnComplete(()->log.info("Publisher COMPLETE 1"))//1~6publish完了之后,打印个1
                .publishOn(Schedulers.elastic())  //publishOn这一行后面的代码,都会执行在elastic线程池里面  elastic做Publish
                .map(i -> {   //map实现了元素的转换,这里主要演示map的执行在哪个线程上发生的  Thread.currentThread()会打印出当前线程的信息和一个数字
                    log.info("Publish {},{}",Thread.currentThread(),i);
                    return 10/(i-3);
                    //return i;
                })
                .doOnComplete(()->log.info("Publisher COMPLETE 2")) //打印完之后 做一个Complete
                .subscribeOn(Schedulers.single()) //让一个单独的可复用线程进行订阅
                .onErrorResume(e->{
                    log.error("Exception {}",e.toString());
                    return Mono.just(-1);
                })
                //.onErrorReturn(-1)
                .subscribe(i->log.info("Subscribe {}; {}",Thread.currentThread(),i),
                        e->log.error("error {}",e.toString()),
                        ()->log.info("Subscriber COMPLETE")
                        //,s->s.request(4)
                );
        Thread.sleep(2000);
    }
}

结果如下:
在这里插入图片描述
在发生异常时,会打印出异常报告并返回一个-1,当然,也可以直接使用onErrorReturn(-1),在遇到异常时,直接打印-1,同时,Subscriber COMPLETE,说明碰到异常,终止Subscribe操作。

指定接收

package geektime.spring.reactor.simple;

import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import javax.security.auth.Subject;
import java.io.Serializable;

/**
 * @Author: 13679
 * @CreateTime: 2019-12-01 13:15
 */
@Slf4j
@SpringBootApplication
public class ReactorTest implements ApplicationRunner {
    public static void main(String[] args) {
        SpringApplication.run(ReactorTest.class,args);
    }
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Flux.range(1,6)            //注意顺序造成的区别
                .publishOn(Schedulers.elastic())  //publishOn这一行后面的代码,都会执行在elastic线程池里面  elastic做Publish
                .doOnRequest(n->log.info("Request {} number",n)) //打印出请求多少个数的日志
                .doOnComplete(()->log.info("Publisher COMPLETE 1"))//1~6publish完了之后,打印个1
                .map(i -> {   //map实现了元素的转换,这里主要演示map的执行在哪个线程上发生的  Thread.currentThread()会打印出当前线程的信息和一个数字
                    log.info("Publish {},{}",Thread.currentThread(),i);
                    //return 10/(i-3);
                    return i;
                })
                .doOnComplete(()->log.info("Publisher COMPLETE 2")) //打印完之后 做一个Complete
                .subscribeOn(Schedulers.single()) //让一个单独的可复用线程进行订阅
                .onErrorResume(e->{
                    log.error("Exception {}",e.toString());
                    return Mono.just(-1);
                })
                //.onErrorReturn(-1)
                .subscribe(i->log.info("Subscribe {}; {}",Thread.currentThread(),i),
                        e->log.error("error {}",e.toString()),
                        ()->log.info("Subscriber COMPLETE"),
                        s->s.request(4)  //只允许接收4个
                );
        Thread.sleep(2000);
    }
}

结果如下:
在这里插入图片描述
因为还有2个没有Publish和Subscribe出来,所以不会打印COMPLETE。当然,如果遇到报错,Subscriber COMPLETE会打印出来,因为终止了Subscribe操作。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值