日期: 2021 年 5 月 24 日

mysql redo log以及binlog

转载自:http://blog.itpub.net/30126024/viewspace-2216099/

总结

1、ib_logfile类似oracle的online redo log,包含commit和uncommit的数据

2、binary log类似oracle的online redo log和archive redo log,但是只有commit的数据

statement 格式的 binlog,*后会有 COMMIT;
row 格式的 binlog,*后会有一个 XID event

3、为什么MySQL有binlog,还要redo log?因为MySQL是多存储引擎的,不管使用那种存储引擎,都会有binlog,而不一定有redo log。而redo log 事务日志ib_logfile文件是InnoDB存储引擎产生的

4、ib_logfile是循环使用,binary log不是循环使用,在写满或者重启之后,会生成新的binary log文件

5、两种日志记录的内容差不多类似,都是事务对应DML、DDL的信息,只是作用不同,内容可能重复,比如一个DML记录在了ib_logfile也记录在了binary log

6、ib_logfile作为异常宕机后启动时恢复使用

7、binary log作为数据恢复使用,主从复制搭建使用

8、两种日志写入磁盘的触发点不同,二进制日志只在事务提交完成后进行一次写入,重做日志在事务提交会写入每隔1秒也会写入。MySQL为了保证master和slave的数据一致性,就必须保证binlog和InnoDB redo日志的一致性(因为备库通过二进制日志重放主库提交的事务,如果主库commit之前就写入binlog,一旦主库crash,再次启动时会回滚事务。但此时从库已经执行,则会造成主备数据不一致)。所以必须保证二进制日志只在事务提交完成后进行一次写入

9、在主从复制结构中,要保证事务的持久性和一致性,对两种日志的相关变量设置为如下*为妥当:sync_binlog=1(即每提交一次事务同步写到磁盘中);innodb_flush_log_at_trx_commit=1(即每提交一次事务都写到磁盘中)。这两项变量的设置保证了:每次提交事务都写入二进制日志和事务日志,并在提交时将它们刷新到磁盘中

10、innodb中,表数据刷盘的规则只有一个:checkpoint。但是触发checkpoint的情况却有几种(1.重用redo log文件;2.脏页达到一定比例)

11、ib_logfile作为redo log记录的是“做了什么改动”,是物理日志,记录的是”在某个数据页上做了什么修改”;

binary log记录的是这个语句的原始逻辑,分两种模式,statement格式记录的是sql语句,row格式记录的是行的内容,记录更新前和更新后的两条数据。

 

详述

ib_logfile

官方文档https://dev.mysql.com/doc/refman/5.7/en/glossary.html

一组文件,通常名为ib_logfile0和ib_logfile1,构成重做日志。 有时也称为日志组。 这些文件记录了尝试更改InnoDB表中数据的语句。 在崩溃后启动时,会自动重播这些语句以更正由不完整事务写入的数据。

此数据不能用于手动恢复; 对于该类型的操作,请使用binlog二进制日志

 

binary log

官方文档https://dev.mysql.com/doc/refman/5.7/en/binary-log.html

二进制日志包含描述数据库更改的“事件”,例如表创建操作或对表数据的更改。 它还包含可能已进行更改的语句的事件(例如,不匹配任何行的DELETE),除非使用基于行的日志记录。 二进制日志还包含有关每个语句获取更新数据的时间长度的信息。

二进制日志有两个重要目的:

用于复制

某些数据恢复操作需要使用二进制日志。备份恢复后,将重新执行备份后记录的二进制日志中的事件。 这些事件使数据库从备份点更新。

二进制日志不用于不修改数据的SELECT或SHOW等语句,也就是说select show等语句不会产生binlog

 

checkpoint

https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_checkpoint

当对缓冲池中缓存的数据页进行更改时,这些更改将在稍后的某个时间写入数据文件,这一过程称为刷新。 检查点是已成功写入数据文件的*新更改(由LSN值表示)的记录。

 

sharp checkpoint

将重做条目包含在重做日志的某些部分中的所有脏缓冲池页面刷新到磁盘的过程。 在InnoDB覆盖重用日志文件之前发生 ; 日志文件以循环方式使用。 通常发生写入密集型工作负载。

 

flush

将发生在内存区域或临时磁盘存储区域中缓冲的 更改写入数据库文件 。 定期刷新的InnoDB存储结构包括重做日志,撤消日志和缓冲池。

刷新可能是因为 内存区域已满并且系统需要释放一些空间 ,因为提交操作意味着可以*终确定事务的更改,或者因为慢速关闭操作意味着应该*终完成所有未完成的工作。 当一次刷新所有缓冲数据并不重要时,InnoDB可以使用一种称为 模糊检查点 的技术来刷新小批量页面以分散I / O开销。

redis 数据库主从不一致问题解决方案

在聊数据库与缓存一致性问题之前,先聊聊数据库主库与从库的一致性问题。

 

问:常见的数据库集群架构如何?

:一主多从,主从同步,读写分离。

%title插图%num

如上图:

(1)一个主库提供写服务

(2)多个从库提供读服务,可以增加从库提升读性能

(3)主从之间同步数据

画外音:任何方案不要忘了本心,加从库的本心,是提升读性能。

 

问:为什么会出现不一致?

:主从同步有时延,这个时延期间读从库,可能读到不一致的数据。

%title插图%num

如上图:

(1)服务发起了一个写请求

(2)服务又发起了一个读请求,此时同步未完成,读到一个不一致的脏数据

(3)数据库主从同步*后才完成

画外音:任何数据冗余,必将引发一致性问题。

 

问:如何避免这种主从延时导致的不一致?

:常见的方法有这么几种。

 

方案一:忽略

任何脱离业务的架构设计都是耍流氓,*大部分业务,例如:百度搜索,淘宝订单,QQ消息,58帖子都允许短时间不一致。

画外音:如果业务能接受,*推崇此法。

 

如果业务能够接受,别把系统架构搞得太复杂。

 

方案二:强制读主

%title插图%num

如上图:

(1)使用一个高可用主库提供数据库服务

(2)读和写都落到主库上

(3)采用缓存来提升系统读性能

这是很常见的微服务架构,可以避免数据库主从一致性问题。

 

方案三:选择性读主

强制读主过于粗暴,毕竟只有少量写请求,很短时间,可能读取到脏数据。

 

有没有可能实现,只有这一段时间,可能读到从库脏数据的读请求读主,平时读从呢?

 

可以利用一个缓存记录必须读主的数据。

%title插图%num

如上图,当写请求发生时:

(1)写主库

(2)将哪个库,哪个表,哪个主键三个信息拼装一个key设置到cache里,这条记录的超时时间,设置为“主从同步时延”

画外音:key的格式为“db:table:PK”,假设主从延时为1s,这个key的cache超时时间也为1s。

 

%title插图%num

如上图,当读请求发生时:

这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果,

(1)cache里有这个key,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该去主库查询

(2)cache里没有这个key,说明*近没有发生过写请求,此时就可以去从库查询

以此,保证读到的一定不是不一致的脏数据。

 

总结

数据库主库和从库不一致,常见有这么几种优化方案:

(1)业务可以接受,系统不优化

(2)强制读主,高可用主库,用缓存提高读性能

(3)在cache里记录哪些记录发生过写请求,来路由读主还是读从

Redis Sentinel 和Redis Cluster对比

1、Redis Sentinel

Redis-Sentinel(哨兵模式)是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自懂切换。它的主要功能有以下几点:

不时地监控redis是否按照预期良好地运行;

如果发现某个redis节点运行出现状况,能够通知另外一个进程(例如它的客户端);

能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。

Redis master-slave 模式如下图:

%title插图%num

从上图片中可以看到,一个master 节点可以挂多个slave  ,Redis Sentinel 管理Redis 节点结构如下:

%title插图%num

从上图中可以得出Sentinel其实就是Client和Redis之间的桥梁,所有的客户端都通过Sentinel程序获取Redis的Master服务。首先Sentinel是集群部署的,Client可以链接任何一个Sentinel服务所获的结果都是一致的。其次,所有的Sentinel服务都会对Redis的主从服务进行监控,当监控到Master服务无响应的时候,Sentinel内部进行仲裁,从所有的 Slave选举出一个做为新的Master。并且把其他的slave作为新的Master的Slave。*后通知所有的客户端新的Master服务地址。如果旧的Master服务地址重新启动,这个时候,它将被设置为Slave服务。

Sentinel 可以管理master-slave节点,看似Redis的稳定性得到一个比较好的保障。但是如果Sentinel是单节点的话,如果Sentinel宕机了,那master-slave这种模式就不能发挥其作用了。幸好Sentinel也支持集群模式,Sentinel的集群模式主要有以下几个好处:

即使有一些sentinel进程宕掉了,依然可以进行redis集群的主备切换;

如果只有一个sentinel进程,如果这个进程运行出错,或者是网络堵塞,那么将无法实现redis集群的主备切换(单点问题);

如果有多个sentinel,redis的客户端可以随意地连接任意一个sentinel来获得关于redis集群中的信息。

 Redis Sentinel 集群模式可以增强整个Redis集群的稳定性与可靠性,但是当某个节点的master节点挂了要重新选取出新的master节点时,Redis Sentinel的集群模式选取的复杂度显然高于单点的Redis Sentinel 模式,此时需要一个比较靠谱的选取算法。下面就来介绍Redis Sentinel 集群模式的 “仲裁会”(多个Redis Sentinel共同商量谁是Redis 的 master节点)

1.1、Redis Sentinel 集群模式的 “仲裁会”

当一个master被sentinel集群监控时,需要为它指定一个参数,这个参数指定了当需要判决master为不可用,并且进行failover时,所需要的sentinel数量,本文中我们暂时称这个参数为票数,不过,当failover主备切换真正被触发后,failover并不会马上进行,还需要sentinel中的大多数sentinel授权后才可以进行failover。当ODOWN时,failover被触发。failover一旦被触发,尝试去进行failover的sentinel会去获得“大多数”sentinel的授权(如果票数比大多数还要大的时候,则询问更多的sentinel)这个区别看起来很微妙,但是很容易理解和使用。例如,集群中有5个sentinel,票数被设置为2,当2个sentinel认为一个master已经不可用了以后,将会触发failover,但是,进行failover的那个sentinel必须先获得至少3个sentinel的授权才可以实行failover。如果票数被设置为5,要达到ODOWN状态,必须所有5个sentinel都主观认为master为不可用,要进行failover,那么得获得所有5个sentinel的授权。

2、Redis Cluster

使用Redis Sentinel 模式架构的缓存体系,在使用的过程中,随着业务的增加不可避免的要对Redis进行扩容,熟知的扩容方式有两种,一种是垂直扩容,一种是水平扩容。垂直扩容表示通过加内存方式来增加整个缓存体系的容量比如将缓存大小由2G调整到4G,这种扩容不需要应用程序支持;水平扩容表示表示通过增加节点的方式来增加整个缓存体系的容量比如本来有1个节点变成2个节点,这种扩容方式需要应用程序支持。垂直扩容看似*便捷的扩容,但是受到机器的限制,一个机器的内存是有限的,所以垂直扩容到一定阶段不可避免的要进行水平扩容,如果预留出很多节点感觉又是对资源的一种浪费因为对业务的发展趋势很快预测。Redis Sentinel 水平扩容一直都是程序猿心中的痛点,因为水平扩容牵涉到数据的迁移。迁移过程一方面要保证自己的业务是可用的,一方面要保证尽量不丢失数据所以数据能不迁移就尽量不迁移。针对这个问题,Redis Cluster就应运而生了,下面简单介绍一下RedisCluster。

Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的分布式集群首要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整个数据的一个子集。Redis Cluster采用哈希分区规则中的虚拟槽分区。虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有的数据映射到一个固定范围内的整数集合,整数定义为槽(slot)。Redis Cluster槽的范围是0 ~ 16383槽是集群内数据管理和迁移的基本单位。采用大范围的槽的主要目的是为了方便数据的拆分和集群的扩展,每个节点负责一定数量的槽。Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0 ~ 16383,计算公式:slot = CRC16(key)&16383。每一个实节点负责维护一部分槽以及槽所映射的键值数据。下图展现一个五个节点构成的集群,每个节点平均大约负责3276个槽,以及通过计算公式映射到对应节点的对应槽的过程。

%title插图%num

Redis Cluster节点相互之前的关系如下图所示:

https://img1.mukewang.com/5bd6ba7a0001ba0705700569.jpg

 

优先选择redis cluster,redis cluster集群可以使用redis-trix.rb脚本完成

Project Reactor 核心原理解析

一、开篇

本文将解析 Spring 的 Reactor 项目的源码。主要目的是让自己能深入理解 Reactor 这个项目,以及 Spring 5 和 Spring Boot 2。

Project Reactor 项目地址:https://github.com/reactor

Reactor 项目主要包含 Reactor Core 和 Reactor Netty 两部分。Reactor Core 实现了反应式编程的核心功能,Reactor Netty 则是 Spring WebFlux 等技术的基础。本文将介绍 Core 模块的核心原理。

本文从一个例子开始:

  1. public static void main(String[] args) {
  2. Flux.just(“tom”, “jack”, “allen”)
  3. .filter(s -> s.length() > 3)
  4. .map(s -> s.concat(“@qq.com”))
  5. .subscribe(System.out::println);
  6. }

整个 Project Reactor 的核心调用分为下面几个阶段:

  • 声明阶段
  • subscribe 阶段
  • onSubscribe 阶段
  • request 阶段
  • 调用阶段

接下来对每个阶段详细介绍。

二、声明阶段

简介

之前的文章介绍过,反应式编程和传统风格的编程一个*大的不同点在于,开发人员编写的大部分代码都是在声明处理过程。即这些代码并不会被实际的执行,直到开始被订阅。这便是为何*阶段是声明阶段的原因。

详解

先来看*个方法 Flux.just 的源码:

  1. public static <T> Flux<T> just(T… data) {
  2. return fromArray(data);
  3. }
  4. public static <T> Flux<T> fromArray(T[] array) {
  5. // 省略不重要的部分
  6. return onAssembly(new FluxArray<>(array));
  7. }

just 方法只是创建反应式流的众多方式的一个。在实际工作中,更常见的通过反应式 Repository 将数据库查询结果,或通过 Spring 5 的 WebClient 将 HTTP 调用结果*为流的开始。

onAssembly 是一个扩展机制,因为实例中并没有使用,所以可简单理解为将入参直接返回。

接下来 new FluxArray<>(array) 做的事情也很简单,仅仅是把入参赋给成员变量。

再接下来 filter(s -> s.length() > 3) 做的事情是把上一个 Flux,即 FluxArray,以及 s -> s.length() > 3 所表示的 Predicate 保存起来。

看一下源码

  1. public final Flux<T> filter(Predicate<? super T> p) {
  2. if (this instanceof Fuseable) {
  3. return onAssembly(new FluxFilterFuseable<>(this, p));
  4. }
  5. return onAssembly(new FluxFilter<>(this, p));
  6. }
  7. FluxFilterFuseable(Flux<? extends T> source,
  8. Predicate<? super T> predicate) {
  9. super(source);
  10. this.predicate =
  11. Objects.requireNonNull(predicate, “predicate”);
  12. }

这里有个逻辑判断,就是返回的值分为了 Fuseable 和非 Fuseable 两种。简单介绍一下,Fuseable 的意思就是可融合的。我理解就是 Flux 表示的是一个类似集合的概念,有一些集合类型可以将多个元素融合在一起,打包处理,而不必每个元素都一步步的处理,从而提高了效率。因为 FluxArray 中的数据一开始就都准备好了,因此可以打包处理,因此就是 Fuseable

而接下来的 map 操作也对应一个 FluxMapFusable。原理与 filter 操作,这里不再重复。

三、subscribe 阶段

简介

subscribe 阶段同行会触发数据发送。在本例中,后面可以看到,对于 FluxArray,数据发送很简单,就是循环发送。而对于像数据库、RPC 这样的长久,则会触发请求的发送。

详解

当调用 subscribe(System.out::println) 时,整个执行过程便进入 subscribe 阶段。经过一系列的调用之后,subscribe 动作会代理给具体的 Flux 来实现。就本例来说,会代理给 FluxMapFuseable,方法实现如下(经过简化):

  1. public void subscribe(CoreSubscriber<? super R> actual) {
  2. source.subscribe(
  3. new MapFuseableSubscriber<>(actual, mapper)
  4. );
  5. }

其中的 source 变量对应的是当前 Flux 的上一个。本例中,FluxMapFuseable 上一个是 FluxFilterFuseable

new MapFuseableSubscriber<>(actual, mapper) 则是将订阅了 FluxMapFuseable 的 Subscriber 和映射器封装在一起,组成一个新的 Subscriber。然后再订阅 source,即 FluxArraysource 是在上一个阶段被保存下来的。

这里强调一下 Publisher 接口中的 subscribe 方法语义上有些奇特,它表示的不是订阅关系,而是被订阅关系。即 aPublisher.subscribe(aSubscriber) 表示的是 aPublisher 被 aSubscriber 订阅。

接下来调用的就是 FluxFilterFuseable 中的 subscribe 方法,类似于 FluxMapFuseable,源码如下:

  1. public void subscribe(CoreSubscriber<? super T> actual) {
  2. source.subscribe(
  3. new FilterFuseableSubscriber<>(actual, predicate)
  4. );
  5. }

这时 source 就成了 FluxArray。于是,接下来是调用 FluxArray 的 subscribe 方法。

  1. public static <T> void subscribe(CoreSubscriber<? super T> s,
  2. T[] array) {
  3. if (array.length == 0) {
  4. Operators.complete(s);
  5. return;
  6. }
  7. if (s instanceof ConditionalSubscriber) {
  8. s.onSubscribe(
  9. new ArrayConditionalSubscription<>(
  10. (ConditionalSubscriber<? super T>) s, array
  11. )
  12. );
  13. }
  14. else {
  15. s.onSubscribe(new ArraySubscription<>(s, array));
  16. }
  17. }

*重要的部分还是 s.onSubscribe(new ArraySubscription<>(s, array))。不同于 FluxMapFuseable 和 FluxFilterFuseableFluxArray 没有再调用 subscribe 方法,因为它是数据源头。而 FluxMapFuseable 和 FluxFilterFuseable 则是中间过程。

可以这样简单理解,对于中间过程的 Mono/Flux,subscribe 阶段是订阅上一个 Mono/Flux;而对于源 Mono/Flux,则是要执行 Subscriber.onSubscribe(Subscription s) 方法。

四、onSubscribe 阶段

简介

在调用 FluxArray 的 subscribe 方法之后,执行过程便进入了 onSubscribe 阶段。onSubscribe 阶段指的是 Subscriber#onSubscribe 方法被依次调用的阶段。这个阶段会让各 Subscriber 知道 subscribe 方法已被触发,真正的处理流程马上就要开始。所以这一阶段的工作相对简单。

详解

s.onSubscribe(new ArraySubscription<>(s, array));

s 是 FilterFuseableSubscriber,看一下 FilterFuseableSubscriber 的 onSubscribe(Subscription s) 源码:

  1. public void onSubscribe(Subscription s) {
  2. if (Operators.validate(this.s, s)) {
  3. this.s = (QueueSubscription<T>) s;
  4. actual.onSubscribe(this);
  5. }
  6. }

actual 对应 MapFuseableSubscriberMapFuseableSubscriber 的 onSubscribe 方法也是这样,但 actual 对于的则是代表 System.out::println 的 LambdaSubscriber

调用过程:

FluxArray.subscribe -> FilterFuseableSubscriber.onSubscribe -> MapFuseableSubscriber.onSubscribe -> LambdaSubscriber.onSubscribe

五、request 阶段

含义

onSubscribe 阶段是表示订阅动作的方式,让各 Subscriber 知悉,准备开始处理数据。当*终的 Subscriber 做好处理数据的准备之后,它便会调用 Subscription 的 request 方法请求数据。

详解

下面是 LambdaSubscriber onSubscribe 方法的源码:

  1. public final void onSubscribe(Subscription s) {
  2. if (Operators.validate(subscription, s)) {
  3. this.subscription = s;
  4. if (subscriptionConsumer != null) {
  5. try {
  6. subscriptionConsumer.accept(s);
  7. }
  8. catch (Throwable t) {
  9. Exceptions.throwIfFatal(t);
  10. s.cancel();
  11. onError(t);
  12. }
  13. }
  14. else {
  15. s.request(Long.MAX_VALUE);
  16. }
  17. }
  18. }

由上可见 request 方法被调用。在本例中,这里的 s 是 MapFuseableSubscriber

这里需要说明,如 MapFuseableSubscriberFilterFuseableSubscriber,它们都有两个角色。一个角色是 Subscriber,另一个角色是 Subscription。因为它们都位于调用链的中间,本身并不产生数据,也不需要对数据暂存,但是需要对数据做各式处理。因此,在 onSubscribe、request 阶段,以及后面要讲到的调用阶段都需要起到代理的作用。这就解释了 actual.onSubscribe(this) onSubscribe 自己的原因。

下面是 FilterFuseableSubscriber 的 request 方法的源码。充分可见其代理的角色。

  1. public void request(long n) {
  2. s.request(n);
  3. }

*后 ArraySubscription 的 request 方法将被调用。

与 map、filter 等操作不同,flatMap 有比较大的差异,感兴趣的同学可以自己研究一下。本文先不详细介绍。

六、调用阶段

含义解释

这一阶段将会通过调用 Subscriber 的 onNext 方法,从而进行真正的反应式的数据处理。

流程分解

在 ArraySubscription 的 request 方法被调用之后,执行流程便开始了*后的调用阶段。

  1. public void request(long n) {
  2. if (Operators.validate(n)) {
  3. if (Operators.addCap(REQUESTED, this, n) == 0) {
  4. if (n == Long.MAX_VALUE) {
  5. fastPath();
  6. }
  7. else {
  8. slowPath(n);
  9. }
  10. }
  11. }
  12. }
  13. void fastPath() {
  14. final T[] a = array;
  15. final int len = a.length;
  16. final Subscriber<? super T> s = actual;
  17. for (int i = index; i != len; i++) {
  18. if (cancelled) {
  19. return;
  20. }
  21. T t = a[i];
  22. if (t == null) {
  23. s.onError(new NullPointerException(“The “
  24. + i
  25. + “th array element was null”)
  26. );
  27. return;
  28. }
  29. s.onNext(t);
  30. }
  31. if (cancelled) {
  32. return;
  33. }
  34. s.onComplete();
  35. }

ArraySubscription 会循环数据中的所有元素,然后调用 Subscriber 的 onNext 方法,将元素交由 Subscriber 链处理。

于是接下来由 FilterFuseableSubscriber 处理。下面是其 onNext 方法(做了一些简化)

  1. public void onNext(T t) {
  2. boolean b;
  3. try {
  4. b = predicate.test(t);
  5. }
  6. catch (Throwable e) {
  7. Throwable e_ =
  8. Operators.onNextError(t, e, this.ctx, s);
  9. if (e_ != null) {
  10. onError(e_);
  11. }
  12. else {
  13. s.request(1);
  14. }
  15. Operators.onDiscard(t, this.ctx);
  16. return;
  17. }
  18. if (b) {
  19. actual.onNext(t);
  20. }
  21. else {
  22. s.request(1);
  23. Operators.onDiscard(t, this.ctx);
  24. }
  25. }

其*要部分是通过 predicate 进行判断,如果满足条件,则交由下一个 Subscriber 处理。

下一个要处理的是 MapFuseableSubscriber,原理类似,不再重复。

*终要处理数据的是 LambdaSubscriber,下面是它的 onNext 方法源码:

  1. public final void onNext(T x) {
  2. try {
  3. if (consumer != null) {
  4. consumer.accept(x);
  5. }
  6. }
  7. catch (Throwable t) {
  8. Exceptions.throwIfFatal(t);
  9. this.subscription.cancel();
  10. onError(t);
  11. }
  12. }

consumer.accept(x) 会使数据打印在命令行中。

整个流程的解释到这里基本结束。

七、总结

接下来我们用一个图来回顾一下整体的流程。

Reactor Core

参考

需要感谢下面这篇文章,帮助我很好理解了 Project Reactor 的核心处理流程。

由表及里学 ProjectReactor:http://blog.yannxia.top/2018/06/26/java/spring/projectreactor/

ubuntu云服务器远程登陆以及基本操作日记

我采购的是腾讯云Ubuntu服务器,现在比较火的像阿里云啊等等。
一.远程登陆
在windows系统登陆云服务器需要借助相关的软件,本人比较喜欢xshell,因为对比于putty它的界面更易于开发,比如它包含的ftp模块,可以实现本地文件和远程服务器文件的传输。

二.ubuntu的简单操作
1.创建目录
如:在data/release文件下创建webapp目录
sudo mkdir -p /data/release/webapp
2.进入工作目录
cd /data/release/webapp
3.在工作目录下创建文件并且修改文件的访问权限
如:在webapp中创建package.json文件
sudo touch package.json
sudo chmod a+r+w package.json
(备注:可以修改package.json添加我们的服务器的名称和版本号 如:
{“name”: “webapp”,
“version”:”1.0.0″};
同时可以在该打开的工作目录下添加我们自己的其它文件 如:.js为后缀的文件等;
目录以及文件的操作也可以通过之前介绍的ftp的方式)
4.查看目录文件下的子文件目录
如:查看data下有什么目录文件
ls /data

注:以上四点操作完全可以用ftp远程文件操作文件夹,新建,删除,移动等,但是操作文件夹一般需要·root权限,这需要你以root的身份登录。
5.编辑文件内容
在云服务器中编辑文件内容需要借助vim这个编辑器
1)安装vim
sudo apt-get install vim-gtk
安装完成后可以使用vim-v命令检查一下版本
2)用vim查看编辑文件内容
如:编辑我们之前创建的package.json文件只需在命令行输入:
vim/package.json
之后按i(insert进入插入模式)来编辑我们的文件
3)vim退出

使用Esc键切换到命令模式:

输入ZZ(保存退出)/ZQ(不保存退出)或者输入冒号进入EX模式并输入q(命令退出·)/wq(保存退出)

6.ubuntu云服务器的快捷键

ctrl+s:保存文档

ctrl+f:搜索文档

ctrl+c:终止服务运行
今天的日记总结就暂时到这里啦~~

ubuntu 利用 ssh 连接 阿里云服务器

1、双方都必须安装了 openssh-server
2、阿里云 端口号 22 打开
3、防火墙关闭
4、ssh root@39.107.46.92   登录的用户名字 和 公网ip地址

ubuntu终端连接云服务器并上传文件

ubuntu终端连接云服务器并上传文件

一、连接
1,打开终端,输入命令:ssh -q  -l 你的用户名 -p 22 你服务器的IP

2,接下来输入yes,和你的密码

二、修改云服务器的配置,使root用户也能登录
1,设置root密码:sudo passwd root

2,修改配置:sudo vim /etc/ssh/ssh_config

3,找到PermitRootLogin 这项 将其改为 yes,保存退出

4,重启ssh服务:sudo service ssh restart

ps:如果不用root登录,会没有权限往服务器上传文件

三、开始上传
命令:scp 本地文件地址+文件名  远程用户名@IP地址:+服务器内存放文件的地址。(这里用户名用root)

例如:scp /home/wj/桌面/aa.txt [email protected]:/home/aa.txt

之后输入密码即可。

ubuntu16.04 SSH连接云服务器

1、首先在服务器上安装SSH的服务器端。

$ sudo aptitude install openssh-server

 

2、启动ssh-server。


$ /etc/init.d/ssh restart

 

3、确认ssh-server是否正常工作

$ netstat -tlp
tcp6 0 0 *:ssh *:* LISTEN -

上面这一行就说明ssh-server已经在运行了。
4、在Ubuntu客户端通过SSH登录服务器。假设服务器的IP地址是113.112.23.124,登录的用户名是name。

$ ssh -l name 113.112.23.124

 

5、*后提示你输入密码,就说明连上远程服务器了。

================== 分界 ======

修改root密码

使用passwd密码来修改密码(如提示没有这个命令行使用yum install passwd安装):

$ passwd xxx密码 xxx确认密码123

Ubuntu使用终端连接云服务器

打开终端

ssh -q -l username -p port ip

username:登录服务器的用户名

port:云服务器ssh端口

ip:云服务器ip

接下来输入yes

Are you sure you want to continue connecting (yes/no)? yes

然后输入用户的密码

[email protected]'s password:

回车,即可连接到服务器

SSH远程登入谷歌云服务器,只需要一步

*近刚刚折腾了谷歌云服务器,比较熟悉ubuntu,所以安装了Ubuntu18.04,网上搜索怎么SSH连上谷歌云服务器,回答基本都是用root账号ssh,步骤也比较多。我换了一个思路,先用账号登入,再切换root,一步搞定。
先使用谷歌云网址自带的SSH进Ubuntu,看看账号是什么,我的账号是cc@Ubuntu, 直接sudo passwd cc,输入新密码,两次确认。然后用电脑端SSH客户端填写IP地址和账号密码点击连接,瞬间就进去。 每个人账号不一样,默认应该是XXX@instance-1,只要sudo passwd XXX输入密码就OK了。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速