【博客550】k8s乐观锁机制:控制并发请求与数据一致性

news2024/9/22 23:20:27

k8s乐观锁机制:控制并发请求与数据一致性

1、乐观锁与悲观锁

悲观锁

悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。

在悲观锁的场景下,假设用户A和B要修改同一个文件,A在锁定文件并且修改的过程中,B是无法修改这个文件的,只有等到A修改完成,并且释放锁以后,B才可以获取锁,然后修改文件。由此可以看出,悲观锁对并发的控制持悲观态度,它在进行任何修改前,首先会为其加锁,确保整个修改过程中不会出现冲突,从而有效的保证数据一致性。但这样的机制同时降低了系统的并发性,尤其是两个同时修改的对象本身不存在冲突的情况。同时也可能在竞争锁的时候出现死锁,所以现在很多的系统例如Kubernetes采用了乐观并发的控制方法。

乐观锁

乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此影响,各事务能够在不请求锁的情况下处理各自的数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

相对于悲观锁对锁的提前控制,乐观锁相信请求之间出现冲突的概率是比较小的,在读取及更改的过程中都是不加锁的,只有在最后提交更新时才会检测冲突,因此在高并发量的系统中占有绝对优势。同样假设用户A和B要修改同一个文件,A和B会先将文件获取到本地,然后进行修改。如果A已经修改好并且将数据提交,此时B再提交,服务器端会告知B文件已经被修改,返回冲突错误。此时冲突必须由B来解决,可以将文件重新获取回来,再一次修改后提交。
乐观锁通常通过增加一个资源版本字段,来判断请求是否冲突。初始化时指定一个版本值,每次读取数据时将版本号一同读出,每次更新数据,同时也对版本号进行更新。当服务器端收到数据时,将数据中的版本号与服务器端的做对比,如果不一致,则说明数据已经被修改,返回冲突错误。

2、k8s采用乐观锁机制

在Kubernetes集群中,外部用户及内部组件频繁的数据更新操作,导致系统的数据并发读写量非常大。假设采用悲观并行的控制方法,将严重损耗集群性能,因此Kubernetes采用乐观并行的控制方法。Kubernetes通过定义资源版本字段实现了乐观并发控制,资源版本(ResourceVersion)字段包含在Kubernetes对象的元数据(Metadata)中。这个字符串格式的字段标识了对象的内部版本号,其取值来自etcd的modifiedindex,且当对象被修改时,该字段将随之被修改。值得注意的是该字段由服务端维护,不建议在客户端进行修改。

3、k8s如何采用乐观锁机制控制并发请求与数据一致性

Kube-Apiserver可以通过该字段判断对象是否已经被修改。当包含ResourceVersion的更新请求到达Apiserver,服务器端将对比请求数据与服务器中数据的资源版本号,如果不一致,则表明在本次更新提交时,服务端对象已被修改,此时Apiserver将返回冲突错误(409),客户端需重新获取服务端数据,重新修改后再次提交到服务器端。

上述并行控制方法可防止如下的data race:

Client1: GET Foo
Client2: GET Foo

Client1: Set Foo.Bar = "one"
Client1: PUT Foo 

Client2</span>: Set Foo.Baz = "two"
Client2: PUT Foo

当未采用并发控制时,假设发生如上请求序列,两个客户端同时从服务端获取同一对象Foo(含有Bar、Baz两个字段),Client#1先将Bar字段置成one,其后Client#2对Baz字段赋值的更新请求到服务端时,将覆盖Client#1对Bar的修改。反之在对象中添加资源版本字段,同样的请求序列将如下:

Client1: GET Foo  //初始Foo.ResourceVersion=1
Client2: GET Foo  //初始Foo.ResourceVersion=1

Client 1: Set Foo.Bar ="one"
Client1: PUT Foo  //更新Foo.ResourceVersion=2</span>

Client2: Set Foo.Baz = "two"
Client2: PUT Foo  //返回409冲突</span>

Client1更新对象后资源版本号将改变,Client#2在更新提交时将返回冲突错误(409),此时Client#2必须在本地重新获取数据,更新后再提交到服务端。

假设更新请求的对象中未设置ResourceVersion值,Kubernetes将会根据硬改写策略(可配置)决定是否进行硬更新。如果配置为可硬改写,则数据将直接更新并存入Etcd,反之则返回错误,提示用户必须指定ResourceVersion。

4、k8s的Update和Patch

Update:

对于Update,客户端更新请求中包含的是整个obj对象,服务器端将对比该请求中的obj对象和服务器端最新obj对象的ResourceVersion值。如果相等,则表明未发生冲突,将成功更新整个对象。反之若不相等则返回409冲突错误

基本流程:

1.获取当前更新请求中obj对象的ResourceVersion值,及服务器端最新obj对象(existing)的ResourceVersion值

2.如果当前更新请求中obj对象的ResourceVersion值等于0,即客户端未设置该值,则判断是否要硬改写(AllowUnconditionalUpdate),如配置为硬改写策略,将直接更新obj对象。

3.如果当前更新请求中obj对象的ResourceVersion值不等于0,则判断两个ResourceVersion值是否一致,不一致返回冲突错误(OptimisticLockErrorMsg)。

Patch

相比Update请求包含整个obj对象,Patch请求实现了更细粒度的对象更新操作,其请求中只包含需要更新的字段。例如要更新pod中container的镜像,可使用如下命令:
kubectl patch pod my-pod -p ‘{“spec”:{“containers”:[{“name”:“my-container”,“image”:“new-image”}]}}’

基本流程:

1.首先判断patch类型,根据类型选择相应的mechanism

2.将patch应用到最新获取的服务器端obj上,生成一个已更新的obj,再对该obj继续执行admission chain中的Admit与Validate。最终调用的还是update方法,因此冲突检测的机制与上述Update方法完全一致。

3.调用Update方法执行更新操作。

5、k8s的对象版本控制ResourceVersion和Generation

etcd version机制

ETCD共四种version:
在这里插入图片描述

ResourceVersion:

基于底层etcd的revision机制,资源对象每次update时都会改变,且集群范围内唯一

resourceversion存在哪里:

更新对象时,Kubernetes会比较该resourceVersion和ETCD中对象的resourceVersion,在一致的情况下都会更新,一旦发生更新,该对象的resourceVersion值也会改变。所以,resourceVersion相当于一把锁。
当然,Kubernetes在resourceVersion值的生成上,并没有实现自己的一套管理机制,而是直接使用了ETCD的index。
在ETCD中,会维护一个全局的index,每发生一个操作,该index会加1。每个key都会维护一个modified index,表明该节点最近的一次更改index。所以Kubernetes就是借用了modified index。
那和,既然从ETCD的节点中能获取到resourceVersion(即modified index),那就没必要把resourceVersion存储到ETCD中了。所以存储在ETCD中的对象并没有resourceVersion字段,而是在获取时动态添加resourceVersion字段。

resourceVersion的维护其实是利用了底层存储etcd的Revision机制:

// Get implements storage.Interface.Get.
func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, out runtime.Object) error {
	key = path.Join(s.pathPrefix, key)
	startTime := time.Now()
	getResp, err := s.client.KV.Get(ctx, key)
	metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
	if err != nil {
		return err
	}
	if err = s.validateMinimumResourceVersion(opts.ResourceVersion, uint64(getResp.Header.Revision)); err != nil {
		return err
	}

	if len(getResp.Kvs) == 0 {
		if opts.IgnoreNotFound {
			return runtime.SetZeroValue(out)
		}
		return storage.NewKeyNotFoundError(key, 0)
	}
	kv := getResp.Kvs[0]

	data, _, err := s.transformer.TransformFromStorage(kv.Value, authenticatedDataString(key))
	if err != nil {
		return storage.NewInternalError(err.Error())
	}

	return decode(s.codec, s.versioner, data, out, kv.ModRevision)
}

kv.ModRevision看到从ETCD读取了key的ModRevision,继续看下decode函数

// decode decodes value of bytes into object. It will also set the object resource version to rev.
// On success, objPtr would be set to the object.
func decode(codec runtime.Codec, versioner storage.Versioner, value []byte, objPtr runtime.Object, rev int64) error {
	if _, err := conversion.EnforcePtr(objPtr); err != nil {
		return fmt.Errorf("unable to convert output object to pointer: %v", err)
	}
	_, _, err := codec.Decode(value, nil, objPtr)
	if err != nil {
		return err
	}
	// being unable to set the version does not prevent the object from being extracted
	if err := versioner.UpdateObject(objPtr, uint64(rev)); err != nil {
		klog.Errorf("failed to update object version: %v", err)
	}
	return nil
}

到这里已经可出Kubernetes的resourceVersion是利用了底层ETCD kv版本机制。

根据更新资源时是否带有resourceVersion分两种情况:

  • 未带resourceVersion:无条件更新,获得etcd中最新的数据然后再此基础上更新
  • 带有resourceVersion:和etcd中modRevision对比,不一样就提示版本冲突,说明数据已发生修改,当前要修改的版本已不是最新数据。

Generation:

初始值为1,随Spec内容的改变而自增

func (deploymentStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
	deployment := obj.(*apps.Deployment)
	deployment.Status = apps.DeploymentStatus{}
	deployment.Generation = 1

	pod.DropDisabledTemplateFields(&deployment.Spec.Template, nil)
}

func (deploymentStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
	newDeployment := obj.(*apps.Deployment)
	oldDeployment := old.(*apps.Deployment)
	newDeployment.Status = oldDeployment.Status

	pod.DropDisabledTemplateFields(&newDeployment.Spec.Template, &oldDeployment.Spec.Template)

	// Spec updates bump the generation so that we can distinguish between
	// scaling events and template changes, annotation updates bump the generation
	// because annotations are copied from deployments to their replica sets.
	if !apiequality.Semantic.DeepEqual(newDeployment.Spec, oldDeployment.Spec) ||
		!apiequality.Semantic.DeepEqual(newDeployment.Annotations, oldDeployment.Annotations) {
		newDeployment.Generation = oldDeployment.Generation + 1
	}
}

6、ResourceVersion在list-watch机制中的使用

ResourceVersion字段在Kubernetes中除了用在上述并发控制机制外,还用在Kubernetes的list-watch机制中。Client端的list-watch分为两个步骤,先list取回所有对象,再以增量的方式watch后续对象。Client端在list取回所有对象后,将会把最新对象的ResourceVersion作为下一步watch操作的起点参数,也即Kube-Apiserver以收到的ResourceVersion为起始点返回后续数据,保证了list-watch中数据的连续性与完整性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/57622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Compose 动画艺术探索之动画规格

本篇文章是此专栏的第四篇文章&#xff0c;如果想阅读前三篇文章的话请点击下方链接&#xff1a; Compose 动画艺术探索之瞅下 Compose 的动画Compose 动画艺术探索之可见性动画Compose 动画艺术探索之属性动画 动画规格在上一篇文章中提到过&#xff0c;不过上一篇文章中说的…

AIGC , 超级热点 or 程序员创富新起点?

作者 | 闫辉 责编 | 朱珂欣出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;对于程序员而言&#xff0c;常常能在新赛道上创造出无限的奇迹。随着今年 8 月 Stable Diffusion&#xff08;SD&#xff09;的正式开源&#xff0c;AI-Generated Content&#…

46. 全排列

一次一粒沙&#xff0c;一次一件事。 ——《人性的优点》 46. 全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],…

SpringBoot 接口加密解密,新姿势!

1. 介绍 在我们日常的Java开发中&#xff0c;免不了和其他系统的业务交互&#xff0c;或者微服务之间的接口调用 如果我们想保证数据传输的安全&#xff0c;对接口出参加密&#xff0c;入参解密。 但是不想写重复代码&#xff0c;我们可以提供一个通用starter&#xff0c;提…

Spring Boot 还在用 if 校验参数?

本文会详细介绍Spring Validation各种场景下的最佳实践及其实现原理&#xff0c;死磕到底&#xff01; 简单使用 Java API规范(JSR303)定义了Bean校验的标准validation-api&#xff0c;但没有提供实现。hibernate validation是对这个规范的实现&#xff0c;并增加了校验注解如…

Linux内核--链表结构

一、前言 Linux内核链表结构是一种双向循环链表结构&#xff0c;与传统的链表结构不同&#xff0c;Linux内核链表结构仅包含前驱和后继指针&#xff0c;不包含数据域。使用链表结构&#xff0c;仅需在结构体成员中包含list_head*成员就行&#xff1b;链表结构的定义在linux…

ABAP学习笔记之——第八章:报表程序

一、程序属性 创建程序类型&#xff1a; 状态&#xff1a; 根据程序状态不能使用特定 Utility。例如&#xff0c;选择系统程序&#xff0c;则不能使用 debug 功能 权限组&#xff1a; 分配程序执行/修改相关的权限组。若是安全相关程序有必要设置权限组。 逻辑数据库&…

C/C++中的内存管理

目录 C/C内存分布 C语言中动态内存管理方式 malloc/calloc/realloc和free C内存管理方式 new/delete操作内置类型 new/delete操作自定义类型 operator new 与 operator delete new/delete实现原理 内置类型 自定义类型 定位new表达式&#xff08;placement-new&…

[附源码]Python计算机毕业设计Django路政管理信息系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

MySQL 从入门到实战讲解,京东 T5 大咖学习笔记分享,看完我哭了

数据库是一个综合系统&#xff0c;其背后是发展了几十年的数据库理论。也许你会觉得数据库并不难&#xff0c;因为你可以熟练地写出 SQL&#xff0c;也可以在各个客户端里玩得游刃有余。但就以最常见的 MySQL 为例&#xff0c;作为程员&#xff0c;你在使用 MySQL 的过程中&…

「Redis」04 发布和订阅

笔记整理自【尚硅谷】Redis 6 入门到精通 超详细 教程 Redis——发布和订阅 1. 什么是发布和订阅 Redis 发布订阅&#xff08; pub/sub &#xff09;是一种消息通信模式&#xff1a;发送者&#xff08; pub &#xff09;发送消息&#xff0c;订阅者&#xff08; sub &#xf…

[附源码]Python计算机毕业设计Django环境保护宣传网站

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

matlab使用移动平均滤波器、重采样和Hampel过滤器进行信号平滑处理

此示例显示如何使用移动平均滤波器和重采样来隔离每小时温度读数的时间周期分量的影响&#xff0c;以及从开环电压测量中消除不需要的线路噪声。 最近我们被客户要求撰写关于信号平滑处理的研究报告&#xff0c;包括一些图形和统计输出。 该示例还显示了如何使用Hampel过滤器…

新时期我国信息技术产业的发展【技术论文,纪念长者,2008】

2008年10月&#xff0c;江泽民在《上海交通大学学报》发表了一篇题为《新时期我国信息技术产业的发展》的论文。作为上海交通大学1947届电机工程系的毕业生&#xff0c;发表这篇论文时&#xff0c;这位曾改变中国的长者已是82岁高龄。在这篇论文中&#xff0c;江泽民提出了“未…

URLDNS链

听说这个链子是最简单的链子之一了&#xff0c;但是却是来来回回看了好多遍才勉强看明白。 在 ysoserial 中我们可以看见链子是这样的&#xff1a; *Gadget Chain: * HashMap.readObject() * HashMap.putVal() * HashMap.hash() * URL.hashCode() 简单流程&#xff1a; 1.Hash…

HTML这一篇就够啦~

HTML这一篇就够啦HTML1、基础认知2、排版标签2.1 标题标签2.2 段落标签2.3 换行标签2.4 水平线标签3、文本格式化标签4、媒体标签4.1 图片标签4.2 路径4.3 音频文件4.4 视频文件5、链接标签6、列表标签、6.1 无序列表&#xff08;最常用&#xff09;6.2 有序列表&#xff08;偶…

2021.06青少年软件编程(Python)等级考试试卷(三级)

2021.06青少年软件编程(Python)等级考试试卷(三级) 一、单选题(共25题,每题2分,共50分) 1.关于open()函数的参数,下列描述正确的是?( D ) A. "w+" 以十六进制格式打开一个文件只用于写入 B. "r+"打开一个文件用于读写。文件指针将会放在文件…

ZMQ之自杀的蜗牛模式和黑箱模式

一、检测慢订阅者&#xff08;自杀的蜗牛模式&#xff09; 在使用发布-订阅模式的时候&#xff0c;最常见的问题之一是如何处理响应较慢的订阅者。理想状况下&#xff0c;发布者能以全速发送消息给订阅者&#xff0c;但现实中&#xff0c;订阅者会需要对消息做较长时间的…

springboot如何增加 application.yml配置文件

新建springboot 项目&#xff0c;默认项目的配置文件为application.properties。 需要将application.properties 修改为application.yml配置文件。 注意&#xff1a; 我发现直接将application.properties文件重命名为application.yml。 新的application.yml没有配置功能的属…

Compose 动画艺术探索之属性动画

本篇文章是此专栏的第三篇文章&#xff0c;如果想阅读前两篇文章的话请点击下方链接&#xff1a; Compose 动画艺术探索之瞅下 Compose 的动画Compose 动画艺术探索之可见性动画 Compose的属性动画 属性动画是通过不断地修改值来实现的&#xff0c;而初始值和结束值之间的过…