上下文感知应用程序和复杂事件处理架构
已发表: 2022-03-11全球移动电话的使用量不断增加。 截至 2013 年,大约 73% 的互联网用户通过移动设备消费内容,预计到 2017 年这一比例将接近 90%。
当然,移动革命有很多原因。 但最重要的一点是,移动应用程序通常可以访问更丰富的上下文,因为当今几乎所有的智能手机都配备了位置传感器、运动传感器、蓝牙和 wifi。 通过利用它们的数据,应用程序可以实现“上下文感知”,从而显着提高它们的能力和价值,并真正让它们在应用商店中脱颖而出。
在本教程中,我们将通过一个复杂的事件处理示例来探索创建上下文感知应用程序。 我们将使用一个相当简单的示例:一个燃料价格应用程序,它可以找到您所在地区的最佳燃料价格。
上下文感知应用
在 Designing Calm Technology 中,Mark Weiser 和 John Seely Brown 将冷静技术描述为“能够提供信息但不需要我们关注或关注的技术”。
上下文感知移动应用程序与这一概念高度一致,并且是沿着这条道路迈出的重要且有价值的一步。 他们使用从传感器收集的上下文信息来主动为用户提供有价值的信息,并且他们以最小的用户努力做到这一点。 Mark Weiser 和 John Seely Brown 无疑会为这种技术进步喝彩。
上下文感知是应用程序可以根据它可以访问的上下文数据感知和反应的想法。 这样的应用程序利用移动设备上可用的丰富传感器数据,在适当的上下文中向用户提供准确和相关的信息。 通过在设备使用过程中观察到的趋势,和/或通过用户提供的反馈,这样的应用程序实际上可以随着时间的推移“学习”,从而变得“更智能”和更有用。
复杂事件处理
复杂事件处理 (CEP) 是一种事件处理形式,它对多个事件(即,随着时间的推移、来自不同来源等)进行更复杂的分析,整合和分析它们的内容以推断出更有意义的信息和模式。
在移动应用程序中,CEP 可以应用于从移动设备的传感器以及应用程序可以访问的外部数据源生成的事件。
我们的燃油价格应用程序的主要功能
出于我们复杂事件处理教程的目的,我们假设我们的燃油价格应用程序的功能仅限于以下内容:
- 自动检测与用户地理相关的位置(例如,用户的家庭位置和用户的工作位置)
- 自动识别用户家和工作地点合理距离内的加油站
- 自动通知用户在家和工作地点附近的最佳燃油价格
好的,让我们开始吧。
检测用户的家庭和工作地点
让我们从自动检测用户的家庭和工作地点的逻辑开始。 为了让我们的复杂事件处理示例保持简单,我们将假设用户有一个相当正常的工作时间表。 因此,我们可以假设用户通常会在凌晨 2 点到 3 点之间在家,并且通常会在下午 2 点到 3 点之间在他们的办公室。
基于这些假设,我们定义了两个 CEP 规则并从用户的智能手机收集位置和时间数据:
- 家庭位置规则
- 在一周的凌晨 2 点到 3 点之间收集位置数据
- 聚类位置数据以获得大致的家庭地址
- 工作地点规则
- 收集工作日下午 2 点到 3 点之间的位置数据
- 聚类位置数据以获得大致的工作位置
检测位置的高级算法如下所示。
让我们假设位置数据有以下简单的 JSON 数据结构:
{ "uid": "some unique identifier for device/user", "location": [longitude, latitude] "time": "time in user's timezone" }
注意:使传感器数据不可变(或值类型)始终是一种很好的做法,以便 CEP 工作流程中的不同模块可以安全地使用它。
执行
我们将使用可组合模块模式来实现我们的算法,其中每个模块只执行一个任务,并在任务完成时调用 next。 这符合 Unix 模块化原则。
具体来说,每个模块都是一个函数,它接受一个config
对象和一个被调用以将数据传递给下一个模块的next
函数。 因此,每个模块都返回一个可以接受传感器数据的函数。 这是模块的基本签名:
// nominal structure of each composable module function someModule(config, next) { // do initialization if required return function(data) { // do runtime processing, handle errors, etc. var nextData = SomeFunction(data); // optionally call next with nextData next(nextData); } }
为了实现我们推断用户家庭和工作地点的算法,我们需要以下模块:
- 时间过滤模块
- 蓄能器模块
- 集群模块
下面的小节将更详细地描述这些模块中的每一个。
时间过滤模块
我们的时间过滤器是一个简单的函数,它将位置数据事件作为输入,并且仅当事件发生在感兴趣的时间片内时才将数据传递给next
模块。 因此,此模块的config
数据由感兴趣的时间片的开始时间和结束时间组成。 (更复杂的模块版本可以基于多个时间片进行过滤。)
这是时间过滤器模块的伪代码实现:
function timeFilter(config, next) { function isWithin(timeval) { // implementation to compare config.start <= timeval <= config.end // return true if within time slice, false otherwise } return function (data) { if(isWithin(data.time)) { next(data); } }; }
蓄能器模块
累加器的职责只是收集位置数据,然后传递给next
模块。 该函数维护一个内部固定大小的存储桶来存储数据。 遇到的每个新位置都会添加到存储桶中,直到存储桶已满。 然后将桶中累积的位置数据作为数组发送到下一个模块。
支持两种类型的累加器桶。 桶的类型影响数据转发到下一阶段后对桶的内容做了什么,如下:
翻滚窗口桶(
type = 'tumbling'
):转发数据后,清空整个桶并重新开始(将桶大小减少回0)运行窗口类型(
type = 'running'
):转发数据后,只丢弃桶中最旧的数据元素(桶大小减1)
这是累加器模块的基本实现:
function accumulate(config, next) { var bucket = []; return function (data) { bucket.unshift(data); if(bucket.length >= config.size) { var newSize = (config.type === 'tumbling' ? 0 : bucket.length - 1); next(bucket.slice(0)); bucket.length = newSize; } }; }
集群模块
当然,坐标几何中有许多复杂的技术可以对 2D 数据进行聚类。 这是对位置数据进行聚类的一种简单方法:
- 在一组位置中查找每个位置的邻居
- 如果某些邻居属于现有集群,则使用集群扩展邻居
- 如果邻居集中的位置超过阈值,则将邻居添加为新集群
这是此聚类算法的实现(使用Lo-Dash
):
var _ = require('lodash'); function createClusters(location_data, radius) { var clusters = []; var min_points = 5; // Minimum cluster size function neighborOf(this_location, all_locations) { return _.filter(all_locations, function(neighbor) { var distance = distance(this_point.location, neighbor.location); // maximum allowed distance between neighbors is 500 meters. return distance && (500 > distance); } } _.each(location_data, function (loc_point) { // Find neighbors of loc_point var neighbors = neighborOf(loc_point, location_data, radius); _.each(clusters, function (cluster, index) { // Check whether some of the neighbors belong to cluster. if(_.intersection(cluster, neighbors).length){ // Expand neighbors neighbors = _.union(cluster, neighbors); // Remove existing cluster. We will add updated cluster later. clusters[index] = void 0; } }); if(neighbors.length >= min_points){ // Add new cluster. clusters.unshift(neighbors); } }); return _.filter(clusters, function(cluster){ return cluster !== void 0; }); }
上面的代码假设存在一个计算两个地理位置之间距离(以米为单位)的distance()
函数。 它以[longitude, latitude]
的形式接受两个位置点,并返回它们之间的距离。 以下是此类函数的示例实现:

function distance(point1, point2) { var EARTH_RADIUS = 6371000; var lng1 = point1[0] * Math.PI / 180; var lat1 = point1[1] * Math.PI / 180; var lng2 = point2[0] * Math.PI / 180; var lat2 = point2[1] * Math.PI / 180; var dLat = lat2 - lat1; var dLon = lng2 - lng1; var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); var arc = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); var distance = EARTH_RADIUS * arc; return distance; }
通过定义和实现我们的聚类算法(在前面显示的createClusters()
函数中),我们可以将它用作我们的聚类模块的基础:
function clusterize(config, next) { return function(data) { var clusters = createClusters(data, config.radius); next(clusters); }; }
将它们拉在一起
现在定义了所有必需的组件功能,因此我们准备编写我们的家庭/工作位置规则。
例如,这里是家庭位置规则的可能实现:
var CLUSTER_RADIUS = 150; // use cluster radius of 150 meters var BUCKET_SIZE = 500; // collect 500 location points var BUCKET_TYPE = 'tumbling'; // use a tumbling bucket in our accumulator var home_cluster = clusterize({radius: CLUSTER_RADIUS}, function(clusters) { // Save clusters in db }); var home_accumulator = accumulate({size: BUCKET_SIZE, type: BUCKET_TYPE}, home_cluster); var home_rule = timeFilter({start: "2AM", end: "3AM"}, home_accumulator);
现在,无论何时从智能手机(通过 websocket、TCP、HTTP)接收到位置数据,我们都会将此数据转发给home_rule
函数,该函数进而检测用户家的集群。
然后假设用户的“家庭位置”是家庭位置集群的中心。
注意:虽然这可能不完全准确,但对于我们的简单示例来说已经足够了,尤其是因为无论如何这个应用程序的目标只是了解用户家周围的区域,而不是了解用户的精确家庭位置。
这是一个简单的示例函数,它通过平均集群集中所有点的纬度和经度来计算集群中一组点的“中心”:
function getCentre(cluster_data) { var len = cluster_data.length; var sum = _.reduce(cluster_data, function(memo, cluster_point){ memo[0] += cluster_point[0]; memo[1] += cluster_point[1]; return memo; }, [0, 0]); return [sum[0] / len, sum[1] / len]; }
可以采用类似的方法来推断工作地点,唯一的区别是它将使用下午 2 点到 3 点之间的时间过滤器(而不是凌晨 2 点到 3 点)。
因此,我们的燃料应用程序能够自动检测用户的工作和家庭位置,而无需任何用户干预。 这是最好的上下文感知计算!
寻找附近的加油站
建立情境感知的艰巨工作现已完成,但我们仍需要一个规则来识别要监控的加油站价格(即,哪些加油站离用户家或工作地点足够近以便相关)。 此规则需要访问燃料应用程序支持的所有区域的所有加油站位置。 规则如下:
- 加油站规则
- 为每个家庭和工作地点查找最近的加油站
这可以使用前面显示的距离函数作为位置过滤器轻松实现,以应用于应用程序已知的所有加油站。
监控燃油价格
一旦燃料应用程序为用户获取了首选(即附近)加油站的列表,它就可以轻松地查看这些加油站的最佳燃油价格。 它还可以在这些加油站之一有特价或优惠时通知用户,尤其是当检测到用户在这些加油站附近时。
结论
在这个复杂的事件处理教程中,我们几乎没有触及上下文感知计算的表面。
在我们的简单示例中,我们将位置上下文添加到原本简单的燃油价格报告应用程序中,并使其更智能。 该应用程序现在在每台设备上的行为都不同,并且随着时间的推移会检测位置模式以自动提高其提供给用户的信息的价值。
当然可以添加更多的逻辑和传感器数据来提高我们的上下文感知应用程序的准确性和实用性。 例如,一个聪明的移动开发者可以利用社交网络数据、天气数据、POS 终端交易数据等,为我们的应用程序添加更多的上下文感知,使其更具可行性和市场价值。
使用上下文感知计算,可能性是无穷无尽的。 越来越多的智能应用程序将继续出现在应用程序商店中,这些应用程序利用这项强大的技术让我们的生活变得更简单。