深入剖析数据多版本 MVCC 机制

a154db9cf700d44e553f2e8455c4d2de.gif

作者 | 奇伢

来源 | 奇伢云存储

d034cf4032914b80df1ace3190de64ec.png

6f7647cd7f72a3926510c957ea10e930.png

你以为删掉就没事了?

0d3a6ba3eede64d6d8aa2b8735c22495.png

有些童鞋以前还真是做过些傻事,以为删掉一些东西,覆盖一些东西就能掩盖某一些"不可告人"的秘密。

来看看 etcd 的例子:

./etcdctl put 张三 是个憨憨

哎呀,这可不大对,怎么能说这么羞耻的话呢!黑历史赶紧删掉。

./etcdctl del 张三

再来写过一个正常的:

./etcdctl put 张三 是个大聪明

这就对了嘛,数据我已经删了,也更新了新的数据。这个黑历史已经永远被埋藏了!

万万没想到,直到某一天还是有人把这些黑历史全部捞了出来:

./etcdctl watch 张三 --rev=1PUT
张三
是个憨憨DELETE
张三PUT
张三
是个大聪明

愣主,半天,我的老天啊。。。。。。这是啥呀?太羞耻了吧。

旁白:这就是 MVCC(数据多版本)机制。

481c1123630e973aa54b19b438307fad.png

存储系统核心功能

8a687ec33404c23e28b77c8fe189bafb.png

存储系统核心功能其实非常简单,聚焦三个操作:读写删。用户把数据写进来好好保存,读的时候安全返回,删的时候释放空间,以便重复利用。

etcd 虽然有很多丰富的功能,也有它特殊的定位,但本质上也是一个存储系统。不过 etcd 有两个特色的功能:watch 机制和 MVCC 机制( 数据多版本 )。

提前思考两个小问题:

  1. 在用户删除 key 的时候却没有释放空间,为什么呢?此时不释放,还待何时?

  2. watch 机制监听一个 key 的所有变化,怎么才能做到可靠?

MVCC 是啥东西?这就先聊聊数据多版本的了。

3014971288c79a0b379f5c7d023dee3a.png

什么是数据多版本?

48a7e475c2a0b91e1b8a0f9ba66bd551.png

大白话就是同一份数据有多个版本。更准确的来讲是同一个 key 的多个版本的数据。举个例子,你反复的三次写入一个 key的 value 值:

  1. 第一次写入 world-1 ;

  2. 第二次写入 world-2 ;

  3. 第三次写入 world-3 ;

常规存储来讲是最新的值不断覆盖以前的。但在数据多版本的系统中,每一次写入都写了新的位置,旧的不会动,每一次写入都对应一个版本。所以这三次的数据,用户其实都能读到。

key => v1=> v2=> v3

数据多版本本质上就是把同一个 key 的历史变化都留下来了。数据多版本这个其实就是 watch 机制可靠的根源,因为来时的路都在呀。

8440ebf2a5e33242cf315e96e282d606.png

什么是 MVCC ?

df8a9f3c15bce627a240c4c1c8304729.png

讲了数据多版本,再来看看 MVCC 是个啥?

MVCC 是 Multiversion Concurrency Control 的英文缩写,也叫多版本并发控制。如果仔细观察过各种数据库的话,你会发现 MVCC 的概念经常在其中出没。数据库领域,事务的并发控制是非常核心的一个话题。

那一般大家怎么做事务的并发控制呢?

最简单的思路:加锁呗。遇事不决就上大锁。但通常这一把大锁就基本上让你跟高性能无缘了。

举个例子,锁机制解决覆盖写的场景:

  • 程序猿 A:我要写 0 这个位置( 我没搞好之前,不要放人进来哦 );

  • 程序猿 B:我要写 0 这个位置;

    • 旁白:不好意思,有人( A )正在更新,你先等等;

  • 程序猿 C:我要读 0 这个位置;

    • 旁白:不好意思,有人( A )正在更新,还没搞完呢,再等等;

还有更好的策略吗?

有的,MVCC 机制也可以解决并发问题。MVCC 解决并发问题原理其实很容易理解,没有覆盖写了嘛,每次更新都对应一个数据版本。虽然是并发,但是各写各的,那就没啥需要控制的,并发问题自然就没了。并发出现问题的根源就在于大家同一时间搞一个东西嘛

每次写入都是新的版本,不会更新旧的位置。新的数据写入跟我旧的数据又有啥关系呢。

93d5da9894ba5ae259d786476e935622.png

"大家各搞各的,互不干扰,天下太平。"

所以,小结一下常见的并发控制有两大类:

  1. 第一种就是锁机制:锁呢,一般还分为悲观锁,或者乐观锁 ;

  2. 第二种就是 MVCC 机制:核心就是不覆盖更新,每次的写都在新的位置,数据存在多个版本;

下面笔者就不再单独区分数据多版本还有 MVCC 机制了,下面就用 MVCC 指代多版本的机制。

00bc15432c96101ee3791a2392ba2202.png

etcd 的 MVCC 带来了什么?

dca7f0953a8df5b0508ee36fe7f2c185.png

  1. 可靠的 watch 机制;

  2. 简单的事务并发控制;

这两点其实非常容易理解,一个 key 的历史版本全部被记录,而不是覆盖更新,那这个对于 watch 就太友好了。毕竟历史都在这嘛,数据又安全读出来又方便。

MVCC 每次写都产生一个版本的数据,并发读写数据各搞各的,并发问题自然就好解决了。

 1   聊聊 revision 是个啥?

这里既然说到多版本,那总有个版本号吧。etcd 里的版本号叫做 revision 。revision 有两个字段:

type revision struct {main int64      // 主版本号sub int64       // 次版本号
}

每个不同的写事务都有唯一一个编号,这个编号就是主版本号,它是单调递增的。次版本号呢,其实是为了区分同一个事务里的多次写操作,因为写事务串行化为了提高吞吐,所以尽量是批量操作,把大批量的写操作聚合到一个事务中,这样次版本号就有用途啦。通过 <主版本号,次版本号> 组成的 revision 就能唯一确定 etcd 中的一次更新操作哦。

这里提前提一下:用户通过 etcdctl 能摸到的一个参数就是 rev ,这个 rev 其实是主版本号哦

faf756762b1d8411384656a84c27bf86.png

直观体验一下

f150e6fd1b7f93bbda79758c13e153d5.png

来 put 一个数据:

./etcdctl put hello world-v1 -w=json{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":2,"raft_term":2}}

我们看到这次的版本号 revision 是 2 。再 put 一个数据

./etcdctl put hello world-v2 -w=json{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":3,"raft_term":2}}

看到这次的版本号 revision 是 3 。

好了,我们来 get 一下这两个数据:

# 默认 get 是最新的 revision: 3
./etcdctl get hello      
hello
world-v2# 指定获取 revision:3 的数据 
./etcdctl get hello --rev=3
hello
world-v2# 指定获取 revision:2 的数据
./etcdctl get hello --rev=2
hello
world-v1

来把它删了,再看看。

./etcdctl del hello1

再来读读数据看看?

# 默认 get 是最新的 revision: 4
./etcdctl get hello      
# 空# 看下最新的版本号
./etcdctl get hello  -w=json        
{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":4,"raft_term":2}}# 再来指定获取 revision:3 的数据 
./etcdctl get hello --rev=3
hello
world-v2# 再来指定获取 revision:2 的数据
./etcdctl get hello --rev=2
hello
world-v1

神奇不神奇,你删了的 key ,还能获取到历史版本的数据呢,神奇!

 1   什么时候真正释放呢?

上面提到,数据是多版本的,哪怕删除其实也是一个特殊数据的插入。但现实是:空间不是无限的,所以无论怎么花里胡哨的系统,删除的数据空间 100% 是一定要释放出来的。

现在的问题是:什么时候释放?

在数据多版本的设计中,空间的由一个叫做 compact 的操作来实现。MVCC 的设计中,可以认为用户的删除都是标记删除( 让你有足够的信息识别到删除 ),compact 回收则完全由内部或者外部触发。

常规的删除就现场释放了空间,而 MVCC 显式的把空间回收的动作拆成了两个步骤:

  1. 用户删除:标记删除;

  2. 系统回收:compact 空间回收;

2768ed3a669f0b3a6dd4e95b56f199a9.png

原理剖析

669a2f82b12b63aaec9400c64bc7ecf2.png

好,讲了那么多,这里来浅析一下 etcd 的原理,来看看 etcd 的具体实现。

 1   用户写了个 key ,变了个身

很早我们就知道,etcd 底层用的是 boltdb 做持久化的存储引擎,boltdb 是一个纯粹的 kv 存储。那现在问题来了,etcd 是怎么来存储用户的 key/value 呢?是否是直接把用户的 key 做 key,用户的 value 做 value,存入 boltdb 呢?

不用问,肯定不是的。etcd 内部生成了一个唯一 id 用作 boltdb 的 key,用户的 key/value 打包在一起作为 boltdb 的 value 存入 boltdb 的。

而这个所谓的 “内部唯一 id” 其实就是 revision 结构,一个主版本号(单调递增),一个次版本号。所以实际写入 boltdb 的 key/value 格式如下:

key:revision
value:< 用户 key, 用户 value >

举个例子,上面举例的用户存储两对键值( hello ,world-v1 ),( hello ,world-v2 )这样的 key/value 。上面两次的 put 操作,其实 boltdb 内部是存储成(注意,版本号我随意写的):

<boltdb>
key: revision={11,} value: <"hello", "world-v1">
key: revision={12,} value: <"hello", "world-v2">

划重点:boltdb 存储的 key 是内部的 revision 结构,value 则是一整个打包 <用户 key,用户 value> 。 整个映射关系搞成一维平坦的样子。

c068824c48aadb998fb3bf3347350097.png

 2   可这,叫我怎么索引呢?

既然存储到 boltdb 的 kv 已经和用户理解的 kv 大变样了。但用户对存储系统内部的原理是不关心的,他们只关系自己的 key/value ,所以必须有个手段能通过用户的 key 找到 boltdb 的 key ,从而捞出用户的 value 。怎么实现呢?

这其实是一个查找需求,查找需求的话,最简单的就是链表,但显然不够高级(查找复杂度太高,随着节点个数的增多,时间线型递增),所以 etcd 用的是 B 树,一个来自开源的库 github.com/google/btree 。

这颗 B 树存储管理的就是用户 key 到 revision 的索引。etcd 把所有的 boltdb 的 key( revision )都解析过一遍,在内存中构建了一颗完整的 B 树,B 树的查找复杂度 O(logN),并且是纯内存操作,查找过程不涉及到磁盘操作,所以速度稳定而高效。

找到 revision 之后,就可以去 boltdb 中捞数据了,捞到数据返回给用户即可。

划重点:这是一颗纯内存的 B 树,里面有所有的用户 key ,还有对应的 revision 记录。

67c4ff325df56472fe8fedd3b53b78b2.png

有两个问题大家要想一下:

  1. 这颗 B 树的 key 是啥?

  2. 这颗 B 树的 value 又是个啥?

第一个问题很简单,这棵树是用来查找用户 key 到 revision 的映射的。那么自然 B 树的 key 就是用户的 key 。

第二个问题稍微要想下,我们大体知道是 revision,但这个 revision 也是组织了一下的。etcd 用了一个叫做 keyIndex 的结构体来装这个用户所有的 revision 。

来看一下 keyIndex 这个结构体,这个结构体还挺有意思的,记录着 key 的“生死轮回”:

type keyIndex struct {key         []byte       // 用户 keymodified    revision     // 最新的版本号;generations []generation // 一个key有可能是有多次创建,删除的,每一次的轮回都是一个 generation
}

每个 generation 代表 key 一次轮回,这里是个数组,那就是说可以记录多次轮回呗。

举个用户增删的例子:

  1. 写入用户 kv 键值对,key 是 hello,value 是 world-v1 ;

  • 产生的 revision {11.0}

写入用户 kv 键值对,key 是 hello,value 是 world-v2 ;

  • 产生的 revision {12.0}

写入用户 kv 键值对,key 是 hello,value 是 world-v3 ;

  • 产生的 revision {13.0}

删除用户 kv ,key 是 hello ;

  • 产生的 revision {14.0}

写入用户 kv 键值对,key 是 hello,value 是 world-v4 ;

  • 产生的 revision {15.0}

写入用户 kv 键值对,key 是 hello,value 是 world-v5 ;

  • 产生的 revision {16.0}

删除用户 kv ,key 是 hello ;

  • 产生的 revision {17.0}

写入用户 kv 键值对,key 是 hello,value 是 world-v6 ;

  • 产生的 revision {18.0}

上面对 key:hello 进行了反复的摩擦,不断的写入删除,经过来上面的操作,在内存中形成的 keyIndex 如下:

世代轮回: 
第一代:    { 11.0 , 12.0 , 13.0 , 14.0(t) }
第二代:    { 15.0 , 16.0 , 17.0(t) }
最新代:    { 18.0 }


 3   用户删除了个 key,做了啥?

既然在多版本的系统中写入的数据上做了一层转化,每一次的更新操作都对应了一条数据并且记录了下来。很多人其实都忘了所谓的“删除”其实本质也是更新。划重点:删除也是写入,一条特殊的数据。 用户删除一个 key 的时候,etcd 其实在 boltdb 里面又写入了一条数据,这个数据格式是这样的:

key:revision
value:< 用户 key >

想必眼尖的小伙伴应该看出来了,这个 value 它只有用户的 key ,没有用户 value ,而这条数据则恰恰是用来用户 key 被删除的标记。举个例子,还是以上面 < hello, world > 为例,用户删除 key: hello 的话,在 boltdb 里面的数据如下:

<boltdb>
key: revision={11,} value: <"hello", "world-v1">
key: revision={12,} value: <"hello", "world-v2">
key: revision={13,} value: <"hello">

当然了,上面说的是持久化的数据,内存里面肯定也是要改的,比如用来索引的那颗 B 树。

4b00d6bf622328e93eda1843bc2af2ca.png


 4   哎,空间总归还是要释放的

现在思考另一个核心的问题:空间总归是要释放的,虽迟但到。只增不减的话,空间一定是会满的。用户既然要删除,那还是说明了这之前的数据用户不要了的。为了防止空间不够用,必须定期释放一些用户已经声明删除的数据。这个动作就叫做 compact !

compact 需要一个版本号。这个版本号就是写事务递增的那个版本号。划重点:这个用的是主版本号。

比如 compact 7 ,就是说把版本 7 以前的标记删除了的数据释放掉。

注意到一个细节,这里其实并不能非常精细的控制每一个 key 的回收。因为它用的是一个全局的版本号。那比这个版本号小的 key/value 都会被回收吗?

当然不是。用户没删除的数据肯定不能回收,即使版本号比 compact 传入的小。

举个例子:

// k1 有 3 个版本的数据
{ revision: 1, key: k1,value:v1 }
{ revision: 4, key: k1,value:v2 }
{ revision: 7, key: k1,value:v3 }// k2 有两个版本的数据
{ revision: 10, key: k2,value:v1 }
{ revision: 12, key: k2,value:v2 }

现在做 compact 9 :

// k1 留下一个在用版本的数据
{ revision: 7, key: k1,value:v3 }// k2 有两个版本的数据
{ revision: 10, key: k2,value:v1 }
{ revision: 12, key: k2,value:v2 }

你看,这不就释放了 revision 1,revision 4 这两条数据嘛。

705d73ec972a5ea53969821d407552d5.png

总结

ffd502eaca398a9917e1ca7394c9f363.png

  1. 数据多版本,不覆盖更新,并发的时候各搞各的,MVCC 嘛。在具有 MVCC 的系统中要小心哦,所有的黑历史都记着呢

  2. 版本用 revision 表示,其内有两个:主版本号和次版本号,主版本在每次写事务单调递增,次版本号在事务内多次更新时候递增;

  3. 在 MVCC 的设计中,删除被显式搞成两个步骤,用户的删其实是写入一个删除标记,真正的空间释放是异步的,;

  4. compact 使用的参数是主版本号,释放这个版本以前所有的被标记删除的数据;

  5. etcd 用的 B 树是全内存的,解析了所有的用户 key ,用来管理用户 key 到 revision 的映射关系;

  6. boltdb 是一个纯粹的 kv 存储引擎,内部无覆盖写,B+ 树索引 kv 的映射,适合读多写少的场景(写事务串行,读事务并发);

  7. etcd 在 boltdb 的基础上又给每一个 key 实现了多版本数据,能够很方便的实现事务并发的控制;

  8. etcd 的 MVCC 其实并没有给性能带来多大提升,很容易理解,因为底层用的是 boltdb ,天然就是读并发、写串行的引擎,上层无论怎么做,都逃不掉这一点,但在 etcd 中 MVCC 机制对于 watch 机制却很关键

63854eeb25ee50fb5399f5a92977f4ad.png

后记

b2adb3dc803b4ccff8cfd32143141e3e.png

etcd 里的 MVCC 是占比非常大的模块,MVCC 也是很多数据库系统的核心,理解它非常有用。

1410d455762468622488cbf80b47f5fc.gif

e6e5fba6c9659042d38356da2ac7717d.png

往期推荐

云计算到底是谁发明的?

从Docker的信号机制看容器的优雅停止

低代码发展专访系列之三:低代码平台会成为企业数字化基础设施么?

内容整理志愿者招募了!

02561a83dc58c066d8b56fd5b45b7ccd.gif

点分享

4058066f5881135897e2cbfbd3a88068.gif

点收藏

af88c7b79a96880a098c393e0fea728d.gif

点点赞

e808f20d4840a73fb68319e0b2ee7ca4.gif

点在看

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

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

相关文章

同程旅行基于 RocketMQ 高可用架构实践

简介&#xff1a; 我们在几年前决定引入 MQ 时&#xff0c;市场上已经有不少成熟的解决方案&#xff0c;比如 RabbitMQ , ActiveMQ&#xff0c;NSQ&#xff0c;Kafka 等。考虑到稳定性、维护成本、公司技术栈等因素&#xff0c;我们选择了 RocketMQ。 背景介绍 ​ 为何选择 R…

重磅 | 数据库自治服务DAS论文入选全球顶会SIGMOD,领航“数据库自动驾驶”新时代

简介&#xff1a; 近日&#xff0c;智能数据库和DAS团队研发的智能调参ResTune系统论文被SIGMOD 2021录用&#xff0c;SIGMOD是数据库三大顶会之首&#xff0c;是三大顶会中唯一一个Double Blind Review的&#xff0c;其权威性毋庸置疑。 近日&#xff0c;智能数据库和DAS团队…

如何帮用户管好云账本?阿里云数据库助力收钱吧 | 甲子光年

简介&#xff1a; “收钱吧到账100万”成为普通商家最想听到的声音。 你有多久没摸过现金了&#xff1f; 中国互联网络信息中心发布的第47次《中国互联网络发展状况统计报告》数据显示&#xff0c;截至2020年12月&#xff0c;我国移动支付用户规模达到8.54亿&#xff1b;中国人…

Redis 会遇到的「坑」,你踩过几个?

作者 | Magic Kaito来源 | 水滴与银弹这篇文章&#xff0c;我想和你聊一聊在使用 Redis 时&#xff0c;可能会踩到的「坑」。如果你在使用 Redis 时&#xff0c;也遇到过以下这些「诡异」的场景&#xff0c;那很大概率是踩到「坑」了&#xff1a;明明一个 key 设置了过期时间&a…

bert 多义词_自然语言处理:Bert及其他

以下内容主要参考了文末列出的参考文献&#xff0c;在此表示感谢&#xff01;2018年被认为是NLP技术的new era的开始。在这一年&#xff0c;提出了多种有创新性的技术&#xff0c;而且最后的集大成者Bert在NLP的多项任务中屠榜&#xff0c;造成的震撼不比当初神经网络初次在Ima…

谈谈JVM内部锁升级过程

简介&#xff1a; 对象在内存中的内存布局是什么样的&#xff1f;如何描述synchronized和ReentrantLock的底层实现和重入的底层原理&#xff1f;为什么AQS底层是CASvolatile&#xff1f;锁的四种状态和锁升级过程应该如何描述&#xff1f;Object o new Object() 在内存中占用多…

JetBrains发布两项重要更新:基于IDE的远程开发解决方案、轻量级编辑器Fleet

近日&#xff0c;JetBrains 对外发布两项重要产品更新&#xff1a;专为云端和其他服务器打造的远程开发解决方案&#xff0c;以及轻量级编辑器Fleet。 为IntelliJ 平台引入远程开发支持 在近期陆续发布的2021.3 版本的各 IDE 中&#xff0c;JetBrains 向 IntelliJ 平台添加了远…

Hologres揭秘:优化COPY,批量导入性能提升5倍+

简介&#xff1a; 揭秘Hologres优化COPY的技术原理&#xff0c;实现批量导入性能提升5倍 Hologres&#xff08;中文名交互式分析&#xff09;是阿里云自研的一站式实时数仓&#xff0c;这个云原生系统融合了实时服务和分析大数据的场景&#xff0c;全面兼容PostgreSQL协议并与…

io密集型和cpu密集型_一次说明白Python爬虫中多线程,多进程,异步IO编程

图/文&#xff1a;迷神我们在Python爬虫中&#xff0c;重要的是讲究速度&#xff0c;如果有10万或者100万Url地址&#xff0c;写过爬虫的都会知道&#xff0c;那估计是非常慢的。我们的Python爬虫一般IO密集型业务&#xff0c;Python爬虫程序需要发起网络请求&#xff0c;必然就…

【详谈 Delta Lake 】系列技术专题 之 湖仓一体( Lakehouse )

简介&#xff1a; 本文翻译自大数据技术公司 Databricks 针对数据湖 Delta Lake 的系列技术文章。众所周知&#xff0c;Databricks 主导着开源大数据社区 Apache Spark、Delta Lake 以及 ML Flow 等众多热门技术&#xff0c;而 Delta Lake 作为数据湖核心存储引擎方案给企业带来…

移动网络安装测试软件,家宽众测中国移动手机版(在线宽带网速测试器)V2.0.3 去广告版...

家宽众测中国移动手机版(在线宽带网速测试器)是一款手机宽带网速测试工具&#xff0c;用户可以通过本软件加入中国移动的宽带评测活动各种&#xff0c;反映家中宽带的具体使用情况&#xff0c;支持在线报名参加&#xff0c;抢先体验则有奖励赠送哦&#xff0c;欢迎来下载。软件…

如何删除第一张单页_单页网站-网站建设中独有的风景线

在这个网页技术飞速发展的时代&#xff0c;单页网站由于其自身的特点已经形成了一种独有的风景线。从本质上来说&#xff0c;单页网站就是只有一个页面&#xff0c;滚动长页面来显示相关的网站内容&#xff0c;网站所有菜单都链接到主页相应的部位。一些前卫的客户已经开始关注…

service注入为null_如何解决quartz调度时候,job中的service为null的问题?

在需要用到调度任务的时候&#xff0c;发现job中的通过依赖注入的service对象为null.如下&#xff1a;Component public class ExpiredOrderJob implements Job {Autowiredprivate EmployeeService employeeService; //这里的service对象为nullOverridepublic void execute(Job…

工作7年,我的10条经验总结

简介&#xff1a; 作者是一名很普通的技术工程师&#xff0c;从14年毕业到现在工作了7年。本文将与大家分享一些在职场中的道理和经验&#xff0c;希望能对大家有所启发和帮助。 作者 | 抱真 来源 | 阿里技术公众号 前言 简单做个自我介绍&#xff0c;我是一名很普通的技术工…

从能用到好用,GIS信创如何做到行稳致远?

艾瑞咨询集团出品的《2021年中国信创产业研究报告》中提到&#xff0c;信创内涵体现在“从‘关键环节、部分市场’走向‘全产业链、全行业’的信息技术升级&#xff0c;构建中国自主的IT标准和生态”。本文将从GIS信创的3大关键技术战略出发&#xff0c;解析当下如何快速突破发…

CPU静默数据错误:存储系统数据不丢不错的设计思考

简介&#xff1a; 对于数据存储系统来说&#xff0c;保障数据不丢不错是底线&#xff0c;也是数据存储系统最难的部分。据统计&#xff0c;丢失数据中心10天的企业&#xff0c;93%会在1年内破产。那么如果想要做到数据不丢不错&#xff0c;我们可以采取怎样的措施呢&#xff1f…

分计算iv值_筛选变量的指标—IV值

这一期咱们聊聊筛选变量的指标——IV值。计算公式如下&#xff1a;看公式有没有很熟悉&#xff0c;大家没有看错&#xff0c;其中一部分就是WOE的计算公式。区别就在于WOE是对一个变量的每个分组的计算&#xff0c;IV值是对一个变量的统计指标。如上图所示&#xff0c;当前使用…

无需 Dockerfile 的镜像构建:BuildPack vs Dockerfile

作者 | Addo Zhang来源 | 云原生指北过去的工作中&#xff0c;我们使用微服务、容器化以及服务编排构建了技术平台。为了提升开发团队的研发效率&#xff0c;我们同时还提供了 CICD 平台&#xff0c;用来将代码快速的部署到 Openshift&#xff08;企业级的 Kubernetes&#xff…

android studio json插件_热门Android Studio 插件,这里是Top 20

Android Studio是Google基于IntelliJ开发的一款功能强大的开发工具&#xff0c;它具有构建出色Android应用所需要的一切。借助基于IntelliJ IDEA的强大的功能&#xff0c;插件非常丰富。正确的使用插件可以帮助你提高工作效率&#xff0c;更智能&#xff0c;更快。但是&#xf…

云原生时代,企业多活容灾体系构建思路与最佳实践

简介&#xff1a; 对于云原生的概念解读&#xff0c;大家经常会听到微服务、容器这些&#xff0c;那么这些技术跟企业容灾到底有什么样的关系&#xff1f;其实容灾的需求各行各业都有&#xff0c;比如金融行业对于容灾也有强烈的需求。但是怎么把容灾和多活能力构建起来&#x…