您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

月薪两万程序员应该知道的编程模型

时间:2021-07-29 10:21:59  来源:  作者:花括号MC

我承认这篇文章有标题党的嫌疑,看完这篇文章并不会让你月薪两万。如果想月薪两万甚至更多,并不是靠一篇文章,一本书,一个项目来实现的。

但是一个合格的程序员对响应式编程多少都应该有些了解,甚至有个清楚的认识。

希望这篇文章能够让你对响应式编程有个基本的认识,以及响应式编程会带来哪些好处,解决哪些问题,或者说为什么响应式编程如此重要。

响应式编程发展过程

响应式编程的概念是微软最开始提出并且在.NET平台上实现的一个库。后来这个模型被大家接受并认可,ReactiveX 就实现了很多其它语言对应的库,大名鼎鼎的RXJAVA就是针对Java语言实现的。

后来ReactiveX 和 Reactor共同制定了Reactive Stream标准,ReactiveX和Reactor都是在这个标准下实现的框架。Spring5 正式引入Reactor 并基于该框架实现了WEB-FLUX。

此外Java8引进了Stream流以及lamada表达式,Java9引入了Flow,也是对响应式编程的一种支持。

什么是响应式编程

reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change

这是维基百科对响应式编程给出的定义。我对这个定义的评价是,懂得自然懂,不懂的还是不懂。

我提炼一下这个定义的关键词 声明式数据流, 传递变化(响应),我自己再给加一个异步,因为实际上生产级别代码都是进行异步响应的,几乎很少进行同步响应。

在具体介绍响应式编程之前,先简单解释一下这几个关键词的含义。

声明式编程

声明式和指令式对应。指令式大家比较熟悉,就是依次写出完成某个任务的每条指令。

比如从一个苹果列表里,找出所有红色的苹果,指令式编程是这样做的。

List<Apple> apples = new ArrayList<Apple>();

for (Apple apple : apples){
    if (apple.getColor() == "red"){
        System.out.println(apple);
    }
}

声明式编程,只要写出你想要什么就OK了。

典型的声明式语言的就是sql,对应上面的找红色苹果的需求,应该是这样的 select * from apple where color = red。

简单的讲,声明式编程就是聊天式编程,和计算机说你想要什么就OK了。

数据流

再说说数据流,其实数据流可以把它想象成水流,里面流淌的是数据,事件,信号等内容。如果大家对Java8引入的Stream流有一定了解的话,就会好理解。如果不了解的可以通过我这篇文章做个入门。

传递变化(响应)

传递变化(响应),其实就是对响应二字的体现。所谓的响应就是你和某个人打了招呼,然后某人回应你了。某人对你的回应就是响应。

将上面的场景对应到面向对象的编程里面,就是观察者(订阅)模式。观察者对被观察者的某些行为做出对应的动作。

有些前端程序员对观察者模式可能比较陌生,那么大家比较熟悉的Ajax回调函数也是响应式编程的一种体现,比如如下JS代码

$.ajax("example.action")
    .done(function(){
        console.log("success")
    })
    .fail(function(){
        console.log("error")
    })
    .always(function(){
        console.log("complete")
    });

这就是典型的异步回调,当请求成功的时候会有一种响应动作,请求失败的时候会有另一种响应动作。

异步

关于响应的方式,有同步响应和异步响应。实际应用中大部分都会采用异步响应。

同步:你给旅行社打电话预定一张机票,接线员接到你的电话后,开始查询航班信息,然后进行预订,这期间你一直拿着电话等他的结果。

异步:你给旅行社打电话预定一张机票,接线员接到电话后,记录下你要预定的航班信息,然后就挂掉电话。等他预定好之后,把预定结果打电话告诉你。这就是异步。

很明显异步操作对你来说效率更高,因为你不用一直等接线员的操作,你可以干其他事情。

上面的场景也被很多人称为好莱坞规则。很多好演员去好莱坞报名拍戏,经纪公司会登记下演员的姓名,等有合适的机会的时候,经纪公司会给演员打电话,而不用演员一直在现场等,或者不断的给经纪公司打电话询问。 don't call me I will call you。

小试牛刀

其实介绍完上面那些东西,可能对响应式编程的理解还是模糊的。那么我们就以Reactor框架为例子做一个简单的说明。毕竟程序员都喜欢show me the code。

上面提到了响应式编程的核心是基于观察者(订阅)模式的。观察者观察被观察者的行为,根据不同的行为做出不同的响应行为。

在Reactor框架中用两个类来表示Publisher,分别是Flux和Mono。Flux表示0...N个元素序列;Mono表示零或一个元素序列。

Flux/Mono可以发布三类值 正常数值,异常信号,完成信号。三类信号不会同时存在,最多同时发布两类信号。

举个例子,我们假设让Flux发射一个1-6的6个整数的数字流,6个数字流发送完成后,会紧跟着发送一个完成信号,告诉订阅者或者观察者,数据流完成。同样的,如果发送正常数据的过程中出现异常,也可以发送一个异常信号给订阅者或者观察者,表示出现异常,将停止发送。异常信号和完成信号不能同时存在,因为出现任何一个该数据流都将结束。但是信号流里面可以即没有异常信号也没有完成信号,这表示该流是一个无限流。

Flux.just(1,2,3,4,5,6)

上面这行代码表示发布者发布了6条消息,下面我们订阅者6条消息,也就是对这6条消息进行响应。

Flux.just(1,2,3,4,5,6).subscribe(System.out::print)

在控制台将会打印出1,2,3,4,5,6。

注意,只有订阅的时候才会对事件或者元素进行响应。

上面的例子,我们对元素或者事件没有做任何操作,仅仅是将它们原封不动地打印了出来,这显然不是我们想要的。接下来我们对元素做一些有意义的操作。

操作符

map

对数据流里面的每个元素执行一次map里面的函数。示意图如下

月薪两万程序员应该知道的编程模型

 

代码示例

Flux.range(1,6).map(i -> i*i).subscribe(System.out::println);

将会输出 1 4 9 16 25 36

flatmap

该操作符逻辑上包含两个操作,第一个操作是map操作,第二个是flatten,flatten类似于merge操作,将对每个元素进行映射之后,合并成一个新的流。示意图如下。

月薪两万程序员应该知道的编程模型

 

代码示例

Flux.just("apple-1","pear-2").flatMap(i -> 
Flux.fromArray(i.split("-"))).subscribe(System.out::println);

以上代码将会输出 apple 1 pear 2;

filter

过滤出符合条件的元素。

代码示例

Flux.range(1,6).filter(i -> i>3).subscribe(System.out::println)

以上代码将会输出 4,5,6

zip zip英文单词有拉链的意思,在Reactor中,表示将两个数据流合并到一起。示意图如下。

月薪两万程序员应该知道的编程模型

 

示例代码

Flux.zip(       
    Flux.just("A","B","C"),
    Flux.just("1","2","3"),
    (x,y) -> x + y        
    ).subscribe(System.out::println);

以上代码输出 A1 B2 C3

还有很多操作符这里不一一介绍了,感兴趣的可以看官网。

线程调度

Reactor自然也是支持多线程的。而且多线程调度很简单。 Reactor中创建线程是通过Scheduler接口来表示的。

//创建一个线程
Scheduler single = Schedulers.single();
//创建等于CPU核心数量的线程
Scheduler parallel = Schedulers.parallel();
//创建有界限的线程池,不传参数的默认创建10倍于CPU核心数量
Scheduler elastic = Schedulers.boundedElastic();

创建了线程,自然要分配线程,也就是线程调度。 切换线程上下文主要通过publishOn()和subscribeOn()两个函数实现。

publishOn()会影响调用该函数之后的操作。而subscribeOn()会从源头影响整个操作链,无论subscribeOn()调用发生在何处。

举个例子:

    Flux.just("hello")
    .map(s -> {
            System.out.println("[map] Thread name: " + Thread.currentThread().getName());
            return s.concat(" world!");
        })
    //只改变publishOn()之后的操作的线程。
    .publishOn(Schedulers.newSingle("thread-publishOn"))
    .filter(s -> {
            System.out.println("[filter] Thread name: " + Thread.currentThread().getName());
            return s.startsWith("h");
        })
    //从源头变整个操作链的线程
    .subscribeOn(Schedulers.newSingle("thread-subscribeOn"))
    .subscribe(s -> {
                System.out.println("[subscribe] Thread name: " + Thread.currentThread().getName());
                System.out.println(s);
        });

上面的代码输出结果是这样的

[map] Thread name: thread-subscribeOn-1
[filter] Thread name: thread-publishOn-2
[subscribe] Thread name: thread-publishOn-2
hello world!

建议各位把上面这段代码复制到本地运行一下,同时可以把subscribeOn()和publishOn()分别注释掉,感受一下区别。

这两个函数经常用于有阻塞操作的时候,将阻塞操作调度到新的线程,以便提高效率。

响应编程解决哪些问题

响应式编程可以帮助解决两类棘手问题,第一个问题就是大家熟悉的callback hell,第二个问题就是同步阻塞效率低的问题。

先说第一个问题,这里拿reactor官方的例子做个说明,找出某个用户最喜爱的五个爱好。通过Callback的方式实现是这样的。

月薪两万程序员应该知道的编程模型

 

  1. 基于回调的服务使用一个匿名 Callback 作为参数。后者的两个方法分别在异步执行成功或异常时被调用。
  2. 获取到Favorite ID的list后调用第一个服务的回调方法onSuccess。
  3. 如果 list 为空, 调用 suggestionService。
  4. 服务 suggestionService 传递 List<Favorite> 给第二个回调。
  5. 既然是处理 UI,我们需要确保消费代码运行在 UI 线程。
  6. 使用 Java 8 Stream 来限制建议数量为5,然后在 UI 中显示。
  7. 在每一层,我们都以同样的方式处理错误:在一个 popup 中显示错误信息。
  8. 回到Favorite ID这一层,如果返回 list,我们需要使用favoriteService 来获取 Favorite对象。由于只想要5个,因此使用 stream 。
  9. 再一次回调。这次对每个ID,获取 Favorite 对象在 UI 线程中推送到前端显示。

采用Reactor 响应式编程代码大概应该是这个样子的

月薪两万程序员应该知道的编程模型

 

  1. 我们获取到Favorite ID的流。
  2. 我们 异步的转换 它们(ID)为 Favorite 对象(使用flatMap),现在我们有了Favorite流。
  3. 一旦 Favorite 为空,切换到 suggestionService。
  4. 我们只关注流中的最多5个元素。
  5. 最后,我们希望在 UI 线程中进行处理。
  6. 通过描述对数据的最终处理(在 UI 中显示)和对错误的处理(显示在 popup 中)来触发(subscribe)。

可以看到通过采用响应式编程,大大提高了代码的可读性,逻辑表达也更清晰。

再来看第二个问题,同步阻塞通常被认为是低效率的。而异步非阻塞被认为是高效率的。而响应式编程,天生就是异步非阻塞的。

来举个简单例子说明一下,为什么同步阻塞是低效率的而异步非阻塞是高效率的。

同步和异步描述的是服务提供者提供服务的能力。当调用者向服务者发起请求后,服务提供者能够立即返回,并且在处理完后通过某种方式通知调用者,那么就是异步的。相反如果服务提供者只在处理完之后才返回,或者要求调用者主动去查询处理结果,就是同步。

阻塞和非阻塞描述的是调用者的状态。当调用者向服务提供者发起请求后,一直等待处理结果返回,否则无法执行后续操作,就是阻塞状态。如果调用后直接返回,继续执行后续操作就是非阻塞状态。

上面提到的打电话的例子就是异步非阻塞的例子,你给旅行社打电话,预定一张机票。旅行社接线员收到你的请求,就立刻给你回复(异步),告诉你请求已经收到,稍后会通知你。然后你就挂掉电话,去处理其他事情(非阻塞),等旅行社预定好之后,会立刻给你打电话通知你结果。

如果是同步阻塞的话,场景应该是这样的,你给旅行社打电话预定机票,接线员接听你的电话,然后处理订票请求,你在电话另一端一直在等待,什么都做不了。更可怕的是,其他旅客的订票请求一直打不进来,因为线路资源一直被你占用。这将是多么低效的处理方式。

总结

响应式编程虽好,但并不是包治百病,首先掌握起来就有一定难度,同时Debug也需要有一定的相关经验。更主要的是,我们要根据业务场景来决定响应式编程是否能给我们带来真正的好处。记住软件工程里面,没有银弹。



Tags:编程模型   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
分层模型分层模型应该是我们编程里面最常用的模型,尤其是web开发里面,会有表示层、业务层和持久化层以及DB层,在表示层主要是web页面,业务层主要是编写核心的业务代码,持久化层主...【详细内容】
2021-08-27  Tags: 编程模型  点击:(635)  评论:(0)  加入收藏
我承认这篇文章有标题党的嫌疑,看完这篇文章并不会让你月薪两万。如果想月薪两万甚至更多,并不是靠一篇文章,一本书,一个项目来实现的。但是一个合格的程序员对响应式编程多少都...【详细内容】
2021-07-29  Tags: 编程模型  点击:(145)  评论:(0)  加入收藏
▌简易百科推荐
1. 前言了解响应式编程,首先我们需要了解函数式操作和Stream的操作,下面我们简单的复习一下喽。1.1 常用函数式编程函数式接口中我们先来回顾一下Java中的函数式接口。常见的...【详细内容】
2022-07-15  二哥学Java    Tags:编程   点击:(1)  评论:(0)  加入收藏
在本文中,我们将学习如何使用 Next.js、 Prisma、 Postgres 和 Fastify 构建一个 Full-stack 应用程序。在本文中,我们将学习如何使用 Next.js、 Prisma、 Postgres 和 Fastif...【详细内容】
2022-07-12  qaseven    Tags:全栈   点击:(9)  评论:(0)  加入收藏
好的软件开发网站有哪些?做软件开发哪些网站能提供帮助呢?这些很多做软件开发的小伙伴都会问到的问题。007出海全球社交流量导航网站,整合了多方出海跨境网站资源,为你介绍出海...【详细内容】
2022-07-08  Chuhai007    Tags:软件开发   点击:(10)  评论:(0)  加入收藏
我们用monkey做压力测试后,会保存一个monkey日志,那如果想快速的分析日志中有哪些异常,我们可以用批处理工具进行快速的筛查,我们一起来看看吧。先编写个小脚本,然后修改为bat后...【详细内容】
2022-07-08  溪流涌动    Tags:monkey   点击:(13)  评论:(0)  加入收藏
白盒测试落地实践分为两个大方向,一个是静态分析,一个是动态分析,当然啦,也可以叫做静态测试和动态测试。那我们如何高质量保效率的做好白盒测试呢?Parasoft已经为您准备好了成熟...【详细内容】
2022-07-08  Parasoft中国    Tags:白盒测试   点击:(11)  评论:(0)  加入收藏
Altium Designer 自带脚本功能的开发项目,可以调用官方AD API接口对原理图或者PCB进行自动操作,本文主要分享开发的流程,和一些基本的概念信息,本文介绍的脚本工具例子可以用在P...【详细内容】
2022-07-07  电子工程师伟哥    Tags:Altium Designer   点击:(21)  评论:(0)  加入收藏
一、目录介绍 前置知识点 NIO Netty 的核心组件 Channel Callback Future 和 Promise 事件和 ChannelHandler Hello World二、前置知识点1、NIO首先我们需要回顾一...【详细内容】
2022-07-06  架构师jickly    Tags:聊天系统   点击:(16)  评论:(0)  加入收藏
1.事件流事件流是对事件执行过程的描述,了解事件的执行过程有助于加深对事件的理解,提升开发实践中对事件运用的灵活度。2.捕获和冒泡捕获阶段是【从父到子】的传导过程,冒泡阶...【详细内容】
2022-07-06  金乾坤    Tags:API   点击:(13)  评论:(0)  加入收藏
刷盘策略CommitLog的asyncPutMessage方法中可以看到在写入消息之后,调用了submitFlushRequest方法执行刷盘策略:public class CommitLog { public CompletableFuture<PutMe...【详细内容】
2022-07-06  Java码农之路    Tags:RocketMQ   点击:(16)  评论:(0)  加入收藏
最近读了本好书-《深度学习推荐系统》,读完不觉全身通畅,于是就有了写这篇文章的想法,把自己的理解和总结分享给大家。 本文将按照从算法到工程的顺序,先介绍一下推荐系统整体...【详细内容】
2022-07-05  InfoQ    Tags:推荐系统   点击:(22)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条