|
缓存条目到期策略
|
说明
|
|---|---|
|
“从不”
|
系统从不根据缓存条目储存在缓存中的时间来移除该缓存条目。然而,该条目在下列情况中仍可能被逐出:缓存达到配置的最大容量上限时,或通过 DeleteEntry 服务进行手动清理时。配置为 Never 到期策略时,系统将忽略“缓存条目到期时间”设置。
|
|
自上次访问后的到期时间
|
如果在指定的截止时间内未读取条目,则条目将被移除。
|
|
自创建以来的到期时间
|
条目自创建起经过指定的时间后将被移除。
|
|
自上次修改后的到期时间
|
如果在指定的截止时间内未修改条目,则条目将被移除。
|
|
自上次操作后的到期时间
|
如果在指定的截止时间内未访问或修改条目,则条目将被移除。
|
|
服务名称
|
说明
|
|---|---|
|
PutEntry
|
• 向缓存中添加条目。
• 输入值 - 使用为 CacheThing 配置的 DataShape 的单行信息表。
|
|
GetEntry
|
• 从缓存返回单行信息表结果。
• 如果未找到条目 (缓存缺失),且缓存未配置 Cache Performs Loading of Missing Entries 选项,则会返回空信息表。如果缓存已配置该选项,则会自动调用 LoadEntry 服务,并返回其结果。
• 条目从未添加至缓存、已从缓存中逐出或到期时,可能会发生缓存未命中。
• 输入值 - 使用为 CacheThing 配置的 DataShape 的单行信息表。应仅填充 Primary Key 值。
|
|
GetEntryByKey
|
• 从缓存返回单行信息表结果。
• 如果未找到该条目,则会返回一个空信息表。条目从未添加至缓存、已从缓存中逐出或到期时,可能会发生缓存未命中。
• 输入值 - 表示正在检索的条目主键的字符串。仅当 DataShape 具有单一主键时,此服务才可用。它最适合字符串主键,但也支持大多数具有明确字符串转换的类型。
|
|
LoadEntry
|
• 用于在发生缓存未命中时自动从数据源获取缓存数据。
• 如果已启用 Cache Performs Loading of Missing Entries 功能,则必须改写此服务。
|
|
DeleteEntry
|
• 删除缓存中的条目。
• 在高可用性环境中,这仅影响调用 DeleteEntry 服务的节点。
• 输入值 - 使用为 CacheThing 配置的 DataShape 的单行信息表。应仅填充 Primary Key 值。
|
|
DeleteEntryByKey
|
• 删除缓存中的条目。
• 在高可用性环境中,这仅影响调用 DeleteEntryByKey 服务的节点。
• 输入值 - 表示正在检索的条目主键的字符串。仅当 DataShape 具有单一主键时,此服务才可用。
|
|
PurgeCache
|
• 移除所有缓存条目。
• 在高可用性环境中,这仅影响调用 PurgeCache 服务的节点。
|
|
GetDataShape
|
返回为缓存配置的 DataShape。
|
|
SetDataShape
|
设置缓存配置表中的 DataShape 字段。更改此值将自动清除缓存。
|
|
GetEstimatedEntryCount
|
返回缓存中预估的条目数。正在进行的 PUT 和 DELETE 操作以及“到期”或“逐出”进程可能会影响准确性。
|
|
EstimateEntrySize
|
• 返回条目的预估大小 (如果已将其添加到缓存中)。此服务不会将条目实际放入缓存中,也不需要缓存中已有的任何条目。此服务主要用于估计适当的“缓存上限 (MB)”或防止将大条目添加到缓存中。
• 输入值 - 使用为 CacheThing 配置的 DataShape 的单行信息表。
|
|
缓存指标
|
说明
|
|---|---|
|
thingworx_cache_hit_rate
|
导致在缓存中找到条目的缓存请求比率
|
|
thingworx_cache_hits
|
缓存查找方法返回缓存值的次数
|
|
thingworx_cache_request_count
|
缓存查找方法返回缓存值或未缓存值的次数
|
|
thingworx_cache_miss_rate
|
缓存查找方法返回未缓存 (新加载的) 值或空值的比率
|
|
thingworx_cache_misses
|
缓存查找方法返回未缓存 (新加载的) 值或空值的次数
|
|
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
|
所有缓存的全局最大配置大小 (字节)
|
// 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;
}
/*
This Service queries QueryImplementingThingsV2 and caches the results as needed
Service Name: CachingQueryImplementingThingsV2 (Overridden on CacheThing)
Input: values InfoTable with Cached_QueryImplementingThingsV2_DataShape
Cached_QueryImplementingThingsV2_DataShape:
maxItems: NUMBER, Primary Key
nameMask: STRING, Primary Key
query: QUERY, Primary Key
isSortFirst: BOOLEAN, Primary Key
result: INFOTABLE, DataShape RootEntityListV2 (NOT a Primary Key!)
*/
let cacheEntryInfoTable = getCacheEntryInfoTable();
cacheEntryInfoTable.AddRow({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */
});
let getEntryResult = me.GetEntry({values: cacheEntryInfoTable});
if (getEntryResult.getRowCount() === 0) {
// Cache didn't have results, so call QueryImplementingThingsV2
let queryResult = ThingTemplates["GenericThing"].QueryImplementingThingsV2({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */
});
// Add the result to the cache, with the primaryKey parameters used to uniquely identify the query.
let newCacheEntryInfoTable = getCacheEntryInfoTable();
newCacheEntryInfoTable.AddRow({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */,
result: queryResult /* INFOTABLE */
});
me.PutEntry({
values: newCacheEntryInfoTable
});
logger.debug("Entry not found in Cache, retrieved from QueryImplementingThingsV2:" + queryResult.ToJSON());
result = queryResult;
} else {
logger.debug("Found entry from Cache:" + getEntryResult.ToJSON());
let row = getEntryResult.getRow(0);
result = row.result;
}
function getCacheEntryInfoTable() {
return Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "Cached_QueryImplementingThingsV2_DataShape"
});
}
/*
This Service simply proxies a query to QueryImplementingThingsV2 for Read Through caching
Service Name: LoadEntry (Overridden on CacheThing)
Input: values InfoTable with Cached_QueryImplementingThingsV2_DataShape
Cached_QueryImplementingThingsV2_DataShape:
maxItems: NUMBER, Primary Key
nameMask: STRING, Primary Key
query: QUERY, Primary Key
isSortFirst: BOOLEAN, Primary Key
result: INFOTABLE, DataShape RootEntityListV2 (NOT a Primary Key!)
*/
let queryResult = ThingTemplates["GenericThing"].QueryImplementingThingsV2({
maxItems: values.maxItems /* NUMBER */,
nameMask: values.nameMask /* STRING */,
query: values.query /* QUERY */,
isSortFirst: values.isSortFirst /* BOOLEAN */
});
// Copy the results into a new InfoTable.
// This prevents some internal ThingWorx casting issues with some InfoTables
let resultTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: me.GetDataShape() // Returns Cached_QueryImplementingThingsV2_DataShape
});
resultTable.AddRow({
maxItems: values.maxItems /* NUMBER {"defaultValue":500} */,
nameMask: values.nameMask /* STRING */,
query: values.query /* QUERY */,
isSortFirst: values.isSortFirst /* BOOLEAN */,
result: queryResult
});
let result = resultTable;
此服务会自动将演示数据填充到查询日期范围内的数据表中。 |
/* 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"
});
}
/* 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);
}