跨域资源共享 CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

简介

CORS需要浏览器和服务器同时支持。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

两种请求

简单请求

只要同时满足以下两大条件,就属于简单请求。

1
2
3
4
5
6
7
8
9
10
11
12
1) 请求方法是以下三种方法之一:

HEAD
GET
POST
2)HTTP的头信息不超出以下几种字段:

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

表单一直可以发出跨域请求, AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

服务器端处理

  1. Access-Control-Allow-Origin
    该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个”星号”,表示接受任意域名的请求。

  2. Access-Control-Allow-Credentials
    字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

  3. Access-Control-Expose-Headers
    该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

  4. Access-Control-Allow-Methods
    该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。

  5. Access-Control-Allow-Headers
    如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。

  6. Access-Control-Max-Age
    该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

  7. Access-Control-Request-Method
    该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法.

  8. Access-Control-Request-Headers
    该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段

简单请求, 返回: 1/2/3
非简单请求, 上送: 7/8, 返回: 1/2/4/5/6

API 调用追踪

目的

开发排查系统问题用得最多的手段就是查看系统日志,但是在分布式环境下使用日志定位问题还是比较麻烦,需要借助”全链路追踪ID”把上下文串联起来.

目前大多数分布式追踪系统的思想模型都来自 Google’s Dapper 论文

  • ID
    64位的整数
  • Trace Tree : 一次跟踪
  • 树节点是Span,包括:
    • Span ID
    • 开始时间和结束时间
    • RPC时间数据
    • 应用自定义标识数据annotations
  • 树节点之间的边是Span 和它的父节点之间的关系
  • 没有父节点的Span是根Span (root span)
  • 所有树节点都有共同的一个Trace ID 标识这次跟踪

方案

无入侵增加 traceId

使用 Logback 的 MDC 机制,在日志模板中加入 traceId 标识,取值方式为 %X{traceId}

  1. 系统入口 创建 traceId 的值
  2. 使用 MDC 保存 traceId
  3. 修改 logback 配置文件模板格式添加标识 %X{traceId}

    MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。

跨进程传递

  1. 「dubbo服务」 使用 org.apache.dubbo.rpc.Filter 创建一个过滤器进行 traceId 传递
  • 服务消费者:负责传递链路追踪 ID
  • 服务提供者:负责接收 ID 并保存到 MDC 中
  1. 基于 Spring 的Rest 服务
  • 服务消费者: 自定义 Http 客户端,统一负责传递链路追踪 ID
  • 服务提供者: 使用 Filter 进行 traceId 传递

Spring Cloud 全家桶解决方案

Spring-Cloud-Sleuth是Spring Cloud的组成部分之一,为SpringCloud应用实现了一种分布式追踪解决方案,其兼容了Zipkin, HTrace和log-based追踪

异常处理

错误返回码

处理错误最直接的方式是通过错误码,这也是传统的方式,在过程式语言中通常都是用这样的方式处理错误的.基本上来说,其通过函数的返回值标识是否有错,然后通过全局的errno变量并配合一个 errstr 的数组来告诉你为什么出错。

捕获异常

try - catch - finally 异常处理

  • 函数接口在 input(参数)和 output(返回值)以及错误处理的语义是比较清楚的。
  • 正常逻辑的代码可以与错误处理和资源清理的代码分开,提高了代码的可读性。
  • 异常不能被忽略(如果要忽略也需要 catch 住,这是显式忽略)。
  • 在面向对象的语言中(如 Java),异常是个对象,所以,可以实现多态式的 catch。

错误返回码 vs 异常捕捉

  • 对于我们并不期望会发生的事,我们可以使用异常捕捉;
  • 对于我们觉得可能会发生的事,使用返回码。

异步的错误处理

在异步编程的世界里,因为被调用的函数是被放到了另外一个线程里运行,这将导致:

  • 无法使用返回码。因为函数在被异步运行中,所谓的返回只是把处理权交给下一条指令,而不是把函数运行完的结果返回。所以,函数返回的语义完全变了,返回码也没有用了。
  • 无法使用抛异常的方式。因为除了上述的函数立马返回的原因之外,抛出的异常也在另外一 个线程中,不同线程中的栈是完全不一样的,所以主线程的 catch 完全看不到另外一个线程 中的异常。

Callback 方式

通过注册错误处理的回调函数,让异步执行的函数在出错的时候,调用被注册进来的错误处理函数,这样的方式比较好地解决了程序的错误处理。而出错的语义从返回码、异常捕捉到了直接耦合错误出处函数的样子。

Promise 模式

1
2
3
4
5
6
7
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

上面代码中的 then() 和 catch() 方法就是 Promise对象的方法,then()方法可以把各个异步的函数给串联起来,而catch() 方法则是出错的处理。

Java 异步编程的 Promise 模式

链式处理:

1
2
3
CompletableFuture.supplyAsync(this::findReceiver)
.thenApply(this::sendMsg)
.thenAccept(this::notify);

异常处理:

1
2
3
4
CompletableFuture.supplyAsync(Integer::parseInt)
.thenApply(r -> r * 2 * Math.PI)
.thenApply(s -> "apply>> " + s)
.exceptionally(ex -> "Error: " + ex.getMessage());

混合处理:

1
2
3
4
5
6
7
8
9
10
CompletableFuture.supplyAsync(Integer::parseInt) 
.thenApply(r -> r * 2 * Math.PI)
.thenApply(s -> "apply>> " + s)
.handle((result, ex) -> {
if (result != null) {
return result;
} else {
return "Error handling: " + ex.getMessage();
}
});

最佳实践

  • 统一分类的错误字典。无论你是使用错误码还是异常捕捉,都需要认真并统一地做好错误的分类。最好是在一个地方定义相关的错误。
  • 定义错误的严重程度。
  • 错误日志的输出最好使用错误码,而不是错误信息。打印错误日志的时候,除了要用统一的格式,最好不要用错误信息,而使用相应的错误码,错误码不一定是数字,也可以是一个能从错误字典里找到的一个唯一的可以让人读懂的关键字。这样,会非常有利于日志分析软件进行自动化监控,而不是要从错误信息中做语义分析。
  • 忽略错误最好有日志
  • 对于同一个地方不停的报错,最好不要都打到日志里,打出一个错误以及出现的次数。
  • 尽可能在错误发生的地方处理错误。
  • 处理错误时,总是要清理已分配的资源。
  • 为你的错误定义提供清楚的文档以及每种错误的代码示例

State Machine

状态机定义

有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

有限状态机体现了两点:首先是离散的,然后是有限的。

  • State:状态这个词有些难以定义,状态存储关于过去的信息,就是说它反映从系统开始到现在时刻的输入变化。
  • Actions & Transitions:转换指示状态变更,并且用必须满足来确使转移发生的条件来描述它。动作是在给定时刻要进行的活动的描述。
  • Guards:检测器出现的原因是为了检测是否满足从一个状态切换到另外一个状态的条件。
  • Event:事件,又见事件,笼统说来,对系统重要的某件事情被称为事件。

状态机示例

现实中的例子:验票闸门
用于控制地铁和游乐园游乐设施的旋转门是一个门,在腰高处有三个旋转臂,一个横跨入口通道。最初,手臂被锁定,阻挡了入口,阻止了顾客通过。将硬币或代币存放在旋转门上的槽中可解锁手臂,允许单个客户穿过。在顾客通过之后,再次锁定臂直到插入另一枚硬币。

旋转门被视为状态机,有两种可能的状态:锁定和解锁。有两种可能影响其状态的输入:将硬币放入槽(硬币)并推动手臂(推动)。在锁定状态下,推动手臂无效; 无论输入推送次数多少,它都处于锁定状态。投入硬币 - 即给机器输入硬币 - 将状态从锁定转换为解锁。在解锁状态下,放入额外的硬币无效; 也就是说,给予额外的硬币输入不会改变状态。然而,顾客推动手臂,进行推动输入,将状态转回Locked。


旋转栅状态机也可以由称为状态图的有向图表示 (上面)。每个状态由节点(圆圈)表示。边(箭头)显示从一个状态到另一个状态的转换。每个箭头都标有触发该转换的输入。不引起状态改变的输入(例如处于未锁定状态的硬币输入)由返回到原始状态的圆形箭头表示。从黑点进入Locked节点的箭头表示它是初始状态。

#主流框架

  • Spring Statemachine
  • Stateless4j
  • Squirrel-Foundation

K8s DeamonSet

作用

DaemonSet 的主要作用,是让你在 Kubernetes 集群里,运行一个 Daemon Pod。

特征

  1. 这个 Pod 运行在 Kubernetes 集群里的每一个节点(Node)上;
  2. 每个节点上只有一个这样的 Pod 实例;
  3. 当有新的节点加入 Kubernetes 集群后,该 Pod 会自动地在新节点上被创建出来;而当旧 节点被删除后,它上面的 Pod 也相应地会被回收掉。

场景

  1. 各种网络插件的 Agent 组件,都必须运行在每一个节点上,用来处理这个节点上的容器网 络;
  2. 各种存储插件的 Agent 组件,也必须运行在每一个节点上,用来在这个节点上挂载远程存 储目录,操作容器的 Volume 目录;
  3. 各种监控组件和日志组件,也必须运行在每一个节点上,负责这个节点上的监控信息和日志 搜集。

区别

Deployment 部署的副本 Pod 会分布在各个 Node 上,每个 Node 都可能运行好几个副本。DaemonSet 的不同之处在于:每个 Node 上最多只能运行一个副本。

K8s StatefulSet

Stateful Application

实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态 应用”(Stateful Application)

拓扑状态

这种情况意味着,应用的多个实例之间不是完全对等的关系。这些应用实例,必 须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到 这个新 Pod。

StatefulSet实现

StatefulSet 这个控制器的主要作用之一,就是使用 Pod 模板创建 Pod 的时候, 对它们进行编号,并且按照编号顺序逐一完成创建工作。而当 StatefulSet 的“控 制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作。通过 Headless Service 的方式,StatefulSet 为每个 Pod 创建了一个固定并且稳定 的 DNS 记录,来作为它的访问入口。

Headless Service 配置

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
clusterIP: None

StatefulSet 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1 
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
name: web

存储状态

这种情况意味着,应用的多个实例分别绑定了不同的存储数据。对于这些应用实 例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一 份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的 多个存储实例。

实现

Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 对象. Kubernetes 中 PVC 和 PV 的设计,实际上类似于“接口”和“实现”的思想。开发者 只要知道并会使用“接口”,即:PVC;而运维人员则负责给“接口”绑定具体的实现,即: PV。而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。
StatefulSet 为每一个 Pod 分配并创建一个同样编号的 PVC。这样,Kubernetes 就可 以通过 Persistent Volume 机制为这个 PVC 绑定上对应的 PV,从而保证了每一个 Pod 都拥有 一个独立的 Volume。

PVC 配置

1
2
3
4
5
6
7
8
9
10
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pv-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

StatefulSet 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

总结

StatefulSet 其实就是一种特殊的 Deployment,而其独特之处在于,它的每个 Pod 都被编号了。而且,这个编号会体现在 Pod 的名字和 hostname 等标识信息上,这不仅代表了 Pod 的创建顺序,也是 Pod 的重要网络标 识(即:在整个集群里唯一的、可被的访问身份)。

HMAC 算法

HMAC Hash-based Message Authentication Code, 哈希消息认证码.

HMAC是密钥相关的哈希运算消息认证码,HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。就是所谓的加盐 !

运算作用

  • 验证TPM接受的授权数据和认证数据;
  • 确认TPM接受到的命令请求是已授权的请求,并且,命令在传送的过程中没有被改动过。
    定义HMAC需要一个加密用散列函数(表示为H,可以是MD5或者SHA-1)和一个密钥K。我们用B来表示数据块的字节数。(以上所提到的散列函数的分割数据块字长B=64),用L来表示散列函数的输出数据字节数(MD5中L=16,SHA-1中L=20)。鉴别密钥的长度可以是小于等于数据块字长的任何正整数值。应用程序中使用的密钥长度若是比B大,则首先用使用散列函数H作用于它,然后用H输出的L长度字符串作为在HMAC中实际使用的密钥。一般情况下,推荐的最小密钥K长度是L个字节。

算法表示

1
2
3
4
5
6
7
8
9
10
11
12
算法公式 : HMAC(K,M)=H(K⊕opad∣H(K⊕ipad∣M)) [1]
H 代表所采用的HASH算法(如SHA-256)
K 代表认证密码
Ko 代表HASH算法的密文
M 代表一个消息输入
B 代表H中所处理的块大小,这个大小是处理块大小,而不是输出hash的大小
如,SHA-1和SHA-256 B = 64
SHA-384和SHA-512 B = 128
L 表示hash的大小
Opad 用0x5c重复B次
Ipad 用0x36重复B次
Apad 用0x878FE1F3重复(L/4)次

HMAC的应用

hmac主要应用在身份验证中,它的使用方法是这样的:

  • (1) 客户端发出登录请求(假设是浏览器的GET请求)
  • (2) 服务器返回一个随机值,并在会话中记录这个随机值
  • (3) 客户端将该随机值作为密钥,用户密码进行hmac运算,然后提交给服务器
  • (4) 服务器读取用户数据库中的用户密码和步骤2中发送的随机值做与客户端一样的hmac运算,然后与用户发送的结果比较,如果结果一致则验证用户合法.

在这个过程中,可能遭到安全攻击的是服务器发送的随机值和用户发送的hmac结果,而对于截获了这两个值的黑客而言这两个值是没有意义的,绝无获取用户密码的可能性,随机值的引入使hmac只在当前会话中有效,大大增强了安全性和实用性。大多数的语言都实现了hmac算法,比如php的mhash、python的hmac.py、java的MessageDigest类,在web验证中使用hmac也是可行的,用js进行md5运算的速度也是比较快的。

Java 生成 AES 秘钥

AES工作模式

电子密码本:Electronic Code Book Mode (ECB)

ECB模式只是将明文按分组大小切分,然后用同样的密钥正常加密切分好的明文分组。ECB的理想应用场景是短数据(如加密密钥)的加密。此模式的问题是无法隐藏原明文数据的模式,因为同样的明文分组加密得到的密文也是一样的。

密码分组链接:Cipher Block Chaining Mode (CBC)

此模式是1976年由IBM所发明,引入了IV(初始化向量:Initialization Vector)的概念。IV是长度为分组大小的一组随机,通常情况下不用保密,不过在大多数情况下,针对同一密钥不应多次使用同一组IV。
CBC要求第一个分组的明文在加密运算前先与IV进行异或;从第二组开始,所有的明文先与前一分组加密后的密文进行异或。[区块链(blockchain)的鼻祖!]
CBC模式相比ECB实现了更好的模式隐藏,但因为其将密文引入运算,加解密操作无法并行操作。同时引入的IV向量,还需要加、解密双方共同知晓方可。

密文反馈:Cipher Feedback Mode (CFB)

与CBC模式类似,但不同的地方在于,CFB模式先生成密码流字典,然后用密码字典与明文进行异或操作并最终生成密文。后一分组的密码字典的生成需要前一分组的密文参与运算。
CFB模式是用分组算法实现流算法,明文数据不需要按分组大小对齐。

输出反馈:Output Feedback Mode (OFB)

OFB模式与CFB模式不同的地方是:生成字典的时候会采用明文参与运算,CFB采用的是密文。
CTR模式同样会产生流密码字典,但同是会引入一个计数,以保证任意长时间均不会产生重复输出。

计数器模式:Counter Mode (CTR)

生成 AES 秘钥

1
2
3
4
5
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = new SecureRandom();
kgen.init(256, secureRandom);
SecretKey key = kgen.generateKey();
System.out.println(Base64.encodeBase64String(key.getEncoded()));

AES/CBC/PKCS5Padding 生成 IV

1
2
3
4
5
SecureRandom secureRandom = new SecureRandom();
kgen.init(256, secureRandom);
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
System.out.println(Base64.encodeBase64String(iv));

AES/CBC/PKCS5Padding 加解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class RijndaelCrypt {

public static final String TAG = "RijndaelCrypt";

private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String ALGORITHM = "AES";

private SecretKey _password;
private IvParameterSpec _IVParamSpec;


private Cipher cipher;

private static Logger log = LoggerFactory.getLogger(RijndaelCrypt.class);

/**
* Constructor
*/
public RijndaelCrypt(byte[] key, byte[] iv) {

try {
_password = new SecretKeySpec(key, ALGORITHM);

//Initialize objects
cipher = Cipher.getInstance(TRANSFORMATION);

_IVParamSpec = new IvParameterSpec(iv);

} catch (NoSuchAlgorithmException e) {
log.error(TAG, "No such algorithm " + ALGORITHM, e);
} catch (NoSuchPaddingException e) {
log.error(TAG, "No such padding PKCS7", e);
}

}


public String decrypt(byte[] cipherText) {
try {
cipher.init(Cipher.DECRYPT_MODE, _password, _IVParamSpec);
byte[] decryptedVal = cipher.doFinal(cipherText);
return new String(decryptedVal);

} catch (IllegalBlockSizeException e) {

} catch (BadPaddingException e) {

} catch (InvalidAlgorithmParameterException e) {

} catch (InvalidKeyException e) {

}
return null;
}


/**
* Encryptor.
*
* @return Base64 encrypted text
* @text String to be encrypted
*/
public String encrypt(byte[] text) {

try {
cipher.init(Cipher.ENCRYPT_MODE, _password, _IVParamSpec);
byte[] encryptedData = cipher.doFinal(text);
return Base64.encodeBase64String(encryptedData);


} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;

}

public String encrypt(String text) {
byte[] data = StringUtils.getBytesUtf8(text);
return encrypt(data);
}


}

K8s 水平扩展

Deployment 关系

Deployment 通过“控制器模式”,来操作 ReplicaSet 的个数和属性,进 而实现“水平扩展 / 收缩”和“滚动更新”这两个编排动作。

增加节点

kubectl scale deployment nginx-deployment --replicas=4

查看扩展状态

kubectl rollout status deployment/nginx-deployment

修改配置

kubectl edit deployment/nginx-deployment

回滚

kubectl rollout undo deployment/nginx-deployment

变更历史

kubectl apply -f nginx-deployment2.yaml --record kubectl rollout history deployment/nginx-deployment

查看版本变更

kubectl rollout history deployment/nginx-deployment --revision=2

回滚指定版本

kubectl rollout undo deployment/nginx-deployment --to-revision=2

暂停滚动更新

kubectl rollout pause deployment/nginx-deployment

恢复滚动更新

kubectl rollout resume deployment/nginx-deployment

Spring Amqp Ack

Ack 模式说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* No acks - {@code autoAck=true} in {@code Channel.basicConsume()}.
*/
NONE,

/**
* Manual acks - user must ack/nack via a channel aware listener.
*/
MANUAL,

/**
* Auto - the container will issue the ack/nack based on whether
* the listener returns normally, or throws an exception.
* <p><em>Do not confuse with RabbitMQ {@code autoAck} which is
* represented by {@link #NONE} here</em>.
*/
AUTO;

代码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
org.springframework.amqp.rabbit.listener.BlockingQueueConsumer#commitIfNecessary

public boolean commitIfNecessary(boolean localTx) throws IOException {

if (this.deliveryTags.isEmpty()) {
return false;
}

/*
* If we have a TX Manager, but no TX, act like we are locally transacted.
*/
boolean isLocallyTransacted = localTx
|| (this.transactional
&& TransactionSynchronizationManager.getResource(this.connectionFactory) == null);
try {

boolean ackRequired = !this.acknowledgeMode.isAutoAck() && !this.acknowledgeMode.isManual();

if (ackRequired && (!this.transactional || isLocallyTransacted)) {
long deliveryTag = new ArrayList<Long>(this.deliveryTags).get(this.deliveryTags.size() - 1);
this.channel.basicAck(deliveryTag, true);
}

if (isLocallyTransacted) {
// For manual acks we still need to commit
RabbitUtils.commitIfNecessary(this.channel);
}

}
finally {
this.deliveryTags.clear();
}

return true;

}