แอปพลิเคชัน Context Aware และสถาปัตยกรรมการประมวลผลเหตุการณ์ที่ซับซ้อน
เผยแพร่แล้ว: 2022-03-11การใช้โทรศัพท์มือถือทั่วโลกเพิ่มขึ้นอย่างต่อเนื่อง ในปี 2556 ผู้ใช้อินเทอร์เน็ตประมาณ 73% ใช้เนื้อหาผ่านอุปกรณ์พกพา และคาดว่าเปอร์เซ็นต์นี้จะเพิ่มขึ้นเกือบ 90% ภายในปี 2560
มีเหตุผลหลายประการสำหรับการปฏิวัติอุปกรณ์พกพา แต่สิ่งหนึ่งที่สำคัญที่สุดคือ โดยทั่วไปแล้วแอปบนอุปกรณ์เคลื่อนที่จะเข้าถึงบริบทที่สมบูรณ์ยิ่งขึ้นได้ เนื่องจากสมาร์ทโฟนเกือบทั้งหมดในปัจจุบันมีเซ็นเซอร์ระบุตำแหน่ง เซ็นเซอร์ตรวจจับความเคลื่อนไหว บลูทูธ และ Wi-Fi การใช้ข้อมูลทำให้แอปสามารถบรรลุ "การรับรู้บริบท" ที่สามารถเพิ่มความสามารถและมูลค่าได้อย่างมาก และทำให้โดดเด่นใน App Store
ในบทช่วยสอนนี้ เราจะสำรวจการสร้างแอปที่รับรู้บริบทผ่านตัวอย่างการประมวลผลเหตุการณ์ที่ซับซ้อน เราจะใช้ตัวอย่างที่ค่อนข้างง่าย: แอปราคาน้ำมันที่ค้นหาราคาน้ำมันที่ดีที่สุดในพื้นที่ของคุณ
แอพที่ทราบบริบท
ในการออกแบบ Calm Technology นั้น Mark Weiser และ John Seely Brown กล่าวถึงเทคโนโลยีที่สงบว่าเป็น “สิ่งที่แจ้งแต่ไม่ต้องการการมุ่งเน้นหรือความสนใจของเรา”
แอปบนอุปกรณ์เคลื่อนที่ที่คำนึงถึงบริบทมีความสอดคล้องอย่างยิ่งกับแนวคิดนี้ และเป็นขั้นตอนที่สำคัญและมีค่าสำหรับเส้นทางนี้ พวกเขาใช้ข้อมูลเชิงบริบทที่รวบรวมจากเซ็นเซอร์ของพวกเขาเพื่อให้ข้อมูลที่มีค่าแก่ผู้ใช้ในเชิงรุก และพวกเขาทำโดยใช้ความพยายามเพียงเล็กน้อยในส่วนของผู้ใช้ Mark Weiser และ John Seely Brown จะปรบมือให้กับความก้าวหน้าทางเทคโนโลยีนี้อย่างไม่ต้องสงสัย
การตระหนักรู้บริบทเป็นแนวคิดที่แอปสามารถรับรู้และตอบสนองตามข้อมูลบริบทที่เข้าถึงได้ แอปดังกล่าวใช้ข้อมูลเซ็นเซอร์ที่หลากหลายซึ่งมีอยู่ในอุปกรณ์เคลื่อนที่เพื่อให้ข้อมูลที่ถูกต้องและเกี่ยวข้องกับผู้ใช้ในบริบทที่เหมาะสม แอปดังกล่าวสามารถ "เรียนรู้" ได้จริงตามแนวโน้มตลอดการใช้งานอุปกรณ์ และ/หรือจากข้อเสนอแนะที่ผู้ใช้ให้มา เมื่อเวลาผ่านไปจึงกลายเป็น "ฉลาดขึ้น" และมีประโยชน์มากขึ้น
การประมวลผลเหตุการณ์ที่ซับซ้อน
การประมวลผลเหตุการณ์ที่ซับซ้อน (CEP) คือรูปแบบหนึ่งของการประมวลผลเหตุการณ์ที่ใช้การวิเคราะห์ที่ซับซ้อนมากขึ้นของหลายเหตุการณ์ (เช่น เมื่อเวลาผ่านไป จากแหล่งที่มาต่างๆ เป็นต้น) การรวมและวิเคราะห์เนื้อหาเพื่อสรุปข้อมูลและรูปแบบที่มีความหมายมากขึ้น
ในแอพมือถือ CEP สามารถใช้กับเหตุการณ์ที่สร้างขึ้นจากเซ็นเซอร์ของอุปกรณ์มือถือรวมถึงแหล่งข้อมูลภายนอกที่แอพสามารถเข้าถึงได้
คุณสมบัติที่สำคัญของแอพราคาน้ำมันของเรา
สำหรับวัตถุประสงค์ของบทช่วยสอนการประมวลผลเหตุการณ์ที่ซับซ้อนของเรา สมมติว่าคุณสมบัติของแอปราคาน้ำมันของเราจำกัดดังต่อไปนี้:
- ตรวจหาตำแหน่งที่เกี่ยวข้องกับผู้ใช้ตามภูมิศาสตร์ โดยอัตโนมัติ (เช่น ตำแหน่งบ้านของผู้ใช้และตำแหน่งที่ทำงานของผู้ใช้)
- ระบุสถานีบริการน้ำมัน โดยอัตโนมัติ ภายในระยะทางที่เหมาะสมจากบ้านและที่ทำงานของผู้ใช้
- แจ้งผู้ใช้บริการน้ำมันราคาดีที่สุดใกล้บ้านและที่ทำงาน โดยอัตโนมัติ
ตกลง มาเริ่มกันเลย
การตรวจจับตำแหน่งบ้านและที่ทำงานของผู้ใช้
เริ่มจากตรรกะในการตรวจจับตำแหน่งบ้านและที่ทำงานของผู้ใช้โดยอัตโนมัติ เพื่อให้ง่ายสำหรับตัวอย่างการประมวลผลเหตุการณ์ที่ซับซ้อน เราจะถือว่าผู้ใช้มีตารางการทำงานที่ค่อนข้างปกติ ดังนั้นเราจึงสามารถสรุปได้ว่าโดยทั่วไปผู้ใช้จะอยู่บ้านระหว่าง 2 ถึง 03.00 น. และโดยทั่วไปจะอยู่ที่สำนักงานระหว่างเวลา 14.00 น. ถึง 15.00 น.
จากสมมติฐานดังกล่าว เรากำหนดกฎ CEP สองข้อและรวบรวมข้อมูลตำแหน่งและเวลาจากสมาร์ทโฟนของผู้ใช้:
- กฎของตำแหน่งบ้าน
- รวบรวมข้อมูลตำแหน่งระหว่าง 2 ถึง 3 โมงเช้าเป็นเวลาหนึ่งสัปดาห์
- จัดกลุ่มข้อมูลตำแหน่งเพื่อรับที่อยู่บ้านโดยประมาณ
- กฎสถานที่ทำงาน
- รวบรวมข้อมูลตำแหน่งระหว่าง 14.00 ถึง 15.00 น. สำหรับวันธรรมดา
- จัดกลุ่มข้อมูลตำแหน่งเพื่อรับตำแหน่งงานโดยประมาณ
อัลกอริทึมระดับสูงในการตรวจจับตำแหน่งแสดงไว้ด้านล่าง
ให้ถือว่าโครงสร้างข้อมูล JSON ง่าย ๆ ต่อไปนี้สำหรับข้อมูลตำแหน่ง:
{ "uid": "some unique identifier for device/user", "location": [longitude, latitude] "time": "time in user's timezone" }
หมายเหตุ: แนวทางปฏิบัติที่ดีคือการทำให้ข้อมูลเซ็นเซอร์ไม่เปลี่ยนรูปแบบ (หรือประเภทค่า) เพื่อให้สามารถใช้งานได้อย่างปลอดภัยโดยโมดูลต่างๆ ในเวิร์กโฟลว์ CEP
การดำเนินการ
เราจะใช้อัลกอริทึมของเราโดยใช้ รูปแบบโมดูล ที่เขียนได้ โดยแต่ละโมดูลทำงานเพียงงานเดียวและเรียกใช้งานถัดไปเมื่องานเสร็จสมบูรณ์ สิ่งนี้สอดคล้องกับปรัชญา Unix Rule of Modularity
โดยเฉพาะอย่างยิ่ง แต่ละโมดูลเป็นฟังก์ชันที่ยอมรับอ็อบเจ็กต์ 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); } }; }
โมดูลสะสม
ความรับผิดชอบของ accumulator คือการรวบรวมข้อมูลตำแหน่งเพื่อส่งต่อไปยังโมดูล 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]; }
สามารถใช้วิธีการที่คล้ายกันในการอนุมานสถานที่ทำงาน โดยมีความแตกต่างเพียงอย่างเดียวคือจะใช้ตัวกรองเวลาระหว่าง 14.00 น. ถึง 15.00 น. (ตรงข้ามกับ 2 และ 3 น.)
แอปเชื้อเพลิงของเราสามารถตรวจจับตำแหน่งที่ทำงานและบ้านของผู้ใช้ได้ โดยอัตโนมัติโดย ที่ผู้ใช้ไม่ต้องดำเนินการใดๆ นี่คือการประมวลผลตามบริบทที่ดีที่สุด!
ค้นหาสถานีบริการน้ำมันใกล้เคียง
งานหนักเพื่อสร้างการรับรู้บริบทได้เสร็จสิ้นแล้ว แต่เรายังคงต้องการกฎอีกหนึ่งข้อในการระบุว่าราคาสถานีบริการน้ำมันใดที่ต้องติดตาม (กล่าวคือ สถานีบริการน้ำมันใดอยู่ใกล้กับบ้านหรือที่ทำงานของผู้ใช้มากพอที่จะมีความเกี่ยวข้อง) กฎนี้จำเป็นต้องเข้าถึงตำแหน่งสถานีบริการน้ำมันทั้งหมดสำหรับทุกภูมิภาคที่แอปเชื้อเพลิงรองรับ กฎมีดังนี้:
- กฎสถานีบริการน้ำมัน
- ค้นหาสถานีบริการน้ำมันที่ใกล้ที่สุดสำหรับแต่ละตำแหน่งบ้านและที่ทำงาน
สามารถใช้งานได้ง่ายโดยใช้ฟังก์ชันระยะทางที่แสดงไว้ก่อนหน้าเป็นตัวกรองตำแหน่งเพื่อนำไปใช้กับสถานีบริการน้ำมันทั้งหมดที่แอปรู้จัก
ติดตามราคาน้ำมัน
เมื่อแอพเชื้อเพลิงได้รับรายการสถานีบริการน้ำมันที่ต้องการ (เช่น ใกล้เคียง) สำหรับผู้ใช้ ก็สามารถติดตามราคาน้ำมันที่ดีที่สุดที่สถานีเหล่านี้ได้อย่างง่ายดาย นอกจากนี้ยังสามารถแจ้งเตือนผู้ใช้เมื่อสถานีบริการน้ำมันเหล่านี้มีราคาหรือข้อเสนอพิเศษ โดยเฉพาะอย่างยิ่งเมื่อตรวจพบผู้ใช้อยู่ใกล้สถานีบริการน้ำมันเหล่านี้
บทสรุป
ในบทช่วยสอนการประมวลผลเหตุการณ์ที่ซับซ้อนนี้ เราแทบไม่ได้ขีดข่วนพื้นผิวของการประมวลผลแบบทราบบริบทเลย
ในตัวอย่างง่ายๆ ของเรา เราได้เพิ่มบริบทของตำแหน่งลงในแอปการรายงานราคาน้ำมันแบบง่ายๆ และทำให้ฉลาดขึ้น ขณะนี้แอปทำงานแตกต่างกันในแต่ละอุปกรณ์ และเมื่อเวลาผ่านไปจะตรวจจับรูปแบบตำแหน่ง เพื่อปรับปรุงคุณค่าของข้อมูลที่มอบให้แก่ผู้ใช้โดยอัตโนมัติ
สามารถเพิ่มข้อมูลตรรกะและเซ็นเซอร์ได้มากขึ้นอย่างแน่นอนเพื่อเพิ่มความแม่นยำและประโยชน์ของแอพที่รับรู้บริบทของเรา ตัวอย่างเช่น นักพัฒนามือถือที่ชาญฉลาดสามารถใช้ประโยชน์จากข้อมูลเครือข่ายสังคม ข้อมูลสภาพอากาศ ข้อมูลธุรกรรมของเทอร์มินัล POS และอื่นๆ เพื่อเพิ่มการรับรู้บริบทให้กับแอปของเรา และทำให้ใช้งานได้จริงและทำการตลาดได้มากขึ้น
ด้วยการคำนวณตามบริบท ความเป็นไปได้ไม่มีที่สิ้นสุด แอพที่ชาญฉลาดมากขึ้นเรื่อยๆ จะยังคงปรากฏในร้านแอพที่ใช้เทคโนโลยีอันทรงพลังนี้เพื่อทำให้ชีวิตของเราง่ายขึ้น