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

「实验记录」MIT 6.824 Raft Lab2C Persist

#Lab2C - Persist

  • I. Source
  • II. My Code
  • III. Motivation
  • IV. Solution
    • S1 - 实现persist()
    • S2 - 实现readPersist()
    • S3 - 持久化三字段
    • S4 - 在newRaft()中初始化nextIdxs和matchIdxs
    • S5 - 适当缩短心跳时间
  • V. Result

I. Source

  1. MIT-6.824 2020 课程官网
  2. Lab2: Raft 实验主页
  3. simviso 精品付费翻译 MIT 6.824 课程
  4. Paper - Raft extended version

II. My Code

  1. source code 的 Gitee 地址
  2. Lab2C: Persist 的 Gitee 地址

课程官网提供的 Lab 代码下载地址,我没有访问成功,于是我从 Github 其他用户那里 clone 到干净的源码,有需要可以访问我的 Gitee 获取

III. Motivation

提出 Raft 的主要目的,是为了解决容错问题,即使集群中有一些机器发生了故障,也不影响整体的运作(对外提供的服务)

我用一个 demo 来说明,假设我们的需求一直都是自己的 PC 能够顺利访问云端的资源(HTTP 或数据库)服务器。在服务器稳定在线的情况下,我们去访问它,一点问题都没有

但是,如果那唯一的一台服务器掉线了,那么我们将无法再访问,即对外的服务到此停止。这是我们无法忍受的,我们希望提供服务的一方能够保持稳定,时时刻刻为我提供访问服务。这就是我们的需求

好,现在问题摆在眼前,提供服务的一方怎样保证稳定性?让唯一的那台服务器永远维持稳定的状态,不允许宕机?这非常地不现实,就好比让一个人练成金刚不坏之身

所以,我们只能琢磨是否可以通过添加服务器的数量来确保对外服务的稳定。更近一步,即是现在服务器不再只有一台,扩充到 3 台,这 3 台中有一台是 primary 服务器,也主要由它对外提供服务;其他 2 台是 secondary 服务器(后备力量),拥有和 primary 服务器相同的数据内容

在 primary 服务器出现故障的时候,secondary 服务器顶上去,替代它的位置。这样就可以保持稳定的对外服务了

这就是我们应对资源服务器崩溃的最常用最有效的法子,但是想实现这个想法,首先要解决数据同步的问题,即如何确保 secondary 服务器拥有和 primary 服务器同样的内容?

这个同步问题,在学术上被称为共识算法,最经典的共识算法是 Paxos,但是它太难理解了。于是,斯坦福那帮人想出了更为简便的共识算法,即 Raft

通过 Raft 算法就可以同步集群中服务器的内容。要实现该算法,分三步走,5 - The Raft consensus algorithm 章节中的 Leader Election、Log Replication 和 Safety

本文主要针对第三步,Lab2C: persist 展开讲解,如有 Lab2A: Leader Election 或 Lab2B: Log Replication 的需要,请移步

IV. Solution

Lab2C: persist 主要就是为了解决一个问题,即网络中的复杂情况会导致集群中的机器掉线 OR 机器本身的宕机。我们希望发生此类的情况,Raft 也能很好地应对

论文中的图 2 也提到了要完成持久化操作,我们需要保存哪些字段放在磁盘中,最重要的莫过于 log[]curTermvotedFor 三个字段。其他的例如 nextIdxs[]matchIdxs[] 是可以通过这三个字段即时计算出来的,从工程角度上来讲可以不用保存,这样减少了读写磁盘的时间,从一定程度上提高了 Lab2C: persist 的效率

好,话不多说,我们可以直接开始具体的编码工作了,只要在 Lab2B: Log Replication 确保没有问题的情况下,Lab2C: persist 将会容易很多

S1 - 实现persist()

第一步,就是要实现持久化函数,即 raft.go:persist() ,这个函数干的事情,即是将 log[]curTermvotedFor 三个字段写入磁盘,具体如何写入,这不是我们操心的事,我们只需要将其序列化交给 Encoder 即可,

func (rf *Raft) persist() {// Your code here (2C).// Example:// w := new(bytes.Buffer)// e := labgob.NewEncoder(w)// e.Encode(rf.xxx)// e.Encode(rf.yyy)// data := w.Bytes()// rf.persister.SaveRaftState(data)w := new(bytes.Buffer)e := gob.NewEncoder(w)e.Encode(rf.curTerm)e.Encode(rf.votedFor)e.Encode(rf.log)data := w.Bytes()rf.persister.SaveRaftState(data)
}

就像这样,按照上面助教已给出的例子,照猫画虎即可

S2 - 实现readPersist()

我们也要实现读的具体操作,同样如何去读取磁盘也不是我们关心的事,我们只需要调用 Decoder 分解序列化即可,

func (rf *Raft) readPersist(data []byte) {if data == nil || len(data) < 1 { // bootstrap without any state?return}// Your code here (2C).// Example:// r := bytes.NewBuffer(data)// d := labgob.NewDecoder(r)// var xxx// var yyy// if d.Decode(&xxx) != nil ||//    d.Decode(&yyy) != nil {//   error...// } else {//   rf.xxx = xxx//   rf.yyy = yyy// }r := bytes.NewBuffer(data)d := gob.NewDecoder(r)var curTerm intvar votedFor intvar log []LogEntryif d.Decode(&curTerm) != nil || d.Decode(&votedFor) != nil || d.Decode(&log) != nil {DPrintf("read persist fail\n")} else {rf.curTerm = curTermrf.votedFor = votedForrf.log = log}
}

又是一个照猫画虎,跟着助教的写法来就可以。每一次的 raft.go:Make() 都会调用 readPersist() 读取已持久化的数据用以初始化,所以它不需要我们操心 OR 考虑应该在何处调用,自带的框架已经帮我们安排好了

S3 - 持久化三字段

我们要在 raft.goappendEntries.gorequestVote.go 中寻找到流程中更新 log[]curTermvotedFor 三个字段的地方,然后在它们完成操作之后持久化此类的数据

raft.go 中第一处出现在 raft.go:Start() 中,即 client 追加日志条目之后,要持久化,

func (rf *Raft) Start(command interface{}) (int, int, bool) {// Your code here (2B).rf.mu.Lock()defer rf.mu.Unlock()/*------------Lab2C Persist---------------*/defer rf.persist()index := -1term := rf.curTermisLeader := rf.role == Leaderif isLeader {rf.log = append(rf.log, LogEntry{Idx: rf.lastLogIdx() + 1, Term: term, Cmd: command})index = rf.lastLogIdx()}return index, term, isLeader
}

第二处出现在 raft.go:run() 的 candidate 阶段,因为 follower 成为 candidate 之后会更新 curTermvotedFor

func (rf *Raft) run() {for !rf.killed() {switch rf.role {case Follower:...case Candidate:rf.mu.Lock()rf.curTerm++rf.votedFor = rf.merf.voteCount = 1rf.persist()rf.mu.Unlock()...}time.Sleep(10 * time.Millisecond)}
}

之后,就是 requestVote.go 中,需要在 RequestVote() 中添加持久化操作,

func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {// Your code here (2A, 2B).rf.mu.Lock()defer rf.mu.Unlock()/*------------Lab2C Persist---------------*/defer rf.persist()...
}

就像上述一样,调用 defer rf.persist() 即可,这样就可以在离开函数之前将数据写回磁盘,以及在 sendRequestVote() 中,

func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {ok := rf.peers[server].Call("Raft.RequestVote", args, reply)rf.mu.Lock()defer rf.mu.Unlock()if !ok {return ok}term := rf.curTerm/* 自身过期的情况下,直接不再唱票 */if rf.role != Candidate || args.Term != term {return ok}/* 碰到一个任期比自己高的人 */if reply.Term > term {rf.curTerm = reply.Termrf.role = Follower /* candidate 主动回滚至 follower */rf.votedFor = NoBodyrf.persist()return ok}if reply.VoteGranted {rf.voteCount++if rf.role == Candidate && rf.voteCount > len(rf.peers)/2 {rf.role = Leader /* 至关重要 */rf.leaderCh <- struct{}{}}}return ok
}

在碰到一个任期比自己高的人之后,candidate 会更新自己的 curTermvotedFor ,这里也需要注意持久化

最后来到 appendEntries.go 中,同样在 AppendEntries() 中添上 defer rf.persist() 即可,

func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {rf.mu.Lock()defer rf.mu.Unlock()/*------------Lab2C Persist---------------*/defer rf.persist()reply.Success = falsereply.Term = rf.curTerm...
}

sendAppendEntries() 中碰见更新任期的情况下,也要持久化,

func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)rf.mu.Lock()defer rf.mu.Unlock()if !ok {return ok}term := rf.curTerm/* 自身过期的情况下,不需要在维护 nextIdx 了 */if rf.role != Leader || args.Term != term {return ok}/* 仅仅是被动退位,不涉及到需要投票给谁 */if reply.Term > term {rf.curTerm = reply.Termrf.role = Follower /* 主动回滚至 follower */rf.votedFor = NoBodyrf.persist()return ok}/*------------Lab2B Log Replication----------------*/if reply.Success {...} else {...}return ok
}

至此,已经功成大半,但测试的准确率还不是 100%,是因为有一些问题还需要考虑到

S4 - 在newRaft()中初始化nextIdxs和matchIdxs

记得要在 raft.go:Make() 的子函数 newRaft() 中写上定义 nextIdxs[]matchIdxs[] 的代码,不然会在 boatcastAE() 时出现 index out of range 切片越界的问题,我想不懂为什么会这样,因为即使机器掉线了,它重新上线之后,也需要经过选举才能成为 leader,而成为 leader 的第一件事就是初始化 nextIdxs[]matchIdxs[]

按理说,这两个切片不可能在发送心跳包时为空,具体什么 bug,我称之为玄学,看我代码吧,

func newRaft(peers []*labrpc.ClientEnd, me int, persister *Persister, applyCh chan ApplyMsg) *Raft {rf := &Raft{peers:       peers,persister:   persister,me:          me,role:        Follower,voteCount:   0,curTerm:     0,votedFor:    NoBody,grantVoteCh: make(chan struct{}, ChanCap),leaderCh:    make(chan struct{}, ChanCap),heartBeatCh: make(chan struct{}, ChanCap),commitCh:    make(chan struct{}, ChanCap),nextIdxs:    make([]int, len(peers)),matchIdxs:   make([]int, len(peers)),applyCh:     applyCh,commitIdx:   0,appliedIdx:  0,}/* 下标从 1 开始,这非常重要,0 号位置存放默认题目 */rf.log = append(rf.log, LogEntry{Idx: 0, Term: 0})for i := range rf.peers {rf.nextIdxs[i] = rf.lastLogIdx() + 1rf.matchIdxs[i] = 0}return rf
}

这样就不会再出现 index out of range 的情况了

S5 - 适当缩短心跳时间

上面的几种手段已经能够解决大部分错误了,测试基本能够到达 200 次只出现一个失败的情况,即 one(%v) fail to agreement

这错误提示的意思就是集群不能在有效的时间内选出 leader,进而完成日志同步的工作

很明显,就是选主不够快,最简单的办法,即是缩短心跳时间,虽然在 Lab2: Raft 实验主页 中建议我们心跳 1 s 中不超过十次,但是按照 100 ms 的频率,从工程角度来看是不行的,我改成了 90 ms 就没有此类的错误了,

ElectionTimeOut  = 250 * time.Millisecond /* 要远大于论文中的 150-300 ms 才有意义,当然也要保证在 5 秒之内完成测试 */
HeartBeatTimeOut = 90 * time.Millisecond  /* 心跳 1 秒不超过 10 次 *//* 生成随机超时时间,在 250ms~500 ms 范围之内 */
func randElectionTimeOut() time.Duration {r := rand.New(rand.NewSource(time.Now().UnixNano()))t := time.Duration(r.Int63()) % ElectionTimeOutreturn ElectionTimeOut + t
}/* 生成固定的心跳时间,固定值为 90 ms */
func fixedHeartBeatTimeOut() time.Duration {return HeartBeatTimeOut
}

所以,我还是称之为玄学。模型正确,不一定代表实现正确!

至此,已然讲明白了 Lab2C: persist 整个一套流程

V. Result

golang 比较麻烦,它有 GOPATH 模式,也有 GOMODULE 模式,6.824-golabs-2020 采用的是 GOPATH,所以在运行之前,需要将 golang 默认的 GOMODULE 关掉,

$ export GO111MODULE="off"

随后,就可以进入 src/raft 中开始运行测试程序,

$ go test -run 2C

仅此一次的测试远远不够,可以通过 shell 循环,让测试跑个两百次就差不多了

$ for i in {1..200}; go test -run 2C   

这样,如果还没错误,那应该是真的通过了。分布式的很多 bug 需要通过反复模拟才能复现出来的,它不像单线程程序那样,永远是幂等的情况。也可以用我写的脚本 test_2c.py,

import osntests = 200
nfails = 0
noks = 0if __name__ == "__main__":for i in range(ntests):print("*************ROUND " + str(i+1) + "/" + str(ntests) + "*************")filename = "out" + str(i+1)os.system("go test -run 2C | tee " + filename)with open(filename) as f:if 'FAIL' in f.read():nfails += 1print("✖️fails, " + str(nfails) + "/" + str(ntests))continueelse:noks += 1print("✔️ok, " + str(noks) + "/" + str(ntests))os.system("rm " + filename)

我已经跑过两百次,无一 FAIL。之后的 Lab3: Fault-tolerant Key/Value Service 和 Lab4: Sharded Key/Value Service 都是基于 Lab2: Raft 的,要确保你实现的 Raft 算法没有 bug,不然 Labs 越做到后面越难受

相关文章:

「实验记录」MIT 6.824 Raft Lab2C Persist

#Lab2C - Persist I. SourceII. My CodeIII. MotivationIV. SolutionS1 - 实现persist()S2 - 实现readPersist()S3 - 持久化三字段S4 - 在newRaft()中初始化nextIdxs和matchIdxsS5 - 适当缩短心跳时间 V. Result I. Source MIT-6.824 2020 课程官网Lab2: Raft 实验主页simviso…...

软件详细设计总复习(三)【太原理工大学】

题型及分值&#xff1a; 选择 30 分&#xff0c;填空 20 分&#xff0c; 判断 10 分&#xff0c;简答 20 分&#xff0c;综合设计 20 分。 文章目录 三、行为型模式1. 命令模式2. 迭代器模式3. 观察者模式4. 状态模式5. 策略模式 三、行为型模式 1. 命令模式 举个例子&#x…...

Vue3(一):创建vue3工程、setup、vue3响应式原理、computed和watch

Vue3&#xff1a;第一章 一、创建Vue3.0工程1.使用vue-cli创建2.使用vite创建 二、Vue3中的响应式1.拉开序幕的setup2.ref函数3.reactive函数4.vue3中响应式的原理&#xff08;1&#xff09;vue2中响应式原理&#xff08;2&#xff09;Vue3中的Proxy 5.reactive和ref的对比6.se…...

Spring中的@Value注解详解

Spring中的Value注解详解 概述 本文配置文件为yml文件 在使用spring框架的项目中&#xff0c;Value是经常使用的注解之一。其功能是将与配置文件中的键对应的值分配给其带注解的属性。在日常使用中&#xff0c;我们常用的功能相对简单。本文使您系统地了解Value的用法。 Value…...

YSL赢麻了?SMI社媒心智品牌榜Top20公布:YSL破局夺魁,国货品牌现后起之秀

全文速览 1.数说故事联合用户说从美妆、彩妆、护肤三板块全新发布《SMI社媒心智品牌榜》。 2.圣罗兰、兰蔻、欧莱雅等法国高端美妆大牌垄断美妆《SMI社媒心智品牌榜》前三甲。 3.彩妆Top20榜单中&#xff0c;底妆产品稳居前列&#xff0c;色彩美妆占据一席之地。 4.护肤TOP…...

链式哈希,一致性哈希,倒排表

在普通的查询中&#xff0c;通过关键码的比较进行查找&#xff0c;而哈希是根据关键码直接定位到数据项 哈希冲突&#xff1a;同一个关键码经过哈希函数后指向同一个记录集 链式哈希 using namespace std; #define M 13 typedef int KeyType; //typedef struct //{ // KeyTyp…...

Python操作XML教程:读取、写入、修改和保存XML文档

目录 导入所需模块解析XML文档获取元素遍历XML文档写入新的元素修改元素的内容和属性删除元素保存修改后的XML文档示例演示python操作xml的常用方法 XML是一种常见的数据交换格式&#xff0c;在许多应用中都被广泛使用。通过掌握Python操作XML的基础知识&#xff0c;您将能够轻…...

Oracle数据库中了locked1勒索病毒,用友nchome配置文件损坏该如何解除

随着互联网技术的不断发展&#xff0c;网络安全问题也越来越受到人们的关注。其中&#xff0c;勒索病毒是一种比较常见的网络安全威胁。最近很多集团企业在使用Oracle数据库的过程中&#xff0c;遭遇到了locked1勒索病毒的攻击&#xff0c;导致企业的用友nchome配置文件损坏&am…...

leecode 数据库: 602. 好友申请 II :谁有最多的好友

数据导入&#xff1a; Create table If Not Exists RequestAccepted (requester_id int not null, accepter_id int null, accept_date date null); Truncate table RequestAccepted; insert into RequestAccepted (requester_id, accepter_id, accept_date) values (1, 2, 20…...

基于 Prometheus 的 SLO告警实战

Prometheus是一个流行的开源监控系统&#xff0c;它可以帮助我们收集、存储和查询应用程序或系统的时间序列数据。在使用Prometheus进行监控时&#xff0c;通常需要根据服务水平指标&#xff08;Service Level Objectives&#xff0c;简称SLO&#xff09;来设置告警规则。 SLO…...

调用百度API实现图像风格转换

目录 1、作者介绍2、基本概念2.1 人工智能云服务与百度智能云2.2 图像风格转换 3、调用百度API实现图像风格转换3.1 配置百度智能云平台3.2 环境配置3.3 完整代码实现3.4 效果展示3.5 问题与分析 1、作者介绍 张元帮&#xff0c;男&#xff0c;西安工程大学电子信息学院&#…...

5个最好的WooCommerce商城自动化动作来增加销售量

您是否正在寻找简单智能的方法来自动执行任务并增加 WooCommerce 商店的销售额&#xff1f; 通过在线商店中的自动化任务&#xff0c;您可以在发展业务和增加销售额的同时节省时间和金钱。 在本文中&#xff0c;我们将向您展示如何使用 WooCommerce商城自动化来增加销售额。 …...

打开数据结构大门——实现小小顺序表

文章目录 前言顺序表的概念及分类搭建项目&#xff08;Seqlist&#xff09;:apple:搭建一个顺序表结构&&定义所需头文件&&函数:banana:初始化:pear:打印:watermelon:数据个数:smile:检查容量:fireworks:判空:tea:在尾部插入数据:tomato:在尾部删除数据:lemon:在…...

一.RxJava

1.RxJava使用场景 RxJava核心思想 Rx思维:响应式编程,从起点到终点,中途不能断掉,并且可以在中途添加拦截. 生活中的例子: 起点(分发事件,我饿了)->下楼->去餐厅->点餐->终点(吃饭,消费事件) 程序中的例子: 起点(分发事件,点击登录)->登录API->请求服务器-…...

如何使用 VSCode 软件运行C代码

VSCode 的下载和扩展的配置可以参考文章&#xff1a;VSCode 的安装与插件配置。 VSCode 是很好用的编辑器&#xff0c;通过给其配置 MinGW-w64 插件就可以在它上面编译运行C代码了。 在没有配置 MinGW-w64 插件时&#xff0c;在 VSCode 中运行下面的代码后打印如下图所示。 这…...

C# 调用Matlab打包的 DLL文件(傻瓜式操作)

1、准备Matlab代码 2. 打包 在matlab命令行窗口输入deploytool,打开MATLAB Complier,选择Library Compiler 在TYPE中选择.NET Assembly;在EXPORTED FUNCTIONS中选择要打包的文件&#xff1b;可以选择为自己打包的文件自定义NameSpace名称&#xff0c;本例中将NameSpace定义为…...

微信小程序学习实录3(环境部署、百度地图微信小程序、单击更换图标、弹窗信息、导航、支持腾讯百度高德地图调起)

百度地图微信小程序 一、环境部署1.need to be declared in the requiredPrivateInfos2.api.map.baidu.com 不在以下 request 合法域名3.width and heigth of marker id 9 are required 二、核心代码&#xff08;一&#xff09;逻辑层index.js&#xff08;二&#xff09;渲染层…...

【面试题】中高级前端工程师都需要熟悉的技能--前端缓存

前端缓存 一、前言二、web缓存分类1. HTTP缓存&#xff1a;2. 浏览器缓存&#xff1a;3. Service Worker&#xff1a;4. Web Storage缓存&#xff1a;5. 内存缓存&#xff1a; 三、http缓存详解1、http缓存类型a. 基于有效时间的缓存控制&#xff1a;b. 基于资源标识的缓存&…...

小红书数据分析:首播卖6亿,小红书直播开启新纪元!

5月22日&#xff0c;章小蕙在小红书开启了第一场带货直播。继董洁之后&#xff0c;小红书又迎来一位超级带货KOL。 据千瓜数据显示&#xff0c;相关话题#章小蕙小红书直播#上线不到30天&#xff0c;话题浏览量就高达2814.89万&#xff0c;笔记互动量达22.24万。 图 | 千瓜数据…...

Weex中,关于组件的水平排列竖直排列居中对齐居左对齐居右对齐低部对齐顶部对齐布局对齐说明

容器内子组件排列方向 子组件竖直方向排列&#xff08;默认&#xff09; 子组件水平方向排列 <style> .container {flex-direction: row;direction: ltr; } </style>子组件在父组件容器中的对齐方式 我们主要使用两个属性实现子组件在父组件的对齐方式&#xff…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

CppCon 2015 学习:Time Programming Fundamentals

Civil Time 公历时间 特点&#xff1a; 共 6 个字段&#xff1a; Year&#xff08;年&#xff09;Month&#xff08;月&#xff09;Day&#xff08;日&#xff09;Hour&#xff08;小时&#xff09;Minute&#xff08;分钟&#xff09;Second&#xff08;秒&#xff09; 表示…...