CacheThings
CacheThing 提供快速且高效的介面來儲存及擷取金鑰-值資料。它非常適合快取昂貴或耗時的操作,例如慢速查詢。
* 
CacheThing 是記憶體中快取,也就是說如果重新啟動 ThingWorx 則需要重建。在高可用性 (HA) 系統中,每個 ThingWorx 節點都有一個獨立的快取,因此在一個節點上設定或更新的快取項目不會在其他節點上自動更新。
CacheThing 可用來快取各種類型的資料,包括:
昂貴或頻繁查詢資料庫物件的結果
昂貴或頻繁查詢 ValueStreams 或 DataTables 的結果
慢速網路呼叫的結果,例如使用 ContentLoaderFunctions 進行的呼叫
昂貴的計算值
FileRepositories 中常用的非常小檔案的內容
由於 CacheThing 在記憶體中操作,因此與存取資料庫或網路資源等外部系統相比,它提供結果的速度明顯更快且耗費更少的資源。
使用指南
不應將 CacheThing 視為永久資料存放區。任何在 CacheThing 中快取的資料都應該能夠從原始來源重新建立。
CacheThing 中的資料可以是「已到期」「已收回」。如果快取傳回空結果,使用 CacheThing 的服務就應該能夠讀取來源資料。如需詳細資訊,請參閱範例
組態
CacheThings 必須使用具有主索引鍵的資料形式進行配置。「主索引鍵」用來從快取中擷取回項目。
* 
允許使用複合主索引鍵,但它們會防止使用以下列出的 *ByKey 便利服務。
到期原則
「到期原則」可確定快取中每個項目的存留時間 (TTL)。到期時間使用 Cache Entry Expiration Time (Seconds) 設定進行配置。過了項目的到期時間之後,擷取值將會傳回空白結果,這種情況與從快取中將其刪除類似。
允許下列組態:
快取項目到期原則
描述
從不
系統從不根據快取項目在快取中花費的時間量來移除快取項目。但是,仍可透過收回的方式 (如果快取達到最大大小組態) 或使用 DeleteEntry 服務來移除它。配置 Never 到期原則時,會忽略 Cache Entry Expiration Time (Seconds) 設定。
自上次存取以來的到期時間
如果未在指定的到期時間內讀取項目,則會從快取中移除該項目。
自建立以來的到期時間
在指定的到期時間之後,將從快取中移除項目。
自上次修改後的到期時間
如果在指定的到期時間內未修改項目,將會將其移除。
自上次接觸後的到期時間
如果在指定的到期時間內未存取或修改項目,將會將其移除。
如需有關介面到期原則的詳細資訊,請參閱介面到期原則
快取最大大小 (收回)
如果項目達到在 Cache Maximum Size (MB) 設定中配置的大小,將會自動從快取中收回 (刪除) 項目。將會收回較舊的、最不常使用的 (LFU) 項目,以便為新項目騰出空間。收回項目之後,擷取值將會傳回空白結果,這種情況與從快取中將其刪除類似。
如果將大型項目新增到快取中,則可能會收回多個較小的項目以騰出空間。
為避免非常大的項目觸發許多較小項目的收回動作,您可以使用 EstimateEntrySize 服務在將潛在項目新增至快取之前檢查其大小。
快取收回和到期會獨立操作,以根據需要從快取中移除項目。收回機制是以配置的快取大小以及與其他項目相比的使用項目頻率為基礎。到期機制是以根據配置的到期原則經過的時間為基礎。
全域最大大小
平台子系統的 Maximum Size Shared between all CacheThings per ThingWorx Node (MB) 組態會限制所有 CacheThings 之 Cache Maximum Size (MB) 的組合大小。此防護措施旨在防止錯誤配置的 CacheThings 意外耗用 ThingWorx 的所有系統記憶體。非使用中 (已停用) 的 CacheThings 不會計入限制。
此組態將防止:
建立超出最大大小的新 CacheThings
更新超過最大大小之 CacheThings 的 Cache Maximum Size (MB)
匯入 CacheThings (作為實體匯入或延伸功能的一部份) 超過最大大小,導致整個匯入失敗
將最大大小減小到所有 CacheThings 的目前組合 Cache Maximum Size (MB) 之下
啟動 (啟用) 超過全域限制的 CacheThing
變更快取組態
對快取組態的任何變更都將自動清除快取以維持內部一致性。編輯以下任何一項都將會清除快取:
DataShape - 這包括編輯目前已配置的資料形式本身,例如新增或移除欄位。
Cache Maximum Size (MB)
Cache Entry Expiration Policy
Expiration Time (Seconds)
服務
下列服務可在 CacheThing 上找到。在高可用性 (HA) 環境中,它們會影響執行相應服務所在的 ThingWorx 節點上的快取。
以下列出的 *ByKey 便利服務只有在快取的資料形式具有單一主索引鍵時才可用。如果快取的資料形式配置了多個主索引鍵,請使用完整資料負載輸入版本的服務。
服務名稱
描述
PutEntry
將項目新增至快取。
輸入值 - 使用 CacheThing 的已配置資料形式的一列資料負載。
GetEntry
從快取傳回一列資料負載結果。如果在快取中找不到項目 (快取未命中),會傳回空白的資料負載。如果項目從未新增至快取、遭到收回或到期,則可能會發生快取未命中的情況。
輸入值 - 使用 CacheThing 的已配置資料形式的一列資料負載。只應填入 Primary Key 值。
GetEntryByKey
從快取傳回一列資料負載結果。如果是快取未命中,會傳回空白的資料負載。如果項目從未新增至快取、遭到收回或到期,則可能會發生快取未命中的情況。
輸入值 - 所擷取項目主索引鍵的字串版本。這最適合字串主索引鍵,但也適用於具有明確字串轉換的大多數類型。只有在已配置的資料形式具有單一主索引鍵時,才允許此服務。
DeleteEntry
從快取中刪除項目。
輸入值 - 使用 CacheThing 的已配置資料形式的一列資料負載。只應填入 Primary Key 值。
DeleteEntryByKey
從快取中刪除項目。
輸入值 - 所擷取項目主索引鍵的字串版本。這最適合字串主索引鍵,但也適用於具有明確字串轉換的大多數類型。只有在已配置的資料形式具有單一主索引鍵時,才允許此服務。
PurgeCache
移除所有快取項目。
GetDataShape
傳回快取的已配置資料形式。
SetDataShape
設定快取組態表中的「資料形式」欄位。變更此值將會自動清除快取。
GetEstimatedEntryCount
取得快取中的預估項目數。任何正在進行的 PUT 與 DELETE 服務都可能會導致計數不準確,執行「到期」或「收回」流程也是如此。
EstimateEntrySize
傳回項目新增至快取後將使用的大小。這樣並不會將項目放進快取中,也不需要在快取中已有任何項目。此服務可用於預估適當的 Cache Maximum Size (MB),或防止將大型項目新增至快取。
輸入值 - 使用 CacheThing 的已配置資料形式的一列資料負載。
指標
有幾個與快取相關的指標可用。在高可用性 (HA) 環境中,每個節點的快取都是獨立的。因此,應根據 platformcache_name 標籤,為每個節點處理快取指標。
以下是可用的快取指標:
快取指標
描述
thingworx_cache_hit_rate
導致在快取中找到項目的快取請求比例
thingworx_cache_hits
快取查詢方法已傳回快取值的次數
thingworx_cache_request_count
快取查詢方法已傳回快取或未快取值的次數
thingworx_cache_miss_rate
已傳回未快取 (新載入) 值或 null 之快取查詢方法的比率
thingworx_cache_misses
快取查詢方法已傳回未快取 (新載入) 值或 null 的次數
thingworx_cache_eviction_count
已收回項目的次數
thingworx_cache_average_load_penalty
載入新值所花費的平均時間
thingworx_cache_weighted_size
快取的目前逼近大小 (以位元組為單位)
thingworx_cache_max_weight
收回前的最大快取大小 (以位元組為單位)
thingworx_cache_estimated_entry_count
快取中的預估項目數
thingworx_cache_global_max_size
所有快取的全域最大配置大小 (以位元組為單位)
警示 (在 PTC 雲端服務託管環境中可用)
PTC 雲端服務託管環境中提供以下警示:
thingworxEntityCacheMissRate - 當快取的未命中率超過 80% 持續 1 小時時發出警示。
thingworxEntityCacheEvictions - 當快取的收回率超過 80% 持續 30 分鐘時發出警示。
範例與最佳實務
實行正確使用 CacheThing 的服務需要注意細節。
以下是主要考量因素:
ThingWorx 節點重新啟動時清除快取。
項目可以在沒有任何使用者輸入的情況下從快取中收回或到期,因此絕不應假設項目將無限期地保留在快取中。
在高可用性 (HA) 環境中,每個節點都有一個獨立的備用快取。新增、刪除或清除一個節點上的快取將不會影響所有 ThingWorx 節點。
以下是使用快取的標準模式:
1. 使用主索引鍵從快取中擷取項目。
2. 檢查是否已填入結果 (傳回的資料負載有一列)。
3. 如果快取中有結果,請使用它。
4. 如果快取中沒有結果:
從來源資料 (例如,資料庫物件、資料表、ContentLoader) 擷取或產生所需項目。
將項目放進快取中。
傳回目前快取的項目。
使用 TimesTwo 服務的範例 
TimesTwo 服務會取一個數字,乘以 2,並將結果儲存在快取中。欲進行設定,請遵循下列步驟:
1. 使用下列欄位建立資料形式 (TimesTwoDataShape):operand: (LONG, primaryKey)timesTwoValue: (LONG)
2. 使用配置的 TimesTwoDataShape 建立 CacheThing (MyTimesTwoCache)。
3. TimesTwo 服務新增至 MyTimesTwoCache
輸入 - x (一個 LONG 數字)
輸出 - result (一個 LONG 數字)
// Input is x as a LONG
// Output is result as LONG
// MyTimesTwoCache CacheThing is configured with a DataShape with two fields:
// operand: (LONG, primaryKey)
// timesTwoValue: (LONG)
result = -1;
// Check the cache first, to see if we have already calculated x*2
// GetEntryByKey is a convenience method that takes the String value the configured DataShape's Primary Key ("key" in this example)
// cacheResult is an InfoTable with the DataShape of the cache
let cacheResult = Things["MyTimesTwoCache"].GetEntryByKey({ operand: x.toString() });
if (cacheResult.getRowCount() === 0) {
// Cache didn't have a result, so calculate it
let timesTwo = x*2;

// Put the result in the cache for the next time we need this multiple
// operand and value are from the Cache's configured DataShape
let cacheInput = getInfoTableForPut(x, timesTwo);
Things["MyTimesTwoCache"].PutEntry({
values: cacheInput
});

// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("PutEntry the following entry into the MyTimesTwoCache: {operand: " + x + ", timesTwoValue: " + timesTwo + "}");

// Set the global result Output variable
result = timesTwo;
} else {
// If results are found, they are always in the first row of the InfoTable
let row = cacheResult.getRow(0);
logger.debug("Found the following entry already in MyTimesTwoCache: {operand: " + row.operand + ", timesTwoValue: " + row.timesTwoValue + "}");
result = row.timesTwoValue;
}
function getInfoTableForPut(operand, timesTwoValue) {
let infoTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: Things["MyTimesTwoCache"].GetDataShape()
});
infoTable.AddRow({"operand": operand, "timesTwoValue ": timesTwoValue });
return infoTable;
}
使用 DataTableQuery 的範例 
此範例示範如何使用有效的 BetweenDatesDataTableQuery 服務來尋找資料表中兩個日期之間的最大值,以及如何將結果儲存在快取中。對於 CachingBetweenDatesDataTableQuery 的唯一呼叫一開始會查詢備用資料表,但後續的相同呼叫則會改用快取。
欲進行設定,請遵循下列步驟:
1. 使用下列欄位建立資料形式 (TimeValueDataShape):longKey: (LONG, primaryKey)dateKey: (DATETIME, primaryKey)
2. 使用 TimeValueDataShape 資料形式建立資料表 (DemoDataTable)。
3. 使用下列欄位建立資料形式 (TimeSpanValueCacheDataShape):startDate: (DATETIME, primaryKey)endDate: (DATETIME, primaryKey)longValue: (LONG)
4. 使用配置的 TimeSpanValueCacheDataShape 資料形式建立 CacheThing (CacheThingDemo)。
5. CacheThingDemo 上建立下列服務:
CachingBetweenDatesDataTableQuery - 輸入:StartDateEndDate 作為 DATETIME。輸出:result 作為 LONG
BetweenDatesDataTableQuery - 輸入:StartDateEndDate 作為 DATETIME。輸出:result 作為 LONG
* 
此服務會將示範資料自動填入到資料表中的查詢日期範圍內。
6. 選用。在 CacheThingDemo 上使用 ExpirationPolicy 而非 Never 來配置短 ExpirationTime,以觀察快取中已到期的項目。
CachingBetweenDatesDataTableQuery
/* Reads the Cached results of BetweenDatesDataTableQuery Service.
If Cache doesn't have the result, call BetweenDatesDataTableQuery and load results into Cache.

Service Name: CachingBetweenDatesDataTableQuery
Inputs: StartDate[DATETIME, required], EndDate[DATETIME, required]
Output: LONG
*/
// This example uses CacheThing.GetEntry, which requires a one-row infoTable populated with the CacheThing's DataShapes primaryKey getEntryByKey
let cacheEntryInfoTable = getCacheEntryInfoTable();
cacheEntryInfoTable.AddRow({"startDate": StartDate, "endDate": EndDate});
let getEntryResult = Things["CacheThingDemo"].GetEntry({
values: cacheEntryInfoTable
});
if (getEntryResult.getRowCount() === 0) {
// Cache didn't have results, so go to backing DataTable, by calling the BetweenDatesDataTableQuery Service
let queryResult = Things["CacheThingDemo"].BetweenDatesDataTableQuery({
StartDate: StartDate,
EndDate: EndDate
});

// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("PutEntry called for BetweenDatesQueryCache: {StartDate: " + StartDate + ", EndDate: " + EndDate + " value: " + queryResult + "}");
if (!queryResult || queryResult < 0) {
// BetweenDatesDataTableQuery should always populate with demo data if no data is in the StartDate - EndDate range
throw new Error("BetweenDatesDataTableQuery did not return results");
}
let newCacheEntryInfoTable = getCacheEntryInfoTable();
newCacheEntryInfoTable.AddRow({"startDate": StartDate, "endDate": EndDate, "longValue": queryResult});
Things["CacheThingDemo"].PutEntry({
values: newCacheEntryInfoTable
});
result = queryResult;
} else {
let row = getEntryResult.getRow(0);
logger.debug("Found the following entry already in Cache : {StartDate: " + row.startDate + ", EndDate: " + row.endDate + ", LongValue: " + row.longValue + "}");
result = row.longValue;
}
function getCacheEntryInfoTable() {
return Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "TimeSpanValueCacheDataShape"
});
}
BetweenDatesDataTableQuery
/* This service queries a range of dates, based on the dateKey column, and returns the greatest longKey value found.
If there are no entries between StartDate and EndDate this Service dynamically populates demo data into demoDataTable, and returns that result.

Service Name: BetweenDatesDataTableQuery
Inputs: StartDate[DATETIME, required], EndDate[DATETIME, required]
Output: LONG
*/
if (StartDate >= EndDate) {
throw Error("StartDate must be before EndDate");
}
let dataTable = Things["DemoDataTable"];
let queryResult = queryGreatestLongKeyBetweenDates(dataTable, StartDate, EndDate);
if (queryResult.getRowCount() === 0) {
// If we don't get any results, first populate the date range with random values, then retry
// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("No values found between [" + StartDate + "] and [" + EndDate + "], populating 100 demo values and re-querying");
populateDemoDataTable(dataTable, StartDate, EndDate);
// Re-query the now populated DateTable
queryResult = queryGreatestLongKeyBetweenDates(dataTable, StartDate, EndDate);
result = queryResult.getRow(0).get("longKey");
} else {
result = queryResult.getRow(0).get("longKey");
}
// Queries the DataTable for the largest longKey column value between the given dates
// Returns: One-row InfoTable with the found row.
function queryGreatestLongKeyBetweenDates(dateTable, startDate, endDate) {
let greatestBetweenDatesQuery = {
"filters": {
"type": "Between",
"fieldName": "dateKey",
"from": startDate,
"to": endDate
},
"sorts": [{
"fieldName": "longKey",
"isAscending": false
}]
};
// Run the Query to find the greatest Value Between Two Dates
let queryResult = dataTable.QueryDataTableEntries({
maxItems: 1,
query: greatestBetweenDatesQuery,
});

logger.debug("Queried between [" + StartDate + "] and [" + EndDate + "], found:" + queryResult.toJSON());
return queryResult;
}
function populateDemoDataTable(dataTable, startDate, endDate) {
// add 100 random entries between startDate and endDate
for (let entry = 0; entry < 100; entry++) {
let randomTimeBetween = randomDate(startDate, endDate);
let entry = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "TimeValueDataShape"
});
entry.AddRow({
"longKey": Math.floor(Math.random() * 1000),
"dateKey": randomTimeBetween
});
Things["DemoDataTable"].AddDataTableEntry({
values: entry /* INFOTABLE */ ,
});
}
}
function randomDate(startDate, endDate) {
let randomTime = Math.random() * (endDate.getTime() - startDate.getTime()) + startDate.getTime();
return new Date(randomTime);
}
這是否有幫助?