设计一个实时定位/轨迹系统

1. 概述

1.1 系统背景

实时定位/轨迹系统广泛应用于:

  • 物流配送:车辆实时定位、轨迹追踪
  • 共享出行:共享单车、网约车定位
  • 人员管理:员工定位、考勤管理
  • 资产管理:设备定位、资产追踪

系统特点

  • 实时性:位置数据实时更新
  • 高并发:支持百万级设备同时在线
  • 大数据量:轨迹数据海量存储
  • 高可用:7×24小时稳定运行

1.2 核心挑战

技术挑战

  • 实时定位:如何实时接收和处理位置数据
  • 轨迹存储:如何存储海量轨迹数据
  • 轨迹查询:如何快速查询历史轨迹
  • 高并发:如何支撑百万级并发
  • 地理计算:如何高效进行地理计算

1.3 本文内容结构

本文将从以下几个方面全面解析实时定位/轨迹系统:

  1. 需求分析:功能需求、非功能需求
  2. 架构设计:整体架构、模块设计
  3. 实时定位:位置上报、实时推送
  4. 轨迹存储:数据模型、存储方案
  5. 轨迹查询:查询优化、地理索引
  6. 高并发处理:限流、削峰、异步处理
  7. 高可用设计:容错、降级、监控
  8. 实战案例:完整实现方案

2. 需求分析

2.1 功能需求

2.1.1 核心功能

实时定位

  • 设备位置上报
  • 实时位置查询
  • 位置推送
  • 电子围栏

轨迹管理

  • 轨迹存储
  • 轨迹查询
  • 轨迹回放
  • 轨迹分析

地理服务

  • 距离计算
  • 路径规划
  • 地理编码
  • 逆地理编码

2.1.2 扩展功能

设备管理

  • 设备注册
  • 设备状态
  • 设备分组
  • 设备统计

告警功能

  • 围栏告警
  • 超速告警
  • 离线告警
  • 异常告警

2.2 非功能需求

2.2.1 性能需求

响应时间

  • 位置上报:< 100ms
  • 位置查询:< 200ms
  • 轨迹查询:< 1s

吞吐量

  • 峰值QPS:10万+
  • 平均QPS:5万+

并发设备

  • 同时在线设备:100万+
  • 峰值并发:50万+

2.2.2 可用性需求

可用性指标

  • 系统可用性:99.99%
  • 数据可靠性:99.9%
  • 故障恢复时间:< 5分钟

容错能力

  • 单点故障不影响服务
  • 自动故障转移
  • 数据备份恢复

2.2.3 数据需求

数据量

  • 单设备:每天1000条位置数据
  • 100万设备:每天10亿条数据
  • 年数据量:3650亿条

存储需求

  • 实时数据:热数据,快速查询
  • 历史数据:冷数据,归档存储

3. 架构设计

3.1 整体架构

3.1.1 架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
设备(GPS/北斗)

Nginx(负载均衡)

API网关(路由 + 鉴权 + 限流)

定位服务集群

├──→ 位置上报服务(接收位置数据)
├──→ 位置查询服务(实时位置查询)
├──→ 轨迹服务(轨迹存储和查询)
└──→ 地理服务(地理计算)

├──→ Redis(实时位置缓存)
├──→ Kafka(消息队列)
├──→ MySQL(设备信息、元数据)
├──→ HBase(轨迹数据存储)
└──→ Elasticsearch(轨迹搜索)

WebSocket服务(实时推送)

3.1.2 架构说明

接入层

  • Nginx:负载均衡、SSL终端
  • API网关:路由、鉴权、限流

服务层

  • 位置上报服务:接收设备位置数据
  • 位置查询服务:查询实时位置
  • 轨迹服务:轨迹存储和查询
  • 地理服务:地理计算服务

数据层

  • Redis:实时位置缓存、设备在线状态
  • Kafka:消息队列、异步处理
  • MySQL:设备信息、元数据
  • HBase:轨迹数据存储
  • Elasticsearch:轨迹搜索

推送层

  • WebSocket服务:实时位置推送

3.2 模块设计

3.2.1 位置上报服务

职责

  • 接收设备位置数据
  • 数据校验
  • 数据入库
  • 触发事件

技术选型

  • Spring Boot
  • Kafka Producer
  • Redis

3.2.2 位置查询服务

职责

  • 查询实时位置
  • 批量查询
  • 位置推送

技术选型

  • Spring Boot
  • Redis(缓存)
  • WebSocket

3.2.3 轨迹服务

职责

  • 轨迹存储
  • 轨迹查询
  • 轨迹分析

技术选型

  • Spring Boot
  • HBase(存储)
  • Elasticsearch(搜索)

3.2.4 地理服务

职责

  • 距离计算
  • 路径规划
  • 地理编码

技术选型

  • Spring Boot
  • 高德地图API
  • 百度地图API

4. 实时定位

4.1 位置上报

4.1.1 位置数据模型

位置数据结构

1
2
3
4
5
6
7
8
9
10
11
12
public class Location {
private String deviceId; // 设备ID
private Double latitude; // 纬度
private Double longitude; // 经度
private Double altitude; // 海拔(可选)
private Float speed; // 速度(km/h)
private Float direction; // 方向(度)
private Integer accuracy; // 精度(米)
private Long timestamp; // 时间戳
private String address; // 地址(可选)
private Map<String, Object> extra; // 扩展字段
}

4.1.2 位置上报接口

上报接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
@RequestMapping("/api/location")
public class LocationController {

@Autowired
private LocationService locationService;

@PostMapping("/report")
public Response<String> reportLocation(@RequestBody Location location) {
try {
// 参数校验
validateLocation(location);

// 限流检查
if (!locationService.checkRateLimit(location.getDeviceId())) {
return Response.error("请求过于频繁,请稍后再试");
}

// 上报位置
locationService.reportLocation(location);

return Response.success("上报成功");
} catch (BusinessException e) {
return Response.error(e.getMessage());
} catch (Exception e) {
log.error("位置上报失败", e);
return Response.error("系统异常,请稍后再试");
}
}
}

4.1.3 位置处理服务

处理服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Service
public class LocationService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Autowired
private KafkaTemplate<String, String> kafkaTemplate;

public void reportLocation(Location location) {
// 1. 更新实时位置缓存
updateRealTimeLocation(location);

// 2. 发送到消息队列(异步存储)
kafkaTemplate.send("location-report", location.getDeviceId(),
JSON.toJSONString(location));

// 3. 触发事件(电子围栏、告警等)
locationEventPublisher.publishLocationEvent(location);
}

private void updateRealTimeLocation(Location location) {
String key = "location:realtime:" + location.getDeviceId();
redisTemplate.opsForValue().set(key, JSON.toJSONString(location),
5, TimeUnit.MINUTES);

// 更新设备在线状态
String onlineKey = "device:online:" + location.getDeviceId();
redisTemplate.opsForValue().set(onlineKey, "1", 5, TimeUnit.MINUTES);
}
}

4.2 实时位置查询

4.2.1 位置查询接口

查询接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@RestController
@RequestMapping("/api/location")
public class LocationController {

@Autowired
private LocationService locationService;

@GetMapping("/realtime/{deviceId}")
public Response<Location> getRealTimeLocation(@PathVariable String deviceId) {
try {
Location location = locationService.getRealTimeLocation(deviceId);
if (location == null) {
return Response.error("设备不在线或位置数据不存在");
}
return Response.success(location);
} catch (Exception e) {
log.error("查询实时位置失败", e);
return Response.error("系统异常,请稍后再试");
}
}

@PostMapping("/batch")
public Response<List<Location>> batchGetLocation(@RequestBody List<String> deviceIds) {
try {
List<Location> locations = locationService.batchGetLocation(deviceIds);
return Response.success(locations);
} catch (Exception e) {
log.error("批量查询位置失败", e);
return Response.error("系统异常,请稍后再试");
}
}
}

4.2.2 位置查询服务

查询服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Service
public class LocationService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

public Location getRealTimeLocation(String deviceId) {
// 1. 查询Redis缓存
String key = "location:realtime:" + deviceId;
String value = redisTemplate.opsForValue().get(key);

if (value != null) {
return JSON.parseObject(value, Location.class);
}

// 2. 查询数据库(最近一次位置)
return locationMapper.selectLatestLocation(deviceId);
}

public List<Location> batchGetLocation(List<String> deviceIds) {
// 批量查询Redis
List<String> keys = deviceIds.stream()
.map(id -> "location:realtime:" + id)
.collect(Collectors.toList());

List<String> values = redisTemplate.opsForValue().multiGet(keys);

return values.stream()
.filter(Objects::nonNull)
.map(v -> JSON.parseObject(v, Location.class))
.collect(Collectors.toList());
}
}

4.3 实时位置推送

4.3.1 WebSocket推送

WebSocket服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new LocationWebSocketHandler(), "/ws/location")
.setAllowedOrigins("*");
}
}

@Component
public class LocationWebSocketHandler extends TextWebSocketHandler {

private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();

@Override
public void afterConnectionEstablished(WebSocketSession session) {
String deviceId = getDeviceId(session);
sessions.put(deviceId, session);
}

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
// 处理客户端消息
String deviceId = getDeviceId(session);
// 订阅设备位置
subscribeLocation(deviceId, session);
}

public void pushLocation(String deviceId, Location location) {
WebSocketSession session = sessions.get(deviceId);
if (session != null && session.isOpen()) {
try {
session.sendMessage(new TextMessage(JSON.toJSONString(location)));
} catch (Exception e) {
log.error("推送位置失败", e);
}
}
}
}

4.3.2 位置推送服务

推送服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class LocationPushService {

@Autowired
private LocationWebSocketHandler webSocketHandler;

@EventListener
public void handleLocationEvent(LocationEvent event) {
Location location = event.getLocation();
String deviceId = location.getDeviceId();

// 推送给订阅该设备的客户端
webSocketHandler.pushLocation(deviceId, location);
}
}

5. 轨迹存储

5.1 数据模型

5.1.1 轨迹数据模型

轨迹数据结构

1
2
3
4
5
6
7
8
9
10
public class TrackPoint {
private String deviceId; // 设备ID
private Double latitude; // 纬度
private Double longitude; // 经度
private Long timestamp; // 时间戳
private Float speed; // 速度
private Float direction; // 方向
private Integer accuracy; // 精度
private String address; // 地址
}

5.1.2 存储方案

存储策略

  • 热数据:最近7天,存储在HBase
  • 温数据:7-30天,存储在HBase
  • 冷数据:30天以上,归档到对象存储

5.2 HBase存储

5.2.1 HBase表设计

表结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// RowKey设计:deviceId + timestamp(倒序)
// 列族:info
// 列:latitude, longitude, speed, direction, accuracy, address

public class TrackPointRowKey {
public static byte[] buildRowKey(String deviceId, long timestamp) {
// deviceId + (Long.MAX_VALUE - timestamp)
// 实现倒序存储,最新数据在前
byte[] deviceBytes = deviceId.getBytes();
long reverseTimestamp = Long.MAX_VALUE - timestamp;
byte[] timestampBytes = Bytes.toBytes(reverseTimestamp);

return Bytes.add(deviceBytes, timestampBytes);
}
}

5.2.2 HBase写入

写入实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Service
public class TrackService {

@Autowired
private HBaseTemplate hBaseTemplate;

private static final String TABLE_NAME = "track_points";
private static final String COLUMN_FAMILY = "info";

public void saveTrackPoint(TrackPoint point) {
Put put = new Put(TrackPointRowKey.buildRowKey(point.getDeviceId(), point.getTimestamp()));
put.addColumn(Bytes.toBytes(COLUMN_FAMILY), Bytes.toBytes("latitude"),
Bytes.toBytes(point.getLatitude()));
put.addColumn(Bytes.toBytes(COLUMN_FAMILY), Bytes.toBytes("longitude"),
Bytes.toBytes(point.getLongitude()));
put.addColumn(Bytes.toBytes(COLUMN_FAMILY), Bytes.toBytes("speed"),
Bytes.toBytes(point.getSpeed()));
put.addColumn(Bytes.toBytes(COLUMN_FAMILY), Bytes.toBytes("direction"),
Bytes.toBytes(point.getDirection()));
put.addColumn(Bytes.toBytes(COLUMN_FAMILY), Bytes.toBytes("accuracy"),
Bytes.toBytes(point.getAccuracy()));
if (point.getAddress() != null) {
put.addColumn(Bytes.toBytes(COLUMN_FAMILY), Bytes.toBytes("address"),
Bytes.toBytes(point.getAddress()));
}

hBaseTemplate.put(TABLE_NAME, put);
}

public void batchSaveTrackPoints(List<TrackPoint> points) {
List<Put> puts = points.stream()
.map(point -> {
Put put = new Put(TrackPointRowKey.buildRowKey(point.getDeviceId(), point.getTimestamp()));
// ... 添加列
return put;
})
.collect(Collectors.toList());

hBaseTemplate.put(TABLE_NAME, puts);
}
}

5.3 异步存储

5.3.1 Kafka消费

轨迹存储消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Component
public class TrackConsumer {

@Autowired
private TrackService trackService;

@KafkaListener(topics = "location-report", groupId = "track-processor")
public void processLocation(String deviceId, String message) {
try {
Location location = JSON.parseObject(message, Location.class);

// 转换为轨迹点
TrackPoint point = convertToTrackPoint(location);

// 批量存储(每100条批量写入)
trackService.addToBatch(point);
} catch (Exception e) {
log.error("处理位置数据失败", e);
}
}

private TrackPoint convertToTrackPoint(Location location) {
TrackPoint point = new TrackPoint();
point.setDeviceId(location.getDeviceId());
point.setLatitude(location.getLatitude());
point.setLongitude(location.getLongitude());
point.setTimestamp(location.getTimestamp());
point.setSpeed(location.getSpeed());
point.setDirection(location.getDirection());
point.setAccuracy(location.getAccuracy());
point.setAddress(location.getAddress());
return point;
}
}

5.3.2 批量写入

批量写入服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class TrackService {

private final List<TrackPoint> batchBuffer = new ArrayList<>();
private final int BATCH_SIZE = 100;

@Scheduled(fixedDelay = 1000) // 每秒批量写入一次
public void batchSave() {
if (batchBuffer.size() >= BATCH_SIZE) {
synchronized (batchBuffer) {
if (batchBuffer.size() >= BATCH_SIZE) {
List<TrackPoint> batch = new ArrayList<>(batchBuffer);
batchBuffer.clear();
batchSaveTrackPoints(batch);
}
}
}
}

public void addToBatch(TrackPoint point) {
synchronized (batchBuffer) {
batchBuffer.add(point);
if (batchBuffer.size() >= BATCH_SIZE) {
batchSave();
}
}
}
}

6. 轨迹查询

6.1 轨迹查询接口

6.1.1 查询接口

轨迹查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@RestController
@RequestMapping("/api/track")
public class TrackController {

@Autowired
private TrackService trackService;

@GetMapping("/{deviceId}")
public Response<List<TrackPoint>> getTrack(
@PathVariable String deviceId,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false) Integer limit) {
try {
List<TrackPoint> track = trackService.getTrack(deviceId, startTime, endTime, limit);
return Response.success(track);
} catch (Exception e) {
log.error("查询轨迹失败", e);
return Response.error("系统异常,请稍后再试");
}
}

@GetMapping("/{deviceId}/replay")
public Response<List<TrackPoint>> replayTrack(
@PathVariable String deviceId,
@RequestParam Long startTime,
@RequestParam Long endTime) {
try {
List<TrackPoint> track = trackService.getTrack(deviceId, startTime, endTime, null);
// 按时间排序
track.sort(Comparator.comparing(TrackPoint::getTimestamp));
return Response.success(track);
} catch (Exception e) {
log.error("轨迹回放失败", e);
return Response.error("系统异常,请稍后再试");
}
}
}

6.2 HBase查询

6.2.1 轨迹查询实现

查询实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Service
public class TrackService {

@Autowired
private HBaseTemplate hBaseTemplate;

public List<TrackPoint> getTrack(String deviceId, Long startTime, Long endTime, Integer limit) {
// 构建Scan
Scan scan = new Scan();

// 设置RowKey范围
byte[] startRow = TrackPointRowKey.buildRowKey(deviceId,
endTime != null ? endTime : Long.MAX_VALUE);
byte[] stopRow = TrackPointRowKey.buildRowKey(deviceId,
startTime != null ? startTime : 0);

scan.setStartRow(startRow);
scan.setStopRow(stopRow);

// 设置列族
scan.addFamily(Bytes.toBytes("info"));

// 设置限制
if (limit != null && limit > 0) {
scan.setLimit(limit);
}

// 执行查询
List<TrackPoint> points = hBaseTemplate.find(TABLE_NAME, scan,
(result, rowNum) -> {
TrackPoint point = new TrackPoint();
point.setDeviceId(deviceId);
point.setLatitude(Bytes.toDouble(result.getValue(
Bytes.toBytes("info"), Bytes.toBytes("latitude"))));
point.setLongitude(Bytes.toDouble(result.getValue(
Bytes.toBytes("info"), Bytes.toBytes("longitude"))));
point.setSpeed(Bytes.toFloat(result.getValue(
Bytes.toBytes("info"), Bytes.toBytes("speed"))));
point.setDirection(Bytes.toFloat(result.getValue(
Bytes.toBytes("info"), Bytes.toBytes("direction"))));
point.setAccuracy(Bytes.toInt(result.getValue(
Bytes.toBytes("info"), Bytes.toBytes("accuracy"))));
byte[] addressBytes = result.getValue(
Bytes.toBytes("info"), Bytes.toBytes("address"));
if (addressBytes != null) {
point.setAddress(Bytes.toString(addressBytes));
}
// 从RowKey解析时间戳
point.setTimestamp(parseTimestampFromRowKey(result.getRow()));
return point;
});

return points;
}
}

6.3 地理索引

6.3.1 GeoHash索引

GeoHash实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class GeoHashService {

public String encode(double latitude, double longitude, int precision) {
// GeoHash编码
return GeoHash.geoHashStringWithCharacterPrecision(latitude, longitude, precision);
}

public List<String> getNeighbors(String geohash) {
// 获取相邻的GeoHash
return GeoHash.fromGeohashString(geohash).getAdjacent().stream()
.map(GeoHash::toBase32)
.collect(Collectors.toList());
}
}

6.3.2 附近设备查询

附近查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Service
public class LocationService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Autowired
private GeoHashService geoHashService;

public List<Location> findNearby(double latitude, double longitude, double radius) {
// 1. 计算GeoHash
String geohash = geoHashService.encode(latitude, longitude, 7);

// 2. 获取相邻GeoHash
List<String> neighbors = geoHashService.getNeighbors(geohash);
neighbors.add(geohash);

// 3. 查询Redis(使用GeoHash作为key)
List<Location> locations = new ArrayList<>();
for (String hash : neighbors) {
String key = "location:geo:" + hash;
Set<String> deviceIds = redisTemplate.opsForSet().members(key);

for (String deviceId : deviceIds) {
Location location = getRealTimeLocation(deviceId);
if (location != null) {
double distance = calculateDistance(latitude, longitude,
location.getLatitude(), location.getLongitude());
if (distance <= radius) {
locations.add(location);
}
}
}
}

return locations;
}

private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
// 使用Haversine公式计算距离
final int R = 6371; // 地球半径(公里)

double latDistance = Math.toRadians(lat2 - lat1);
double lonDistance = Math.toRadians(lon2 - lon1);
double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
* Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double distance = R * c;

return distance;
}
}

7. 高并发处理

7.1 限流

7.1.1 限流策略

多级限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class LocationService {

@Autowired
private RedisTemplate<String, String> redisTemplate;

public boolean checkRateLimit(String deviceId) {
String key = "rate:limit:location:" + deviceId;
Long count = redisTemplate.opsForValue().increment(key);

if (count == 1) {
redisTemplate.expire(key, 60, TimeUnit.SECONDS);
}

// 每分钟最多60次(每秒1次)
return count <= 60;
}
}

7.2 削峰

7.2.1 消息队列削峰

Kafka削峰

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class LocationService {

@Autowired
private KafkaTemplate<String, String> kafkaTemplate;

public void reportLocation(Location location) {
// 快速响应
// 发送到消息队列
kafkaTemplate.send("location-report", location.getDeviceId(),
JSON.toJSONString(location));
}
}

7.3 异步处理

7.3.1 异步存储

异步处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class LocationService {

@Async
public CompletableFuture<Void> processLocationAsync(Location location) {
// 1. 更新实时位置
updateRealTimeLocation(location);

// 2. 存储轨迹
trackService.saveTrackPoint(convertToTrackPoint(location));

// 3. 触发事件
locationEventPublisher.publishLocationEvent(location);

return CompletableFuture.completedFuture(null);
}
}

8. 高可用设计

8.1 容错设计

8.1.1 服务降级

降级策略

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class LocationService {

@HystrixCommand(fallbackMethod = "getRealTimeLocationFallback")
public Location getRealTimeLocation(String deviceId) {
return doGetRealTimeLocation(deviceId);
}

public Location getRealTimeLocationFallback(String deviceId) {
// 降级处理:从数据库查询
return locationMapper.selectLatestLocation(deviceId);
}
}

8.2 数据备份

8.2.1 数据备份策略

备份方案

  • 实时备份:Redis主从
  • 定期备份:HBase快照
  • 归档备份:冷数据归档到对象存储

8.3 监控告警

8.3.1 监控指标

关键指标

  • 位置上报QPS
  • 位置查询QPS
  • 轨迹存储QPS
  • 设备在线数
  • 存储使用率

9. 实战案例

9.1 完整实现

9.1.1 位置上报完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
@RequestMapping("/api/location")
public class LocationController {

@Autowired
private LocationService locationService;

@PostMapping("/report")
public Response<String> reportLocation(@RequestBody Location location) {
try {
// 1. 参数校验
validateLocation(location);

// 2. 限流检查
if (!locationService.checkRateLimit(location.getDeviceId())) {
return Response.error("请求过于频繁,请稍后再试");
}

// 3. 上报位置
locationService.reportLocation(location);

return Response.success("上报成功");
} catch (BusinessException e) {
return Response.error(e.getMessage());
} catch (Exception e) {
log.error("位置上报失败", e);
return Response.error("系统异常,请稍后再试");
}
}
}

9.1.2 轨迹查询完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping("/api/track")
public class TrackController {

@Autowired
private TrackService trackService;

@GetMapping("/{deviceId}")
public Response<List<TrackPoint>> getTrack(
@PathVariable String deviceId,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime) {
try {
List<TrackPoint> track = trackService.getTrack(deviceId, startTime, endTime, null);
return Response.success(track);
} catch (Exception e) {
log.error("查询轨迹失败", e);
return Response.error("系统异常,请稍后再试");
}
}
}

10. 总结

10.1 核心要点

  1. 架构设计:分层架构、服务拆分、数据分离
  2. 实时定位:位置上报、实时查询、实时推送
  3. 轨迹存储:HBase存储、批量写入、数据归档
  4. 轨迹查询:HBase查询、地理索引、附近查询
  5. 高并发处理:限流、削峰、异步处理
  6. 高可用设计:容错、降级、监控

10.2 关键设计

  1. 实时位置:Redis缓存,快速查询
  2. 轨迹存储:HBase存储,支持海量数据
  3. 地理索引:GeoHash索引,支持附近查询
  4. 异步处理:消息队列,削峰填谷
  5. 批量写入:批量写入HBase,提高性能

10.3 最佳实践

  1. 分层设计:清晰的架构分层
  2. 服务拆分:微服务架构
  3. 数据分离:热数据、温数据、冷数据分离
  4. 缓存策略:实时位置缓存
  5. 监控告警:实时监控,及时告警

相关文章