世界首例針對(duì)特斯拉自動(dòng)駕駛判罰:德國(guó)裁定autopilot廣告誤導(dǎo)買家,特斯拉柏林工廠可能受阻:保留上訴權(quán),但特斯拉還是改了宣傳這個(gè)案件中,做出最終裁決的是慕尼黑法院,但原告并不是德國(guó)政府監(jiān)管機(jī)構(gòu),而是德國(guó)反不公平競(jìng)爭(zhēng)與保護(hù)中心(wettbewerbszentrale),一個(gè)反不當(dāng)競(jìng)爭(zhēng)的民間獨(dú)立機(jī)構(gòu)
此外,特斯拉的宣傳,還帶著暗示——相關(guān)的自動(dòng)駕駛系統(tǒng)已經(jīng)在德國(guó)得到法律法規(guī)允許。但實(shí)際并沒(méi)有。外媒路透也評(píng)價(jià)說(shuō),特斯拉這種宣傳還帶來(lái)更進(jìn)一步的社會(huì)問(wèn)題。?
但德國(guó)法院的判決也不是完全沒(méi)有影響,外媒分析,至少特斯拉柏林工廠可能會(huì)受阻。路透社透露,特斯拉柏林工廠一開(kāi)始籌建就不是一帆風(fēng)順的。
首先是德國(guó)地方政府給了特斯拉建廠一些支持政策,但并不像中國(guó)上海一樣大力支持特斯拉。中央政府層面,德國(guó)聯(lián)邦政府給予的電動(dòng)車購(gòu)置優(yōu)惠也沒(méi)有中國(guó)政府力度大。
所以現(xiàn)在德國(guó)的判例,對(duì)特斯拉的自動(dòng)駕駛,可能只是一個(gè)開(kāi)始。更多國(guó)家的交通安全部門、車主幸存者,或許會(huì)以此為契機(jī),在更多國(guó)家和地區(qū),向特斯拉討要說(shuō)法。
運(yùn)維大數(shù)據(jù)平臺(tái)落地構(gòu)想:微信圖片_20190801133837.jpg
現(xiàn)在全國(guó)政務(wù)行業(yè)都在推行數(shù)字政府、數(shù)字中國(guó)的落地。
大部分省市都在進(jìn)行iaas資源、paas資源、daas資源以及saas資源的整合;構(gòu)建基于ipds架構(gòu)的云平臺(tái)數(shù)據(jù)中心,通過(guò)ipds云平臺(tái)數(shù)據(jù)中心,為用戶提供各類資源服務(wù)。
上述是以政務(wù)為例,回望企業(yè)客戶,建設(shè)路徑亦如此。
同時(shí)傳統(tǒng)的監(jiān)控能對(duì)單個(gè)點(diǎn)進(jìn)行監(jiān)控,很難結(jié)合cmdb配置平臺(tái)實(shí)現(xiàn)關(guān)聯(lián)關(guān)系的監(jiān)控,即某個(gè)點(diǎn)出現(xiàn)故障時(shí),會(huì)影響到哪些業(yè)務(wù),哪些操作系統(tǒng)。
作者:何世曉
【陽(yáng)光私塾】告密者,一種歷史幽靈的閃現(xiàn):這所我曾經(jīng)應(yīng)邀前往演講的學(xué)校,涌現(xiàn)出兩名杰出的學(xué)生告密者,她們將自己的“古代漢語(yǔ)”老師告到市教委和公安局,理由是在課堂上“批評(píng)文化”和“批評(píng)政府”。
這種復(fù)雜的四重監(jiān)視體系,培訓(xùn)了龐大的告密者隊(duì)伍,成為專制王朝的最大幫手。
這場(chǎng)從告密開(kāi)始的運(yùn)動(dòng),最終升溫到令人發(fā)指的地步,據(jù)廣西《武宣縣無(wú)產(chǎn)階級(jí)文化大革命大事件》記載,該縣武宣中學(xué),甚至出現(xiàn)食人盛宴——眾學(xué)生在校園內(nèi)揭發(fā)批斗完自己的老師之后,將他們剖腹肢解,就地架設(shè)爐灶,烹煮至熟
眾所周知,批評(píng)是幫助政府改進(jìn)工作和推動(dòng)社會(huì)進(jìn)步的重要方式,也是憲法賦予的基本權(quán)利。
容忍和聽(tīng)取不同意見(jiàn),乃是衡量政治清明的基本標(biāo)尺,而在以“和諧”為政治目標(biāo)的社會(huì)中營(yíng)造斗爭(zhēng)氣氛,置敢說(shuō)真話的教師于被告密的恐懼之中,這不僅是以司法教育為使命的高等學(xué)府的恥辱,更是社會(huì)正義和民主進(jìn)程的敵人。
美國(guó)對(duì)勒索軟件重拳出擊,成立特別小組,懸賞1000萬(wàn)美元:(nist) 以及財(cái)政部、衛(wèi)生與公眾服務(wù)部的最新勒索軟件相關(guān)警報(bào)和威脅的指南。
拜登政府顯然也在考慮對(duì)黑客團(tuán)伙發(fā)動(dòng)破壞性網(wǎng)絡(luò)攻擊的可能性,并努力與私營(yíng)部門組織(包括網(wǎng)絡(luò)保險(xiǎn)提供商和關(guān)鍵基礎(chǔ)設(shè)施公司)建立伙伴關(guān)系,以分享有關(guān)勒索軟件攻擊的信息。
阿波羅信息系統(tǒng)公司和ciso公司副總裁安迪·貝內(nèi)特說(shuō),現(xiàn)在的問(wèn)題是接下來(lái)會(huì)發(fā)生什么。他們將如何超越聯(lián)邦政府的職能,使整個(gè)國(guó)家都有能力和賦權(quán)?
他還指出,各機(jī)構(gòu)之間的合作對(duì)于制定戰(zhàn)略和結(jié)合專業(yè)知識(shí)來(lái)應(yīng)對(duì)當(dāng)前勒索軟件攻擊的流行至關(guān)重要。與傳統(tǒng)恐怖主義不同,網(wǎng)絡(luò)攻擊和反擊手段并非政府所獨(dú)有。
這個(gè)特別小組是絕對(duì)值得的,如果做對(duì)了,將對(duì)打擊和建立政府所有領(lǐng)域的勒索軟件的復(fù)原力產(chǎn)生重大影響,他總結(jié)道。原文翻譯自helpnetsecurity
開(kāi)源政務(wù)OA系統(tǒng):巫山政府辦公政務(wù)OA系統(tǒng)中自己動(dòng)手寫數(shù)據(jù)庫(kù)系統(tǒng):容災(zāi)恢復(fù)原理和容災(zāi)恢復(fù)日志的設(shè)計(jì)勢(shì),并討論如何在實(shí)踐中有效地應(yīng)用和管理這一系統(tǒng)。一、公文管理系統(tǒng)的定義 公文管理系統(tǒng)是一種基于計(jì)算機(jī)技術(shù)的軟件系統(tǒng),用于管理和處理各類公文文件。它通過(guò)數(shù)字化和自動(dòng)化的方式,將公文的創(chuàng)建、審批、傳遞和歸檔等環(huán)節(jié)進(jìn)行整合和優(yōu)化,從而提高工作效率和管理水平。二、公文管理系統(tǒng)的功能 1.公文創(chuàng)建和編輯:公文管理系統(tǒng)提供了豐富的模板和格式,使得公文的創(chuàng)建和編輯變得簡(jiǎn)單和規(guī)范化。用戶可以根據(jù)需要選擇相應(yīng)的模板,填寫相關(guān)內(nèi)容,并進(jìn)行格式調(diào)整和排版。2.公文審批和流轉(zhuǎn):公文管理系統(tǒng)實(shí)現(xiàn)了公文的電子審批和流轉(zhuǎn),取代了傳統(tǒng)的紙質(zhì)審批流程。通過(guò)系統(tǒng)的設(shè)置,可以實(shí)現(xiàn)多級(jí)審批、并行審批和串行審批等不同的審批方式,大大縮短了審批時(shí)間和流轉(zhuǎn)周期。3.公文傳遞和共享:公文管理系統(tǒng)支持公文的電子傳遞和共享,使得公文的傳遞更加數(shù)據(jù)庫(kù)系統(tǒng)有一個(gè)極其重要的功能,那就是要保持?jǐn)?shù)據(jù)一致性。在用戶往數(shù)據(jù)庫(kù)寫入數(shù)據(jù)后,如果數(shù)據(jù)庫(kù)返回寫入成功,那么數(shù)據(jù)就必須永久性的保存在磁盤上。此外作為一個(gè)系統(tǒng),它必須具備自恢復(fù)功能,也就是如果系統(tǒng)出現(xiàn)意外奔潰,無(wú)論是內(nèi)部錯(cuò)誤,還是外部原因,例如突然斷電等,系統(tǒng)都必須要保持?jǐn)?shù)據(jù)的一致性。
例如我們從數(shù)據(jù)庫(kù)中訂購(gòu)一張機(jī)票,假設(shè)機(jī)票數(shù)量正確減一,但還沒(méi)扣款,此時(shí)系統(tǒng)突然奔潰,如果系統(tǒng)沒(méi)有預(yù)防措施就會(huì)導(dǎo)致數(shù)據(jù)出現(xiàn)不一致性,也就是機(jī)票出票數(shù)量和相應(yīng)的支付款項(xiàng)不一致,沒(méi)有容錯(cuò)性的數(shù)據(jù)庫(kù)系統(tǒng)就不會(huì)有市場(chǎng),本節(jié)的目的是設(shè)計(jì)恢復(fù)機(jī)制,確保數(shù)據(jù)在任何突如其來(lái)的意外情況下依然保持?jǐn)?shù)據(jù)一致性。
因此數(shù)據(jù)庫(kù)系統(tǒng)必須遵守acid原則,他們分別是atomicity, consistency, isolation, durability:
atomicity: 其意思是任何數(shù)據(jù)操作要不完全執(zhí)行,要不就一點(diǎn)作用也沒(méi)有。數(shù)據(jù)庫(kù)中有一個(gè)叫“交易”的概念,也就是transation,它表示一系列必須全部完成的讀寫操作,必須是序列化的,也就是交易所給定的執(zhí)行步驟在運(yùn)行時(shí)不能被打斷,或者是中間突然插入其他交易的步驟,所以它也叫原子化。
consistency:意思是任何交易都必須確保數(shù)據(jù)處于一致?tīng)顟B(tài)。也就是說(shuō)交易中所定義的一系列讀寫步驟必須作為一個(gè)統(tǒng)一的單元進(jìn)行執(zhí)行,當(dāng)交易進(jìn)行時(shí),數(shù)據(jù)庫(kù)系統(tǒng)的運(yùn)行狀態(tài)就好像是一個(gè)單線程應(yīng)用。
isolation:意思是交易執(zhí)行時(shí),它的執(zhí)行環(huán)境或者上下文使得它好像是整個(gè)系統(tǒng)唯一正在運(yùn)行的交易,實(shí)際上同一時(shí)刻可能有多個(gè)交易正在執(zhí)行,但系統(tǒng)必須保證每個(gè)交易運(yùn)行時(shí)就好像整個(gè)系統(tǒng)只有它一個(gè)。
durability: 思思是任何被執(zhí)行完畢的交易所更改的數(shù)據(jù)必須持久化的存儲(chǔ)在磁盤或相關(guān)介質(zhì)上。
要保證acid原則的執(zhí)行,我們需要設(shè)計(jì)兩個(gè)模塊,分別是恢復(fù)管理器和并發(fā)管理器,前者確保系統(tǒng)在出現(xiàn)意外奔潰或關(guān)閉時(shí),數(shù)據(jù)依然處于一致性狀態(tài),后者確保多個(gè)交易在同時(shí)進(jìn)行時(shí),相互之間不產(chǎn)生干擾,本節(jié)先著重前者的實(shí)現(xiàn)。
恢復(fù)管理器的功能依賴于日志,系統(tǒng)在將數(shù)據(jù)寫入磁盤前,必須將寫入前的數(shù)據(jù)和寫入后的數(shù)據(jù)記錄在日志中,這樣恢復(fù)管理器才能從日志中將數(shù)據(jù)還原,相應(yīng)的日志格式如下:
代碼語(yǔ)言:javascript
復(fù)制
<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>
上面日志的邏輯為表示系統(tǒng)啟動(dòng)一次交易,交易對(duì)應(yīng)的號(hào)碼為1, 從上面日志可以看到,交易1啟動(dòng)后什么數(shù)據(jù)都沒(méi)有寫入就直接完成交易。然后系統(tǒng)啟動(dòng)交易2,日志表示交易2向文件testfile寫入整形數(shù)據(jù),寫入的區(qū)塊號(hào)為1,在區(qū)塊內(nèi)部的偏移為80,在寫入前給定位置的數(shù)據(jù)為數(shù)值1,寫入后數(shù)據(jù)變?yōu)?。我們可以發(fā)現(xiàn)有了這樣的日志,恢復(fù)管理器就能執(zhí)行災(zāi)后恢復(fù),例如系統(tǒng)在進(jìn)行交易2時(shí),在執(zhí)行setint操作時(shí),系統(tǒng)突然奔潰,下次重啟后回復(fù)管理器讀取日志,它會(huì)發(fā)現(xiàn)有但是找不到對(duì)應(yīng)的于是這時(shí)它就明白交易2在進(jìn)行過(guò)程中發(fā)送了錯(cuò)誤使得交易沒(méi)有完成,此時(shí)它就能執(zhí)行恢復(fù),它讀取日志,于是就能知道交易2在文件testfile的區(qū)塊1中,偏移80字節(jié)處寫入了數(shù)值2,在寫入前數(shù)值為1,于是它就能將數(shù)值1重新寫入到testfile文件區(qū)塊1偏移為80字節(jié)位置,于是就相當(dāng)于恢復(fù)了原來(lái)的寫操作。
從上面日志可以看出,對(duì)于交易的記錄總共有四種類型,分別為start, commit, rollback, 和update,update分為兩種情況,也就是setint,寫入整形數(shù)值,setstring,寫入字符串。這里需要注意的是,系統(tǒng)為了支持高并發(fā)就會(huì)允許多個(gè)交易同時(shí)進(jìn)行,于是有關(guān)交易的日志就會(huì)交叉出現(xiàn)在日志中,例如有可能,之后就會(huì)跟著等等,不同交易的日志記錄交叉出現(xiàn)不會(huì)影響我們的識(shí)別邏輯,因?yàn)橥粋€(gè)交易不同時(shí)間操作一定會(huì)從上到下的呈現(xiàn)。
有了日志系統(tǒng)也能支持回滾操作,假設(shè)交易3寫入數(shù)值9999到文件testfile區(qū)塊號(hào)為1,偏移為80的位置,那么它會(huì)先生成日志,然后它立刻進(jìn)行回滾操作,這時(shí)候我們可以從日志中發(fā)現(xiàn),寫入9999前,對(duì)應(yīng)位置的數(shù)值是2,于是我們只要把數(shù)值2重新寫入?yún)^(qū)塊號(hào)為1偏移為80的位置就相當(dāng)于還原了寫入操作。因此回滾操作的步驟如下:
1,獲得要執(zhí)行回滾操作的交易號(hào)x
2,從下往上讀取日志,如果記錄對(duì)應(yīng)的交易號(hào)不是x,那么忽略,繼續(xù)往上讀取
3,如果交易號(hào)是x,讀取日志中數(shù)據(jù)寫入前的數(shù)據(jù),
4,將寫入前的數(shù)據(jù)重新寫入到日志記錄的位置,繼續(xù)執(zhí)行步驟2
注意執(zhí)行回滾時(shí),我們要從日志文件的底部往前讀,因?yàn)橐粋€(gè)地方的數(shù)值可能會(huì)被寫入多次,假設(shè)testfile區(qū)塊號(hào)為1,偏移為80的地方,在第一次寫入前數(shù)值為1,假設(shè)交易對(duì)這個(gè)位置分別寫入了3次,寫入的數(shù)值為2,3,4,那么回滾后給定位置的數(shù)值應(yīng)該恢復(fù)為1,要實(shí)現(xiàn)這個(gè)效果,我們必須要從日志的底部往上讀取。
我們?cè)倏慈轂?zāi)恢復(fù),每次系統(tǒng)啟動(dòng)時(shí)它首先要執(zhí)行災(zāi)后恢復(fù)工作。其目的是要保持?jǐn)?shù)據(jù)的“一致性”,所謂“一致性”是指,所有沒(méi)有執(zhí)行commit的交易,它所寫入的數(shù)據(jù)都要恢復(fù)為寫入前的數(shù)據(jù),所有已經(jīng)執(zhí)行了commit的交易,一定要確保寫入的數(shù)據(jù)都已經(jīng)存儲(chǔ)到磁盤上。第二種情況完全有可能發(fā)生,因?yàn)閿?shù)據(jù)會(huì)首先寫入內(nèi)存,然后系統(tǒng)會(huì)根據(jù)具體情況有選擇的將數(shù)據(jù)寫入磁盤,這是出于效率考慮,假設(shè)交易執(zhí)行了commit操作,部分寫入的數(shù)據(jù)還存儲(chǔ)在內(nèi)存中,此時(shí)系統(tǒng)突然奔潰,那么這部分在內(nèi)存中的數(shù)據(jù)就不會(huì)寫入到磁盤。
在恢復(fù)管理器看來(lái),只要日志中有了commit記錄,那么交易就完成了,但是它并不能保證交易寫入的數(shù)據(jù)都已經(jīng)存儲(chǔ)在磁盤上了。所以恢復(fù)管理器有可能需要將日志中已經(jīng)完成的交易再執(zhí)行一次。
從上面描述可以看到,恢復(fù)管理器嚴(yán)重依賴于日志,因此我們必須確保在數(shù)據(jù)寫入前,日志必須要先完成,如果順序倒過(guò)來(lái),先寫入數(shù)據(jù),再寫入日志,如果寫入數(shù)據(jù)后系統(tǒng)突然奔潰,那么寫入信息就不會(huì)記錄在日志里,那么恢復(fù)管理器就不能執(zhí)行恢復(fù)功能了。要執(zhí)行交易的重新執(zhí)行功能,需要執(zhí)行的步驟如下:
1,從頭開(kāi)始讀取日志
2,當(dāng)遇到”\“ 類似的日志時(shí),記錄下當(dāng)前交易號(hào)。
3,如果讀到的日志時(shí),將數(shù)值2再次寫入到文件testfile,區(qū)塊號(hào)為1,偏移為80的地方
恢復(fù)管理器在重新執(zhí)行交易時(shí),它需要對(duì)日志進(jìn)行兩次掃描,第一次掃描是從底部往上讀取日志,這樣恢復(fù)管理器才能知道哪些交易已經(jīng)執(zhí)行了commit操作,同時(shí)執(zhí)行undo功能,也就是將沒(méi)有執(zhí)行commit操作的交易修改進(jìn)行恢復(fù),于是第一次掃描時(shí)它把那些已經(jīng)執(zhí)行commit操作的交易號(hào)記錄下來(lái),第二次掃描則是從日志的頭開(kāi)始讀取,一旦讀到\這樣的日志時(shí),它會(huì)查找x是否是第一次掃描時(shí)已經(jīng)記錄下來(lái)的執(zhí)行了commit操作的日志,如果是,那么它將x對(duì)應(yīng)的setint,setstring操作再執(zhí)行一次,然后要求緩存管理器立馬將寫入的數(shù)據(jù)存儲(chǔ)到磁盤上。
問(wèn)題在于第二步也就是重新執(zhí)行交易對(duì)應(yīng)操作可能不必要,因?yàn)榻灰仔薷臉O有可能已經(jīng)寫入到磁盤,如果再次進(jìn)行磁盤寫操作就會(huì)降低系統(tǒng)效率。我們可以避免第二步重寫操作,只要我們讓緩存管理器把所有修改先寫入磁盤,然后再把commit記錄寫入日志即可,這樣帶來(lái)的代價(jià)是由于系統(tǒng)要頻繁的寫入磁盤由此會(huì)降低系統(tǒng)效率。同時(shí)我們也能讓第一步變得沒(méi)有必要,只要我們確保交易在執(zhí)行commit前數(shù)據(jù)不寫入磁盤即可,但如此帶來(lái)的代價(jià)是,緩存的數(shù)據(jù)不寫入磁盤,那么系統(tǒng)的吞吐量就會(huì)下降,因?yàn)榫彺鏀?shù)據(jù)不寫入磁盤,緩存頁(yè)面就不能重新分配,于是新的交易就無(wú)法執(zhí)行,因?yàn)榈貌坏骄彺妗?現(xiàn)在還存在一個(gè)問(wèn)題是,系統(tǒng)運(yùn)行久了日志會(huì)非常龐大,它的數(shù)量甚至比數(shù)據(jù)要大,如果每次恢復(fù)都要讀取日志,那么恢復(fù)流程會(huì)越來(lái)越久。因此恢復(fù)管理器在執(zhí)行時(shí),它只能讀取部分日志,問(wèn)題在于它如何決定讀取多少日志數(shù)據(jù)呢。它只需要知道兩個(gè)條件就能停止繼續(xù)讀取日志:
1,當(dāng)前讀取位置以上的日志都對(duì)應(yīng)已經(jīng)執(zhí)行了commit操作的交易
2,所有已經(jīng)執(zhí)行commit的交易,其數(shù)據(jù)都已經(jīng)寫入到了磁盤。
當(dāng)恢復(fù)管理器知道第一點(diǎn),那么它就不用在執(zhí)行回滾操作,知道第二點(diǎn)就不需要再將已經(jīng)commit的操作再次執(zhí)行。為了滿足滿足以上兩點(diǎn),系統(tǒng)需要執(zhí)行以下步驟:
1,停止啟動(dòng)新的交易
2,等待當(dāng)前所有正在進(jìn)行的交易全部完成
3,將所有修改的緩存寫入磁盤
4,插入一個(gè)中斷點(diǎn)日志表示上面操作已經(jīng)完成,并將中斷點(diǎn)日志寫入磁盤文件
5,開(kāi)始接收新的交易
我們看一個(gè)具體例子:
代碼語(yǔ)言:javascript
復(fù)制
<start, 0>
<setint, 0, junk, 33, 8, 542, 543>
<start, 1>
<start, 2>
<setstring, 2, junk, 44, 20, hello, ciao>
//在這里啟動(dòng)上面步驟,停止接收新的交易
<setint, 0, junk, 33, 12, joe, joseph>
<commit, 0>
//交易3準(zhǔn)備發(fā)起,但是它只能等待
<setint, 2 , junk, 66, 8, 0, 116>
<commit, 2>
\<checkpont\> //中斷點(diǎn),上面的日志不用再考慮,下面交易3可以啟動(dòng)
<start, 3>
<setint, 3, junk, 33, 8, 43, 120>
從上面日志中,恢復(fù)管理器從下往上讀取時(shí),只要看到checkpoint記錄就可以停止了。這種做法也有明顯缺陷,那就是整個(gè)系統(tǒng)必須要停止一段時(shí)間,這對(duì)于數(shù)據(jù)吞吐量大的情形是不可接受的。為了處理這個(gè)問(wèn)題,我們對(duì)原來(lái)算法進(jìn)行改進(jìn),其步驟如下:
1,假設(shè)當(dāng)前正在運(yùn)行的交易為1,2,3,。。。。k
2,停止創(chuàng)建新的交易
3,將所有修改的緩存頁(yè)面數(shù)據(jù)寫入磁盤
4,將當(dāng)前正在進(jìn)行的交易號(hào)記錄下來(lái),例如
5,運(yùn)行新交易創(chuàng)建
有了上面步驟后,恢復(fù)管理器在執(zhí)行恢復(fù)時(shí),依然要從底部往上讀取日志,那么它如何知道怎么停止繼續(xù)讀取日志呢,當(dāng)它讀取到nqchkpt這條記錄時(shí),它把記錄中的交易號(hào)用一個(gè)隊(duì)列存儲(chǔ)起來(lái),然后繼續(xù)往上讀取日志,當(dāng)它讀取到\這樣的日志時(shí),它查看x是否在隊(duì)列中,如果在,那么就將它從隊(duì)列中去除,這個(gè)步驟一直進(jìn)行到隊(duì)列為空,此時(shí)它就不用再繼續(xù)讀取日志了。
這個(gè)辦法能大大縮短系統(tǒng)停止交易創(chuàng)建的時(shí)間,我們看個(gè)具體例子:
代碼語(yǔ)言:javascript
復(fù)制
<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>
恢復(fù)管理器在執(zhí)行恢復(fù)任務(wù)時(shí),依然從底部往上讀取,當(dāng)它讀取最后一條日志時(shí)發(fā)現(xiàn)交易3沒(méi)有對(duì)應(yīng)的commit日志,于是系統(tǒng)知道它沒(méi)有完成,于是執(zhí)行回滾操作。讀取時(shí)同樣執(zhí)行回滾操作。當(dāng)讀取到時(shí),將0加入交易完成列表,注意系統(tǒng)并不能確定交易3的對(duì)應(yīng)的數(shù)據(jù)是否都已經(jīng)寫入磁盤,因此需要找到交易0的起始處,讓后把所有寫入緩存的日志重新寫入磁盤。
因此系統(tǒng)繼續(xù)往上讀取,此時(shí)系統(tǒng)知道交易0已經(jīng)執(zhí)行commit,所以忽略這條日志。繼續(xù)往上讀,讀取到時(shí),執(zhí)行回滾操作,然后繼續(xù)往上讀取,一直讀到時(shí)停止繼續(xù)往上讀,此時(shí)它開(kāi)始從這里往下讀,把所有有關(guān)交易0的操作對(duì)應(yīng)的數(shù)據(jù)再次執(zhí)行,然后寫入磁盤,往下讀取一直遇到時(shí)停止。
理論已經(jīng)夠多了,我們需要進(jìn)入代碼設(shè)計(jì)。首先在工程目錄下創(chuàng)建一個(gè)子文件夾叫tx,它里面包含了所有與交易相關(guān)的模塊,例如恢復(fù)管理器和并發(fā)管理器,后者我們?cè)谙乱还?jié)討論。首先我們先定義交易對(duì)象的接口,等完成并發(fā)管理器完成后再討論它的實(shí)現(xiàn),增加一個(gè)文件叫interface.go,添加代碼如下:
代碼語(yǔ)言:javascript
復(fù)制
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
}
從上面代碼看到,“交易”接口跟原先實(shí)現(xiàn)的buffer接口很像,它其實(shí)是對(duì)buffer接口的封裝,在調(diào)用后者前,先使用恢復(fù)管理器和并發(fā)管理器做一些前提工作,交易對(duì)象的實(shí)現(xiàn)在后面再實(shí)現(xiàn)。
首先我們先看恢復(fù)日志的實(shí)現(xiàn),從前面例子看,總共有六種用于恢復(fù)的日志,分別為start, commit, rollback, setint, setstring, checkpoint,所以我們先設(shè)定日志記錄的接口,然后針對(duì)每種記錄類型再實(shí)現(xiàn)對(duì)應(yīng)實(shí)例,繼續(xù)在interface.go中添加內(nèi)容如下:
代碼語(yǔ)言:javascript
復(fù)制
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 //對(duì)應(yīng)交易的號(hào)碼
undo(tx transationinterface) //回滾操作
tostring() string //獲得記錄的字符串內(nèi)容
}
接下來(lái)我們分別創(chuàng)建繼承l(wèi)ogrecordinterface接口的記錄實(shí)例,首先是start 記錄,增加文件start_record.go,添加內(nèi)容如下:
代碼語(yǔ)言:javascript
復(fù)制
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字節(jié)對(duì)應(yīng)日志的類型,從偏移8開(kāi)始對(duì)應(yīng)交易號(hào)
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() {
//該記錄沒(méi)有回滾操作的必要
}
func (s *startrecord) tostring() string {
str := fmt.sprintf("<start %d>", s.tx_num)
return str
}
func (s *startrecord) writetolog() (uint64, error) {
//日志寫的不是字符串而是二進(jìn)制數(shù)值
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)
}
它的邏輯很簡(jiǎn)單,只需要關(guān)注tostring()和writetolog兩個(gè)函數(shù),前者返回其字符串格式,后者將start常量和交易號(hào)以二進(jìn)制的形式寫入緩存頁(yè)面,下面我們運(yùn)行上面的代碼看看,增加record_test.go,添加代碼如下:
代碼語(yǔ)言:javascript
復(fù)制
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) //交易號(hào)
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()
//檢查寫入的日志是否符號(hào)預(yù)期
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)
}
在測(cè)試中,我們初始化了startrecord實(shí)例,然后調(diào)用其tostring和writetolog兩個(gè)接口,然后檢驗(yàn)其返回或者是寫入緩存的數(shù)據(jù)是否正確,上面測(cè)試用例可以通過(guò),因此我們當(dāng)前實(shí)現(xiàn)的startrecord邏輯能保證基本正確性。
接下來(lái)我們繼續(xù)實(shí)現(xiàn)其他幾種恢復(fù)日志,首先是setstring格式的日志,創(chuàng)建set_string_record.go,實(shí)現(xiàn)代碼如下:
代碼語(yǔ)言:javascript
復(fù)制
package tx
import (
fm "file_manager"
"fmt"
lg "log_manager"
)
/*
在理論上一條setstring記錄有7個(gè)字段,例如<setstring, 0, junk, 33, 12, joe, joseph>,
在實(shí)現(xiàn)上我們只用6個(gè)字段,上面的記錄實(shí)際上對(duì)應(yīng)了兩次字符串的寫入,第一次寫入字符串"joseph",
第二次寫入joe,因此在實(shí)現(xiàn)上它對(duì)應(yīng)了兩條包含六個(gè)字段的記錄:
<setstring, 0, junk, 33, 12, joseph>
....
<setstring, 0, junk, 33, 12, joe>
回憶一下前面我們實(shí)現(xiàn)日志,日志是從下往上寫,也就是<setstring, 0, junk, 33, 12, joe>會(huì)寫在前面,
<setstring, 0, junk, 33, 12, joseph>會(huì)寫在后面,
在回滾的時(shí)候,我們從上往下讀取,因此我們會(huì)先讀到j(luò)oe,然后讀到j(luò)oseph,于是執(zhí)行回滾時(shí)我們只要把
讀到的字符串寫入到給定位置就可以,例如我們先讀到j(luò)oe,然后寫入junk文件區(qū)塊為33偏移為12的地方,
然后又讀取joseph,再次將它寫入到j(luò)unk文件區(qū)塊為33偏移為12的地方,于是就實(shí)現(xiàn)了回滾效果,
所以實(shí)現(xiàn)上setstring記錄不用寫入7個(gè)字段,只有6個(gè)就可以
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) //將日志中的字符串再次寫入給定位置
代碼語(yǔ)言:javascript
復(fù)制
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)
代碼語(yǔ)言:javascript
復(fù)制
return str
}
func (s *setstringrecord) undo(tx transationinterface) {
tx.pin(s.blk)
tx.setstring(s.blk, s.offset, s.val, false) //將原來(lái)的字符串寫回去
tx.unpin(s.blk)
}
func writesetstringlog(log_manager lg.logmanager, tx_num uint64,
blk fm.blockid, offset uint64, val string) (uint64, error) {
/
構(gòu)造字符串內(nèi)容的日志,setstringreord在構(gòu)造中默認(rèn)給定緩存頁(yè)面已經(jīng)有了字符串信息,
但是在初始狀態(tài),緩存頁(yè)面可能還沒(méi)有相應(yīng)日志信息,這個(gè)接口的作用就是為給定緩存寫入
字符串日志 /
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)
代碼語(yǔ)言:javascript
復(fù)制
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)
}
代碼語(yǔ)言:javascript
復(fù)制
需要注意的是上面代碼實(shí)現(xiàn)的setstring記錄跟前面理論有所不同,傳遞給setstringrecord的是一個(gè)緩存頁(yè)面,它其實(shí)對(duì)應(yīng)了setstring的日志記錄,writesetstringlog方法用于在給定日志中寫入setstring記錄。同時(shí)需要注意的是,它的undo方法需要通過(guò)實(shí)現(xiàn)了transationinterface的對(duì)象來(lái)完成,由于我們現(xiàn)在還沒(méi)有實(shí)現(xiàn)交易對(duì)象,因此我們需要實(shí)現(xiàn)一個(gè)偽對(duì)象來(lái)測(cè)試上面代碼,創(chuàng)建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 {
代碼語(yǔ)言:javascript
復(fù)制
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
}
代碼語(yǔ)言:javascript
復(fù)制
下面我們寫測(cè)試用例,以便檢測(cè)代碼的邏輯,在record_test.go中添加代碼如下:
func testsetstringrecord(t *testing.t) {
filemanager, := fm.newfilemanager(“recordtest”, 400)
logmanager, := lm.newlogmanager(file_manager, “setstring”)
代碼語(yǔ)言:javascript
復(fù)制
str := "original string"
blk := uint64(1)
dummy_blk := fm.newblockid("dummy_id", blk)
tx_num := uint64(1)
offset := uint64(13)
//寫入用于恢復(fù)的日志
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)
}
代碼語(yǔ)言:javascript
復(fù)制
我們繼續(xù)實(shí)現(xiàn)setint記錄,它的實(shí)現(xiàn)就是把setstring記錄的實(shí)現(xiàn)代碼拷貝一份然后簡(jiǎn)單修改一下,創(chuàng)建set_int_record.go,然后把set_string_record.go的代碼拷貝進(jìn)去然后做一些修改如下:
package tx
import (
fm “file_manager”
“fmt”
lg “l(fā)og_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) //將日志中的字符串再次寫入給定位置
代碼語(yǔ)言:javascript
復(fù)制
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)
代碼語(yǔ)言:javascript
復(fù)制
return str
}
func (s *setintrecord) undo(tx transationinterface) {
tx.pin(s.blk)
tx.setint(s.blk, s.offset, s.val, false) //將原來(lái)的字符串寫回去
tx.unpin(s.blk)
}
func writesetintlog(log_manager lg.logmanager, tx_num uint64,
blk fm.blockid, offset uint64, val uint64) (uint64, error) {
代碼語(yǔ)言:javascript
復(fù)制
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)
}
代碼語(yǔ)言:javascript
復(fù)制
然后在record_test.go里面添加新的測(cè)試用例:
func testsetintrecord(t *testing.t) {
filemanager, := fm.newfilemanager(“recordtest”, 400)
logmanager, := lm.newlogmanager(file_manager, “setstring”)
代碼語(yǔ)言:javascript
復(fù)制
val := uint64(11)
blk := uint64(1)
dummy_blk := fm.newblockid("dummy_id", blk)
tx_num := uint64(1)
offset := uint64(13)
//寫入用于恢復(fù)的日志
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)
}
代碼語(yǔ)言:javascript
復(fù)制
最后還剩下rollback 和 commit兩個(gè)記錄,它們內(nèi)容簡(jiǎn)單,我們一并放出來(lái),創(chuàng)建rollback_record.go,添加代碼如下:
package tx
import (
fm “file_manager”
“fmt”
lg “l(fā)og_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() {
//它沒(méi)有回滾操作
}
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)
代碼語(yǔ)言:javascript
復(fù)制
return lgmr.append(rec)
}
代碼語(yǔ)言:javascript
復(fù)制
同理在record_test.go中添加測(cè)試用例如下:
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)
代碼語(yǔ)言:javascript
復(fù)制
roll_back_rec := newrollbackrecord(pp)
expected_str := fmt.sprintf("<rollback %d>", tx_num)
require.equal(t, expected_str, roll_back_rec.tostring())
}
代碼語(yǔ)言:javascript
復(fù)制
接下來(lái)我們添加commit記錄,它的實(shí)現(xiàn)跟rollback差不多,添加commit_record.go然后添加代碼如下:
package tx
import (
fm “file_manager”
“fmt”
lg “l(fā)og_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() {
//它沒(méi)有回滾操作
}
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)
代碼語(yǔ)言:javascript
復(fù)制
return lgmr.append(rec)
}
代碼語(yǔ)言:javascript
復(fù)制
然后在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)
代碼語(yǔ)言:javascript
復(fù)制
roll_back_rec := newcommitkrecordrecord(pp)
expected_str := fmt.sprintf("<commit %d>", tx_num)
require.equal(t, expected_str, roll_back_rec.tostring())
}
代碼語(yǔ)言:javascript
復(fù)制
最后我們完成最簡(jiǎn)單的checkpoint記錄,添加checkpoint_record.go,添加代碼如下:
package tx
import (
fm “file_manager”
lg “l(fā)og_manager”
“math”
)
type checkpointrecord struct{
}
func newcheckpointrecord() *checkpointrecord {
return &checkpointrecord{
代碼語(yǔ)言:javascript
復(fù)制
}
}
func (c *checkpointrecord) op() record_type {
return checkpoint
}
func (c *checkpointrecord) txnumber() uint64 {
return math.maxuint64 //它沒(méi)有對(duì)應(yīng)的交易號(hào)
}
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)
}
代碼語(yǔ)言:javascript
復(fù)制
最后在record_test.go中添加相應(yīng)測(cè)試用例:
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)
代碼語(yǔ)言:javascript
復(fù)制
require.equal(t, val, uint64(checkpoint))
check_point_rec := newcheckpointrecord()
expected_str := "<checkpoint>"
require.equal(t, expected_str, check_point_rec.tostring())
}
```
經(jīng)過(guò)調(diào)試,所有測(cè)試用例都能通過(guò)。要想更好的了解代碼邏輯,請(qǐng)?jiān)赽站搜索coding迪斯尼,我會(huì)在視頻中進(jìn)行調(diào)試和演示。代碼下載:https://github.com/wycl16514/database-system-recovery-record.git,[更多干貨](méi)(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
山東金麒麟專場(chǎng) — 純前端表格技術(shù)應(yīng)用研討會(huì):2018 年 7 月 16 日,“賦能開(kāi)發(fā)者,走進(jìn)你身邊——純前端表格技術(shù)應(yīng)用研討會(huì)” 走進(jìn)山東金麒麟股份有限公司(以下簡(jiǎn)稱金麒麟)。
西安葡萄城業(yè)務(wù)總監(jiān)郭瑋、資深前端技術(shù)專家姚堯受邀與山東金麒麟股份有限公司展開(kāi)深入探討,圍繞葡萄城企業(yè)文化、數(shù)據(jù)管理系統(tǒng)實(shí)踐、前端技術(shù)發(fā)展趨勢(shì)以及?純前端表格控件 spreadjs?在各領(lǐng)域應(yīng)用場(chǎng)景等四大核心議題
作為全球領(lǐng)先的集開(kāi)發(fā)工具、商業(yè)智能解決方案、管理系統(tǒng)設(shè)計(jì)工具于一身的軟件和服務(wù)提供商,通過(guò)本次研討會(huì),葡萄城實(shí)實(shí)在在地感受到了所提供的產(chǎn)品和服務(wù)為各領(lǐng)域行業(yè)帶來(lái)的價(jià)值,為此,葡萄城的技術(shù)專家們也感到無(wú)比自豪和驕傲
葡萄城公司成立于 1980 年,是全球領(lǐng)先的集開(kāi)發(fā)工具、商業(yè)智能解決方案、管理系統(tǒng)設(shè)計(jì)工具于一身的軟件和服務(wù)提供商。
葡萄城的控件和軟件產(chǎn)品在國(guó)內(nèi)外屢獲殊榮,在全球被數(shù)十萬(wàn)家企業(yè)、學(xué)校和政府機(jī)構(gòu)廣泛應(yīng)用。
「鎂客·請(qǐng)講」魔魚互動(dòng)韓宇:專注行業(yè)應(yīng)用,打造可看可用的ar vr產(chǎn)品:“那是2009年,經(jīng)過(guò)評(píng)估后自己在技術(shù)和社會(huì)閱歷上的積累都非常薄弱,直接創(chuàng)業(yè)成功的概率幾乎為零,所以特意選擇了一個(gè)未來(lái)應(yīng)用廣泛又相對(duì)冷門的專業(yè)——地理信息系統(tǒng)繼續(xù)深造?!表n宇回憶說(shuō)。
魔魚互動(dòng)創(chuàng)始人-韓宇“2015年,感覺(jué)積累的差不多了,而且趕上政府大力扶持創(chuàng)業(yè),整體創(chuàng)業(yè)環(huán)境非常好的時(shí)機(jī)。于是果斷投入了創(chuàng)業(yè)大軍?!蹦且荒辏?jīng)過(guò)多年積累的韓宇,聯(lián)合兩位摯友正式啟動(dòng)了他們的創(chuàng)業(yè)之旅。
截止2015年底,魔魚互動(dòng)營(yíng)業(yè)額不足10萬(wàn)元,一名核心成員由于家庭原因被迫離開(kāi)了公司,最慘淡時(shí)只剩下韓宇和另一名聯(lián)合創(chuàng)始人黃致堯。
幸好,2015年5月公司通過(guò)了新城科技園入孵項(xiàng)目評(píng)審,在政府的扶持下,公司一步步走上正軌?!澳鞘且粓?chǎng)及時(shí)雨,我們有幸通過(guò)了新城科技園入孵項(xiàng)目評(píng)審,有了固定辦公室,才逐步形成了一個(gè)穩(wěn)定的團(tuán)隊(duì)?!?他們已經(jīng)研發(fā)了三維機(jī)房實(shí)時(shí)監(jiān)控系統(tǒng)、基于三維城市的統(tǒng)計(jì)信息可視化平臺(tái)、web版三維城市在線漫游系統(tǒng)等,正在研發(fā)融合人文環(huán)境影響因素的虛擬樓盤展示系統(tǒng)。
用 python 慶祝拜登當(dāng)選的十種方式:還記得上周三的時(shí)候川普在搖擺州上領(lǐng)先拜登好幾個(gè)點(diǎn),我都不敢打開(kāi)炒股軟件,結(jié)果周四醒來(lái)一下就反轉(zhuǎn)了,一路逆襲到今天,基本上穩(wěn)了:
?
musa_zadeh[4]
import time
for i in range(2021,2025):
print("hello biden")
time.sleep(365*24*60*60)
# 拜登被稱為睡王
1分鐘鏈圈 | 曝光!區(qū)塊鏈游戲5天就能復(fù)制一款,玩家靠擊鼓傳花獲利!bch將于5月16日0點(diǎn)左右執(zhí)行硬分叉:為瑞波幣產(chǎn)品和項(xiàng)目提供資金支持bch將于北京時(shí)間5月16日0點(diǎn)左右執(zhí)行硬分叉,區(qū)塊大小從8mb增加至32mb全球俄羅斯一銀行通過(guò)加密貨幣幫助委內(nèi)瑞拉石油幣發(fā)展歐盟批準(zhǔn)aml(反洗錢)在加密貨幣市場(chǎng)的匿名立法美國(guó)佛羅里達(dá)州塞米諾爾縣稅務(wù)辦公室接受
(news.bitcoin)5.圣路易聯(lián)儲(chǔ)主席james bullard:匯率問(wèn)題是數(shù)字貨幣獲廣泛承認(rèn)的最大障礙美聯(lián)儲(chǔ)圣路易聯(lián)儲(chǔ)主席james bullard在coindesk 2018年度共識(shí)大會(huì)期間接受
根據(jù)美聯(lián)社報(bào)道,早期的石油幣投資者將向委內(nèi)瑞拉政府注冊(cè)并下載數(shù)字貨幣的錢包,通過(guò)向evrofinance mosnarbank的政府所有賬戶提供1000歐元(約合1190美元)的最低金額來(lái)購(gòu)買該錢包。
(cointelegraph)11.美國(guó)佛羅里達(dá)州塞米諾爾縣稅務(wù)辦公室接受btc和bch塞米諾爾縣稅務(wù)員joel m.
區(qū)塊鏈允許塞米諾爾縣的稅務(wù)師在提高支付準(zhǔn)確性、透明度和效率的同時(shí),消除大部分的高額費(fèi)用。
轉(zhuǎn)載請(qǐng)注明出處,本站網(wǎng)址:
http://www.nds518.com/news_2274.html