Fork me on GitHub

深入理解并发 / 并行,阻塞 / 非阻塞,同步 / 异步

作者:HuangQinJian

原文地址:https://juejin.im/entry/58ae4636b123db0052b1caf8

总结:

阻塞调用是指调用结果返回之前,调用者会进入阻塞状态等待。只有在得到结果之后才会返回。

非阻塞调用是指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

同步:在发出一个同步调用时,在没有得到结果之前,该调用就不返回。

异步:在发出一个异步调用后,调用者不会立刻得到结果,该调用就返回了。

CPU调度策略:

  • 先来先服务 - 时间片轮转调度
  • 优先级调度
  • 最短作业优先
  • 最高响应比优先
  • 多级反馈队列调度

1. 阻塞,非阻塞

首先,阻塞这个词来自操作系统的线程/进程的状态模型中,如下图:

一个线程/进程经历的 5 个状态, 创建,就绪,运行,阻塞,终止。各个状态的转换条件如上图,其中有个阻塞状态,就是说当线程中调用某个函数,需要 IO 请求,或者暂时得不到竞争资源的,操作系统会把该线程阻塞起来,避免浪费 CPU 资源,等到得到了资源,再变成就绪状态,等待 CPU 调度运行。

阻塞调用是指调用结果返回之前,调用者会进入阻塞状态等待。只有在得到结果之后才会返回。

非阻塞调用是指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

阻塞调用:比如 socket 的 recv(),调用这个函数的线程如果没有数据返回,它会一直阻塞着,也就是 recv() 后面的代码都不会执行了,程序就停在 recv() 这里等待,所以一般把 recv() 放在单独的线程里调用。

非阻塞调用:比如非阻塞 socket 的 send(),调用这个函数,它只是把待发送的数据复制到 TCP 输出缓冲区中,就立刻返回了,线程并不会阻塞,数据有没有发出去 send() 是不知道的,不会等待它发出去才返回的。

拓展

如果线程始终阻塞着,永远得不到资源,于是就发生了死锁。

比如 A 线程要 X,Y 资源才能继续运行,B 线程也要 X,Y 资源才能运行,但 X,Y 同时只能给一个线程用(即互斥条件)用的时候其他线程又不能抢夺。

A 有 X,等待 Y。
B 有 Y,等待 X。

于是 A,B 发生了循环等待,造成死锁。给用户的感觉就是程序卡着不动了。

在写代码的时候要特别注意共享资源的使用,用信号量控制好,避免造成死锁。死锁的解除有个著名的银行家算法

阻塞和挂起:阻塞是被动的,比如抢不到资源。挂起是主动的,线程自己调用 suspend() 把自己退出运行态了,某些时候调用 resume() 又恢复运行。

线程执行完就会被销毁,如果不想线程被频繁的创建,销毁,怎么办?可以给线程里面写个死循环,或者让线程有任务的时候执行,没任务的时候挂起,就像iOS中的 runloop 机制一样。线程就不会随便的终止了。

2. 同步,异步

同步:在发出一个同步调用时,在没有得到结果之前,该调用就不返回。

异步:在发出一个异步调用后,调用者不会立刻得到结果,该调用就返回了。

同步例子

1
2
3
int n = func();
next();
// func() 的结果没有返回,next() 就不会执行,直到 func() 运行完。

异步例子

1
2
3
4
5
6
7
8
9
10
func(callback);
next();
...

void callback(int n) // func 结果回调
{
int k = n;
}
// func() 执行后,还没得出结果就立即返回,然后执行 next() 了
// 等到结果出来,func() 回调 callback() 通知调用者结果。

同步的定义看起来跟阻塞很像,但是同步跟阻塞是两个概念, 同步调用的时候,线程不一定阻塞,调用虽然没返回,但它还是在运行状态中的,CPU 很可能还在执行这段代码,而阻塞的话,它就肯定不在 CPU 中跑这个代码了。这就是同步和阻塞的区别。同步是肯定可以在,阻塞是肯定不在。

异步和非阻塞的定义比较像,两者的区别是异步是说调用的时候结果不会马上返回,线程可能被阻塞起来,也可能不阻塞,两者没关系。非阻塞是说调用的时候,线程肯定不会进入阻塞状态。

上面两组概念,就有 4 种组合。

同步阻塞调用:得不到结果不返回,线程进入阻塞态等待。

同步非阻塞调用:得不到结果不返回,线程不阻塞一直在 CPU 运行。

异步阻塞调用:去到别的线程,让别的线程阻塞起来等待结果,自己不阻塞。

异步非阻塞调用:去到别的线程,别的线程一直在运行,直到得出结果。

3. 并发,并行

先从定义说起,定义经过我通俗化了,原定义有点难理解。

并发是指一个时间段内,有几个程序都在同一个 CPU 上运行,但任意一个时刻点上只有一个程序在处理机上运行。

并行是指一个时间段内,有几个程序都在几个 CPU 上运行,任意一个时刻点上,有多个程序在同时运行,并且多道程序之间互不干扰。 两者区别如下图

并行是多个程序在多个 CPU 上同时运行,任意一个时刻可以有很多个程序同时运行,互不干扰。

并发是多个程序在一个 CPU 上运行,CPU 在多个程序之间快速切换,微观上不是同时运行,任意一个时刻只有一个程序在运行,但宏观上看起来就像多个程序同时运行一样,因为 CPU 切换速度非常快,时间片是 64ms(每 64ms 切换一次,不同的操作系统有不同的时间),人类的反应速度是 100ms,你还没反应过来,CPU 已经切换了好几个程序了。

举个例子吧,并行就是,多个人,有人在扫地,有人在做饭,有人在洗衣服,扫地,做饭,洗衣服都是同时进行的。
并发就是,有一个人,这个人一会儿扫地,一会儿做饭,一会儿洗衣服,他在这 3 件事中来回做,同一时刻只做一件事,不是同时做的,但最后 3 件事都可以做完。

时间片大小的选取
时间片取的小,假设是 20ms,切换耗时假设是 10ms。
那么用户感觉不到多个程序之间会卡,响应很快,因为切换太快了,但是 CPU 的利用率就低了,20 / (20 + 10) = 66% 只有这么多,33%都浪费了。

时间片取的大,假设是 200ms,切换耗时是 10ms
那么用户会觉得程序卡,响应慢,因为要 200ms 后才轮到我的程序运行,但是 CPU 利用率就高了,200 / (200 + 10) = 95% 有这么多被利用的。

所以时间片取太大或者太小都不好,一般在 10 - 100 ms 之间。

3.1 CPU 调度策略

在并发运行中,CPU 需要在多个程序之间来回切换,那么如何切换就有一些策略

3.1.1 先来先服务 - 时间片轮转调度

这个很简单,就是谁先来,就给谁分配时间片运行,缺点是有些紧急的任务要很久才能得到运行。

3.1.2 优先级调度

每个线程有一个优先级,CPU 每次去拿优先级高的运行,优先级低的等等,为了避免等太久,每等一定时间,就给线程提高一个优先级

3.1.3 最短作业优先

把线程任务量排序,每次拿处理时间短的线程运行,就像我去银行办业务一样,我的事情很快就处理完了,所以让我插队先办完,后面时间长的人先等等,时间长的人就很难得到响应了。

3.1.4 最高响应比优先

用线程的等待时间除以服务时间,得到响应比,响应比小的优先运行。这样不会造成某些任务一直得不到响应。

3.1.5 多级反馈队列调度

有多个优先级不同的队列,每个队列里面有多个等待线程。
CPU 每次从优先级高的遍历到低的,取队首的线程运行,运行完了放回队尾,优先级越高,时间片越短,即响应越快,时间片就不是固定的了。
队列内部还是用先来先服务的策略。

同步和异步的区别

原文地址:https://blog.csdn.net/tennysonsky/article/details/45111623

总结:

区别:

一个需要等待,一个不需要等待

同步需要等待调用返回结果,异步调用直接返回,没有返回结果

怎样理解阻塞非阻塞与同步异步的区别? - 严肃的回答 - 知乎 https://www.zhihu.com/question/19732473/answer/20851256

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由调用者主动等待这个调用的结果。

而异步则是相反,调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。

典型的异步编程模型比如Node.js

举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

神一样的CAP理论被应用在何方

作者:陈于喆

原文地址:https://juejin.im/post/5d720e86f265da03cc08de74

参考文章:基于数据库的分布式锁实现

总结:

  1. CAP:C 是一致性(Consistency),A 是可用性(Availability),P 是分区容错性(Partition Tolerance),CAP 定理中,要么只能 CP,要么只能 AP

    网络无法做到 100%可靠,因此必须选择 CP 或 AP 架构。

  2. 分布式事务 BASE 理论:基本可用,AP 方案,采用适合的方式达到最终一致性

  3. 服务注册选择 AP,可用性比数据一致更加的重要

  4. 分布式锁方案选择:搞社交的选 redis 实现分布式锁,AP架构。搞支付金融的选 Zookeeper 实现分布式锁,CP架构。

  5. 分布式事务:

    • 两阶段提交(2PC):CP架构,没被改造过的两阶段提交基本很少被业界应用
    • 补偿事务(TCC):TCC是服务化的两阶段变成模型,每个业务服务都必须实现 try,confirm,calcel三个方法,这三个方式可以对应到SQL事务中Lock,Commit,Rollback。
    • 本地消息表:本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理。
    • MQ 事务消息:RocketMQ中实现了分布式事务,实际上是对本地消息表的一个封装,将本地消息表移动到了MQ内部。

究竟是选AP还是选CP,真的在于对业务的了解,例如金钱,库存相关会优先考虑CP模型,例如社区发帖相关可以优先选择AP模型,这个说白了其实基于对业务的了解是一个选择和妥协的过程。

对于开发或设计分布式系统的架构师工程师来说,CAP 是必须要掌握的理论。

But:这个文章的重点并不是讨论 CAP 理论和细节,重点是说说 CAP 在微服务中的开发怎么起到一个指引作用,会通过几个微服务开发的例子说明,尽量的去贴近开发。

CAP 定理又被称为布鲁尔定理,是加州大学计算机科学家埃里克·布鲁尔提出来的猜想,后来被证明成为分布式计算领域公认的定理。

不过布鲁尔在出来 CAP 的时候并没有对 CAP 三者(Consistency,Availability,Partition tolerance)进行详细的定义,所以在网上也出现了不少对 CAP 不同解读的声音。

1. CAP 定理

CAP 定理在发展中存在过两个版本,我们以第二个版本为准

在一个分布式系统中(指互相连接并共享数据的节点集合)中,当涉及到读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。

这个版本的 CAP 理论在探讨分布式系统,更加强调两点是互联和共享数据,其实也是理清楚了第一个版本中三选二的一些缺陷。

分布式系统不一定都存在互联和共享数据,例如 Memcached 集群相互间就没有存在连接和共享数据。

所以 Memcached 集群这类的分布式系统并不在 CAP 理论讨论的范围,而像 MySQL 集群就是互联和数据共享复制,因此 MySQL 集群是属于 CAP 理论讨论的对象。

1.1 一致性(Consistency)

一致性意思就是写操作之后进行读操作无论在哪个节点都需要返回写操作的值

1.2 可用性(Availability)

非故障的节点在合理的时间内返回合理的响应

1.3 分区容错性(Partition Tolerance)

在分布式的环境下,网络无法做到 100% 可靠,有可能出现故障,因此分区是一个必须的选项。

如果选择了 CA 而放弃了 P,若发生分区现象,为了保证 C,系统需要禁止写入,此时就与 A 发生冲突;如果是为了保证 A,则会出现正常的分区可以写入数据,有故障的分区不能写入数据,则与 C 就冲突了。

因此分布式系统理论上不可能选择 CA 架构,而必须选择 CP 或 AP 架构

1.4 分布式事务 BASE 理论

BASE 理论是对 CAP 的延伸和补充,是对 CAP 中的 AP 方案的一个补充,即在选择 AP 方案的情况下,如何更好的最终达到 C。

BASE 是基本可用,柔性状态,最终一致性三个短语的缩写,核心的思想是即使无法做到强一致性,但应用可以采用适合的方式达到最终一致性。

2. CAP 在服务中实际的应用例子

理解貌似讲多了,项目的 CAP 可以参考下李运华的《从零开始学架构》的书,里面的 21,22 章比较详细的描绘了 CAP 的理论细节和 CAP 的版本演化过程。

这里着重的讲解的是神一样的 CAP 在我们的微服务中怎么去指导和应用起来,大概会举几个平时常见的例子

3. 服务注册中心,是选择 AP 还是选择 CP ?

3.1 服务注册中心解决的问题

在讨论 CAP 之前先明确下服务注册中心主要是解决什么问题:一个是服务注册,一个是服务发现。

  • 服务注册:实例将自身服务信息注册到注册中心,这部分信息包括服务的主机 IP 和服务的 Port,以及暴露服务自身状态和访问协议信息等。
  • 服务发现:实例请求注册中心所依赖的服务信息,服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。

目前作为注册中心的一些组件大致有:

  • Dubbo 的 Zookeeper
  • Spring Cloud 的 Eureka,Consul
  • RocketMQ 的 nameServer
  • HDFS 的 nameNode

目前微服务主流是 Dubbo 和 Spring Cloud,使用最多是 Zookeeper 和 Eureka,我们就来看看应该根据 CAP 理论怎么去选择注册中心。(Spring Cloud 也可以用 ZK,不过不是主流不讨论)

3.2 zookeeper 选择 CP

Zookeeper 保证 CP,即任何时刻对 Zookeeper 的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性。

从实际情况来分析,在使用 Zookeeper 获取服务列表时,如果 ZK 正在选举或者 ZK 集群中半数以上的机器不可用,那么将无法获取数据。所以说,ZK 不能保证服务可用性。

3.3 eureka 选择 AP

Eureka 保证 AP,Eureka 在设计时优先保证可用性,每一个节点都是平等的。

一部分节点挂掉不会影响到正常节点的工作,不会出现类似 ZK 的选举 Leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点。

只要有一台 Eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。

3.4 zookeeper 和 eureka 的数据一致性问题

先要明确一点,Eureka 的创建初心就是为一个注册中心,但是 ZK 更多是作为分布式协调服务的存在。

只不过因为它的特性被 Dubbo 赋予了注册中心,它的职责更多是保证数据(配置数据,状态数据)在管辖下的所有服务之间保持一致。

所以这个就不难理解为何 ZK 被设计成 CP 而不是 AP,ZK 最核心的算法 ZAB,就是为了解决分布式系统下数据在多个服务之间一致同步的问题。

更深层的原因,ZK 是按照 CP 原则构建,也就是说它必须保持每一个节点的数据都保持一致。

如果 ZK 下节点断开或者集群中出现网络分割(例如交换机的子网间不能互访),那么 ZK 会将它们从自己的管理范围中剔除,外界不能访问这些节点,即使这些节点是健康的可以提供正常的服务,所以导致这些节点请求都会丢失。

而 Eureka 则完全没有这方面的顾虑,它的节点都是相对独立,不需要考虑数据一致性的问题,这个应该是 Eureka 的诞生就是为了注册中心而设计。

相对 ZK 来说剔除了 Leader 节点选取和事务日志机制,这样更有利于维护和保证 Eureka 在运行的健壮性。

再来看看,数据不一致性在注册服务中会给 Eureka 带来什么问题,无非就是某一个节点被注册的服务多,某个节点注册的服务少,在某一个瞬间可能导致某些 IP 节点被调用数多,某些 IP 节点调用数少的问题。

也有可能存在一些本应该被删除而没被删除的脏数据。

3.5 小结:服务注册应该选择 AP 还是 CP

对于服务注册来说,针对同一个服务,即使注册中心的不同节点保存的服务注册信息不相同,也并不会造成灾难性的后果。

对于服务消费者来说,能消费才是最重要的,就算拿到的数据不是最新的数据,消费者本身也可以进行尝试失败重试。总比为了追求数据的一致性而获取不到实例信息整个服务不可用要好。

所以,对于服务注册来说,可用性比数据一致性更加的重要,选择 AP。

4. 分布式锁,是选择 AP 还是选择 CP ?

这里实现分布式锁的方式选取了三种:

  • 基于数据库实现分布式锁

  • 基于 Redis 实现分布式锁

  • 基于 Zookeeper 实现分布式锁

4.1 基于数据库实现分布式锁

构建表结构

利用表的 UNIQUE KEY idx_lock (method_lock) 作为唯一主键,当进行上锁时进行 insert 动作,数据库成功录入则以为上锁成功,当数据库报出 Duplicate entry 则表示无法获取该锁。

不过这种方式对于单主却无法自动切换主从的 MySQL 来说,基本就无法实现 P 分区容错性(MySQL 自动主从切换在目前并没有十分完美的解决方案)。

可以说这种方式强依赖于数据库的可用性,数据库写操作是一个单点,一旦数据库挂掉,就导致锁的不可用。这种方式基本不在 CAP 的一个讨论范围。。

4.2 基于 Redis 实现分布式锁

Redis 单线程串行处理天然就是解决串行化问题,用来解决分布式锁是再适合不过。

实现方式:

1
2
setnx key value Expire_time
获取到锁 返回 1 , 获取失败 返回 0

为了解决数据库锁的无主从切换的问题,可以选择 Redis 集群,或者是 Sentinel 哨兵模式,实现主从故障转移,当 Master 节点出现故障,哨兵会从 Slave 中选取节点,重新变成新的 Master 节点。

所以 Redis 的复制模式是属于 AP 的模式。保证可用性,在主从复制中“主”有数据,但是可能“从”还没有数据。

这个时候,一旦主挂掉或者网络抖动等各种原因,可能会切换到“从”节点,这个时候可能会导致两个业务线程同时获取得两把锁。

这个过程如下:

  1. 业务线程-1 向主节点请求锁
  2. 业务线程-1 获取锁
  3. 业务线程-1 获取到锁并开始执行业务
  4. 这个时候 redis 刚生成的锁在主从之间还未进行同步
  5. redis 这时候主节点挂掉了
  6. redis 的从节点升级为主节点
  7. 业务线程-2 向新的主节点请求锁
  8. 业务线程-2 获取到新的主节点返回的锁
  9. 业务线程-2 获取到锁开始执行业务
  10. 这个时候 业务线程-1 和 业务线程-2 同时在执行任务

上述的问题其实并不是 Redis 的缺陷,只是 Redis 采用了 AP 模型,它本身无法确保我们对一致性的要求。

Redis 官方推荐 Redlock 算法来保证,问题是 Redlock 至少需要三个 Redis 主从实例来实现,维护成本比较高。

相当于 Redlock 使用三个 Redis 集群实现了自己的另一套一致性算法,比较繁琐,在业界也使用得比较少。

4.2.1 能否使用 redis 作为分布式锁?

这个本身就不是 Redis 的问题,还是取决于业务场景。

我们先要自己确认我们的场景是适合 AP 还是 CP , 如果在社交发帖等场景下,我们并没有非常强的事务一致性问题,Redis 提供给我们高性能的 AP 模型是非常适合的。

但如果是交易类型,对数据一致性非常敏感的场景,我们可能要寻找一种更加适合的 CP 模型。

4.3 基于 Zookeeper 实现分布式锁

刚刚也分析过,Redis 其实无法确保数据的一致性,先来看 Zookeeper 是否适合作为我们需要的分布式锁。

首先 ZK 的模式是 CP 模型,也就是说,当 ZK 锁提供给我们进行访问的时候,在 ZK 集群中能确保这把锁在 ZK 的每一个节点都存在。

这个实际上是 ZK 的 Leader 通过二阶段提交写请求来保证的,这个也是 ZK 的集群规模大了的一个瓶颈点。

4.3.1 ZK 锁实现的原理

说 ZK 的锁问题之前先看看 Zookeeper 中几个特性,这几个特性构建了 ZK 的一把分布式锁。

ZK 的特性如下:

  • 有序节点: 当在一个父目录下如 /lock 下创建 有序节点,节点会按照严格的先后顺序创建出自节点 lock000001,lock000002,lock0000003,以此类推,有序节点能严格保证各个自节点按照排序命名生成。
  • 临时节点: 客户端建立了一个临时节点,在客户端的会话结束或会话超时,Zookepper 会自动删除该节点 ID。
  • 事件监听: 在读取数据时,我们可以对节点设置监听,当节点的数据发生变化(1 节点创建 2 节点删除 3 节点数据变成 4 自节点变成)时,Zookeeper 会通知客户端。

结合这几个特点,来看下 zk 是怎么组合分布式锁。

  1. 业务线程-1 业务线程-2 分别向 zk 的/lock 目录下,申请创建有序的临时节点
  2. 业务线程-1 抢到/lock0001 的文件,也就是在整个目录下最小序的节点,也就是线程-1 获取到了锁
  3. 业务线程-2 只能抢到/lock0002 的文件,并不是最小序的节点,线程 2 未能获取锁
  4. 业务线程-1 与 lock0001 建立了连接,并维持了心跳,维持的心跳也就是这把锁的租期
  5. 当业务线程-1 完成了业务,将释放掉与 zk 的连接,也就是释放了这把锁
4.3.2 zk 分布式锁的代码实现

ZK 官方提供的客户端并不支持分布式锁的直接实现,我们需要自己写代码去利用 ZK 的这几个特性去进行实现。

4.4 小结:究竟该用 CP 还是 AP 的分布式锁

首先得了解清楚我们使用分布式锁的场景,为何使用分布式锁,用它来帮我们解决什么问题,先聊场景后聊分布式锁的技术选型。

无论是 Redis,ZK,例如 Redis 的 AP 模型会限制很多使用场景,但它却拥有了几者中最高的性能。

Zookeeper 的分布式锁要比 Redis 可靠很多,但他繁琐的实现机制导致了它的性能不如 Redis,而且 ZK 会随着集群的扩大而性能更加下降。

简单来说,先了解业务场景,后进行技术选型。

5. 分布式事务,是怎么从 ACID 解脱,投身 CAP/BASE

如果说到事务,ACID 是传统数据库常用的设计理念,追求强一致性模型,关系数据库的 ACID 模型拥有高一致性+可用性,所以很难进行分区。

在微服务中 ACID 已经是无法支持,我们还是回到 CAP 去寻求解决方案,不过根据上面的讨论,CAP 定理中,要么只能 CP,要么只能 AP。

如果我们追求数据的一致性而忽略可用性这个在微服务中肯定是行不通的,如果我们追求可用性而忽略一致性,那么在一些重要的数据(例如支付,金额)肯定出现漏洞百出,这个也是无法接受。所以我们既要一致性,也要可用性。

都要是无法实现的,但我们能不能在一致性上作出一些妥协,不追求强一致性,转而追求最终一致性,所以引入 BASE 理论。

在分布式事务中,BASE 最重要是为 CAP 提出了最终一致性的解决方案,BASE 强调牺牲高一致性,从而获取可用性,数据允许在一段时间内不一致,只要保证最终一致性就可以了。

5.1 实现最终一致性

弱一致性:系统不能保证后续访问返回更新的值。需要在一些条件满足之后,更新的值才能返回。

从更新操作开始,到系统保证任何观察者总是看到更新的值的这期间被称为不一致窗口。

最终一致性:这是弱一致性的特殊形式;存储系统保证如果没有对某个对象的新更新操作,最终所有的访问将返回这个对象的最后更新的值。

5.2 BASE 模型

BASE 模型是传统 ACID 模型的反面,不同于 ACID,BASE 强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了。

BASE 模型反 ACID 模型,完全不同 ACID 模型,牺牲高一致性,获得可用性或可靠性:Basically Available 基本可用。

支持分区失败(e.g. sharding碎片划分数据库)Soft state 软状态,状态可以有一段时间不同步,异步。

Eventually consistent 最终一致,最终数据是一致的就可以了,而不是时时一致。

5.3 分布式事务

在分布式系统中,要实现分布式事务,无外乎几种解决方案。方案各有不同,不过其实都是遵循 BASE 理论,是最终一致性模型。

  • 两阶段提交(2PC)
  • 补偿事务(TCC)
  • 本地消息表
  • MQ 事务消息

5.4 两阶段提交(2PC)

其实还有一个数据库的 XA 事务,不过目前在真正的互联网中实际的应用基本很少,两阶段提交就是使用 XA 原理。

在 XA 协议中分为两阶段:

  1. 事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交。
  2. 事务协调器要求每个数据库提交数据,或者回滚数据。

说一下,为何在互联网的系统中没被改造过的两阶段提交基本很少被业界应用,最大的缺点就是同步阻塞问题。

在资源准备就绪之后,资源管理器中的资源就一直处于阻塞,直到提交完成之后,才进行资源释放。

这个在互联网高并发大数据的今天,两阶段的提交是不能满足现在互联网的发展。

还有就是两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能。

例如:在第二阶段中,假设协调者发出了事务 Commit 的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了 Commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

5.5 补偿事务(TCC)

TCC 是服务化的两阶段变成模型,每个业务服务都必须实现 try,confirm,calcel 三个方法,这三个方式可以对应到 SQL 事务中 Lock,Commit,Rollback。

相比两阶段提交,TCC 解决了几个问题: 同步阻塞,引入了超时机制,超时后进行补偿,并不会像两阶段提交锁定了整个资源,将资源转换为业务逻辑形式,粒度变小。

因为有了补偿机制,可以由业务活动管理器进行控制,保证数据一致性。

Try 阶段: Try 只是一个初步的操作,进行初步的确认,它的主要职责是完成所有业务的检查,预留业务资源。

Confirm 阶段: Confirm 是在 Try 阶段检查执行完毕后,继续执行的确认操作,必须满足幂等性操作,如果 Confirm 中执行失败,会有事务协调器触发不断的执行,直到满足为止。

Cancel 是取消执行: 在 Try 没通过并释放掉 Try 阶段预留的资源,也必须满足幂等性,跟 Confirm 一样有可能被不断执行。

一个下订单,生成订单扣库存的例子:

接下来看看,我们的下单扣减库存的流程怎么加入 TCC

在 Try 的时候,会让库存服务预留 N 个库存给这个订单使用,让订单服务产生一个“未确认”订单,同时产生这两个预留的资源。

在 Confirm 的时候,会使用在 Try 预留的资源,在 TCC 事务机制中认为,如果在 Try 阶段能正常预留的资源,那么在 Confirm 一定能完整的提交。

在 Try 的时候,有任务一方为执行失败,则会执行 Cancel 的接口操作,将在 Try 阶段预留的资源进行释放。

这个并不是重点要论 TCC 事务是怎么实现,重点还是讨论分布式事务在 CAP+BASE 理论的应用。实现可以参考:github.com/changmingxi…

5.6 本地消息表

本地消息表这个方案最初是 eBay 提出的,eBay 的完整方案 queue.acm.org/detail.cfm?…

本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理。

对于本地消息队列来说,核心就是将大事务转变为小事务,还是用上面下订单扣库存的例子说说明

  1. 当我们去创建订单的时候,我们新增一个本地消息表,把创建订单和扣减库存写入到本地消息表,放在同一个事务(依靠数据库本地事务保证一致性)
  2. 配置一个定时任务去轮训这个本地事务表,扫描这个本地事务表,把没有发送出去的消息,发送给库存服务,当库存服务收到消息后,会进行减库存,并写入服务器的事务表,更新事务表的状态。
  3. 库存服务器通过定时任务或直接通知订单服务,订单服务在本地消息表更新状态。

这里须注意的是,对于一些扫描发送未成功的任务,会进行重新发送,所以必须保证接口的幂等性。

本地消息队列是 BASE 理论,是最终一致性模型,适用对一致性要求不高的情况。

5.7 MQ 事务

RocketMq 在 4.3 版本已经正式宣布支持分布式事务,在选择 Rokcetmq 做分布式事务请务必选择 4.3 以上的版本。

RocketMQ 中实现了分布式事务,实际上是对本地消息表的一个封装,将本地消息表移动到了 MQ 内部。

事务消息作为一种异步确保型事务, 将两个事务分支通过 MQ 进行异步解耦,RocketMQ 事务消息的设计流程同样借鉴了两阶段提交理论,整体交互流程如下图所示:

MQ 事务是对本地消息表的一层封装,将本地消息表移动到了 MQ 内部,所以也是基于 BASE 理论,是最终一致性模式,对强一致性要求不那么高的事务适用,同时 MQ 事务将整个流程异步化了,也非常适合在高并发情况下使用。

6. RocketMQ 选择异步/同步刷盘,异步/同步复制,背后的 CP 和 AP 思考

虽然同步刷盘/异步刷盘,同步/异步复制,并没有对 CAP 直接的应用,但在配置的过程中也一样涉及到可用性和一致性的考虑。

6.1 同步刷盘/异步刷盘

RocketMQ 的消息是可以做到持久化的,数据会持久化到磁盘,RocketMQ 为了提高性能,尽可能保证磁盘的顺序写入,消息在 Producer 写入 RocketMq 的时候,有两种写入磁盘方式:

  1. 异步刷盘:消息快速写入到内存的 Pagecache,就立马返回写成功状态,当内存的消息累计到一定程度的时候,会触发统一的写磁盘操作。这种方式可以保证大吞吐量,但也存在着消息可能未存入磁盘丢失的风险。
  2. 同步刷盘:消息快速写入内存的 Pagecahe,立刻通知刷盘线程进行刷盘,等待刷盘完成之后,唤醒等待的线程,返回消息写成功的状态。

6.2 同步复制/异步复制

一个 Broker 组有 Master 和 Slave,消息需要从 Master 复制到 Slave 上,所以有同步和异步两种复制方式:

  1. 同步复制:是等 Master 和 Slave 均写成功后才反馈给客户端写成功状态。
  2. 异步复制:是只要 Master 写成功即可反馈给客户端写成功状态。

异步复制的优点是可以提高响应速度,但牺牲了一致性 ,一般实现该类协议的算法需要增加额外的补偿机制。

同步复制的优点是可以保证一致性(一般通过两阶段提交协议),但是开销较大,可用性不好(参见 CAP 定理),带来了更多的冲突和死锁等问题。

值得一提的是 Lazy+Primary/Copy 的复制协议在实际生产环境中是非常实用的。

RocketMQ 的设置要结合业务场景,合理设置刷盘方式和主从复制方式,尤其是 SYNC_FLUSH 方式,由于频繁的触发写磁盘动作,会明显降低性能。

通常情况下,应该把 Master 和 Slave 设置成 ASYNC_FLUSH 的刷盘方式,主从之间配置成 SYNC_MASTER 的复制方式,这样即使有一台机器出故障,仍然可以保证数据不丢。

7. 总结

在微服务的构建中,永远都逃离不了 CAP 理论,因为网络永远不稳定,硬件总会老化,软件可能出现 Bug,所以分区容错性在微服务中是躲不过的命题。

可以这么说,只要是分布式,只要是集群都面临着 AP 或者 CP 的选择,但你很贪心的时候,既要一致性又要可用性,那只能对一致性作出一点妥协,也就是引入了 BASE 理论,在业务允许的情况下实现最终一致性。

究竟是选 AP 还是选 CP,真的在于对业务的了解,例如金钱,库存相关会优先考虑 CP 模型,例如社区发帖相关可以优先选择 AP 模型,这个说白了其实基于对业务的了解是一个选择和妥协的过程。

CentOS 7 安装GUI界面及远程连接

CentOS 7 安装GUI界面及远程连接

参考文章:

https://segmentfault.com/a/1190000019557600

1. 安装GUI界面

大多数云服务器厂商提供的镜像都无GUI界面,所以要先安装图形环境。本文使用GNOME桌面环境:

1
yum -y groups install "GNOME Desktop"

这条命令将安装GNOME桌面的必要软件包,执行完后配置X系统使用GNOME:

1
echo "exec gnome-session" >> ~/.xinitrc

2. 安装xrdp

安装源

1
yum install  epel* -y

安装xrdp

1
yum --enablerepo=epel -y install xrdp

启动xrdp

1
2
systemctl start xrdp
systemctl enable xrdp

关闭防火墙

1
2
firewall-cmd --permanent --zone=public --add-port=3389/tcp
firewall-cmd --reload

3. 连接效果

因为我使用的是mac笔记本,所以远程桌面的软件使用的是 Microsoft Remote Desktop

输入 centos 用户名,密码

连接后的效果

《阿里巴巴 Java 开发手册》第五章MySQL数据库解读

基于手册最新版本《阿里巴巴 Java 开发手册(华山版)》

《阿里巴巴 Java 开发手册》下载地址: https://github.com/alibaba/p3c

参考文章:

点评阿里JAVA手册之MySQL数据库

详细解读阿里手册之MySQL

开发不规范,亲人两行泪 🤣

(一) 建表规约

1.强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint( 1 表示是,0 表示否)。

说明:任何字段如果为非负数,必须是 unsigned。

注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在 设置从 is_xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。

正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

unsigned 属性就是将数字类型无符号化

”POJO 类中的任何布尔类型的变量,都不要加 is 前缀“,这个可以参考 Hollis 大佬的这篇文章 为什么阿里巴巴禁止开发人员使用isSuccess作为变量名

2.强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。

说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。

正例:aliyun_admin,rdc_config,level3_name

反例:AliyunAdmin,rdcConfig,level_3_name

表名、字段名一定要规范,并且要见名知意。因为一旦使用之后,想要修改的话会比较麻烦。起一个好的名字真的很重要,能够在无形中减少沟通成本。

3.强制】表名不使用复数名词。

说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。

有些单词的复数形式可能是非常规的,或者就没有复数形式,因此单数形式更简单

4.强制】禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。

文档地址: https://dev.mysql.com/doc/refman/8.0/en/keywords.html

5.强制】主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。

说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。

这样见名知意,通过前缀就可以知道是什么类型的索引

6.强制】小数类型为 decimal,禁止使用 float 和 double。

说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。

对于和钱相关的系统,精度损失是会造成金钱损失的

7.强制】如果存储的字符串长度几乎相等,使用 char 定长字符串类型。

CHAR(M)定义的列的长度为固定的,M取值可以为0~255之间,当保存CHAR值时,在它们的右边填充空格以达到指定的长度。当检索到CHAR值时,尾部的空格被删除掉。在存储或检索过程中不进行大小写转换。CHAR存储定长数据很方便,CHAR字段上的索引效率极高,比如定义char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间,不足的自动用空格填充。

VARCHAR(M)定义的列的长度为可变长字符串,M取值可以为0~65535之间,(VARCHAR的最大有效长度由最大行大小和使用的字符集确定。整体最大长度是65,532字节)。VARCHAR值保存时只保存需要的字符数,另加一个字节来记录长度(如果列声明的长度超过255,则使用两个字节)。VARCHAR值保存时不进行填充。当值保存和检索时尾部的空格仍保留,符合标准SQL。varchar存储变长数据,但存储效率没有CHAR高。如果一个字段可能的值是不固定长度的,我们只知道它不可能超过10个字符,把它定义为 VARCHAR(10)是最合算的。VARCHAR类型的实际长度是它的值的实际长度+1。为什么”+1”呢?这一个字节用于保存实际使用了多大的长度。从空间上考虑,用varchar合适;从效率上考虑,用char合适,关键是根据实际情况找到权衡点。

来自: https://blog.csdn.net/qq_24549805/article/details/53426668

8.强制】varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于 5000,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

MySQL中char、varchar和text的区别

它们的存储方式和数据的检索方式都不一样。
数据的检索效率是:char > varchar > text
空间占用方面,就要具体情况具体分析了。

1.char: 存储定长数据很方便,CHAR字段上的索引效率极高,必须在括号里定义长度,可以有默认值,比如定义char(10),那么不论你存储的数据是否达到了10个字符,都要占去10个字符的空间(自动用空格填充),且在检索的时候后面的空格会隐藏掉,所以检索出来的数据需要记得用什么trim之类的函数去过滤空格。

2.varchar: 存储变长数据,但存储效率没有CHAR高,必须在括号里定义长度,可以有默认值。保存数据的时候,不进行空格自动填充,而且如果数据存在空格时,当值保存和检索时尾部的空格仍会保留。另外,varchar类型的实际长度是它的值的实际长度+1,这一个字节用于保存实际使用了多大的长度。

3.text: 存储可变长度的非Unicode数据,最大长度为2^31-1个字符。text列不能有默认值,存储或检索过程中,不存在大小写转换,后面如果指定长度,不会报错误,但是这个长度是不起作用的,意思就是你插入数据的时候,超过你指定的长度还是可以正常插入。

来自: https://www.jianshu.com/p/cc2d99559532

9.强制】表必备三字段:id, create_time, update_time。

说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。create_time, update_time 的类型均为 datetime 类型。

create_time 为一行记录的创建时间,update_time 为一行记录的更新时间

datetime、timestamp精确度都是秒,datetime与时区无关,存储的范围广(1001-9999),timestamp与时区有关,存储的范围小(1970-2038)。

10.推荐】表的命名最好是遵循 “业务名称_表的作用”。

正例:alipay_task / force_project / trade_config

方便区分和查找

11.推荐】库名与应用名称尽量一致。

比如你有个项目名称叫做 alipay-adapter ,那数据库名可以起名为 alipay_adapter

12.推荐】如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。

字段注释要及时更新,降低沟通成本

13.推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:

​ 1)不是频繁修改的字段。

​ 2)不是 varchar 超长字段,更不能是 text 字段。

​ 3) 不是唯一索引的字段。

正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。

数据库三范式可以不用严格遵循,有时候适当的冗余能够提高查询效率

14.推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。

说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。

15.参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。

正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。

对象 年龄区间 类型 字节 表示范围
150 岁之内 tinyint unsigned 1 无符号值:0 到 255
数百岁 smallint unsigned 2 无符号值:0 到 65535
恐龙化石 数千万年 int unsigned 4 无符号值:0 到约 42.9 亿
太阳 约 50 亿年 bigint unsigned 8 无符号值:0 到约 10 的 19 次方

(二) 索引规约

  1. 【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

    说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外, 即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

  2. 【强制】超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。

  3. 【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。

    说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。

  4. 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。

    说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

  5. 【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。

    正例:where a=? and b=? order by c; 索引:a_b_c

    反例:索引如果存在范围查询,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。

  6. 【推荐】利用覆盖索引来进行查询操作,避免回表。

    说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这 个目录就是起到覆盖索引的作用。

    正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效 果,用 explain 的结果,extra 列会出现:using index。

  7. 【推荐】利用延迟关联或者子查询优化超多分页场景。

    说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。

    正例:先快速定位需要获取的 id 段,然后再关联:

    SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

  8. 【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是

consts 最好。

​ 说明:

1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。

2) ref 指的是使用普通的索引(normal index)。

3) range 对索引进行范围检索。

​ 反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range 还低,与全表扫描是小巫见大巫。

  1. 【推荐】建组合索引的时候,区分度最高的在最左边。

    正例:如果 where a=? and b=? ,如果 a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。

    说明:存在非等号和等号混合时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么 即使 c 的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。

  2. 【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。

  3. 【参考】创建索引时避免有如下极端误解:

    1) 宁滥勿缺。认为一个查询就需要建一个索引。

    2) 宁缺勿滥。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度。

    3) 抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。

RESTful API规范(详细版)

作者: RyuGou

原文地址: RESTful API 规范(详细版)

作者公众号: 互联网技术窝

看的时候感觉写的非常不错 😆,看完自我介绍发现果然是个大佬

1. 简介

rest是一种软件架构风格,如果你们的接口是rest接口,那么就可被认为你们的的接口是 restful 的,英文名词和形容词的区别。

rest接口是围绕“资源”展开的,利用 HTTP 的协议,其实 rest 本也可以和 HTTP 无关,但是现在大家普遍的使用rest都是依托于 HTTP 协议。HTTP 的 url 即资源。

RFC 3986 定义了通用的 URI 语法:

1
URI = scheme “://” authority “/” path [ “?” query ][ “#” fragment ]
  • scheme: 指底层用的协议,如 http、https、ftp
  • host: 服务器的 IP 地址或者域名
  • port: 端口,http 中默认 80
  • path: 访问资源的路径,就是咱们各种 web 框架中定义的 route 路由
  • query: 为发送给服务器的参数
  • fragment: 锚点,定位到页面的资源,锚点为资源 id

2. RESTful API 设计

2.1 资源路径

对于 rest 资源的定义,即 URL 的定义,是最重要的;想要设计出优雅的、易读的 rest 接口,其实还是挺不容易的。

2.2 URL 中不能有动词

在 Restful 架构中,每个网址代表的是一种资源,所以网址中不能有动词,只能有名词,动词由 HTTP 的 get、post、put、delete 四种方法来表示。

2.3 URL 结尾不应该包含斜杠“/”

这是作为 URL 路径中处理中最重要的规则之一,正斜杠(/)不会增加语义值,且可能导致混淆。REST API 不允许一个尾部的斜杠,不应该将它们包含在提供给客户端的链接的结尾处。

许多 Web 组件和框架将平等对待以下两个 URI:

http://api.canvas.com/shapes/

http://api.canvas.com/shapes

但是,实际上 URI 中的每个字符都会计入资源的唯一身份的识别中。

两个不同的 URI 映射到两个不同的资源。如果 URI 不同,那么资源也是如此,反之亦然。因此,REST API 必须生成和传递精确的 URI,不能容忍任何的客户端尝试不精确的资源定位。

有些 API 碰到这种情况,可能设计为让客户端重定向到相应没有尾斜杠的 URI(也有可能会返回 301 - 用来资源重定向)。

2.4 正斜杠分隔符”/“必须用来指示层级关系

url 的路径中的正斜杠“/“字符用于指示资源之间的层次关系。

例如:

http://api.user.com/schools/grades/classes/boys - 学校中所有的男生

http://api.college.com/students/3248234/courses - 检索 id 为 3248234 的学生学习的所有课程的清单。

2.5 应该使用连字符”-“来提高 URL 的可读性,而不是使用下划线”_”

为了使 URL 容易让人们理解,请使用连字符”-“字符来提高长路径中名称的可读性。

一些文本查看器为了区分强调 URI,常常会在 URI 下加上下划线。这样下划线”_”字符可能被文本查看器中默认的下划线部分地遮蔽或完全隐藏。

为避免这种混淆,请使用连字符”-“而不是下划线

2.6 URL 路径中首选小写字母

RFC 3986 将 URI 定义为区分大小写,但 scheme 和 host components 除外。

2.7 URL 路径名词均为复数

为了保证 url 格式的一致性,建议使用复数形式。

3. RESTful API 对资源的操作

对于 rest api 资源的操作,由 HTTP 动词表示

3.1 CURD 操作

  • GET:获取资源
  • POST:新建资源
  • PUT:在服务器更新资源(向客户端提供改变后的所有资源)
  • PATCH:在服务器更新资源(向客户端提供改变的属性)
  • DELETE:删除资源

PATCH一般不用,用PUT

3.2 资源过滤

在获取资源的时候,有可能需要获取某些“过滤”后的资源,例如指定前 10 行数据

http://api.user.com/schools/grades/classes/boys?page=1&page-size=10

3.3 返回状态码推荐标准 HTTP 状态码

有很多服务器将返回状态码一直设为 200,然后在返回 body 里面自定义一些状态码来表示服务器返回结果的状态码。由于 rest api 是直接使用的 HTTP 协议,所以它的状态码也要尽量使用 HTTP 协议的状态码。

  • 200 OK 服务器返回用户请求的数据,该操作是幂等的
  • 201 CREATED 新建或者修改数据成功
  • 204 NOT CONTENT 删除数据成功
  • 400 BAD REQUEST 用户发出的请求有问题,该操作是幂等的
  • 401 Unauthoried 表示用户没有认证,无法进行操作
  • 403 Forbidden 用户访问是被禁止的
  • 422 Unprocesable Entity 当创建一个对象时,发生一个验证错误
  • 500 INTERNAL SERVER ERROR 服务器内部错误,用户将无法判断发出的请求是否成功
  • 503 Service Unavailable 服务不可用状态,多半是因为服务器问题,例如 CPU 占用率大,等等

3.4 返回结果

  • GET /collections 返回资源列表
  • GET /collections/:id 返回单独的资源
  • POST /collections 返回新生成的资源对象
  • PUT /collections/:id 返回完整的资源对象
  • PATCH /collections/:id 返回被修改的属性
  • DELETE /collections/:id 返回一个空文档

《刻意练习》高效学习的3F法则

视频地址: https://www.bilibili.com/video/av62616953

Part 1 刻意练习的黄金法则-3F法则

1. 专注

  • 起步使用较短时段(交叉安排)

    可以开始先学十分钟,接着休息五分钟,然后再学十分钟,不断重复。

  • 缓慢延长学习时间(循序渐进)

    坚持一段时间,再尝试二十分钟,三十分钟

  • 若无法坚持则缩短(以退为进)

    如果在提升过程中遇到了瓶颈,再把学习时间缩短一点,等适应了之后,再继续延长学习时间。

2. 反馈

  • 来自内部,自己发现自己的不足。

  • 来自外部,其他人指出自己的不足。

    找个好导师,是各个领域学习的通用捷径,但外部反馈多了之后,也会引起内部反馈。

3. 纠正

  • 给出反馈后的下一步,就是纠正不足。

Part 2 如何保持学习动力

1. 如何保持学习动力。

意志力不是天生,绝对的,我们可能在某件事上意志力充足,但在另一件事上毫无意志,这个差异的关键在于动机是不是足够。

2 保持动机的做法有两种:

正向:强化进步的理由

  • 内部动机-Tips:

    ①真心的喜欢

  • 外部动机-Tips:

    ①物质奖励
    ②团体约束感

反向:弱化退步的理由

①拒绝会使你失败外界诱因。不要被杂七杂八事情拖累你

做你喜欢的事就没有这么难

使用这些idea插件让开发效率提高5倍

idea 有很多非常好用的插件,用好了这些插件能够极大的提高开发效率

插件用的好,bug 就追不上了我 😆

0. idea 插件如何安装

打开 idea 的设置页面,选择 Plugins 选项即可搜索和安装插件

1. JRebel for IntelliJ

日常开发中,当你修改任意一个 java 文件时,tomcat 并不能将此文件的修改实时编译并反映到运行的项目中去,所以只能重启项目。这样做非常耗时和麻烦。使用 JRebel 之后只要选择 Bulid 中的 Build Project 选项即可热部署项目,用着非常爽,强烈推荐

具体安装和使用参考之前的文章: idea 热部署插件 jrebel,开发必备

2. Codota

Codota 是一款智能 AI 代码补全插件,它从数百万 Java 程序中学习代码,最终能根据程序上下文提示并补全代码,帮助开发者减少失误,提升工作效率,并且它还是免费的。

3. Translation

翻译插件,有了它妈妈再也不用担心我的英语渣了

我常用它两个功能:

  • 翻译, 就是中英翻译, 可以当词典用
  • 翻译替换, 就是将原文直接翻译替换, 起变量名字时非常有用: 起个中文名, 然后翻译替换就好了.

4. RestfulToolkit

这款插件可以根据 URL 直接跳转到对应的方法。

快捷键

  • windows:ctrl + \
  • mac:cmd + \

5. Lombok

在过往的 Java 项目中,充斥着太多不友好的代码:POJO 的 getter/setter/toString;异常处理;I/O 流的关闭操作等等,这些样板代码既没有技术含量,又影响着代码的美观,Lombok 应运而生。它让代码变得非常简洁。

使用 @Data 注解可以自动生成以上的方法,再也不用手动生成烦人的 getter/setter/toString 方法了

具体 Lombok 使用方法可以参考这篇文章: https://juejin.im/post/5b00517cf265da0ba0636d4b

6. GenerateAllSetter

一键调用一个对象的所有 set 方法并且赋予默认值 在对象字段多的时候非常方便

Screenshot 2

7. Alibaba Cloud Toolkit

Cloud Toolkit 帮助开发者将本地应用程序一键部署到线下自有 VM,或阿里云 ECS、EDAS 和 Kubernetes 中去。内置终端 Terminal、文件上传、数据库 SQL Console 能功能。用它来部署项目非常方便。

8. Material Theme UI

Material Theme UI 是 JetBrains IDE(IntelliJ IDEA,WebStorm,Android Studio 等)的插件,可将原始外观更改为 Material Design外观。

使用之后的 idea 界面变得非常漂亮,心情好了,写代码的速度也变快了。

9. Properties to YAML Converter

将 Properties 配置文件转换为 YAML 配置文件

  • 首先选择属性文件。

  • 在属性文件上单击鼠标右键后,在菜单中使用“转换”操作。

10. MyBatis plugin

mapper.java 和 mapper.xml 之间任性跳转。点击小箭头就能跳转。

  • Copyrights © 2015-2023 高行行
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信