当前位置: 首页 > news >正文

etcd-v3.5release-(2)-STM

a.b.c表示a文件里的b类的方法c,注意a不一定是包名,因为文件名不一定等于包名

!!!!etcd在put的过程中使用的batchTxBuffered,这个事务是写bbolt数据库使用的事务,是对bbolt.Tx的一个封装,就是攒一批事务作为一个大事务,一次性提交到bbolt。etcd对外服务得像一个数据库,但是外部看到的不是bbolt,也就是说外部对etcd提交的事务和etcd用来写bbolt时使用的事务完全是两个东西,etcd使用的事务是STM(软件事务内存)

!!!etcd smt在单个事务中对同一key的修改在提交的时候只有最终的修改有效,因为他用了一个rset/wset分别表示客户端在事务中要读取哪些key和修改哪些key,在一条path中对某个key只允许有一个put操作,如果同一条path里有对同一个key的两个put操作,那么clientsdk就会报错

STM、ReadView

!!!!总的来说,etcd的stm事务模型是在客户端实现的,但事务的执行是在etcd集群中的成员节点上完成的。

apply的时候是一条一条的应用的,但是如果要原子的同时apply一批日志,那么就必须使用STM

STM内部包含一个wset和一个rset,保存了本次事务访问到的数据和版本,然后在提交的时候如果发现事务变了,那么就会报错

commited时日志的索引顺序和apply时revision顺序是一致的

!!!wal虽然是顺序写入的,apply的时候也是顺序提交到bbolt的,apply的时候他是会把从chan收到的数据包装成一个job,然后丢到一个fifo队列,这个fifo队列会等当前任务运行完毕才会运行下一个job,也就是apply的时候也必定是按顺序apply的,也就是说只有上一个事务提交到bbolt才会开始提交下一个事务(看了源码),这样在开始x事务的时候x以前的日志都已经写完了,也就是说如果x事务读取的数据被x-k版本的事务修改了,那么x事务是可以检测出来的,如果检测到冲突,那么就不会把该数据写入数据库,也即是说即使日志已经写入了磁盘,但是数据是不一定会写入数据库的。举个例子:一个事务t1,先读取key a,此时a的版本(即revision)为v1,然后事务t1在本地执行了很久才提交事务,然后t1提交到etcd的时候raft日志索引为index1,然后apply的时候对应的revision为 rev1,然后执行的时候检测到key a此时的版本是v1+x,但是他读的时候a的版本为v1,所以此时就检测到了冲突(从treeIndex检测,因为treeIndex包含所有key和key对应的revision,比较一下revision就行),那么事务就会执行失败,虽然事务执行失败,但是日志是apply成功的,也就是说事务执行的结果就是数据不写入数据库。再举个例子,假设事务t1对应的raft日志已经写入磁盘,然后apply但是还没apply完成的时候系统崩溃了,这个没关系,重做就是了,当再次重做到事务t1对应的日志的时候,因为t1以前的日志必定已经重做完了,所以此时还是可以再次检测到冲突的,所以只要wal日志已经持久化了,那么崩溃就对这些已经持久化的日志无影响。

!!!区分revision和applyIndex:etcd的修改操作比如txn或者put,底层都是提交一个事务到bbolt,但是底层是batchTxBuffered即批量提交的,所以就是说上层执行完一个事务后,事务缓存在batchTxBuffered中并没有提交,但是只要把事务提交到了batchTxBuffered,s.currentRev就会+1,也就是说下一次put或者writetxn事务分配的revision就是s.currentRev了,但是!!!在底层batchTxBuffered提交事务前,新增的数据对外部是不可见的,假设batchTxBuffered提交时,本批次最大的revision为x,一旦提交以后,那么rev<=x的素有key都对外可见了,但是x<rev<=s.currentRev之间的而所有key此时对外是不可见的,也就是说可见的数据是落后于已做的数据(因为底层没有提交,所以这些数据对外不可见),这与applyIndex落后于commiteIndex原理是一致的,已经apply的数据对外可见,但是已经commite但是还没有apply的数据不可见

STM内对同一个值多次修改,那么只有最后一次写会成功,因为他是先在本地执行函数,然后得到一个最终的结果,再在commited时候直接提交最终的结果

!!mvcc写,也就是说多个版本

Server上Txn请求流程,分Read和Write

!!!etcd直接提供的if-then-else不支持嵌套事务,但是不知道提供的NewSTM支不支持嵌套事务,有待确定,但是etcd server是肯定支持嵌套事务的

batchtxn内部有一个batchtx表示最后一个事务

!!!stm原理:

stm if xx then yy else zz
stm执行时先判断if里面的条件是否成立,一旦成立就表示true就执行then 路径,一旦失败就表示false就执行else路径,then和else都叫做txnpath即事务路径。stm是允许嵌套stm的比如if里面有一个子if-then-else或者then里面还有if-then-else或者else里也有子if-then-else,当然,也允许子stm里还有子stm。then-else里面的叫做操作即op,因为可能有很多,所以叫做ops。所以stm对于嵌套事务的执行是这样的:先执行最顶层if,然后根据if结果true或者false选择对应的路径,然后执行路径下的op,每个op可以是子事务也可以是简单的put/get/range等操作,然后就根据op执行对应的操作就行,如果是子事务,就再执行一遍if-then-else流程就行

etcd stm代码使用样例(抄自网络):

func getEtcdClient() *v3.Client {// 配置 etcd 客户端cfg := v3.Config{Endpoints:   []string{"30.128.101.96:2379"}, // 集群中的任意一个节点DialTimeout: 5 * time.Second,}// 创建 etcd 客户端cli, err := v3.New(cfg)if err != nil {log.Fatal(err)}return cli}func stm() {client := getEtcdClient()txnTransfer(client, "k1", "k2")
}
func txnTransfer(etcd *v3.Client, sender, receiver string) error {// 失败重试for {if ok, err := doTxn(etcd, sender, receiver); err != nil {fmt.Println("dotxn failed:", err.Error())} else if ok {fmt.Println("dotxn success")}}
}
func doTxn(etcd *v3.Client, sender, receiver string) (bool, error) {fmt.Println("\n开始事务前的读取操作:")getresp, err := etcd.Txn(context.TODO()).Then(v3.OpGet(sender), v3.OpGet(receiver)).Commit()if err != nil {return false, err}senderKV := getresp.Responses[0].GetResponseRange().Kvs[0]receiverKV := getresp.Responses[0].GetResponseRange().Kvs[0]// 发起转账事务,冲突判断 ModRevision 是否发生变化var s stringfmt.Println("senderKv: revision=" + strconv.FormatInt(getresp.Header.Revision, 10) +" key=" + string(senderKV.Key) + "  value=" + string(senderKV.Value))fmt.Println("senderKv: revision=" + strconv.FormatInt(getresp.Header.Revision, 10) +" key=" + string(receiverKV.Key) + "  value=" + string(receiverKV.Value))fmt.Println("请输入newValue:")_, err = fmt.Scan(&s)if err != nil {fmt.Println("scan error:", err.Error())return false, err}fmt.Println("开始执行事务:修改key的值为newValue......")txn := etcd.Txn(context.TODO()).If(                                     #etcd stm是通过if来判断是否可以执行#一旦if里面设置的条件通过了#那么就说明该事物没有发生冲突可以执行#这是if的两个compare即两个比较器v3.Compare(v3.ModRevision(sender), "=", senderKV.ModRevision),      #compare1: key=k1,比较字段=ModRevision,result="="#result表示我们用什么比较方式#即比较看k1这个key的ModRevision这个字段#在事务执行时是否和旧的ModRevision相等#如果不相等则说明在这期间被其他人编辑了,#就说明if失败即事务失败v3.Compare(v3.ModRevision(receiver), "=", receiverKV.ModRevision))  #compare2: key=k2,比较字段=ModRevision,result="="#即比较看k2这个key的ModRevision这个字段#在事务执行时是否和旧的ModRevision相等#如果不相等则说明在这期间被其他人编辑了#!!!比较哪些key是任意的,是自己选的 #!!!也支持一次比较多个key  #!!!当然还支持比较key的其他字段比如xx#!!!当然也支持其他result即>、<、=、!=等#!!!我们这里只比较ModRevision且用的是=txn = txn.Then(                                                         #path then里面又有两个op,每个op又是一个put操作v3.OpPut(sender, s),   v3.OpPut(receiver, s),).Else(                                                                 #path else里面又有两个op,每个op又是一个put操作v3.OpPut(sender, "faild to set"),v3.OpPut(receiver, "faild to set"),)  resp, err := txn.Commit() fmt.Println("事务执行完成")if err != nil {return false, err}return resp.Succeeded, nil
}
//txn事务有两种,一种是读事务,一种是写事务,通通都是通过这个函数来处理,读事务比较简单,重点在于后半段的写事务
//!!!!etcd只读事务不走raft流程,直接本地读
//!!!!个人疑惑:只读事务里面读不需要readIndex吗?
key.quotaKVServer.Txnkey.kvServer.Txnif isTxnReadOnly                                  #如果是只读事务,那么不需要走raft请求,直接事务读就行if !isTxnSerializable(r):                       #判断是不是线性读,!isTxnSerializable表示线性读s.linearizableReadNotify(ctx)                 #如果需要线性读则阻塞等待直到读的条件满足#就是记录当前的commitedIndex为cx,然后等待,直到applyIndex>=cxs.doSerialize                                   #此时applyIndex>=cx,所以可以执行序列化读apply.applierV3backend.Txn                    #事务读也是一个事务操作,只不过这个事务执行的是range读操作#所以这里的具体操作就是读取操作...txn函数在下面详解else:                       etcdserver.EtcdServer.raftRequest               #写事务因为对数据库有修改,所以必须先走一遍raft日志流程#也就是先写一条raft日志,然后再apply这条日志#是的,所有对数据库有修改的操作都叫apply,#只不过apply可以进一步细分为put/txn等很多操作...raft写日志流程略...                          #1:raft写日志流程,略etcdserver.apply                              #2:apply日志,这里就是执行写事务......apply.applierV3backend.Apply              #所有修改数据库的操作都可以叫做applycase r.Txn != nil:                      #apply可以进一步细分,这里是实务操作,即txnop = "Txn"  apply_auth.authApplierV3.Txn          #鉴权准入apply.quotaApplierV3.Txn            #配额准入apply.applierV3backend.Txn        #执行事务isWrite := !isTxnReadonly(rt)   #判断是不是写事务if isWrite && a.s.Cfg.ExperimentalTxnModeWriteWithSharedBuffer:readTx=kvstore_txn.store.Read(mvcc.SharedBufReadTxMode) #共享buf读事务,可以避免复制buffer#因为是共享buf,所以会对buf加读锁s.mu.RLock()                              #kvstore代表数据库,kvstore是一个watchablestore对象#watchablestore对象内部又含有一个store对象#watchablestore和store对象都有一个mu对象#但是watchablestore对象没有Read函数而store对象有#所以go这里kvstore.Read代表的就是store的Read函数#所以store.Read函数里使用的肯定就是store.mu了#操蛋的go语法,刚开始没了解数据结构,迷惑了好久#!!!这里是给store.mu加读锁即给数据库加读锁#!!!加读锁后会阻止数据库写操作#!!!也就是说虽然读写会并发进行#!!!但是读的时候会阻止写#!!!注意:这里是store.mu后面还有把watchable.mu锁#!!!简单粗暴,s.mu.RLock会在txn.End中释放s.revMu.RLock()                           #临时给revMu加读锁,#因为我们要读取curretnRev和compactMainRevvar tx backend.ReadTx                     #backend.ReadTx是对bolt readtx的一个封装if mode == ConcurrentReadTxMode:          #根据mode参数创建对应的txntx = s.b.ConcurrentReadTx()             #conncurrentReadTx创建一个新的readTx,并且复制buf#readBuf中保存的是最新commit的数据backend.readTx.RLock()                #给readBuf加读锁,因为事务提交的时候会把writebuf写回到readbufbackend.readTx.txWg.Add(1)            #???不太懂,bbolt回滚和事务机制curBuf := b.readTx.buf.unsafeCopy()   #复制readBuf,readTx.buf就是readBuf#提交事务的时候会把batchTxBuffered的writeBuffer#中的内容写到readTx.buf#查询操作先查readBuf,然后再查bbolt数据库backend.readTx.RUnlock()              #!!!操作完后解锁#!!!也就是说复制完readBuf后就会释放事务读锁了#!!!后续读操作就是读readBuf的副本了#!!!所以不用担心commite的时候会用writebuf覆盖readBuf#!!!虽然concurrentRead创建后就会释放readTx的读锁#!!!但是创建concurrentReadTx的时候需要对readTx加读锁#!!!所以如果有地方已经加了写锁,比如提交事务的时候#!!!那么这里也会等到写事务完成后才能成功加读锁else:tx = backend.backend.ReadTx()           #如果是sharedBuff模式return b.readTx                       #则直接返回backend内部的readTx而不是新创一个readTxtx.RLock()                                #因为如果是sharedbuff模式则是共用一个readTxtx.mu.RLock                             #所以tx.RLock是一个mutex.RLock操作#如果是concurrentReadTx则tx.RLock是一个空操作#!!!etcd自己包装了bbolt.tx,即包装出一个readTx#!!!这个readTx主要同步读事务和写事务的#!!!etcd读流程会先从readbuf读,读不到再去bbolt读#!!!sharedBuff模式下所有请求都使用同一个readTx#!!!即都会访问同一个readTx.buf,所以读写需要加锁#!!!对这个readTx加读写锁来表示是否有读写事务正在运行#!!!如果是读操作那么就对这个readTx加读锁#!!!比如这里compare操作需要加读锁阻止修改#!!!如果是写操作则对这个readTx加写锁#!!!比如commit的时候就要对readTx加写锁阻止后续读#!!!直到写操作完成#总结一下两种模式:#concurrent模式:加读锁-复制-解读锁-读,加写锁-写-解写锁,#因为锁住事件断,这样就可以读、写交替,但是有复制的开销#shared模式:加锁-读-读-读-读-解锁-写#无复制,但是这样就必须全部读事务都读完写事务才能写,#这样就会导致写阻塞的时间可能偏长firstRev, rev := s.compactMainRev, s.currentRev    #读取共享变量 compactMainRev/currentRev#因为会有其他goroute并发修改,所以读之前需要加锁#currentRev就是当前的revisions.revMu.RUnlock()                                  #释放revMu读锁return newMetricsTxnRead(&storeTxnRead{s, tx, firstRev, rev, trace}) #创建事务对象txn = mvcc.NewReadOnlyTxnWrite(readTx)   else:  readTx=kvstore_txn.store.Read(mvcc.ConcurrentReadTxMode)#并发读事务,创建时会复制buffer#所以读取的时候不会加锁txn = mvcc.NewReadOnlyTxnWrite(readTx) #并发读#!!!stm事务在执行比较前会创建一个只读视图用来确保视图一致性#!!!即compare期间看到的数据是不变的#!!!虽然apply是串行apply的即写是串行的但是读与写却不是串行的而是并发的#!!!即可能同时进行读写操作,因为compare操作需要读数据库#!!!所有compare操作时必须通过一个只读视图来compare#!!!(个人猜测):换句话说bbolt只能读或者写不能同时读写因为会加读写锁return &txnReadWrite{txn} txnPath := make([]bool, 1)txnPath = apply.compareToPath(txn, rt)             #!!!执行比较,stm事务的核心就是执行比较#!!!rt就代表一个事务if-then-else#!!!这里会递归遍历所有事务及子事务#!!!所有事务的比较结果都会顺序存放到txnPath数组里面#!!!也就是说compareToPath这个函数#!!!就是用来确定所有事务该走哪条path true还是false#!!!也就是说不管有多少个嵌套事务,最终的路径一定是确定的#!!!所以txnPath[i]是事务i,#!!!那么txnPath[i+1]必定是执行路径上的下一个事务的compare结果#!!!也就是说不管有多少个嵌套事务,最终的路径一定是确定的txnPath[0] = apply.applyCompares(rv, rt.Compare) #rt.Compare即if块,一个if可以包含很多个比较器for _, c := range rt.Compare:                  #c代表compare中的一个比较器,这里遍历所有比较器#我们的例子中含有两个比较器#compare1: key=k1,比较字段=ModRevision,result="="#compare2: key=k2,比较字段=ModRevision,result="="ok2=apply.applyCompare(rv, c):               #这里以compare1为例metrics_txn.metricsTxnWrite.Range          #读取比较器中的key for _, kv := range rr.KVs:                 #遍历compare1中的所有的key,我们这里只有一个key,即k1ok3=apply.compareKV(c, kv):              #对该key应用比较器,这里是对key k1应用比较器compare1switch c.Target:                       #target表示要比较哪个字段,它支持很多字段#比如value/createVersion/ModVersion......#我们这里是用的ModVersion这个字段case pb.Compare_VALUE:tv=c.TargetUnion.(*pb.Compare_Value)v=tv.Valueresult = bytes.Compare(ckv.Value, v)case pb.Compare_CREATE:tv=c.TargetUnion.(*pb.Compare_CreateRevision)rev = tv.CreateRevisionresult = compareInt64(ckv.CreateRevision, rev)case pb.Compare_MOD:                           #我们用的是ModVersion这个字段tv= c.TargetUnion.(*pb.Compare_ModRevision)  #TargetUnion是一个联合体,rev = tv.ModRevision                         #表示此时请求中的字段表示的含义是ModRevisionresult = compareInt64(ckv.ModRevision, rev)  #执行比较,然后得到一个结果-1(小于),0(等于),1(大于)case pb.Compare_VERSION:                       #version是每个key独有的,tv= c.TargetUnion.(*pb.Compare_Version)rev = tv.Versionresult = compareInt64(ckv.Version, rev)case pb.Compare_LEASE:tv= c.TargetUnion.(*pb.Compare_Lease)rev = tv.Leaseresult = compareInt64(ckv.Lease, rev)switch c.Result:                                 #Result表示我们选择的比较方式是什么#支持很多,比如=、!=、>、<等case pb.Compare_EQUAL:                          #我们这里compare1的k1选择的是=比较return result == 0                            #所以我们返回result是否等于0#true表示if成功,那么稍后执行的就是then路径#false表示if失败,那么稍后执行的就行else路径case pb.Compare_NOT_EQUAL:return result != 0case pb.Compare_GREATER:return result > 0case pb.Compare_LESS:return result < 0if !ok3:                                            #只要有任意一个key比较失败,就返回falsereturn false                                      #就返回falsereturn trueif !ok2                                                #只要有任意一个比较器返回false即比较失败return false                                         #那么就立即返回falsereturn trueops := rt.Success                            if !txnPath[0]:                                            #如果比较失败就执行false路径,否则执行success路径ops = rt.Failure                                         #success对应then,failure对应elsefor _, op := range ops {                                   #选择好执行路径后就遍历该路径下的所有optv, ok := op.Request.(*pb.RequestOp_RequestTxn)if !ok || tv.RequestTxn == nil:                          #compareToPath只是为了确定事务及其子事务该走哪条pathcontinue                                               #所以如果该op里不包含事务,就跳过txnPath = append(txnPath, compareToPath(rv, tv.RequestTxn)...) #递归调用compareToPath来确定所有事务的路径if isWrite:apply.checkRequests(txn, rt, txnPath, a.checkPut)        #检查写请求正确性apply.checkRequests(txn, rt, txnPath, a.checkRange)        #检查range请求正确性if isWrite:  metrics_txn.metricsTxnWrite.End                   #结束我们为了compare而创建的只读事务kvstore_txn.storeTxnRead.Endread_tx.readTx.RUnlock                         #释放backend对应的事务的读锁#一个backend对应一个readTxstore.mu.RUnlock                               #释放store.mu的读锁txn = watchable_store_txn.watchableStore.Write    #compare完毕,可以写了,所以这里创建写事务txn=kvstore_txn.store.Write  s.mu.RLock()                                  #store代表kvstore,写事务会先加读锁#等所有读事务完成后释放读锁并加写锁                  tx := s.b.BatchTx()                           #获取底层的batchtx#底层数据结构:store含有一个backend#backend内部有一个batchTxBuffered#batchTxBuffered内嵌一个batchTx#所以调用batchTxBuffered.LockInsideApply#实际就是调用内嵌的batchTx的LockInsideApply#!!!batchTx对应批量提交,内部含有一个bbolt.tx对象#!!!所有事务都是直接写这个bblot.tx#!!!写完以后这个bbolt.tx不会提交#!!!对于上层来说事务只要写到bbolt.tx就会返回#!!!上层会等到bbolt.tx提交以后才会返回结果给client#!!!也就是异步+batch#!!!这个bbolt.tx可以写很多次都不提交#!!!对于上层来说,这个bbolt.tx里包含了一批事务#!!!但是对于底层bbolt来说自始至终都是一个事务#!!!即上层看到的批量事务在底层实际就是一个事务#!!!所以只需要提交一个事务就行,即batchTx.bbolt.txtx.LockInsideApply()                          #batchtx加写锁,tw := &storeTxnWrite{storeTxnRead: storeTxnRead{s, tx, 0, 0, trace},tx:           tx,beginRev:     s.currentRev,                        #!!!beginRev记录的是当前最后一次操作后的revision#!!!执行事务里面的操作比如put的时候#!!!用的revision是beginRev+1changes:      make([]mvccpb.KeyValue, 0, 4),return newMetricsTxnWrite(tw)return &watchableStoreTxnWrite{s.store.Write(trace), s}apply.applierV3backend.applyTxn                          #执行事务reqs := rt.Success                                     #这里判断是走success路径if !txnPath[0]:reqs = rt.Failure                                    #还是走failure路径#!!!stm中不存在事务冲突#!!!stm中只有path  #!!!如果没有发生冲突即比较成功#!!!那么就走success路径#!!!如果发生冲突,那就走failure路径#!!!即不管冲突还是没有冲突,都有path可走#!!!当然,如果走success路径,#!!!但我们没有设置对应的语句,#!!!那么就相当于一个空操作,什么也不做for i, req := range reqs:                              #遍历该path中的所有opswitch tv := req.Request.(type):                     #该op有很多类型case *pb.RequestOp_RequestRange:                   #如果是range读取操作,那么就执行range读取操作apply.applierV3backend.Range(txn, tv.RequestRange)case *pb.RequestOp_RequestPut:                     #如果是put操作,那么就执行put操作,这里以put为例apply.applierV3backend.Put(txn, tv.RequestPut)   #!!!那么就直接执行普通的put流程#!!!即使是stm事务,本质上也算一个操作#!!!也就是说每个stm事务都对应一个版本号#!!!也就是说客户端一次stm事务操作和客户端一个put操作#!!!本质上没有任何区别,都是一个修改数据库的操作#!!!都是先写一条日志,然后apply执行日志完成修改#!!!注意,这里的一次stm事务是指一次完整的事务操作#!!!stm事务以及他嵌套的所有子事务用的是同一个版本号#!!!换句话说,不管是子事务还是put等操作#!!!同一个事务中的所有操作都用同一个版本号case *pb.RequestOp_RequestDeleteRange:             #如果是deleteRange操作,那么就执行deleteRange操作apply.applierV3backend.DeleteRange(txn, tv.RequestDeleteRange)  case *pb.RequestOp_RequestTxn:                     #如果是子事务,就递归执行applyTxn#!!!子事务也是使用txn这个事务对象#!!!也就是说子事务的版本号和父事务是同一个apply.applierV3backend.applyTxn(txn, tv.RequestTxn, txnPath[1:], resp)txns += applyTxns + 1txnPath = txnPath[applyTxns+1:]                   #txnpath保存了执行路径上所有事物的path#所以txnPath[applyTxns+1]就表示下一个事务的path索引default:                                            #如果是非预期的操作类型,则啥也不干no-oprev := txn.Rev()                                #获取txn开始时最新的revisionif len(txn.Changes()) != 0:                     #每次修改操作都会改变revision,读操作不会rev++watchable_store_txn.watchableStoreTxnWrite.End  #结束事务,主要是释放锁和提交事务tw.s.mu.Lock()                                #!!!我TM真找不到在哪释放的读锁,难道不是同一个store?#!!!破案了:tw.s代表watchable,tw.s.mu是另一把锁#!!!tw.s.store才表示kvstore,tw.s.store.mu才是数据库锁#!!!kvstore是一个watchablestore对象,就表示会有watch#!!!也就是说这边在end的时候那边可能会有一个watch在不断处理#!!!(不知道compaction会不会也需要访问这把锁)#!!!所以就需要临时锁一下kvstore,注意,这是watchablestore.mu#!!!etcdserver是一条一条apply日志的#!!!也就是说apply是串行执行的#!!!执行一次写事务就执行一次提交操作#!!!但是并不是每次提交都会真的提交 #!!!而是每次提交都会把事务放到batchTx buf#!!!当达到一定条数后才会执行commit操作,#!!!由BatchLimit参数指定tw.s.notify(rev, evs)                         #通知watch相关metrics_txn.metricsTxnWrite.End               #更新一些metrics后继续调用内部事务的endkvstore_txn.storeTxnWrite.Endtw.s.revMu.Lock()  tw.s.currentRev++                         #更新server.currentRev,#因为一次writeTxn代表一次修改操作                        batch_tx.batchTxBuffered.Unlock()         #解锁batchtx#注意:batchTxBufered加锁是直接调用内部batchTx.lock#但是解锁的时候不能只调用batchTx.Unlock#因为batchTxBufered还需要做其他事,所以得用自己的Unlock函数#batchTxBufered.unlock做三件事:1:把数据从writebuf写入readBuf#2:如果未提交事务数达到batchLimit就提交事务#3:做完上面两条后才能释放batchTx.lock#所以加锁用batchTx.lock即可#但是解锁则batchTxBufered需实现自己的Unlock函数if t.pending != 0:t.backend.readTx.Lock()               #batchTxn的所有读请求都会对这个readTx加读锁,#会等到所有读请求完成并释放锁后才对buf加写锁  txWriteBuffer.writeback               #把writebuf中的内容写到readbufft.backend.readTx.Unlock()             #释放buf锁if t.pending >= t.backend.batchLimit ||t.pendingDeleteOperations > 0   #batchTxn表示批量事务,如果pending的事务数超过了阈值#那么就提交,默认是1w或者挂起的delete操作数大于0也会提交{t.commit(false)                            #提交事务t.backend.readTx.Lock()                  #要提交事务必须先阻止所有读batch_tx.batchTxBuffered.unsafeCommit    #提交事务backend.hooks.OnPreCommitUnsafe(t)     #执行hookif t.backend.readTx.tx != nil:         #tx是bbolt.tx  #如果bbolt.readTx不为null#则需要等待这些读事务完成才能commit  go func(tx *bolt.Tx, wg *sync.WaitGroup):wg.Wait()bbolt.tx.Rollback()t.backend.readTx.reset()batch_tx.batchTx.commitbbolt.tx.Commit()                    #bbolt的事务提交sdkt.pending = 0t.pendingDeleteOperations = 0t.backend.readTx.Unlock()                #提交完毕,释放锁}t.batchTx.Unlock()                           #解锁batchtxtw.s.revMu.Unlock()tw.s.mu.RUnlock()                         #???释放读锁。我TM真找不到在哪加的读锁#下班 20241202 18:35#我靠,根本不是同一把锁,watchableStore有一把mu锁#watchablestore.store也有一把叫做mu的锁#这里是释放store上的读锁,#我们在创建txnWrite的时候会加读锁,在End里释放读锁#!!!store.mu是一把读写锁,因为采用的是mvcc。#!!!任何put/txn/delete等操作对于store来说#!!!都是一个读操作即只会追加数据不会修改旧数据#!!!所以此时加读锁就行了#!!!但是compact会压缩数据也就是修改旧数据#!!!所以此时就必须对store.mu加读锁#!!!捋一下:store.mu是保护compact和mvcc操作#!!!而watchablestore.mu则是保护watch与notify 操作#!!!所以txnwrite的时候只需要对store加读锁就行了#!!!不影响watch线程tw.s.mu.Unlock()                            #这里是watchablestore.mu.unlock#此后watch goroute就可以继续访问kvstore了txnResp.Header.Revision = rev                   #返回给客户端的revision

相关文章链接:

STM客户端api相关文章

https://juejin.cn/post/7134326187064557575

https://juejin.cn/post/7134326187064557575

相关文章:

etcd-v3.5release-(2)-STM

a.b.c表示a文件里的b类的方法c&#xff0c;注意a不一定是包名&#xff0c;因为文件名不一定等于包名 &#xff01;&#xff01;&#xff01;&#xff01;etcd在put的过程中使用的batchTxBuffered&#xff0c;这个事务是写bbolt数据库使用的事务&#xff0c;是对bbolt.Tx的一个…...

多系统萎缩锻炼如何好起来?

多系统萎缩&#xff08;Multiple System Atrophy, MSA&#xff09;是一种复杂的神经系统退行性疾病&#xff0c;影响着患者的自主神经系统、运动系统和平衡功能等多个方面。面对这一挑战&#xff0c;科学、合理的锻炼对于缓解症状、提高生活质量至关重要。本文将详细介绍多系统…...

非对称任意进制转换器(安卓)

除了正常进制转换&#xff0c;还可以输入、输出使用不同的数字符号&#xff0c;达成对数值进行加密的效果 点我下载APK安装包提取码&#xff1a;h4nw 使用unity开发。新建一个c#代码文件&#xff0c;把代码覆盖进去&#xff0c;再把代码文件添加给main camera即可。 using Sy…...

MySQL数据库安全与管理

1、创建两个新用户U_student1和U_student2,密码分别为1234和5678 create user U_student1@localhost identified by 1234, U_student2@localhost identified by 5678; 2、创建两个新用户的详细信息保存在MySQL数据库的user表中 use mysql; select user, host, authentication…...

python使用PyPDF2 和 pdfplumber操作PDF文件

文章目录 一、第三方库介绍二、基本使用1、拆分pdf2、合并pdf3、提取文字内容4、提取表格内容5、PDF加密6、PDF解密 一、第三方库介绍 Python 操作 PDF 会用到两个库&#xff0c;分别是&#xff1a;PyPDF2 和 pdfplumber。 PyPDF2 可以更好的读取、写入、分割、合并PDF文件&a…...

【vue2自定义指令】v-disabled 实现el-switch,el-button等elementUI禁用(disabled)效果

如果你搜过类似的功能&#xff0c;肯定看到过千篇一律的 // 实现按钮禁用el.disabled true// 增加 elementUI 的禁用样式类el.classList.add(is-disabled)但是这个方案明显对el-switch&#xff0c;不起作用&#xff0c;所以我这边直接把方案贴出来&#xff0c;不想了解具体原理…...

从容面对大规模作业:利用PMI提升作业启用和结束效率

1.进程管理瓶颈 随着集群规模的不断扩大和处理器性能的不断提升&#xff0c;高性能计算HPC(High Performance Computing)系统性能已经进入百亿亿次时代&#xff0c;进程管理是高性能计算的一个重要组成部分&#xff0c;传统进程管理已经不能满足海量处理器的管理需求&#xff…...

2024年认证杯SPSSPRO杯数学建模C题(第一阶段)云中的海盐解题全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 C题 云中的海盐 原题再现&#xff1a; 巴黎气候协定提出的目标是&#xff1a;在2100年前&#xff0c;把全球平均气温相对于工业革命以前的气温升幅控制在不超过2摄氏度的水平&#xff0c;并为1.5摄氏度而努力。但事实上&#xff0c;许多之前的…...

burp(6)暴力破解与验证码识别绕过

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…...

不同系统查看软件占用端口的方式

Windows 使用命令提示符&#xff08;CMD&#xff09; 打开命令提示符: 按 Win R 键打开“运行”对话框&#xff0c;输入 cmd 并按回车。为了执行某些命令&#xff0c;您可能需要以管理员身份运行命令提示符。可以通过右键点击“开始”按钮并选择“命令提示符&#xff08;管理…...

【已解决】黑马点评项目中-实战篇11-状态登录刷新章节设置RefreshTokenInterceptor拦截器后登录异常的问题

黑马点评项目中-实战篇11-状态登录刷新章节设置RefreshTokenInterceptor拦截器后登录异常的问题 在 MvcConfig 文件中添加好RefreshTokenInterceptor拦截器 出现异常情况 按照验证码登录后&#xff0c;进入主页面&#xff0c;再点击“我的”&#xff0c;又跳入登录界面 原因…...

Artec Leo 3D扫描仪 革新家具行业的数字化展示【沪敖3D】

随着科技的飞速进步&#xff0c;三维扫描技术已被广泛应用于包括家居行业在内的多个行业。面对现代消费者对家居产品日益增长的个性化和多样化需求&#xff0c;传统的家居设计和展示方法已难以满足市场需求。三维扫描技术的出现&#xff0c;为家居行业带来了新的发展机遇&#…...

UG NX二次开发(Python)-UIStyler-选取点

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、设计一个UI界面3、创建长方体的代码4、需要引入的库5、测试验证1、前言 采用Python语言进行UG NX二次开发的资料比较少,我本来不是很认可采用Python进行二次开发的,但是近期有读者咨询…...

Linux网络编程之---组播和广播

一.组播 1.概述 单播地址标识单个IP 接口&#xff0c;广播地址标识某个子网的所有IP 接口&#xff0c; 多播地址标识一组IP 接口。单播和广播是寻址方案的两个极端(要么单个要么全部)&#xff0c; 多播则意在两者之间提供一种折中方案。多播数据报只应该由对它感兴趣的接口接收…...

[计算机网络] HTTP/HTTPS

一. HTTP/HTTPS简介 1.1 HTTP HTTP&#xff08;超文本传输协议&#xff0c;Hypertext Transfer Protocol&#xff09;是一种用于从网络传输超文本到本地浏览器的传输协议。它定义了客户端与服务器之间请求和响应的格式。HTTP 工作在 TCP/IP 模型之上&#xff0c;通常使用端口 …...

运动模糊效果

1、运动模糊效果 运动模糊效果&#xff0c;是一种用于 模拟真实世界中快速移动物体产生的模糊现象 的图像处理技术&#xff0c;当一个物体以较高速度移动时&#xff0c;由于人眼或摄像机的曝光时间过长&#xff0c;该物体会在图像中留下模糊的运动轨迹。这种效果游戏、动画、电…...

扫二维码进小程序的指定页面

草料二维码解码器 微信开发者工具 获取二维码解码的参数->是否登陆->跳转 options.q onLoad: function (options) {// console.log("options",options.q)if (options && options.q) {// 解码二维码携带的链接信息let qrUrl decodeURIComponent(optio…...

uniapp的生命周期

在 UniApp 中&#xff0c;生命周期函数是指在组件&#xff08;如页面、视图等&#xff09;创建和销毁过程中会自动触发的一些函数。UniApp 提供了多种生命周期函数&#xff0c;帮助开发者在适当的时机进行相关的逻辑处理。 UniApp 的生命周期函数可以分为 页面生命周期 和 组件…...

vue3-生命周期钩子函数

在 Vue 3 中&#xff0c;**生命周期函数**是指在组件的不同阶段自动执行的一些特殊函数。这些函数就像组件的“生老病死”的各个阶段&#xff0c;你可以在不同阶段执行不同的操作。 生命周期的主要阶段&#xff1a; 1. **创建阶段**&#xff08;组件还没有出现在页面上&#x…...

科技云报到:数智化转型风高浪急,天翼云如何助力产业踏浪而行?

科技云报到原创。 捷径消亡&#xff0c;破旧立新&#xff0c;是今年千行百业的共同底色。 穿越产业周期&#xff0c;用数字化的力量重塑企业经营与增长的逻辑&#xff0c;再次成为数字化技术应用的主旋律&#xff0c;也是下一阶段产业投资的重点。 随着数字化转型行至“深水区…...

利用 360 安全卫士极速版关闭电脑开机自启动软件教程

在使用电脑的过程中&#xff0c;过多的开机自启动软件会严重拖慢电脑的开机速度&#xff0c;影响我们的使用体验。本教程中简鹿办公将详细介绍如何使用 360 安全卫士极速版关闭电脑开机自启动软件&#xff0c;让您的电脑开机更加迅速流畅。 一、打开 360 安全卫士极速版 在电…...

调用大模型api 批量处理图像 保存到excel

最近需要调用大模型&#xff0c;并将结果保存到excel中&#xff0c;效果如下&#xff1a; 代码&#xff1a; import base64 from zhipuai import ZhipuAI import os import pandas as pd from openpyxl import Workbook from openpyxl.drawing.image import Image from io i…...

【Spring 全家桶】Spring MVC 快速入门,开始web 更好上手(上篇) , 万字解析, 建议收藏 ! ! !

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…...

Redisson看门狗机制

redis分布式锁我们需要手动为锁设置超时过期时间&#xff0c;但是这个时间不好判断&#xff0c;如果太短&#xff0c;持有锁的客户端还在执行业务&#xff0c;锁就超时释放了&#xff1b;如果太长了&#xff0c;持有锁的客户端释放锁失败&#xff0c;则其他客户端一直没法获取锁…...

LeetCode279. 完全平方数(2024冬季每日一题 27)

给你一个整数 n &#xff0c;返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数&#xff0c;其值等于另一个整数的平方&#xff1b;换句话说&#xff0c;其值等于一个整数自乘的积。例如&#xff0c;1、4、9 和 16 都是完全平方数&#xff0c;而 3 和 11 不是。 …...

HTML礼物圣诞树

系列文章 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心&#xff08;双心版&#xff09;1…...

【JVM】JVM基础教程(二)

上一篇&#xff1a;【JVM】JVM基础教程&#xff08;一&#xff09;-CSDN博客 类加载器 什么是类加载器&#xff08;ClassLoader&#xff09; 是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。 从这个定义可以得出结论&#xff1a;Java是允许开发者写点代码…...

Appium 安装问题汇总

好生气好生气&#xff0c;装了几天了&#xff0c; opencv4nodejs 和 mjpeg-consumer 就是装不了&#xff0c;气死我了不管了&#xff0c;等后面会装的时候再来完善&#xff0c;气死了气死了。 目录 前言 1、apkanalyzer.bat 2、opencv4nodejs 3、ffmpeg 4、mjpeg-consume…...

在网安中什么是白帽子

在网络安全领域&#xff0c;白帽子是指那些专门从事网络安全研究&#xff0c;帮助企业或个人发现并修复安全漏洞的专家。以下是对白帽子的详细解释&#xff1a; 一、定义与角色 白帽子是网络安全领域的术语&#xff0c;通常指那些具备专业技能和知识的网络安全专家。他们的工作…...

C++解析URL的方式

一、URL格式为http://192.168.80.166:6666/designResLib/test/test std::vector<CString> ThkCabRMaterialStaDlg::splitAddress(const CString& address) {std::vector<CString> result;CString remaining address;result.push_back(remaining.Left(remaini…...

【软件安全】软件安全设计规范,软件系统安全设计制度(Word原件)

1.1安全建设原则 1.2 安全管理体系 1.3 安全管理规范 1.4 数据安全保障措施 1.4.1 数据库安全保障 1.4.2 操作系统安全保障 1.4.3 病毒防治 1.5安全保障措施 1.5.1实名认证保障 1.5.2 接口安全保障 1.5.3 加密传输保障 1.5.4终端安全保障 软件全面文档清单涵盖以下核心内容&a…...

基于ZooKeeper搭建Hadoop高可用集群

ZooKeeper搭建Hadoop高可用集群 在之前安装的Hadoop3.3.6集群中HDFS NameNode 和 YARN ResourceManager 都是单节点&#xff0c;集群不具有高可用性。 HDFS 高可用架构 HDFS 高可用架构主要组件&#xff1a; Active NameNode 和 Standby NameNode&#xff1a; 两台 NameNode…...

华硕主板不能开启

正常流程&#xff1a; [主機板]BIOS如何設置主機板整合圖形(內顯)和獨立顯示卡同時顯示輸出 | 官方支援 | ASUS 台灣 如果开启了CSR兼容性模式&#xff0c;在BIOS里面&#xff0c;就必须关掉&#xff0c;才能支持多显示器&#xff0c;如下图显示的标识才会出现。...

[C++]构造函数和析构函数

一、构造函数 1.构造函数的基本概念 1.对构造函数的理解&#xff1a; 构造函数是类的一种特殊成员函数&#xff0c;其主要功能是在创建对象时进行初始化操作。它的名字与类名相同&#xff0c;并且没有返回值类型&#xff08;不能是void&#xff09;。例如&#xff0c;对于一个…...

查询品牌涉及两张表(brand、brand_admin_mapping)

文章目录 1、BrandController2、AdminCommonService3、BrandApiService3、BrandCommonService4、BrandSqlService涉及的表SQL 查询逻辑参数处理执行查询完整 SQL 逻辑参数映射总结 SELECT * FROM brand_admin_mapping WHERE admin_id 252SELECT * FROM brand WHERE id 44SELE…...

CVPR和其他2024顶会论文阅读(资源整理【1】)

CVPR 2024论文阅读(资源整理【1】) 一、3d 重建与建模论文1-Deformable 3D Gaussians for High-Fidelity Monocular Dynamic Scene Reconstruction论文2- 4D Gaussian Splatting for Real-Time Dynamic Scene Rendering论文3-GaussianDreamer: Fast Generation from Text to …...

数据库表约束完全指南:提升数据完整性和准确性

数据库表约束完全指南&#xff1a;提升数据完整性和准确性 在数据库设计中&#xff0c;表约束是确保数据完整性和准确性的关键工具。本文将详细介绍各种类型的表约束及其使用方法&#xff0c;包括非空约束、唯一约束、主键约束、外键约束、默认值约束、检查约束以及自动递增约…...

保姆级教程用vite创建vue3项目并初始化添加PrimeVue UI踩坑实录

文章目录 一、什么是PrimeVue二、详细教程1.添加PrimeVue2.配置main.js3.添加自动引入4.配置vite.config.js5.创建测试页面 一、什么是PrimeVue PrimeVue 是一个用于 Vue.js 3.x 开发的一款高质量、广受欢迎的 Web UI 组件库。 官网地址&#xff1a;https://primevue.org/ 二、…...

数据库索引

以下是关于数据库索引的详细介绍&#xff0c;包括其概念、优点和缺点&#xff0c;并附带丰富的例子&#xff1a; 一、什么是数据库索引 数据库索引是一种数据结构&#xff0c;类似于书籍的目录&#xff0c;它能够帮助数据库管理系统&#xff08;DBMS&#xff09;迅速定位和访问…...

硬件选型规则

光源选型: 先用型号中带H的&#xff0c;没有的选标准的. 光源和光源控制器的搭配需要确保接口一致。 根据型号表中的最佳工作距离和相机的尺寸。 光源控制器选型&#xff1a; 首先选择海康风格系列光源控制器考虑与光源的接口匹配。功率应该满足接近光源功率。检查是否退市…...

linux 架构详解

Linux 是一种开源的操作系统内核&#xff0c;最初由 Linus Torvalds 于 1991 年创建。它是一个基于 Unix 的操作系统内核&#xff0c;用于构建完整的操作系统。Linux 架构是指 Linux 操作系统的内部结构和组成组件的工作方式。 整体架构 Linux系统通常被看作是一个层次化的结…...

Mybatis 学习 之 XML 手册

目录 单次执行单次新增单次更新单次删除 批量执行批量新增批量更新for 循环执行更新for 循环生成多条 sql&#xff0c;一次执行 批量删除 参数传递预处理方式 (OGNL表达式 #{})数据类型转换 直接替换 (EL表达式 ${}) 安全 单次执行 单次新增 <insert id"insert"…...

平面直角坐标系

图1 平面直角坐标系 横轴为x&#xff0c;竖轴为y&#xff0c;x、y的交点为O。 表示一个点经常表示为( x, y )&#xff0c;如图上的红点( 2, 3 )。 图2 点( 2, 3 )...

K8S对接ceph的RBD块存储

1 PG数量限制问题 1.1 原因分析 1.还是老样子&#xff0c;先创建存储池&#xff0c;在初始化为rbd。 [rootceph141~]# ceph osd pool create wenzhiyong-k8s 128 128 Error ERANGE: pg_num 128 size 3 for this pool would result in 295 cumulative PGs per OSD (2067 tot…...

使用 OpenCV 进行 Android 开发

在本节中&#xff0c;我们将创建一个简单的应用程序&#xff0c;它除了加载 OpenCV 之外什么都不做。在下一节中&#xff0c;我们将扩展它以支持相机。 除了这个说明&#xff0c;你还可以使用一些视频指南&#xff0c;例如这个 打开 Android Studio 并选择Empty Views Activi…...

ubuntu下 grep 如何过滤包括G或者T字符串

在 grep 中&#xff0c;你可以通过使用正则表达式来匹配包含 G 或者 T 的字符串。例如&#xff1a; 1. 过滤包括 G 或 T 的字符串&#xff1a; grep -E G|T filename -E 表示启用扩展正则表达式&#xff08;也可以用 egrep&#xff09;。G|T 表示匹配 G 或 T。 2. 不区分大…...

【C语言】整数每一位数字出现次数

提相信你是最棒哒&#xff01;&#xff01;&#xff01; 文章目录 题目描述 题目代码 法一解析版C 法二解析版C 总结 题目描述 给出两个整数 M 和 N&#xff0c;求在序列[M, M 1, M 2,…, N - 1, N]中每一个数码出现了多少次。 输入格式 输入两个整数 M 和 N 。 输出格式…...

opencv Mat To Heif

高效率图像文件格式&#xff08;英语&#xff1a;High Efficiency Image File Format, HEIF&#xff1b;也称高效图像文件格式&#xff09;是一个用于单张图像或图像序列的文件格式。它由运动图像专家组&#xff08;MPEG&#xff09;开发&#xff0c;并在MPEG-H Part 12&#x…...

[GStreamer] gstbasesink 的 chain 函数

chain函数的定位&#xff1a; chain函数是上一个element到当前element的入口&#xff0c;chain函数处理完毕后&#xff0c;上一个函数的push_buffer 就得以返回。因此通常情况下 parent element 的核心逻辑都在 chain 函数里&#xff0c;再通过再 chain 函数里调用虚函数让 ch…...

评估大语言模型(LLM)在分子预测任务能够理解分子几何形状性能

摘要 论文地址&#xff1a;https://arxiv.org/pdf/2403.05075 近年来&#xff0c;机器学习模型在各个领域越来越受欢迎。学术界和工业界都投入了大量精力来提高机器学习的效率&#xff0c;以期实现人工通用智能&#xff08;AGI&#xff09;。其中&#xff0c;大规模语言模型&a…...