真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

輕量級內(nèi)存計算引擎

內(nèi)存計算指數(shù)據(jù)事先存儲于內(nèi)存,各步驟中間結(jié)果不落硬盤的計算方式,適合性能要求較高,并發(fā)較大的情況。

成都創(chuàng)新互聯(lián)公司是一家以成都網(wǎng)站建設(shè)公司、網(wǎng)頁設(shè)計、品牌設(shè)計、軟件運維、網(wǎng)站推廣、小程序App開發(fā)等移動開發(fā)為一體互聯(lián)網(wǎng)公司。已累計為成都格柵板等眾行業(yè)中小客戶提供優(yōu)質(zhì)的互聯(lián)網(wǎng)建站和軟件開發(fā)服務(wù)。

HANA、TimesTen等內(nèi)存數(shù)據(jù)庫可實現(xiàn)內(nèi)存計算,但這類產(chǎn)品價格昂貴結(jié)構(gòu)復(fù)雜實施困難,總體擁有成本較高。本文介紹的集算器同樣可實現(xiàn)內(nèi)存計算,而且結(jié)構(gòu)簡單實施方便,是一種輕量級內(nèi)存計算引擎。

下面就來介紹一下集算器實現(xiàn)內(nèi)存計算的一般過程。

一、        啟動服務(wù)器

集算器有兩種部署方式:獨立部署、內(nèi)嵌部署,區(qū)別首先在于啟動方式有所不同。

l   獨立部署

作為獨立服務(wù)部署時,集算器與應(yīng)用系統(tǒng)分別使用不同的JVM,兩者可以部署在同一臺機(jī)器上,也可分別部署。應(yīng)用系統(tǒng)通常使用集算器驅(qū)動(ODBC或JDBC)訪問集算服務(wù),也可通過HTTP訪問。

n   Windows下啟動獨立服務(wù),執(zhí)行“安裝目錄\esProc\bin\esprocs.exe”,然后點擊“啟動”按鈕。

                                              輕量級內(nèi)存計算引擎

n   Linux下應(yīng)執(zhí)行“安裝目錄/esProc/bin/ServerConsole.sh”。

啟動服務(wù)器及配置參數(shù)的細(xì)節(jié),請參考:http://doc.raqsoft.com.cn/esproc/tutorial/fuwuqi.html。

 

l   內(nèi)嵌部署

作為內(nèi)嵌服務(wù)部署時,集算器只能與JAVA應(yīng)用系統(tǒng)集成,兩者共享JVM。應(yīng)用系統(tǒng)通過JDBC訪問內(nèi)嵌的集算服務(wù),無需特意啟動。

詳情參考http://doc.raqsoft.com.cn/esproc/tutorial/bjavady.html。

二、        加載數(shù)據(jù)

加載數(shù)據(jù)是指通過集算器腳本,將數(shù)據(jù)庫、日志、WebService等外部數(shù)據(jù)讀入內(nèi)存的過程。

比如Oracle中訂單表如下:

訂單ID(key)

客戶ID

訂單日期

運貨費

10248

VINET

2012-07-04

32.38

10249

TOMSP

2012-07-05

11.61

10250

HANAR

2012-07-08

65.83

10251

VICTE

2012-07-08

41.34

10252

SUPRD

2012-07-09

51.3

訂單明細(xì)如下:

訂單ID(key)(fk)

產(chǎn)品ID(key)

單價

數(shù)量

10248

17

14

12

10248

42

9

10

10248

72

34

5

10249

14

18

9

10249

51

42

40

將上述兩張表加載到內(nèi)存,可以使用下面的集算器腳本(initData.dfx):


A

1

=connect("orcl")

2

=A1.query("select   訂單ID,客戶ID,訂單日期,運貨費 from 訂單").keys(訂單ID)

3

=A1.query@x("select   訂單ID,產(chǎn)品ID,單價,數(shù)量 from 訂單明細(xì)") .keys(訂單ID,產(chǎn)品ID)

4

=env(訂單,A2)

5

=env(訂單明細(xì),A3)

A1:連接Oracle數(shù)據(jù)庫。

A2-A3:執(zhí)行SQL查詢,分別取出訂單表和訂單明細(xì)表。query@x表示執(zhí)行SQL后關(guān)閉連接。函數(shù)keys可建立主鍵,如果數(shù)據(jù)庫已定義主鍵,則無需使用該函數(shù)。

A4-A5:將兩張表常駐內(nèi)存,分別命名為訂單和訂單明細(xì),以便將來在業(yè)務(wù)計算時引用。函數(shù)env的作用是設(shè)置/釋放全局共享變量,以便在同一個JVM下被其他算法引用,這里將內(nèi)存表設(shè)為全局變量,也就是將全表數(shù)據(jù)保存在內(nèi)存中,供其他算法使用,也就實現(xiàn)了內(nèi)存計算。事實上,對于外存表、文件句柄等資源也可以用這個辦法設(shè)為全局變量,使變量駐留在內(nèi)存中。

 

腳本需要執(zhí)行才能生效。

對于內(nèi)嵌部署的集算服務(wù),通常在應(yīng)用系統(tǒng)啟動時執(zhí)行腳本。如果應(yīng)用系統(tǒng)是JAVA程序,可以在程序中通過JDBC執(zhí)行initData.dfx,關(guān)鍵代碼如下:

1.       com.esproc.jdbc.InternalConnection con=null;

2.       try{

3.           Class.forName("com.esproc.jdbc.InternalDriver");

4.             con   =(com.esproc.jdbc.InternalConnection)DriverManager.getConnection("jdbc:esproc:local://");

5.             ResultSet rs = con.executeQuery("call   initData()");

6.       } catch(SQLException e){

7.           out.println(e);

8.       }finally{

9.           if  (con!=null) con.close();

10.   }

這篇文章詳細(xì)介紹了JAVA調(diào)用集算器的過程http://doc.raqsoft.com.cn/esproc/tutorial/bjavady.html

如果應(yīng)用系統(tǒng)是JAVA WebServer,那么需要編寫一個Servlet,在Servlet的init方法中通過JDBC執(zhí)行initData.dfx,同時將該servlet設(shè)置為啟動類,并在web.xml里進(jìn)行如下配置:

 
     myServlet
     com.myCom.myProject.myServlet  
     3
   

 

對于獨立部署的集算服務(wù)器,JAVA應(yīng)用系統(tǒng)同樣要用JDBC接口執(zhí)行集算器腳本,用法與內(nèi)嵌服務(wù)類似。區(qū)別在于腳本存放于遠(yuǎn)端,所以需要像下面這樣指定服務(wù)器地址和端口:

st = con.createStatement();

st.executeQuery("=callx(\“initData.dfx\”;[\“127.0.0.1:8281\”])");

如果應(yīng)用系統(tǒng)非JAVA架構(gòu),則應(yīng)當(dāng)使用ODBC執(zhí)行集算器腳本,詳見http://doc.raqsoft.com.cn/esproc/tutorial/odbcbushu.html

 

對于獨立部署的服務(wù)器,也可以脫離應(yīng)用程序,在命令行手工執(zhí)行initData.dfx。這種情況下需要再寫一個腳本(如runOnServer.dfx):


A

1

=callx(“initData.dfx”;[“127.0.0.1:8281”])

然后在命令行用esprocx.exe調(diào)用runOnServer.dfx:

D:\raqsoft64\esProc\bin>esprocx   runOnServer.dfx

Linux下用法類似,參考http://doc.raqsoft.com.cn/esproc/tutorial/minglinghang.html

三、        執(zhí)行運算獲得結(jié)果

數(shù)據(jù)加載到內(nèi)存之后,就可以編寫各種算法進(jìn)行訪問,執(zhí)行計算并獲得結(jié)果,下面舉例說明:以客戶ID為參數(shù),統(tǒng)計該客戶每年每月的訂單數(shù)量。

該算法對應(yīng)的Oracle中的SQL語句如下:

select   to_char(訂單日期,'yyyy') AS 年份,to_char(訂單日期,'MM') AS 月份, count(1) AS 訂單數(shù)量

from   訂單  

where客戶ID=?

group   by to_char(訂單日期,'yyyy'),to_char(訂單日期,'MM')

在集算器中,應(yīng)當(dāng)編寫如下業(yè)務(wù)算法(algorithm_1.dfx)


A

1

=訂單.select@m(客戶ID==pCustID).groups(year(訂單日期):年份, month(訂單日期):月份;count(1):訂單數(shù)量)

為方便調(diào)試和維護(hù),也可以分步驟編寫:


A

1

=訂單.select@m(客戶ID==pCustID)

2

=A1.groups(year(訂單日期):年份, month(訂單日期):月份;

count(1):訂單數(shù)量)

A1:按客戶ID過濾數(shù)據(jù)。其中,“訂單”就是加載數(shù)據(jù)時定義的全局變量,pCustID是外部參數(shù),用于指定需要統(tǒng)計的客戶ID,函數(shù)select執(zhí)行查詢。@m表示并行計算,可顯著提高性能。

A2:執(zhí)行分組匯總,輸出計算結(jié)果。集算器默認(rèn)返回有表達(dá)式的最后一個單元格,也就是A2。如果要返回指定單元的值,可以用return語句

當(dāng)pCustID=”VINET”時,計算結(jié)果如下:

年份

月份

訂單數(shù)量

2012

7

3

2012

8

2

2012

9

1

2013

11

4

 

需要注意的是,假如多個業(yè)務(wù)計算都要對客戶ID進(jìn)行查詢,那不妨在加載數(shù)據(jù)時把訂單按客戶ID排序,這樣后續(xù)業(yè)務(wù)算法中就可以使用二分法進(jìn)行快速查詢,也就是使用select@b函數(shù)。具體實現(xiàn)上,initData.dfx中SQL應(yīng)當(dāng)改成:

=A1.query("select   訂單ID,客戶ID,訂單日期,運貨費 from 訂單 order by 客戶ID")

相應(yīng)的,algorithm_1.dfx中的查詢應(yīng)當(dāng)改成:

=訂單.select@b(客戶ID==pCustID)

 

執(zhí)行腳本獲得結(jié)果的方法,前面已經(jīng)提過,下面重點說說報表,這類最常用的應(yīng)用程序。

由于報表工具都有可視化設(shè)計界面,所以無需用JAVA代碼調(diào)用集算器,只需將數(shù)據(jù)源配置為指向集算服務(wù),在報表工具中以存儲過程的形式調(diào)用集算器腳本。

對于內(nèi)嵌部署的集算服務(wù)器,調(diào)用語句如下:

call algorithm_1(”VINET”)

由于本例中算法非常簡單,所以事實上可以不用編寫?yīng)毩⒌膁fx腳本,而是在報表中直接以SQL方式書寫表達(dá)式:

=訂單.select@m(客戶ID==”VINET”).groups(year(訂單日期):年份, month(訂單日期):月份;count(1):訂單數(shù)量)

對于獨立部署的集算服務(wù)器,遠(yuǎn)程調(diào)用語句如下:

=callx(“algorithm_1.dfx”,”VINET”;[“127.0.0.1:8281”])

 

有時,需要在內(nèi)存進(jìn)行的業(yè)務(wù)算法較少,而web.xml不方便添加啟動類,這時可以在業(yè)務(wù)算法中調(diào)用初始化腳本,達(dá)到自動初始化的效果,同時也省去編寫servlet的過程。具體腳本如下:


A

B

1

if   !ifv(訂單)

=call("initData.dfx")

2

=訂單.select@m(客戶ID==pCustID)


3

=A2.groups(year(訂單日期):年份, month(訂單日期):月份;

count(1):訂單數(shù)量)


A1-B1:判斷是否存在全局變量“訂單明細(xì)”,如果不存在,則執(zhí)行初始化數(shù)據(jù)腳本initData.dfx。

A2-A3:繼續(xù)執(zhí)行原算法。

 

四、        引用思維

       前面例子用到了select函數(shù),這個函數(shù)的作用與SQL的where語句類似,都可進(jìn)行條件查詢,但兩者的底層原理大不相同。where語句每次都會復(fù)制一遍數(shù)據(jù),生成新的結(jié)果集;而select函數(shù)只是引用原來的記錄指針,并不會復(fù)制數(shù)據(jù)。以按客戶查詢訂單為例,引用和復(fù)制的區(qū)別如下圖所示:

 

輕量級內(nèi)存計算引擎

可以看到,集算器由于采用了引用機(jī)制,所以計算結(jié)果占用空間更小,計算性能更高(分配內(nèi)存更快)。此外,對于上述計算結(jié)果還可再次進(jìn)行查詢,集算器中新結(jié)果集同樣引用最初的記錄,而SQL就要復(fù)制出很多新記錄。

除了查詢之外,還有很多集算器算法都采用了引用思維,比如排序、集合交并補、關(guān)聯(lián)、歸并。

五、        常用計算

回顧前面案例,可以看到集算器語句和SQL語句存在如下的對應(yīng)關(guān)系:

計算

SQL

集算器

查詢

select

select

條件

Where….訂單.客戶ID=?

訂單ID.客戶ID==pCustID

分組匯總

group   by

groups

 

日期函數(shù)

to_char(訂單日期,'yyyy')

year(訂單日期)

別名

AS   年份

:年份

事實上,集算器支持完善的結(jié)構(gòu)化數(shù)據(jù)算法,比如:

l   GROUP BY…HAVING


A


1

=訂單.groups(year(訂單日期):年份;count(1):訂單數(shù)量).select(訂單數(shù)量>300)


 

l   ORDER BY…ASC/DESC


A


1

=訂單.sort(客戶ID,-訂單日期)

/排序只是變換了記錄指針的次序,并沒有復(fù)制記錄

l   DISTINCT


A


1

=訂單.id(year(訂單日期))

/取唯一值

2

=A1.(客戶ID)

/所有出現(xiàn)值

3

=訂單.([ year(訂單日期),客戶ID])

/組合的所有出現(xiàn)值

l    UNION/UNION ALL/INTERSECT/MINUS


A


1

=訂單.select(運貨費>100)


2

=訂單.select([2011,2012].pos(year(訂單日期))


3

=A2|A3

/UNION   ALL

4

=A2&A3

/UNION

5

=A2^A3

/INTERSECTION

6

=A2\A3

/DIFFERENCE

與SQL的交并補不同,集算器只是組合記錄指針,并不會復(fù)制記錄。

l   SELECT … FROM (SELECT …)


A


1

=訂單.select(訂單日期>date("2010-01-01"))

/執(zhí)行查詢

2

=A1.count()

/對結(jié)果集再統(tǒng)計

l    SELECT (SELECT … FROM) FROM


A


1

=訂單.new(訂單ID,客戶.select(客戶ID==訂單.客戶ID).客戶名)

/客戶表和訂單表都是全局變量

l    CURSOR/FETCH

游標(biāo)有兩種用法,其一是外部JAVA程序調(diào)用集算器,集算器返回游標(biāo),比如下面腳本:


A


1

=訂單.select(訂單日期>=date("2010-01-01")).cursor()


JAVA獲得游標(biāo)后可繼續(xù)處理,與JDBC訪問游標(biāo)的方法相同。

其二,在集算器內(nèi)部使用游標(biāo),遍歷并完成計算。比如下面腳本:


A

B



1

=訂單.cursor()




2

for A1,100

=A2.select(訂單日期>=date("2010-01-01"))

 /每次取100條運算


3




 

集算器適合解決復(fù)雜業(yè)務(wù)邏輯的計算,但考慮到簡單算法占大多數(shù),而很多程序員習(xí)慣使用SQL語句,所以集算器也支持所謂“簡單SQL”的語法。比如algorithm_1.dfx也可寫作:


A

1

$()   select year(訂單日期) AS 年份,month(訂單日期) AS 月份,count(1) AS 訂單數(shù)量

From   {訂單} 

where訂單.客戶ID='VINET'

group   by year(訂單日期),month(訂單日期)

上述腳本通用于任意SQL,$()表示執(zhí)行默認(rèn)數(shù)據(jù)源(集算器)的SQL語句,如果指定數(shù)據(jù)源名稱比如$(orcl),則可以執(zhí)行相應(yīng)數(shù)據(jù)庫(數(shù)據(jù)源名稱是orcl的Oracle數(shù)據(jù)庫)的SQL語句。

from {}語句可從任意集算器表達(dá)式取數(shù),比如:from {訂單.groups(year(訂單日期):年份;count(1):訂單數(shù)量)}

from 也可從文件或excel取數(shù),比如:from d:/emp.xlsx

簡單SQL同樣支持join…on…語句,但由于SQL語句(指任意RDB)在關(guān)聯(lián)算法上性能較差,因此不建議輕易使用。對于關(guān)聯(lián)運算,集算器有專門的高性能實現(xiàn)方法,后續(xù)章節(jié)會有介紹。

簡單SQL的詳情可以參考:http://doc.raqsoft.com.cn/esproc/func/dbquerysql.html#db_sql_

六、        有序引用

SQL基于無序集合做運算,不能直接用序號取數(shù),只能臨時生成序號,效率低且用法繁瑣。集算器與SQL體系不同,能夠基于有序集合運算,可以直接用序號取數(shù)。例如:


A


1

=訂單.sort(訂單日期)

/如果加載時已排序,這步可省略

2

=A1.m(1).訂單ID

/第一條

3

=A1.m(-1).訂單ID

/最后一條

4

=A1.m(to(3,5))

/第3-5條

函數(shù)m()可按指定序號獲取成員,參數(shù)為負(fù)表示倒序。參數(shù)也可以是集合,比如m([3,4,5])。而利用函數(shù)to()可按起止序號生成集合,to(3,5)=[3,4,5]。

前面例子提到過二分法查詢select@b,其實已經(jīng)利用了集算器有序訪問的特點。

 

有時候我們想取前 N名,常規(guī)的思路就是先排序,再按位置取前N個成員,集算器腳本如下:

=訂單.sort(訂單日期).m(to(100))

對應(yīng)SQL寫法如下:

select   top(100) * from 訂單 order by 訂單日期   --MSSQL

select   * from (select * from 訂單 order by 訂單日期) where rownum<=100    --Oracle

但上述常規(guī)思路要對數(shù)據(jù)集大排序,運算效率很低。除了常規(guī)思路,集算器還有更高效的實現(xiàn)方法:使用函數(shù)top。

=訂單.top(100;訂單日期)

函數(shù)top只排序出訂單日期最早的N條記錄,然后中斷排序立刻返回,而不是常規(guī)思路那樣進(jìn)行全量排序。由于底層模型的限制,SQL不支持這種高性能算法。

 

函數(shù)top還可應(yīng)用于計算列,比如擬對訂單采取新的運貨費規(guī)則,求新規(guī)則下運貨費最大的前100條訂單,而新規(guī)則是:如果原運貨費大于等于1000,則運貨費打八折。

集算器腳本為:

=訂單.top(-100;if(運貨費>=1000,運貨費*0.8,運貨費))

 

七、        關(guān)聯(lián)計算

關(guān)聯(lián)計算是關(guān)系型數(shù)據(jù)庫的核心算法,在內(nèi)存計算中應(yīng)用廣泛,比如:統(tǒng)計每年每月的訂單數(shù)量和訂單金額。

該算法對應(yīng)Oracle的SQL語句為:

select   to_char(訂單.訂單日期,'yyyy') AS 年份,to_char(訂單.訂單日期,'MM') AS 月份,sum(訂單明細(xì).單價*訂單明細(xì).數(shù)量) AS 銷售金額,count(1) AS 訂單數(shù)量

from   訂單明細(xì) left join 訂單 on 訂單明細(xì).訂單ID=訂單.訂單ID

group   by to_char(訂單.訂單日期,'yyyy'),to_char(訂單.訂單日期,'MM')

用集算器實現(xiàn)上述算法時,加載數(shù)據(jù)的腳本不變,業(yè)務(wù)算法如下(algorithm_2.dfx)


A

1

=join(訂單明細(xì):子表,訂單ID;訂單:主表,訂單ID)

2

=A1.groups(year(主表.訂單日期):年份, month(主表.訂單日期):月份; sum(子表.單價*子表.數(shù)量):銷售金額, count(1):訂單數(shù)量)

A1:將訂單明細(xì)與訂單關(guān)聯(lián)起來,子表主表為別名,點擊單元格可見結(jié)果如下

輕量級內(nèi)存計算引擎

可以看到,集算器join函數(shù)與SQL join語句雖然作用一樣,但結(jié)構(gòu)原理大不相同。函數(shù)join關(guān)聯(lián)形成的結(jié)果,其字段值不是原子數(shù)據(jù)類型,而是記錄,后續(xù)可用“.”號表達(dá)關(guān)系引用,多層關(guān)聯(lián)非常方便。

A2:分組匯總。

計算結(jié)果如下:

年份

月份

銷售金額

訂單數(shù)量

2012

7

28988

57

2012

8

26799

71

2012

9

27201

57

2012

10

37793.7

69

2012

11

49704

66

關(guān)聯(lián)關(guān)系分很多類,上述訂單和訂單明細(xì)屬于其中一類:主子關(guān)聯(lián)。針對主子關(guān)聯(lián),只需在加載數(shù)據(jù)時各自按關(guān)聯(lián)字段排序,業(yè)務(wù)算法中就可用歸并算法來提高性能。例如:

=join@m(訂單明細(xì):子表,訂單ID;訂單:主表,訂單ID)

函數(shù)join@m表示歸并關(guān)聯(lián),只對同序的兩個或多個表有效。

集算器的關(guān)聯(lián)計算與RDB不同,RDR對所有類型的關(guān)聯(lián)關(guān)系都采用相同的算法,無法進(jìn)行有針對性的優(yōu)化,而集算器采取分而治之的理念,對不同類型的關(guān)聯(lián)關(guān)系提供了不同的算法,可進(jìn)行有針對性的透明優(yōu)化。

 

除了主子關(guān)聯(lián),最常用的就是外鍵關(guān)聯(lián),常用的外鍵表(或字典表)有分類、地區(qū)、城市、員工、客戶等。對于外鍵關(guān)聯(lián),集算器也有相應(yīng)的優(yōu)化方法,即在數(shù)據(jù)加載階段事先建立關(guān)聯(lián),如此一來業(yè)務(wù)算法就不必臨時關(guān)聯(lián),性能因此提高,并發(fā)時效果尤為明顯。另外,集算器用指針建立外鍵關(guān)聯(lián),訪問速度更快。

比如這個案例:訂單表的客戶ID字段是外鍵,對應(yīng)客戶表(客戶ID、客戶名稱、地區(qū)、城市),需要統(tǒng)計出每個地區(qū)每個城市的訂單數(shù)量。

數(shù)據(jù)加載腳本(initData_3.dfx)如下:


A

1

=connect("orcl")

2

=A1.query("select   訂單ID,客戶ID,訂單日期,運貨費 from 訂單").keys(訂單ID)

3

=A1.query@x(“select   客戶ID,地區(qū),城市 from 客戶”).keys(客戶ID)

4

=A2.switch(客戶ID,A3:客戶ID)

5

=env(訂單,A2)

6

=env(客戶,A3)

A4:用函數(shù)switch建立外鍵關(guān)聯(lián),將訂單表的客戶ID字段,替換為客戶表相應(yīng)記錄的指針。

業(yè)務(wù)算法腳本如下(algorithm_3.dfx)如下


A

1

=訂單.groups(客戶ID.地區(qū):地區(qū) ,客戶ID.城市:城市;count(1):訂單數(shù)量)

加載數(shù)據(jù)時已經(jīng)建立了外鍵指針關(guān)聯(lián),所以A1中的“客戶ID”表示:訂單表的客戶ID字段所指向的客戶表記錄,“客戶ID.地區(qū)”即客戶表的地區(qū)字段。

腳本中多處使用“.”號表達(dá)關(guān)聯(lián)引用,語法比SQL直觀易懂,遇到多表多層關(guān)聯(lián)時尤為便捷。而在SQL中,關(guān)聯(lián)一多如同天書。

       上述計算結(jié)果如下:

地區(qū)

城市

訂單數(shù)量

東北

大連

40

華東

南京

89

華東

南昌

15

華東

常州

35

華東

溫州

18

 

 

 

 

 

八、        內(nèi)外混合計算

內(nèi)存計算雖然快,但是內(nèi)存有限,因此通常只駐留最常用、并發(fā)訪問最多的數(shù)據(jù),而內(nèi)存放不下或訪問頻率低的數(shù)據(jù),還是要留在硬盤,用到的時候再臨時加載,并與內(nèi)存數(shù)據(jù)共同參與計算。這就是所謂的內(nèi)外混合計算。

下面舉例說明集算器中的內(nèi)外混合計算。

案例描述:某零售行業(yè)系統(tǒng)中,訂單明細(xì)訪問頻率較低,數(shù)據(jù)量較大,沒必要也沒辦法常駐內(nèi)存?,F(xiàn)在要將訂單明細(xì)與內(nèi)存里的訂單關(guān)聯(lián)起來,統(tǒng)計出每年每種產(chǎn)品的銷售數(shù)量。

數(shù)據(jù)加載腳本(initData_4.dfx)如下:


A

1

=connect("orcl")

2

=A1.query@x("select   訂單ID,客戶ID,訂單日期,運貨費 from 訂單 order by 訂單ID").keys(訂單ID)

4

=env(訂單,A2)

業(yè)務(wù)算法腳本(algorithm_4.dfx)如下:


A

1

=connect("orcl")

2

=A1.cursor@x("select   訂單ID,產(chǎn)品ID,數(shù)量 from 訂單明細(xì)order by 訂單ID")

3

=訂單.cursor()

4

=joinx(A2:子表,訂單ID; A3:主表,訂單ID)

5

=A4.groups(year(主表.訂單日期):年份,子表.產(chǎn)品ID:產(chǎn)品 ;sum(子表.數(shù)量):銷售數(shù)量)

A2:執(zhí)行SQL,以游標(biāo)方式取訂單明細(xì),以便計算遠(yuǎn)超內(nèi)存的大量數(shù)據(jù)。

A3:將訂單表轉(zhuǎn)為游標(biāo)模式,下一步會用到。

A4:關(guān)聯(lián)訂單明細(xì)表和訂單表。函數(shù)joinx與join@m作用類似,都可對有序數(shù)據(jù)進(jìn)行歸并關(guān)聯(lián),區(qū)別在于前者對游標(biāo)有效,后者對序表有效。

A5:執(zhí)行分組匯總。

 

九、        數(shù)據(jù)更新

數(shù)據(jù)庫中的物理表總會變化,這種變化應(yīng)當(dāng)及時反映到共享的內(nèi)存表中,才能保證內(nèi)存計算結(jié)果的正確,這種情況下就需要更新內(nèi)存。如果物理表較小,那么解決起來很容易,只要定時執(zhí)行初始化數(shù)據(jù)腳本(initData.dfx)就可以了。但如果物理表太大,就不能這樣做了,因為初始化腳本會進(jìn)行全量加載,本身就會消耗大量時間,而且加載時無法進(jìn)行內(nèi)存計算。例如:某零售巨頭訂單數(shù)據(jù)量較大,從數(shù)據(jù)庫全量加載到內(nèi)存通常超過5分鐘,但為保證一定的實時性,內(nèi)存數(shù)據(jù)又需要5分鐘更新一次,顯然,兩者存在明顯的矛盾。

解決思路其實很自然,物理表太大的時候,應(yīng)該進(jìn)行增量更新,5分鐘的增量業(yè)務(wù)數(shù)據(jù)通常很小,增量不會影響更新內(nèi)存的效率。

要實現(xiàn)增量更新,就需要知道哪些是增量數(shù)據(jù),不外乎以下三種方法:

方法A:在原表加標(biāo)記字段以識別。缺點是會改動原表。

方法B:在原庫創(chuàng)建一張“變更表”,將變更的數(shù)據(jù)記錄在內(nèi)。好處是不動原表,缺點是仍然要動數(shù)據(jù)庫。

方法C:將變更表記錄在另一個數(shù)據(jù)庫,或文本文件Excel中。好處是對原數(shù)據(jù)庫不做任何改動,缺點是增加了維護(hù)工作量。

集算器支持多數(shù)據(jù)源計算,所以方法B、C沒本質(zhì)區(qū)別,下面就以B為例更新訂單表。

 

第一步,在數(shù)據(jù)庫中建立“訂單變更表”,繼承原表字段,新加一個“變更標(biāo)記”字段,當(dāng)用戶修改原始表時,需要在變更表同步記錄。如下所示的訂單變更表,表示新增1條修改2條刪除1條。

訂單ID(key)

客戶ID

訂單日期

運貨費

變更標(biāo)記

10247

VICTE

2012-07-08

101

新增

10248

VINET

2012-07-04

102

修改

10249

TOMSP

2012-07-05

103

修改

10250

HANAR

2012-07-08

104

刪除

 

第二步,編寫集算器腳本updatemem_4.dfx,進(jìn)行數(shù)據(jù)更新。


A

B

1

=connect("orcl")


2

=訂單cp=訂單.derive()


3

=A1.query("select 訂單ID,客戶ID,訂購日期 訂單日期,運貨費,變更標(biāo)記 from 訂單變更")


4

=訂單刪除=A3.select(變更標(biāo)記=="刪除")

=訂單cp.select(訂單刪除.(訂單ID).contain(訂單ID))

5


=訂單cp.delete(B4)

6

=訂單新增=A3.select(變更標(biāo)記=="新增")

=訂單cp.insert@f(0:訂單新增)

7

=訂單修改=A3.select(變更標(biāo)記=="修改")

=訂單cp.select(訂單修改.(訂單ID).pos(訂單ID))

8


=訂單cp.delete(B7)

9


=訂單cp.insert@f(0:訂單修改)

10

=env(訂單,訂單cp)


11

=A1.execute("delete from 訂單變更")


12

=A1.close()


A1:建立數(shù)據(jù)庫連接。

A2:將內(nèi)存中的訂單復(fù)制一份,命名為訂單cp。下面過程只針對訂單cp進(jìn)行修改,修改完畢再替代內(nèi)存中的訂單,期間訂單仍可正常進(jìn)行業(yè)務(wù)計算。

A3:取數(shù)據(jù)庫訂單變更表。

A4-B5:取出訂單變更表中需刪除的記錄,在訂單cp中找到這些記錄,并刪除。

A6-B6:取出訂單變更表中需新增的記錄,在訂單cp中追加。

A7-B9:這一步是修改訂單cp,相當(dāng)于先刪除再追加。也可用modify函數(shù)實現(xiàn)修改。

A10:將修改后的訂單cp常駐內(nèi)存,命名為訂單。

A11-A12:清空“變更表”,以便下次取新的變更記錄。

上述腳本實現(xiàn)了完整的數(shù)據(jù)更新,而實際上很多情況下只需要追加數(shù)據(jù),這樣腳本還會簡單很多。

       腳本編寫完成后,還需第三步:定時5分鐘執(zhí)行該腳本。   

       定時執(zhí)行的方法有很多。如果集算器部署為獨立服務(wù),與Web應(yīng)用沒有共用JVM,那么可以使用操作系統(tǒng)自帶的定時工具(計劃任務(wù)或crontab),使其定時執(zhí)行集算器命令(esprocx.exe或esprocx.sh)。

有些web應(yīng)用有自己的定時任務(wù)管理工具,可定時執(zhí)行某個JAVA類,這時可以編寫JAVA類,用JDBC調(diào)用集算器腳本。

       如果web應(yīng)用沒有定時任務(wù)管理工具,那就需要手工實現(xiàn)定時任務(wù),即編寫JAVA類,繼承java內(nèi)置的定時類TimerTask,在其中調(diào)用集算器腳本,再在啟動類中調(diào)用定時任務(wù)類。

       其中啟動類myServle4為:

1.     import java.io.IOException;      

2.     import java.util.Timer;      

3.     import javax.servlet.RequestDispatcher;      

4.     import javax.servlet.ServletContext;      

5.     import javax.servlet.ServletException;      

6.     import javax.servlet.http.HttpServlet;      

7.     import javax.servlet.http.HttpServletRequest;      

8.     import javax.servlet.http.HttpServletResponse;      

9.     import org.apache.commons.lang.StringUtils;      

10.   public class myServlet4 extends HttpServlet {      

11.       private static final long serialVersionUID = 1L;      

12.       private Timer timer1 = null;      

13.       private Task task1;                

14.       public ConvergeDataServlet() {      

15.           super();      

16.       }      

17.       public void destroy() {      

18.           super.destroy();       

19.           if(timer1!=null){      

20.               timer1.cancel();      

21.           }      

22.       }      

23.       public void doGet(HttpServletRequest request, HttpServletResponse response)      

24.               throws ServletException, IOException {      

25.       }      

26.       public void doPost(HttpServletRequest request, HttpServletResponse response)      

27.               throws ServletException, IOException {      

28.           doGet(request, response);             

29.       }      

30.       public void init() throws ServletException {      

31.           ServletContext context = getServletContext();      

32.           // 定時刷新時間(5分鐘)      

33.           Long delay = new Long(5);      

34.           // 啟動定時器      

35.           timer1 = new Timer(true);      

36.           task1 = new Task(context);      

37.           timer1.schedule(task1, delay * 60 * 1000, delay * 60 * 1000);      

38.       }      

39.   } 

定時任務(wù)類Task為:

11.  import java.util.TimerTask;      

12.  import javax.servlet.ServletContext;  

13.   importjava.sql.*;

14.   importcom.esproc.jdbc.*;    

15.  public class Task extends TimerTask{      

16.      private ServletContext context;      

17.      private static boolean isRunning = true;      

18.      public Task(ServletContext context){      

19.          this.context = context;      

20.      }      

21.      @Override     

22.      public void run() {      

23.          if(!isRunning){      

24.                com.esproc.jdbc.InternalConnection con=null;

25.                try{

26.                      Class.forName("com.esproc.jdbc.InternalDriver");

27.                      con =(com.esproc.jdbc.InternalConnection)DriverManager.getConnection("jdbc:esproc:local://");

28.                      ResultSet rs = con.executeQuery("call updatemem_4()");

29.                }

30.                catch(SQLException e){

31.                      out.println(e);

32.                }finally{

33.                    //關(guān)閉數(shù)據(jù)集

34.                          if(con!=null) con.close();

35.                }

36.          }      

37.      }      

38.  }      

十、        綜合示例

下面,通過一個綜合示例來看一下在數(shù)據(jù)源多樣、算法復(fù)雜的情況下,集算器如何很好地實現(xiàn)內(nèi)存計算:

案例描述:某B2C網(wǎng)站需要試算訂單的郵寄總費用,以便在一定成本下挑選合適的郵費規(guī)則。大部分情況下,郵費由包裹的總重量決定,但當(dāng)訂單的價格超過指定值時(比如300美元),則提供免費付運。結(jié)果需輸出各訂單郵寄費用以及總費用。

其中訂單表已加載到內(nèi)存,如下:

Id

cost

weight

Josh2

150

6

Drake

100

3

Megan

100

1

Josh3

200

3

Josh4

500

1

郵費規(guī)則每次試算時都不同,因此由參數(shù)“pRule”臨時傳入,格式為json字符串,某次規(guī)則如下:

[{"field":"cost","minVal":300,"maxVal":1000000,"Charge":0},

{"field":"weight","minVal":0,"maxVal":1,"Charge":10},

{"field":"weight","minVal":1,"maxVal":5,"Charge":20},

{"field":"weight","minVal":5,"maxVal":10,"Charge":25},

{"field":"weight","minVal":10,"maxVal":1000000,"Charge":40}]

上述json串表示各字段在各種取值范圍內(nèi)時的郵費。第一條記錄表示,cost字段取值在300與1000000之間的時候,郵費為0(免費付運);第二條記錄表示,weight字段取值在0到1(kg)之間時,郵費為10(美元)。

思路:將json串轉(zhuǎn)為二維表,分別找出filed字段為cost和weight的記錄,再對整個訂單表進(jìn)行循環(huán)。循環(huán)中先判斷訂單記錄中的cost值是否滿足免費標(biāo)準(zhǔn),不滿足則根據(jù)重量判斷郵費檔次,之后計算郵費。算完各訂單郵費后再計算總郵費,并將匯總結(jié)果附加為訂單表的最后一條記錄。

數(shù)據(jù)加載過程很簡單,這里不再贅述,即:讀數(shù)據(jù)庫表,并命名為“訂單表”。

業(yè)務(wù)算法相對復(fù)雜,具體如下:


A

B

C

D

1

= pRule.export@j()

/解析json,轉(zhuǎn)二維表



2

=免費=A1.select(field=="cost")

/取免費標(biāo)準(zhǔn),單條



3

=收費=A1.select(field=="weight").sort(-minVal)

/取收費階梯,多條



4

=訂單表.derive(postage)

/復(fù)制并新增字段



5

for A4

if 免費.minVal < A5.cost

>A5. postage= 免費.Charge


6



next


7


for 收費

if A5.weight > B7.minVal

>A5.postage=B7.Charge

8




next A5

9

=A4.record(["sum",,,A4.sum(postage)])




A1:解析json,將其轉(zhuǎn)為二維表。集算器支持多數(shù)據(jù)源,不僅支持RDB,也支持NOSQL、文件、webService。

A2-A3:查詢郵費規(guī)則,分為免費和收費兩種。

A4:新增空字段postage。

A5-D8:按兩種規(guī)則循環(huán)訂單表,計算相應(yīng)的郵費,并填入postage字段。這里多處用到流程控制,集算器用縮進(jìn)表示,其中A5、B7為循環(huán)語句,C6、D8跳入下一輪循環(huán),B5、C7為判斷語句

A9:在訂單表追加新紀(jì)錄,填入?yún)R總值。

計算結(jié)果如下:

Id

cost

weight

postage

Josh2

150

6

25

Drake

100

3

20

Megan

100

1

10

Josh3

200

3

20

Josh4

500

1

0

sum



75

 

       至此,本文詳細(xì)介紹了集算器用作內(nèi)存計算引擎的完整過程,同時包括了常用計算方法和高級運算技巧??梢钥吹?,集算器具有以下顯著優(yōu)點:

l   結(jié)構(gòu)簡單實施方便,可快速實現(xiàn)內(nèi)存計算;

l   支持多種調(diào)用接口,應(yīng)用集成沒有障礙;

l   支持透明優(yōu)化,可顯著提升計算性能;

l   支持多種數(shù)據(jù)源,便于實現(xiàn)混合計算;

l   語法敏捷精妙,可輕松實現(xiàn)復(fù)雜業(yè)務(wù)邏輯。

       關(guān)于內(nèi)存計算,還有個多機(jī)分布式計算的話題,將在后續(xù)文章中進(jìn)行介紹。

 



文章題目:輕量級內(nèi)存計算引擎
本文網(wǎng)址:http://weahome.cn/article/jshjgc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部