开源
政务OA系统:巫山政府办公
政务OA系统中自己动手写数据库系统:容灾恢复原理和容灾恢复日志的设计代化和数字化已成为企业和机构发展的必然趋势。作为一种重要的办公工具,公文管理系统在提高工作效率和便利性方面发挥着重要作用数据出现不一致性,也就是机票出票数量和相应的支付款项不一致,没有容错性的数据库系统就不会有市场,本节的目的是设计恢复机制,确保数据在任何突如其来的意外情况下依然保持数据一致性。
因此数据库系统必须遵守acid原则,他们分别是atomicity, consistency, isolation, durability:
atomicity: 其意思是任何数据操作要不完全执行,要不就一点作用也没有。数据库中有一个叫“交易”的概念,也就是tra
世界首例针对特斯拉自动驾驶判罚:德国裁定autopilot广告误导买家,特斯拉柏林工厂可能受阻:保留上诉权,但特斯拉还是改了宣传这个案件中,做出最终裁决的是慕尼黑法院,但原告并不是德国政府监管机构,而是德国反不公平竞争与保护中心(wettbewerbszentrale),一个反不当竞争的民间独立机构
此外,特斯拉的宣传,还带着暗示——相关的自动驾驶系统已经在德国得到法律法规允许。但实际并没有。外媒路透也评价说,特斯拉这种宣传还带来更进一步的社会问题。?
但德国法院的判决也不是完全没有影响,外媒分析,至少特斯拉柏林工厂可能会受阻。路透社透露,特斯拉柏林工厂一开始筹建就不是一帆风顺的。
首先是德国地方政府给了特斯拉建厂一些支持政策,但并不像中国上海一样大力支持特斯拉。中央政府层面,德国联邦政府给予的电动车购置优惠也没有中国政府力度大。
所以现在德国的判例,对特斯拉的自动驾驶,可能只是一个开始。更多国家的交通安全部门、车主幸存者,或许会以此为契机,在更多国家和地区,向特斯拉讨要说法。
运维大数据平台落地构想:微信图片_20190801133837.jpg
现在全国政务行业都在推行数字政府、数字中国的落地。
大部分省市都在进行iaas资源、paas资源、daas资源以及saas资源的整合;构建基于ipds架构的云平台数据中心,通过ipds云平台数据中心,为用户提供各类资源服务。
上述是以政务为例,回望企业客户,建设路径亦如此。
同时传统的监控能对单个点进行监控,很难结合cmdb配置平台实现关联关系的监控,即某个点出现故障时,会影响到哪些业务,哪些操作系统。
作者:何世晓
【阳光私塾】告密者,一种历史幽灵的闪现:这所我曾经应邀前往演讲的学校,涌现出两名杰出的学生告密者,她们将自己的“古代汉语”老师告到市教委和公安局,理由是在课堂上“批评文化”和“批评政府”。
这种复杂的四重监视体系,培训了庞大的告密者队伍,成为专制王朝的最大帮手。
这场从告密开始的运动,最终升温到令人发指的地步,据广西《武宣县无产阶级文化大革命大事件》记载,该县武宣中学,甚至出现食人盛宴——众学生在校园内揭发批斗完自己的老师之后,将他们剖腹肢解,就地架设炉灶,烹煮至熟
众所周知,批评是帮助政府改进工作和推动社会进步的重要方式,也是宪法赋予的基本权利。
容忍和听取不同意见,乃是衡量政治清明的基本标尺,而在以“和谐”为政治目标的社会中营造斗争气氛,置敢说真话的教师于被告密的恐惧之中,这不仅是以司法教育为使命的高等学府的耻辱,更是社会正义和民主进程的敌人。
美国对勒索软件重拳出击,成立特别小组,悬赏1000万美元:(nist) 以及财政部、卫生与公众服务部的最新勒索软件相关警报和威胁的指南。
拜登政府显然也在考虑对黑客团伙发动破坏性网络攻击的可能性,并努力与私营部门组织(包括网络保险提供商和关键基础设施公司)建立伙伴关系,以分享有关勒索软件攻击的信息。
阿波罗信息系统公司和ciso公司副总裁安迪·贝内特说,现在的问题是接下来会发生什么。他们将如何超越联邦政府的职能,使整个国家都有能力和赋权?
他还指出,各机构之间的合作对于制定战略和结合专业知识来应对当前勒索软件攻击的流行至关重要。与传统恐怖主义不同,网络攻击和反击手段并非政府所独有。
这个特别小组是绝对值得的,如果做对了,将对打击和建立政府所有领域的勒索软件的复原力产生重大影响,他总结道。原文翻译自helpnetsecurity
开源政务OA系统:巫山政府办公政务OA系统中自己动手写数据库系统:容灾恢复原理和容灾恢复日志的设计势,并讨论如何在实践中有效地应用和管理这一系统。一、公文管理系统的定义 公文管理系统是一种基于计算机技术的软件系统,用于管理和处理各类公文文件。它通过数字化和自动化的方式,将公文的创建、审批、传递和归档等环节进行整合和优化,从而提高工作效率和管理水平。二、公文管理系统的功能 1.公文创建和编辑:公文管理系统提供了丰富的模板和格式,使得公文的创建和编辑变得简单和规范化。用户可以根据需要选择相应的模板,填写相关内容,并进行格式调整和排版。2.公文审批和流转:公文管理系统实现了公文的电子审批和流转,取代了传统的纸质审批流程。通过系统的设置,可以实现多级审批、并行审批和串行审批等不同的审批方式,大大缩短了审批时间和流转周期。3.公文传递和共享:公文管理系统支持公文的电子传递和共享,使得公文的传递更加数据库系统有一个极其重要的功能,那就是要保持数据一致性。在用户往数据库写入数据后,如果数据库返回写入成功,那么数据就必须永久性的保存在磁盘上。此外作为一个系统,它必须具备自恢复功能,也就是如果系统出现意外奔溃,无论是内部错误,还是外部原因,例如突然断电等,系统都必须要保持数据的一致性。
例如我们从数据库中订购一张机票,假设机票数量正确减一,但还没扣款,此时系统突然奔溃,如果系统没有预防措施就会导致数据出现不一致性,也就是机票出票数量和相应的支付款项不一致,没有容错性的数据库系统就不会有市场,本节的目的是设计恢复机制,确保数据在任何突如其来的意外情况下依然保持数据一致性。
因此数据库系统必须遵守acid原则,他们分别是atomicity, consistency, isolation, durability:
atomicity: 其意思是任何数据操作要不完全执行,要不就一点作用也没有。数据库中有一个叫“交易”的概念,也就是transation,它表示一系列必须全部完成的读写操作,必须是序列化的,也就是交易所给定的执行步骤在运行时不能被打断,或者是中间突然插入其他交易的步骤,所以它也叫原子化。
consistency:意思是任何交易都必须确保数据处于一致状态。也就是说交易中所定义的一系列读写步骤必须作为一个统一的单元进行执行,当交易进行时,数据库系统的运行状态就好像是一个单线程应用。
isolation:意思是交易执行时,它的执行环境或者上下文使得它好像是整个系统唯一正在运行的交易,实际上同一时刻可能有多个交易正在执行,但系统必须保证每个交易运行时就好像整个系统只有它一个。
durability: 思思是任何被执行完毕的交易所更改的数据必须持久化的存储在磁盘或相关介质上。
要保证acid原则的执行,我们需要设计两个模块,分别是恢复管理器和并发管理器,前者确保系统在出现意外奔溃或关闭时,数据依然处于一致性状态,后者确保多个交易在同时进行时,相互之间不产生干扰,本节先着重前者的实现。
恢复管理器的功能依赖于日志,系统在将数据写入磁盘前,必须将写入前的数据和写入后的数据记录在日志中,这样恢复管理器才能从日志中将数据还原,相应的日志格式如下:
代码语言:javascript
复制
<start, 1>
<commit , 1>
<start , 2>
<setint, 2, testfile, 1, 80, 1 ,2>
<setstring, 2, testfile, 1, 40, one, one!>
<commit, 2>
<start, 3>
<setint, 3, testfile, 1, 80, 2, 9999>
<rollback, 3>
<start, 4>
<commit, 4>
上面日志的逻辑为表示系统启动一次交易,交易对应的号码为1, 从上面日志可以看到,交易1启动后什么数据都没有写入就直接完成交易。然后系统启动交易2,日志表示交易2向文件testfile写入整形数据,写入的区块号为1,在区块内部的偏移为80,在写入前给定位置的数据为数值1,写入后数据变为2。我们可以发现有了这样的日志,恢复管理器就能执行灾后恢复,例如系统在进行交易2时,在执行setint操作时,系统突然奔溃,下次重启后回复管理器读取日志,它会发现有但是找不到对应的于是这时它就明白交易2在进行过程中发送了错误使得交易没有完成,此时它就能执行恢复,它读取日志,于是就能知道交易2在文件testfile的区块1中,偏移80字节处写入了数值2,在写入前数值为1,于是它就能将数值1重新写入到testfile文件区块1偏移为80字节位置,于是就相当于恢复了原来的写操作。
从上面日志可以看出,对于交易的记录总共有四种类型,分别为start, commit, rollback, 和update,update分为两种情况,也就是setint,写入整形数值,setstring,写入字符串。这里需要注意的是,系统为了支持高并发就会允许多个交易同时进行,于是有关交易的日志就会交叉出现在日志中,例如有可能,之后就会跟着等等,不同交易的日志记录交叉出现不会影响我们的识别逻辑,因为同一个交易不同时间操作一定会从上到下的呈现。
有了日志系统也能支持回滚操作,假设交易3写入数值9999到文件testfile区块号为1,偏移为80的位置,那么它会先生成日志,然后它立刻进行回滚操作,这时候我们可以从日志中发现,写入9999前,对应位置的数值是2,于是我们只要把数值2重新写入区块号为1偏移为80的位置就相当于还原了写入操作。因此回滚操作的步骤如下:
1,获得要执行回滚操作的交易号x
2,从下往上读取日志,如果记录对应的交易号不是x,那么忽略,继续往上读取
3,如果交易号是x,读取日志中数据写入前的数据,
4,将写入前的数据重新写入到日志记录的位置,继续执行步骤2
注意执行回滚时,我们要从日志文件的底部往前读,因为一个地方的数值可能会被写入多次,假设testfile区块号为1,偏移为80的地方,在第一次写入前数值为1,假设交易对这个位置分别写入了3次,写入的数值为2,3,4,那么回滚后给定位置的数值应该恢复为1,要实现这个效果,我们必须要从日志的底部往上读取。
我们再看容灾恢复,每次系统启动时它首先要执行灾后恢复工作。其目的是要保持数据的“一致性”,所谓“一致性”是指,所有没有执行commit的交易,它所写入的数据都要恢复为写入前的数据,所有已经执行了commit的交易,一定要确保写入的数据都已经存储到磁盘上。第二种情况完全有可能发生,因为数据会首先写入内存,然后系统会根据具体情况有选择的将数据写入磁盘,这是出于效率考虑,假设交易执行了commit操作,部分写入的数据还存储在内存中,此时系统突然奔溃,那么这部分在内存中的数据就不会写入到磁盘。
在恢复管理器看来,只要日志中有了commit记录,那么交易就完成了,但是它并不能保证交易写入的数据都已经存储在磁盘上了。所以恢复管理器有可能需要将日志中已经完成的交易再执行一次。
从上面描述可以看到,恢复管理器严重依赖于日志,因此我们必须确保在数据写入前,日志必须要先完成,如果顺序倒过来,先写入数据,再写入日志,如果写入数据后系统突然奔溃,那么写入信息就不会记录在日志里,那么恢复管理器就不能执行恢复功能了。要执行交易的重新执行功能,需要执行的步骤如下:
1,从头开始读取日志
2,当遇到”“ 类似的日志时,记录下当前交易号。
3,如果读到的日志时,将数值2再次写入到文件testfile,区块号为1,偏移为80的地方
恢复管理器在重新执行交易时,它需要对日志进行两次扫描,第一次扫描是从底部往上读取日志,这样恢复管理器才能知道哪些交易已经执行了commit操作,同时执行undo功能,也就是将没有执行commit操作的交易修改进行恢复,于是第一次扫描时它把那些已经执行commit操作的交易号记录下来,第二次扫描则是从日志的头开始读取,一旦读到这样的日志时,它会查找x是否是第一次扫描时已经记录下来的执行了commit操作的日志,如果是,那么它将x对应的setint,setstring操作再执行一次,然后要求缓存管理器立马将写入的数据存储到磁盘上。
问题在于第二步也就是重新执行交易对应操作可能不必要,因为交易修改极有可能已经写入到磁盘,如果再次进行磁盘写操作就会降低系统效率。我们可以避免第二步重写操作,只要我们让缓存管理器把所有修改先写入磁盘,然后再把commit记录写入日志即可,这样带来的代价是由于系统要频繁的写入磁盘由此会降低系统效率。同时我们也能让第一步变得没有必要,只要我们确保交易在执行commit前数据不写入磁盘即可,但如此带来的代价是,缓存的数据不写入磁盘,那么系统的吞吐量就会下降,因为缓存数据不写入磁盘,缓存页面就不能重新分配,于是新的交易就无法执行,因为得不到缓存。
现在还存在一个问题是,系统运行久了日志会非常庞大,它的数量甚至比数据要大,如果每次恢复都要读取日志,那么恢复流程会越来越久。因此恢复管理器在执行时,它只能读取部分日志,问题在于它如何决定读取多少日志数据呢。它只需要知道两个条件就能停止继续读取日志:
1,当前读取位置以上的日志都对应已经执行了commit操作的交易
2,所有已经执行commit的交易,其数据都已经写入到了磁盘。
当恢复管理器知道第一点,那么它就不用在执行回滚操作,知道第二点就不需要再将已经commit的操作再次执行。为了满足满足以上两点,系统需要执行以下步骤:
1,停止启动新的交易
2,等待当前所有正在进行的交易全部完成
3,将所有修改的缓存写入磁盘
4,插入一个中断点日志表示上面操作已经完成,并将中断点日志写入磁盘文件
5,开始接收新的交易
我们看一个具体例子:
代码语言:javascript
复制
<start, 0>
<setint, 0, junk, 33, 8, 542, 543>
<start, 1>
<start, 2>
<setstring, 2, junk, 44, 20, hello, ciao>
//在这里启动上面步骤,停止接收新的交易
<setint, 0, junk, 33, 12, joe, joseph>
<commit, 0>
//交易3准备发起,但是它只能等待
<setint, 2 , junk, 66, 8, 0, 116>
<commit, 2>
<checkpont> //中断点,上面的日志不用再考虑,下面交易3可以启动
<start, 3>
<setint, 3, junk, 33, 8, 43, 120>
从上面日志中,恢复管理器从下往上读取时,只要看到checkpoint记录就可以停止了。这种做法也有明显缺陷,那就是整个系统必须要停止一段时间,这对于数据吞吐量大的情形是不可接受的。为了处理这个问题,我们对原来算法进行改进,其步骤如下:
1,假设当前正在运行的交易为1,2,3,。。。。k
2,停止创建新的交易
3,将所有修改的缓存页面数据写入磁盘
4,将当前正在进行的交易号记录下来,例如
5,运行新交易创建
有了上面步骤后,恢复管理器在执行恢复时,依然要从底部往上读取日志,那么它如何知道怎么停止继续读取日志呢,当它读取到nqchkpt这条记录时,它把记录中的交易号用一个队列存储起来,然后继续往上读取日志,当它读取到这样的日志时,它查看x是否在队列中,如果在,那么就将它从队列中去除,这个步骤一直进行到队列为空,此时它就不用再继续读取日志了。
这个办法能大大缩短系统停止交易创建的时间,我们看个具体例子:
代码语言:javascript
复制
<start, 0>
<setint, 0, junk, 33, 8, 542, 543>
<start, 1>
<start, 2>
<commit, 1>
<setstring, 2, junk, 44, 20, hello, ciao>
<nqckpt, 0, 2>
<setstring, 0, junk, 33, 12, joe, joseph>
<commit, 0>
<start, 3>
<setint, 2, junk, 66, 8, 0, 116>
<setint, 3, junk, 33, 8, 543, 120>
恢复管理器在执行恢复任务时,依然从底部往上读取,当它读取最后一条日志时发现交易3没有对应的commit日志,于是系统知道它没有完成,于是执行回滚操作。读取时同样执行回滚操作。当读取到时,将0加入交易完成列表,注意系统并不能确定交易3的对应的数据是否都已经写入磁盘,因此需要找到交易0的起始处,让后把所有写入缓存的日志重新写入磁盘。
因此系统继续往上读取,此时系统知道交易0已经执行commit,所以忽略这条日志。继续往上读,读取到时,执行回滚操作,然后继续往上读取,一直读到时停止继续往上读,此时它开始从这里往下读,把所有有关交易0的操作对应的数据再次执行,然后写入磁盘,往下读取一直遇到时停止。
理论已经够多了,我们需要进入代码设计。首先在工程目录下创建一个子文件夹叫tx,它里面包含了所有与交易相关的模块,例如恢复管理器和并发管理器,后者我们在下一节讨论。首先我们先定义交易对象的接口,等完成并发管理器完成后再讨论它的实现,增加一个文件叫interface.go,添加代码如下:
代码语言:javascript
复制
package tx
import(
fm "file_manager"
lg "log_manager"
)
type transationinterface interface {
commit()
rollback()
recover()
pin(blk *fm.blockid)
unpin(blk *fm.blockid)
getint(blk *fm.blockid, offset uint64) uint64
getstring(blk *fm.blockid, offset uint64) string
setint(blk *fm.blockid, offset uint64, val uint64, oktolog bool)
setstring(blk *fm.blockid, offset uint64, val string, oktolog bool)
availablebuffers() uint64
size(filename string) uint64
append(filename string) *fm.blockid
blocksize() uint64
}
从上面代码看到,“交易”接口跟原先实现的buffer接口很像,它其实是对buffer接口的封装,在调用后者前,先使用恢复管理器和并发管理器做一些前提工作,交易对象的实现在后面再实现。
首先我们先看恢复日志的实现,从前面例子看,总共有六种用于恢复的日志,分别为start, commit, rollback, setint, setstring, checkpoint,所以我们先设定日志记录的接口,然后针对每种记录类型再实现对应实例,继续在interface.go中添加内容如下:
代码语言:javascript
复制
type record_type uint64
const (
checkpoint record_type = iota
start
commit
rollback
setint
setstring
)
const (
uint64_length = 8
)
type logrecordinterface interface {
op() record_type //返回记录的类别
txnumber() uint32 //对应交易的号码
undo(tx transationinterface) //回滚操作
tostring() string //获得记录的字符串内容
}
接下来我们分别创建继承logrecordinterface接口的记录实例,首先是start 记录,增加文件start_record.go,添加内容如下:
代码语言:javascript
复制
package tx
import (
fm "file_manager"
"fmt"
lg "log_manager"
)
type startrecord struct {
tx_num uint64
log_manager *lg.logmanager
}
func newstartrecord(p *fm.page, log_manager *lg.logmanager) *startrecord {
//p的头8字节对应日志的类型,从偏移8开始对应交易号
tx_num := p.getint(uint64_length)
return &startrecord{
tx_num: tx_num,
log_manager: log_manager,
}
}
func (s *startrecord) op() record_type {
return start
}
func (s *startrecord) txnumber() uint64 {
return s.tx_num
}
func (s *startrecord) undo() {
//该记录没有回滚操作的必要
}
func (s *startrecord) tostring() string {
str := fmt.sprintf("<start %d>", s.tx_num)
return str
}
func (s *startrecord) writetolog() (uint64, error) {
//日志写的不是字符串而是二进制数值
record := make([]byte, 2*uint64_length)
p := fm.newpagebybytes(record)
p.setint(uint64(0), uint64(start))
p.setint(uint64_length, s.tx_num)
return s.log_manager.append(record)
}
它的逻辑很简单,只需要关注tostring()和writetolog两个函数,前者返回其字符串格式,后者将start常量和交易号以二进制的形式写入缓存页面,下面我们运行上面的代码看看,增加record_test.go,添加代码如下:
代码语言:javascript
复制
package tx
import (
"fmt"
"github.com/stretchr/testify/require"
"testing"
fm "file_manager"
lm "log_manager"
"encoding/binary"
)
func teststartrecord(t *testing.t) {
file_manager, _ := fm.newfilemanager("recordtest", 400)
log_manager, _ := lm.newlogmanager(file_manager, "record_file")
tx_num := uint64(13) //交易号
p := fm.newpagebysize(32)
p.setint(0, uint64(start))
p.setint(8, uint64(tx_num))
start_record := newstartrecord(p, log_manager)
expected_str := fmt.sprintf("<start %d>", tx_num)
require.equal(t, expected_str, start_record.tostring())
_, err := start_record.writetolog()
require.nil(t, err)
iter := log_manager.iterator()
//检查写入的日志是否符号预期
rec := iter.next()
rec_op := binary.littleendian.uint64(rec[0:8])
rec_tx_num := binary.littleendian.uint64(rec[8:len(rec)])
require.equal(t, rec_op, start)
require.equal(t, rec_tx_num, tx_num)
}
在测试中,我们初始化了startrecord实例,然后调用其tostring和writetolog两个接口,然后检验其返回或者是写入缓存的数据是否正确,上面测试用例可以通过,因此我们当前实现的startrecord逻辑能保证基本正确性。
接下来我们继续实现其他几种恢复日志,首先是setstring格式的日志,创建set_string_record.go,实现代码如下:
代码语言:javascript
复制
package tx
import (
fm "file_manager"
"fmt"
lg "log_manager"
)
/*
在理论上一条setstring记录有7个字段,例如<setstring, 0, junk, 33, 12, joe, joseph>,
在实现上我们只用6个字段,上面的记录实际上对应了两次字符串的写入,第一次写入字符串"joseph",
第二次写入joe,因此在实现上它对应了两条包含六个字段的记录:
<setstring, 0, junk, 33, 12, joseph>
....
<setstring, 0, junk, 33, 12, joe>
回忆一下前面我们实现日志,日志是从下往上写,也就是<setstring, 0, junk, 33, 12, joe>会写在前面,
<setstring, 0, junk, 33, 12, joseph>会写在后面,
在回滚的时候,我们从上往下读取,因此我们会先读到joe,然后读到joseph,于是执行回滚时我们只要把
读到的字符串写入到给定位置就可以,例如我们先读到joe,然后写入junk文件区块为33偏移为12的地方,
然后又读取joseph,再次将它写入到junk文件区块为33偏移为12的地方,于是就实现了回滚效果,
所以实现上setstring记录不用写入7个字段,只有6个就可以
type setstringrecord struct {
tx_num uint64
offset uint64
val string
blk *fm.blockid
}
func newsetstringrecord(p fm.page) setstringrecord {
tpos := uint64(uint64_length)
tx_num := p.getint(tpos)
fpos := tpos + uint64_length
filename := p.getstring(fpos)
bpos := fpos + p.maxlengthforstring(filename)
blknum := p.getint(bpos)
blk := fm.newblockid(filename, blknum)
opos := bpos + uint64_length
offset := p.getint(opos)
vpos := opos + uint64_length
val := p.getstring(vpos) //将日志中的字符串再次写入给定位置
代码语言:javascript
复制
return &setstringrecord{
tx_num: tx_num,
offset: offset,
val: val,
blk: blk,
}
}
func (s *setstringrecord) op() record_type {
return setstring
}
func (s *setstringrecord) txnumber() uint64 {
return s.tx_num
}
func (s *setstringrecord) tostring() string {
str := fmt.sprintf(““, s.tx_num, s.blk.number(),
s.offset, s.val)
代码语言:javascript
复制
return str
}
func (s *setstringrecord) undo(tx transationinterface) {
tx.pin(s.blk)
tx.setstring(s.blk, s.offset, s.val, false) //将原来的字符串写回去
tx.unpin(s.blk)
}
func writesetstringlog(log_manager lg.logmanager, tx_num uint64,
blk fm.blockid, offset uint64, val string) (uint64, error) {
/
构造字符串内容的日志,setstringreord在构造中默认给定缓存页面已经有了字符串信息,
但是在初始状态,缓存页面可能还没有相应日志信息,这个接口的作用就是为给定缓存写入
字符串日志 /
tpos := uint64(uint64_length)
fpos := uint64(tpos + uint64_length)
p := fm.newpagebysize(1)
bpos := uint64(fpos + p.maxlengthforstring(blk.filename()))
opos := uint64(bpos + uint64_length)
vpos := uint64(opos + uint64_length)
rec_len := uint64(vpos + p.maxlengthforstring(val))
rec := make([]byte, rec_len)
代码语言:javascript
复制
p = fm.newpagebybytes(rec)
p.setint(0, uint64(setstring))
p.setint(tpos, tx_num)
p.setstring(fpos, blk.filename())
p.setint(bpos, blk.number())
p.setint(opos, offset)
p.setstring(vpos, val)
return log_manager.append(rec)
}
代码语言:javascript
复制
需要注意的是上面代码实现的setstring记录跟前面理论有所不同,传递给setstringrecord的是一个缓存页面,它其实对应了setstring的日志记录,writesetstringlog方法用于在给定日志中写入setstring记录。同时需要注意的是,它的undo方法需要通过实现了transationinterface的对象来完成,由于我们现在还没有实现交易对象,因此我们需要实现一个伪对象来测试上面代码,创建tx_sub.go,添加代码如下:
package tx
import (
fm “file_manager”
)
type txstub struct {
p *fm.page
}
func newtxstub(p fm.page) txstub {
return &txstub{
p: p,
}
}
func (t *txstub) commit() {
}
func (t *txstub) rollback() {
}
func (t *txstub) recover() {
}
func (t txstub) pin(_ fm.blockid) {
}
func (t txstub) unpin(_ fm.blockid) {
}
func (t txstub) getint(_ fm.blockid, offset uint64) uint64 {
代码语言:javascript
复制
return t.p.getint(offset)
}
func (t txstub) getstring(_ fm.blockid, offset uint64) string {
val := t.p.getstring(offset)
return val
}
func (t txstub) setint(_ fm.blockid, offset uint64, val uint64, _ bool) {
t.p.setint(offset, val)
}
func (t txstub) setstring(_ fm.blockid, offset uint64, val string, _ bool) {
t.p.setstring(offset, val)
}
func (t *txstub) availablebuffers() uint64 {
return 0
}
func (t *txstub) size(_ string) uint64 {
return 0
}
func (t txstub) append(_ string) fm.blockid {
return nil
}
func (t *txstub) blocksize() uint64 {
return 0
}
代码语言:javascript
复制
下面我们写测试用例,以便检测代码的逻辑,在record_test.go中添加代码如下:
func testsetstringrecord(t *testing.t) {
filemanager, := fm.newfilemanager(“recordtest”, 400)
logmanager, := lm.newlogmanager(file_manager, “setstring”)
代码语言:javascript
复制
str := "original string"
blk := uint64(1)
dummy_blk := fm.newblockid("dummy_id", blk)
tx_num := uint64(1)
offset := uint64(13)
//写入用于恢复的日志
writesetstringlog(log_manager, tx_num, dummy_blk, offset, str)
pp := fm.newpagebysize(400)
pp.setstring(offset, str)
iter := log_manager.iterator()
rec := iter.next()
log_p := fm.newpagebybytes(rec)
setstrrec := newsetstringrecord(log_p)
expectd_str := fmt.sprintf("<setstring %d %d %d %s>", tx_num, blk, offset, str)
require.equal(t, expectd_str, setstrrec.tostring())
pp.setstring(offset, "modify string 1")
pp.setstring(offset, "modify string 2")
txstub := newtxstub(pp)
setstrrec.undo(txstub)
recover_str := pp.getstring(offset)
require.equal(t, recover_str, str)
}
代码语言:javascript
复制
我们继续实现setint记录,它的实现就是把setstring记录的实现代码拷贝一份然后简单修改一下,创建set_int_record.go,然后把set_string_record.go的代码拷贝进去然后做一些修改如下:
package tx
import (
fm “file_manager”
“fmt”
lg “log_manager”
)
type setintrecord struct {
tx_num uint64
offset uint64
val uint64
blk *fm.blockid
}
func newsetintrecord(p fm.page) setintrecord {
tpos := uint64(uint64_length)
tx_num := p.getint(tpos)
fpos := tpos + uint64_length
filename := p.getstring(fpos)
bpos := fpos + p.maxlengthforstring(filename)
blknum := p.getint(bpos)
blk := fm.newblockid(filename, blknum)
opos := bpos + uint64_length
offset := p.getint(opos)
vpos := opos + uint64_length
val := p.getint(vpos) //将日志中的字符串再次写入给定位置
代码语言:javascript
复制
return &setintrecord{
tx_num: tx_num,
offset: offset,
val: val,
blk: blk,
}
}
func (s *setintrecord) op() record_type {
return setstring
}
func (s *setintrecord) txnumber() uint64 {
return s.tx_num
}
func (s *setintrecord) tostring() string {
str := fmt.sprintf(““, s.tx_num, s.blk.number(),
s.offset, s.val)
代码语言:javascript
复制
return str
}
func (s *setintrecord) undo(tx transationinterface) {
tx.pin(s.blk)
tx.setint(s.blk, s.offset, s.val, false) //将原来的字符串写回去
tx.unpin(s.blk)
}
func writesetintlog(log_manager lg.logmanager, tx_num uint64,
blk fm.blockid, offset uint64, val uint64) (uint64, error) {
代码语言:javascript
复制
tpos := uint64(uint64_length)
fpos := uint64(tpos + uint64_length)
p := fm.newpagebysize(1)
bpos := uint64(fpos + p.maxlengthforstring(blk.filename()))
opos := uint64(bpos + uint64_length)
vpos := uint64(opos + uint64_length)
rec_len := uint64(vpos + uint64_length)
rec := make([]byte, rec_len)
p = fm.newpagebybytes(rec)
p.setint(0, uint64(setstring))
p.setint(tpos, tx_num)
p.setstring(fpos, blk.filename())
p.setint(bpos, blk.number())
p.setint(opos, offset)
p.setint(vpos, val)
return log_manager.append(rec)
}
代码语言:javascript
复制
然后在record_test.go里面添加新的测试用例:
func testsetintrecord(t *testing.t) {
filemanager, := fm.newfilemanager(“recordtest”, 400)
logmanager, := lm.newlogmanager(file_manager, “setstring”)
代码语言:javascript
复制
val := uint64(11)
blk := uint64(1)
dummy_blk := fm.newblockid("dummy_id", blk)
tx_num := uint64(1)
offset := uint64(13)
//写入用于恢复的日志
writesetintlog(log_manager, tx_num, dummy_blk, offset, val)
pp := fm.newpagebysize(400)
pp.setint(offset, val)
iter := log_manager.iterator()
rec := iter.next()
log_p := fm.newpagebybytes(rec)
setintrec := newsetintrecord(log_p)
expectd_str := fmt.sprintf("<setint %d %d %d %d>", tx_num, blk, offset, val)
require.equal(t, expectd_str, setintrec.tostring())
pp.setint(offset, 22)
pp.setint(offset,33)
txstub := newtxstub(pp)
setintrec.undo(txstub)
recover_val := pp.getint(offset)
require.equal(t, recover_val, val)
}
代码语言:javascript
复制
最后还剩下rollback 和 commit两个记录,它们内容简单,我们一并放出来,创建rollback_record.go,添加代码如下:
package tx
import (
fm “file_manager”
“fmt”
lg “log_manager”
)
type rollbackrecord struct {
tx_num uint64
}
func newrollbackrecord(p fm.page) rollbackrecord {
return &rollbackrecord {
tx_num : p.getint(uint64_length),
}
}
func (r *rollbackrecord) op() record_type {
return rollback
}
func (r *rollbackrecord) txnumber() uint64 {
return r.tx_num
}
func(r *rollbackrecord) undo() {
//它没有回滚操作
}
func (r *rollbackrecord) tostring() string {
return fmt.sprintf(““, r.tx_num)
}
func writerollbacklog(lgmr lg.logmanager, tx_num uint64) (uint64, error){
rec := make([]byte, 2 uint64_length)
p := fm.newpagebybytes(rec)
p.setint(0, uint64(rollback))
p.setint(uint64_length, tx_num)
代码语言:javascript
复制
return lgmr.append(rec)
}
代码语言:javascript
复制
同理在record_test.go中添加测试用例如下:
func testrollbackrecord(t *testing.t) {
filemanager, := fm.newfilemanager(“recordtest”, 400)
logmanager, := lm.newlogmanager(file_manager, “rollback”)
tx_num := uint64(13)
writerollbacklog(log_manager, tx_num)
iter := log_manager.iterator()
rec := iter.next()
pp := fm.newpagebybytes(rec)
代码语言:javascript
复制
roll_back_rec := newrollbackrecord(pp)
expected_str := fmt.sprintf("<rollback %d>", tx_num)
require.equal(t, expected_str, roll_back_rec.tostring())
}
代码语言:javascript
复制
接下来我们添加commit记录,它的实现跟rollback差不多,添加commit_record.go然后添加代码如下:
package tx
import (
fm “file_manager”
“fmt”
lg “log_manager”
)
type commitrecord struct {
tx_num uint64
}
func newcommitkrecordrecord(p fm.page) commitrecord {
return &commitrecord {
tx_num : p.getint(uint64_length),
}
}
func (r *commitrecord) op() record_type {
return commit
}
func (r *commitrecord) txnumber() uint64 {
return r.tx_num
}
func(r *commitrecord) undo() {
//它没有回滚操作
}
func (r *commitrecord) tostring() string {
return fmt.sprintf(““, r.tx_num)
}
func writecommitkrecordlog(lgmr lg.logmanager, tx_num uint64) (uint64, error){
rec := make([]byte, 2 uint64_length)
p := fm.newpagebybytes(rec)
p.setint(0, uint64(commit))
p.setint(uint64_length, tx_num)
代码语言:javascript
复制
return lgmr.append(rec)
}
代码语言:javascript
复制
然后在record_test.go添加代码如下:
func testcommitrecord(t *testing.t) {
filemanager, := fm.newfilemanager(“recordtest”, 400)
logmanager, := lm.newlogmanager(file_manager, “commit”)
tx_num := uint64(13)
writecommitkrecordlog(log_manager, tx_num)
iter := log_manager.iterator()
rec := iter.next()
pp := fm.newpagebybytes(rec)
代码语言:javascript
复制
roll_back_rec := newcommitkrecordrecord(pp)
expected_str := fmt.sprintf("<commit %d>", tx_num)
require.equal(t, expected_str, roll_back_rec.tostring())
}
代码语言:javascript
复制
最后我们完成最简单的checkpoint记录,添加checkpoint_record.go,添加代码如下:
package tx
import (
fm “file_manager”
lg “log_manager”
“math”
)
type checkpointrecord struct{
}
func newcheckpointrecord() *checkpointrecord {
return &checkpointrecord{
代码语言:javascript
复制
}
}
func (c *checkpointrecord) op() record_type {
return checkpoint
}
func (c *checkpointrecord) txnumber() uint64 {
return math.maxuint64 //它没有对应的交易号
}
func (c *checkpointrecord) undo() {
}
func (c *checkpointrecord) tostring() string{
return ““
}
func writecheckpointtolog(lgmr *lg.logmanager) (uint64, error) {
rec := make([]byte, uint64_length)
p := fm.newpagebybytes(rec)
p.setint(0, uint64(checkpoint))
return lgmr.append(rec)
}
代码语言:javascript
复制
最后在record_test.go中添加相应测试用例:
func testcheckpointrecord(t *testing.t) {
filemanager, := fm.newfilemanager(“recordtest”, 400)
logmanager, := lm.newlogmanager(file_manager, “checkpoint”)
writecheckpointtolog(log_manager)
iter := log_manager.iterator()
rec := iter.next()
pp := fm.newpagebybytes(rec)
val := pp.getint(0)
代码语言:javascript
复制
require.equal(t, val, uint64(checkpoint))
check_point_rec := newcheckpointrecord()
expected_str := "<checkpoint>"
require.equal(t, expected_str, check_point_rec.tostring())
}
```
经过调试,所有测试用例都能通过。要想更好的了解代码逻辑,请在b站搜索coding迪斯尼,我会在视频中进行调试和演示。代码下载:https://github.com/wycl16514/database-system-recovery-record.git,[更多干货](http://m.study.163.com/provider/7600199/index.htm?share=2&shareid=7600199):http://m.study.163.com/provider/7600199/index.htm?share=2&shareid=7600199
山东金麒麟专场 — 纯前端表格技术应用研讨会:2018 年 7 月 16 日,“赋能开发者,走进你身边——纯前端表格技术应用研讨会” 走进山东金麒麟股份有限公司(以下简称金麒麟)。
西安葡萄城业务总监郭玮、资深前端技术专家姚尧受邀与山东金麒麟股份有限公司展开深入探讨,围绕葡萄城企业文化、数据管理系统实践、前端技术发展趋势以及 纯前端表格控件 spreadjs 在各领域应用场景等四大核心议题
作为全球领先的集开发工具、商业智能解决方案、管理系统设计工具于一身的软件和服务提供商,通过本次研讨会,葡萄城实实在在地感受到了所提供的产品和服务为各领域行业带来的价值,为此,葡萄城的技术专家们也感到无比自豪和骄傲
葡萄城公司成立于 1980 年,是全球领先的集开发工具、商业智能解决方案、管理系统设计工具于一身的软件和服务提供商。
葡萄城的控件和软件产品在国内外屡获殊荣,在全球被数十万家企业、学校和政府机构广泛应用。
「镁客·请讲」魔鱼互动韩宇:专注行业应用,打造可看可用的ar vr产品:“那是2009年,经过评估后自己在技术和社会阅历上的积累都非常薄弱,直接创业成功的概率几乎为零,所以特意选择了一个未来应用广泛又相对冷门的专业——地理信息系统继续深造。”韩宇回忆说。
魔鱼互动创始人-韩宇“2015年,感觉积累的差不多了,而且赶上政府大力扶持创业,整体创业环境非常好的时机。于是果断投入了创业大军。”那一年,经过多年积累的韩宇,联合两位挚友正式启动了他们的创业之旅。
截止2015年底,魔鱼互动营业额不足10万元,一名核心成员由于家庭原因被迫离开了公司,最惨淡时只剩下韩宇和另一名联合创始人黄致尧。
幸好,2015年5月公司通过了新城科技园入孵项目评审,在政府的扶持下,公司一步步走上正轨。“那是一场及时雨,我们有幸通过了新城科技园入孵项目评审,有了固定办公室,才逐步形成了一个稳定的团队。”
他们已经研发了三维机房实时监控系统、基于三维城市的统计信息可视化平台、web版三维城市在线漫游系统等,正在研发融合人文环境影响因素的虚拟楼盘展示系统。
用 python 庆祝拜登当选的十种方式:还记得上周三的时候川普在摇摆州上领先拜登好几个点,我都不敢打开炒股软件,结果周四醒来一下就反转了,一路逆袭到今天,基本上稳了:
?
musa_zadeh[4]
import time
for i in range(2021,2025):
print("hello biden")
time.sleep(365*24*60*60)
# 拜登被称为睡王
1分钟链圈 | 曝光!区块链游戏5天就能复制一款,玩家靠击鼓传花获利!bch将于5月16日0点左右执行硬分叉:为瑞波币产品和项目提供资金支持bch将于北京时间5月16日0点左右执行硬分叉,区块大小从8mb增加至32mb全球俄罗斯一银行通过加密货币帮助委内瑞拉石油币发展欧盟批准aml(反洗钱)在加密货币市场的匿名立法美国佛罗里达州塞米诺尔县税务办公室接受
(news.bitcoin)5.圣路易联储主席james bullard:汇率问题是数字货币获广泛承认的最大障碍美联储圣路易联储主席james bullard在coindesk 2018年度共识大会期间接受
根据美联社报道,早期的石油币投资者将向委内瑞拉政府注册并下载数字货币的钱包,通过向evrofinance mosnarbank的政府所有账户提供1000欧元(约合1190美元)的最低金额来购买该钱包。
(cointelegraph)11.美国佛罗里达州塞米诺尔县税务办公室接受btc和bch塞米诺尔县税务员joel m.
区块链允许塞米诺尔县的税务师在提高支付准确性、透明度和效率的同时,消除大部分的高额费用。
转载请注明出处,本站网址:
http://www.831209.com.cn/news_2274.html