前言

查找附近的人功能作为现代社交应用和LBS(Location-Based Services)应用的核心功能,广泛应用于社交、外卖、打车、旅游等各个领域。Redis作为高性能的内存数据库,提供了丰富的地理位置处理能力,是实现查找附近的人功能的理想选择。本文从地理位置服务到GeoHash算法,从基础实现到企业级方案,系统梳理基于Redis的查找附近的人功能完整解决方案。

一、Redis地理位置服务架构设计

1.1 Redis地理位置服务整体架构

1.2 附近的人功能架构

二、Redis Geo数据结构

2.1 Redis Geo基础操作

2.1.1 Geo命令使用

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
* Redis Geo地理位置服务
*/
@Service
public class RedisGeoService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

private final String GEO_PREFIX = "geo:";

/**
* 添加地理位置
*/
public void addLocation(String key, String member, double longitude, double latitude) {
String redisKey = GEO_PREFIX + key;
Point point = new Point(longitude, latitude);
redisTemplate.opsForGeo().add(redisKey, point, member);
}

/**
* 批量添加地理位置
*/
public void batchAddLocations(String key, Map<String, Point> locations) {
String redisKey = GEO_PREFIX + key;

for (Map.Entry<String, Point> entry : locations.entrySet()) {
redisTemplate.opsForGeo().add(redisKey, entry.getValue(), entry.getKey());
}
}

/**
* 获取地理位置
*/
public List<Point> getLocation(String key, String member) {
String redisKey = GEO_PREFIX + key;
return redisTemplate.opsForGeo().position(redisKey, member);
}

/**
* 批量获取地理位置
*/
public Map<String, Point> batchGetLocations(String key, List<String> members) {
String redisKey = GEO_PREFIX + key;
List<Point> points = redisTemplate.opsForGeo().position(redisKey, members.toArray(new String[0]));

Map<String, Point> locationMap = new HashMap<>();
for (int i = 0; i < members.size(); i++) {
if (i < points.size() && points.get(i) != null) {
locationMap.put(members.get(i), points.get(i));
}
}

return locationMap;
}

/**
* 计算两点间距离
*/
public Distance calculateDistance(String key, String member1, String member2, Metric metric) {
String redisKey = GEO_PREFIX + key;
return redisTemplate.opsForGeo().distance(redisKey, member1, member2, metric);
}

/**
* 获取附近的人
*/
public List<GeoResult<RedisGeoCommands.GeoLocation<String>>> getNearbyPeople(String key, String member,
double radius, Metric metric, long limit) {
String redisKey = GEO_PREFIX + key;

Circle circle = new Circle(new Point(0, 0), new Distance(radius, metric));
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
.newGeoRadiusArgs()
.includeDistance()
.includeCoordinates()
.sortAscending()
.limit(limit);

return redisTemplate.opsForGeo().radius(redisKey, member, circle, args).getContent();
}

/**
* 获取指定范围内的用户
*/
public List<GeoResult<RedisGeoCommands.GeoLocation<String>>> getUsersInRange(String key,
double longitude, double latitude, double radius, Metric metric, long limit) {
String redisKey = GEO_PREFIX + key;

Point center = new Point(longitude, latitude);
Circle circle = new Circle(center, new Distance(radius, metric));

RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
.newGeoRadiusArgs()
.includeDistance()
.includeCoordinates()
.sortAscending()
.limit(limit);

return redisTemplate.opsForGeo().radius(redisKey, circle, args).getContent();
}

/**
* 删除地理位置
*/
public void removeLocation(String key, String member) {
String redisKey = GEO_PREFIX + key;
redisTemplate.opsForZSet().remove(redisKey, member);
}

/**
* 获取地理位置总数
*/
public Long getLocationCount(String key) {
String redisKey = GEO_PREFIX + key;
return redisTemplate.opsForGeo().zCard(redisKey);
}
}

2.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
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/**
* 高级地理位置服务
*/
@Service
public class AdvancedGeoService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private RedisGeoService redisGeoService;

private final String GEO_PREFIX = "geo:";
private final String USER_INFO_PREFIX = "user_info:";

/**
* 更新用户位置
*/
public void updateUserLocation(String userId, double longitude, double latitude, Map<String, Object> userInfo) {
String geoKey = GEO_PREFIX + "users";
String userInfoKey = USER_INFO_PREFIX + userId;

// 更新地理位置
redisGeoService.addLocation("users", userId, longitude, latitude);

// 更新用户信息
Map<String, Object> info = new HashMap<>();
info.put("longitude", longitude);
info.put("latitude", latitude);
info.put("updateTime", System.currentTimeMillis());
info.putAll(userInfo);

redisTemplate.opsForHash().putAll(userInfoKey, info);
redisTemplate.expire(userInfoKey, Duration.ofDays(7));
}

/**
* 获取附近的人(带详细信息)
*/
public List<NearbyUser> getNearbyUsersWithDetails(String userId, double radius, long limit) {
String geoKey = GEO_PREFIX + "users";

// 获取附近的人
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> nearbyResults =
redisGeoService.getNearbyPeople("users", userId, radius, DistanceUnit.KILOMETERS, limit);

List<NearbyUser> nearbyUsers = new ArrayList<>();

for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : nearbyResults) {
String nearbyUserId = result.getContent().getName();

// 跳过自己
if (nearbyUserId.equals(userId)) {
continue;
}

// 获取用户详细信息
Map<Object, Object> userInfo = getUserInfo(nearbyUserId);

NearbyUser nearbyUser = new NearbyUser();
nearbyUser.setUserId(nearbyUserId);
nearbyUser.setDistance(result.getDistance().getValue());
nearbyUser.setLongitude(result.getContent().getPoint().getX());
nearbyUser.setLatitude(result.getContent().getPoint().getY());
nearbyUser.setUserInfo(userInfo);

nearbyUsers.add(nearbyUser);
}

return nearbyUsers;
}

/**
* 获取用户信息
*/
private Map<Object, Object> getUserInfo(String userId) {
String userInfoKey = USER_INFO_PREFIX + userId;
return redisTemplate.opsForHash().entries(userInfoKey);
}

/**
* 搜索附近的服务
*/
public List<NearbyService> searchNearbyServices(double longitude, double latitude, String serviceType,
double radius, long limit) {
String geoKey = GEO_PREFIX + "services:" + serviceType;

// 获取附近的服务
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> serviceResults =
redisGeoService.getUsersInRange("services:" + serviceType, longitude, latitude,
radius, DistanceUnit.KILOMETERS, limit);

List<NearbyService> nearbyServices = new ArrayList<>();

for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : serviceResults) {
String serviceId = result.getContent().getName();

// 获取服务详细信息
Map<Object, Object> serviceInfo = getServiceInfo(serviceType, serviceId);

NearbyService nearbyService = new NearbyService();
nearbyService.setServiceId(serviceId);
nearbyService.setServiceType(serviceType);
nearbyService.setDistance(result.getDistance().getValue());
nearbyService.setLongitude(result.getContent().getPoint().getX());
nearbyService.setLatitude(result.getContent().getPoint().getY());
nearbyService.setServiceInfo(serviceInfo);

nearbyServices.add(nearbyService);
}

return nearbyServices;
}

/**
* 获取服务信息
*/
private Map<Object, Object> getServiceInfo(String serviceType, String serviceId) {
String serviceInfoKey = "service_info:" + serviceType + ":" + serviceId;
return redisTemplate.opsForHash().entries(serviceInfoKey);
}

/**
* 计算两点间距离
*/
public double calculateDistance(double lon1, double lat1, double lon2, double lat2) {
return redisGeoService.calculateDistance("users", "temp1", "temp2", DistanceUnit.KILOMETERS).getValue();
}

/**
* 获取用户轨迹
*/
public List<UserTrack> getUserTrack(String userId, long startTime, long endTime) {
String trackKey = "user_track:" + userId;

// 获取轨迹数据
Set<ZSetOperations.TypedTuple<Object>> trackData = redisTemplate.opsForZSet()
.rangeWithScores(trackKey, startTime, endTime);

List<UserTrack> tracks = new ArrayList<>();

for (ZSetOperations.TypedTuple<Object> tuple : trackData) {
String trackInfo = (String) tuple.getValue();
long timestamp = tuple.getScore().longValue();

// 解析轨迹信息
String[] parts = trackInfo.split(",");
if (parts.length >= 2) {
UserTrack track = new UserTrack();
track.setUserId(userId);
track.setLongitude(Double.parseDouble(parts[0]));
track.setLatitude(Double.parseDouble(parts[1]));
track.setTimestamp(timestamp);

tracks.add(track);
}
}

return tracks;
}

/**
* 记录用户轨迹
*/
public void recordUserTrack(String userId, double longitude, double latitude) {
String trackKey = "user_track:" + userId;
String trackInfo = longitude + "," + latitude;
long timestamp = System.currentTimeMillis();

redisTemplate.opsForZSet().add(trackKey, trackInfo, timestamp);

// 只保留最近7天的轨迹
long sevenDaysAgo = timestamp - 7 * 24 * 60 * 60 * 1000;
redisTemplate.opsForZSet().removeRangeByScore(trackKey, 0, sevenDaysAgo);
}
}

2.2 GeoHash算法实现

2.2.1 GeoHash编码解码

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/**
* GeoHash算法实现
*/
@Component
public class GeoHashAlgorithm {

private static final String BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
private static final int[] BITS = {16, 8, 4, 2, 1};

/**
* 编码经纬度为GeoHash
*/
public String encode(double latitude, double longitude, int precision) {
double[] latRange = {-90.0, 90.0};
double[] lonRange = {-180.0, 180.0};

StringBuilder geohash = new StringBuilder();
boolean isEven = true;
int bit = 0;
int ch = 0;

while (geohash.length() < precision) {
if (isEven) {
double mid = (lonRange[0] + lonRange[1]) / 2;
if (longitude >= mid) {
ch |= BITS[bit];
lonRange[0] = mid;
} else {
lonRange[1] = mid;
}
} else {
double mid = (latRange[0] + latRange[1]) / 2;
if (latitude >= mid) {
ch |= BITS[bit];
latRange[0] = mid;
} else {
latRange[1] = mid;
}
}

isEven = !isEven;

if (bit < 4) {
bit++;
} else {
geohash.append(BASE32.charAt(ch));
bit = 0;
ch = 0;
}
}

return geohash.toString();
}

/**
* 解码GeoHash为经纬度
*/
public double[] decode(String geohash) {
double[] latRange = {-90.0, 90.0};
double[] lonRange = {-180.0, 180.0};

boolean isEven = true;

for (char c : geohash.toCharArray()) {
int idx = BASE32.indexOf(c);
if (idx == -1) {
throw new IllegalArgumentException("Invalid geohash character: " + c);
}

for (int bit : BITS) {
if (isEven) {
double mid = (lonRange[0] + lonRange[1]) / 2;
if ((idx & bit) != 0) {
lonRange[0] = mid;
} else {
lonRange[1] = mid;
}
} else {
double mid = (latRange[0] + latRange[1]) / 2;
if ((idx & bit) != 0) {
latRange[0] = mid;
} else {
latRange[1] = mid;
}
}
isEven = !isEven;
}
}

double latitude = (latRange[0] + latRange[1]) / 2;
double longitude = (lonRange[0] + lonRange[1]) / 2;

return new double[]{latitude, longitude};
}

/**
* 获取GeoHash的邻居
*/
public List<String> getNeighbors(String geohash) {
List<String> neighbors = new ArrayList<>();

// 获取8个方向的邻居
String[] directions = {"top", "right", "bottom", "left"};

for (String direction : directions) {
String neighbor = getNeighbor(geohash, direction);
if (neighbor != null) {
neighbors.add(neighbor);
}
}

return neighbors;
}

/**
* 获取指定方向的邻居
*/
private String getNeighbor(String geohash, String direction) {
double[] coords = decode(geohash);
double latitude = coords[0];
double longitude = coords[1];

double latOffset = 0;
double lonOffset = 0;

switch (direction) {
case "top":
latOffset = 1;
break;
case "right":
lonOffset = 1;
break;
case "bottom":
latOffset = -1;
break;
case "left":
lonOffset = -1;
break;
}

// 计算偏移后的经纬度
double newLat = latitude + latOffset * getLatitudePrecision(geohash.length());
double newLon = longitude + lonOffset * getLongitudePrecision(geohash.length());

return encode(newLat, newLon, geohash.length());
}

/**
* 获取纬度精度
*/
private double getLatitudePrecision(int length) {
return 180.0 / Math.pow(2, (length * 5) / 2);
}

/**
* 获取经度精度
*/
private double getLongitudePrecision(int length) {
return 360.0 / Math.pow(2, (length * 5 + 1) / 2);
}

/**
* 计算两个GeoHash之间的距离
*/
public double calculateDistance(String geohash1, String geohash2) {
double[] coords1 = decode(geohash1);
double[] coords2 = decode(geohash2);

return calculateDistance(coords1[0], coords1[1], coords2[0], coords2[1]);
}

/**
* 计算两点间距离(Haversine公式)
*/
public double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
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));

return R * c;
}
}

2.2.2 基于GeoHash的附近的人查询

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/**
* 基于GeoHash的附近的人查询服务
*/
@Service
public class GeoHashNearbyService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private GeoHashAlgorithm geoHashAlgorithm;

private final String GEOHASH_PREFIX = "geohash:";
private final int GEOHASH_PRECISION = 6; // 6位精度约1.2km

/**
* 添加用户位置(基于GeoHash)
*/
public void addUserLocation(String userId, double latitude, double longitude) {
// 生成GeoHash
String geohash = geoHashAlgorithm.encode(latitude, longitude, GEOHASH_PRECISION);

// 存储到Redis
String key = GEOHASH_PREFIX + geohash;
redisTemplate.opsForSet().add(key, userId);

// 存储用户详细信息
String userKey = "user_location:" + userId;
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("latitude", latitude);
userInfo.put("longitude", longitude);
userInfo.put("geohash", geohash);
userInfo.put("updateTime", System.currentTimeMillis());

redisTemplate.opsForHash().putAll(userKey, userInfo);
redisTemplate.expire(userKey, Duration.ofDays(1));

// 设置过期时间
redisTemplate.expire(key, Duration.ofDays(1));
}

/**
* 查找附近的人(基于GeoHash)
*/
public List<NearbyUser> findNearbyUsers(String userId, double radius) {
// 获取用户位置
String userKey = "user_location:" + userId;
Map<Object, Object> userInfo = redisTemplate.opsForHash().entries(userKey);

if (userInfo.isEmpty()) {
return Collections.emptyList();
}

double latitude = Double.parseDouble(userInfo.get("latitude").toString());
double longitude = Double.parseDouble(userInfo.get("longitude").toString());
String userGeohash = userInfo.get("geohash").toString();

// 获取附近的人
Set<String> nearbyUserIds = new HashSet<>();

// 1. 获取当前GeoHash的用户
String currentKey = GEOHASH_PREFIX + userGeohash;
Set<Object> currentUsers = redisTemplate.opsForSet().members(currentKey);
if (currentUsers != null) {
currentUsers.forEach(user -> nearbyUserIds.add(user.toString()));
}

// 2. 获取邻居GeoHash的用户
List<String> neighbors = geoHashAlgorithm.getNeighbors(userGeohash);
for (String neighbor : neighbors) {
String neighborKey = GEOHASH_PREFIX + neighbor;
Set<Object> neighborUsers = redisTemplate.opsForSet().members(neighborKey);
if (neighborUsers != null) {
neighborUsers.forEach(user -> nearbyUserIds.add(user.toString()));
}
}

// 3. 计算距离并筛选
List<NearbyUser> nearbyUsers = new ArrayList<>();
for (String nearbyUserId : nearbyUserIds) {
if (nearbyUserId.equals(userId)) {
continue; // 跳过自己
}

// 获取用户位置
String nearbyUserKey = "user_location:" + nearbyUserId;
Map<Object, Object> nearbyUserInfo = redisTemplate.opsForHash().entries(nearbyUserKey);

if (!nearbyUserInfo.isEmpty()) {
double nearbyLat = Double.parseDouble(nearbyUserInfo.get("latitude").toString());
double nearbyLon = Double.parseDouble(nearbyUserInfo.get("longitude").toString());

// 计算距离
double distance = geoHashAlgorithm.calculateDistance(latitude, longitude, nearbyLat, nearbyLon);

// 筛选距离范围内的用户
if (distance <= radius) {
NearbyUser nearbyUser = new NearbyUser();
nearbyUser.setUserId(nearbyUserId);
nearbyUser.setDistance(distance);
nearbyUser.setLatitude(nearbyLat);
nearbyUser.setLongitude(nearbyLon);
nearbyUser.setUserInfo(nearbyUserInfo);

nearbyUsers.add(nearbyUser);
}
}
}

// 按距离排序
nearbyUsers.sort((u1, u2) -> Double.compare(u1.getDistance(), u2.getDistance()));

return nearbyUsers;
}

/**
* 查找附近的服务
*/
public List<NearbyService> findNearbyServices(double latitude, double longitude, String serviceType, double radius) {
// 生成GeoHash
String geohash = geoHashAlgorithm.encode(latitude, longitude, GEOHASH_PRECISION);

// 获取附近的服务
Set<String> nearbyServiceIds = new HashSet<>();

// 1. 获取当前GeoHash的服务
String currentKey = GEOHASH_PREFIX + serviceType + ":" + geohash;
Set<Object> currentServices = redisTemplate.opsForSet().members(currentKey);
if (currentServices != null) {
currentServices.forEach(service -> nearbyServiceIds.add(service.toString()));
}

// 2. 获取邻居GeoHash的服务
List<String> neighbors = geoHashAlgorithm.getNeighbors(geohash);
for (String neighbor : neighbors) {
String neighborKey = GEOHASH_PREFIX + serviceType + ":" + neighbor;
Set<Object> neighborServices = redisTemplate.opsForSet().members(neighborKey);
if (neighborServices != null) {
neighborServices.forEach(service -> nearbyServiceIds.add(service.toString()));
}
}

// 3. 计算距离并筛选
List<NearbyService> nearbyServices = new ArrayList<>();
for (String serviceId : nearbyServiceIds) {
// 获取服务位置
String serviceKey = "service_location:" + serviceType + ":" + serviceId;
Map<Object, Object> serviceInfo = redisTemplate.opsForHash().entries(serviceKey);

if (!serviceInfo.isEmpty()) {
double serviceLat = Double.parseDouble(serviceInfo.get("latitude").toString());
double serviceLon = Double.parseDouble(serviceInfo.get("longitude").toString());

// 计算距离
double distance = geoHashAlgorithm.calculateDistance(latitude, longitude, serviceLat, serviceLon);

// 筛选距离范围内的服务
if (distance <= radius) {
NearbyService nearbyService = new NearbyService();
nearbyService.setServiceId(serviceId);
nearbyService.setServiceType(serviceType);
nearbyService.setDistance(distance);
nearbyService.setLatitude(serviceLat);
nearbyService.setLongitude(serviceLon);
nearbyService.setServiceInfo(serviceInfo);

nearbyServices.add(nearbyService);
}
}
}

// 按距离排序
nearbyServices.sort((s1, s2) -> Double.compare(s1.getDistance(), s2.getDistance()));

return nearbyServices;
}

/**
* 更新用户位置
*/
public void updateUserLocation(String userId, double latitude, double longitude) {
// 获取旧位置
String userKey = "user_location:" + userId;
Map<Object, Object> oldUserInfo = redisTemplate.opsForHash().entries(userKey);

if (!oldUserInfo.isEmpty()) {
String oldGeohash = oldUserInfo.get("geohash").toString();

// 从旧GeoHash中移除用户
String oldKey = GEOHASH_PREFIX + oldGeohash;
redisTemplate.opsForSet().remove(oldKey, userId);
}

// 添加新位置
addUserLocation(userId, latitude, longitude);
}
}

三、企业级附近的人应用

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
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
/**
* 社交附近的人服务
*/
@Service
public class SocialNearbyService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private AdvancedGeoService advancedGeoService;

@Autowired
private UserService userService;

private final String SOCIAL_GEO_PREFIX = "social_geo:";
private final String USER_PREFERENCE_PREFIX = "user_preference:";

/**
* 更新用户社交位置
*/
public void updateSocialLocation(String userId, double longitude, double latitude,
Map<String, Object> socialInfo) {
String geoKey = SOCIAL_GEO_PREFIX + "users";

// 更新地理位置
advancedGeoService.updateUserLocation(userId, longitude, latitude, socialInfo);

// 更新社交信息
updateSocialInfo(userId, socialInfo);

// 更新用户偏好
updateUserPreferences(userId, socialInfo);
}

/**
* 查找附近的人(社交版)
*/
public List<SocialNearbyUser> findNearbySocialUsers(String userId, double radius, long limit) {
// 获取用户偏好
Map<String, Object> userPreferences = getUserPreferences(userId);

// 获取附近的人
List<NearbyUser> nearbyUsers = advancedGeoService.getNearbyUsersWithDetails(userId, radius, limit * 2);

// 根据社交偏好筛选和排序
List<SocialNearbyUser> socialNearbyUsers = new ArrayList<>();

for (NearbyUser nearbyUser : nearbyUsers) {
// 获取用户社交信息
Map<Object, Object> socialInfo = getUserSocialInfo(nearbyUser.getUserId());

// 计算社交匹配度
double socialScore = calculateSocialScore(userPreferences, socialInfo);

// 只返回匹配度较高的用户
if (socialScore > 0.3) {
SocialNearbyUser socialUser = new SocialNearbyUser();
socialUser.setUserId(nearbyUser.getUserId());
socialUser.setDistance(nearbyUser.getDistance());
socialUser.setLatitude(nearbyUser.getLatitude());
socialUser.setLongitude(nearbyUser.getLongitude());
socialUser.setSocialScore(socialScore);
socialUser.setSocialInfo(socialInfo);

socialNearbyUsers.add(socialUser);
}
}

// 按社交匹配度排序
socialNearbyUsers.sort((u1, u2) -> Double.compare(u2.getSocialScore(), u1.getSocialScore()));

// 限制返回数量
return socialNearbyUsers.stream().limit(limit).collect(Collectors.toList());
}

/**
* 查找附近的活动
*/
public List<NearbyActivity> findNearbyActivities(String userId, double radius, long limit) {
// 获取用户位置
List<Point> userLocation = advancedGeoService.getLocation("users", userId);
if (userLocation.isEmpty()) {
return Collections.emptyList();
}

Point userPoint = userLocation.get(0);

// 获取附近的活动
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> activityResults =
advancedGeoService.getUsersInRange("activities", userPoint.getX(), userPoint.getY(),
radius, DistanceUnit.KILOMETERS, limit);

List<NearbyActivity> nearbyActivities = new ArrayList<>();

for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : activityResults) {
String activityId = result.getContent().getName();

// 获取活动详细信息
Map<Object, Object> activityInfo = getActivityInfo(activityId);

NearbyActivity activity = new NearbyActivity();
activity.setActivityId(activityId);
activity.setDistance(result.getDistance().getValue());
activity.setLongitude(result.getContent().getPoint().getX());
activity.setLatitude(result.getContent().getPoint().getY());
activity.setActivityInfo(activityInfo);

nearbyActivities.add(activity);
}

return nearbyActivities;
}

/**
* 更新社交信息
*/
private void updateSocialInfo(String userId, Map<String, Object> socialInfo) {
String socialKey = "social_info:" + userId;
redisTemplate.opsForHash().putAll(socialKey, socialInfo);
redisTemplate.expire(socialKey, Duration.ofDays(30));
}

/**
* 获取用户社交信息
*/
private Map<Object, Object> getUserSocialInfo(String userId) {
String socialKey = "social_info:" + userId;
return redisTemplate.opsForHash().entries(socialKey);
}

/**
* 更新用户偏好
*/
private void updateUserPreferences(String userId, Map<String, Object> socialInfo) {
String preferenceKey = USER_PREFERENCE_PREFIX + userId;

// 提取偏好信息
Map<String, Object> preferences = new HashMap<>();
preferences.put("age", socialInfo.get("age"));
preferences.put("gender", socialInfo.get("gender"));
preferences.put("interests", socialInfo.get("interests"));
preferences.put("hobbies", socialInfo.get("hobbies"));

redisTemplate.opsForHash().putAll(preferenceKey, preferences);
redisTemplate.expire(preferenceKey, Duration.ofDays(30));
}

/**
* 获取用户偏好
*/
private Map<String, Object> getUserPreferences(String userId) {
String preferenceKey = USER_PREFERENCE_PREFIX + userId;
Map<Object, Object> rawPreferences = redisTemplate.opsForHash().entries(preferenceKey);

Map<String, Object> preferences = new HashMap<>();
for (Map.Entry<Object, Object> entry : rawPreferences.entrySet()) {
preferences.put(entry.getKey().toString(), entry.getValue());
}

return preferences;
}

/**
* 计算社交匹配度
*/
private double calculateSocialScore(Map<String, Object> userPreferences, Map<Object, Object> socialInfo) {
double score = 0.0;
int factors = 0;

// 年龄匹配
if (userPreferences.containsKey("age") && socialInfo.containsKey("age")) {
int userAge = Integer.parseInt(userPreferences.get("age").toString());
int socialAge = Integer.parseInt(socialInfo.get("age").toString());
int ageDiff = Math.abs(userAge - socialAge);

if (ageDiff <= 5) {
score += 0.3;
} else if (ageDiff <= 10) {
score += 0.2;
} else if (ageDiff <= 15) {
score += 0.1;
}
factors++;
}

// 性别匹配
if (userPreferences.containsKey("gender") && socialInfo.containsKey("gender")) {
String userGender = userPreferences.get("gender").toString();
String socialGender = socialInfo.get("gender").toString();

if (!userGender.equals(socialGender)) {
score += 0.2;
}
factors++;
}

// 兴趣匹配
if (userPreferences.containsKey("interests") && socialInfo.containsKey("interests")) {
String userInterests = userPreferences.get("interests").toString();
String socialInterests = socialInfo.get("interests").toString();

// 计算兴趣重叠度
double interestOverlap = calculateInterestOverlap(userInterests, socialInterests);
score += interestOverlap * 0.5;
factors++;
}

return factors > 0 ? score / factors : 0.0;
}

/**
* 计算兴趣重叠度
*/
private double calculateInterestOverlap(String userInterests, String socialInterests) {
String[] userInterestArray = userInterests.split(",");
String[] socialInterestArray = socialInterests.split(",");

Set<String> userInterestSet = Arrays.stream(userInterestArray)
.map(String::trim)
.collect(Collectors.toSet());

Set<String> socialInterestSet = Arrays.stream(socialInterestArray)
.map(String::trim)
.collect(Collectors.toSet());

Set<String> intersection = new HashSet<>(userInterestSet);
intersection.retainAll(socialInterestSet);

Set<String> union = new HashSet<>(userInterestSet);
union.addAll(socialInterestSet);

return union.isEmpty() ? 0.0 : (double) intersection.size() / union.size();
}

/**
* 获取活动信息
*/
private Map<Object, Object> getActivityInfo(String activityId) {
String activityKey = "activity_info:" + activityId;
return redisTemplate.opsForHash().entries(activityKey);
}
}

3.2 外卖应用附近的人

3.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/**
* 外卖附近的人服务
*/
@Service
public class DeliveryNearbyService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private AdvancedGeoService advancedGeoService;

@Autowired
private RestaurantService restaurantService;

private final String DELIVERY_GEO_PREFIX = "delivery_geo:";
private final String RESTAURANT_PREFIX = "restaurant:";

/**
* 更新餐厅位置
*/
public void updateRestaurantLocation(String restaurantId, double longitude, double latitude,
Map<String, Object> restaurantInfo) {
String geoKey = DELIVERY_GEO_PREFIX + "restaurants";

// 更新地理位置
advancedGeoService.updateUserLocation(restaurantId, longitude, latitude, restaurantInfo);

// 更新餐厅信息
updateRestaurantInfo(restaurantId, restaurantInfo);
}

/**
* 查找附近的餐厅
*/
public List<NearbyRestaurant> findNearbyRestaurants(String userId, double radius, long limit) {
// 获取用户位置
List<Point> userLocation = advancedGeoService.getLocation("users", userId);
if (userLocation.isEmpty()) {
return Collections.emptyList();
}

Point userPoint = userLocation.get(0);

// 获取附近的餐厅
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> restaurantResults =
advancedGeoService.getUsersInRange("restaurants", userPoint.getX(), userPoint.getY(),
radius, DistanceUnit.KILOMETERS, limit);

List<NearbyRestaurant> nearbyRestaurants = new ArrayList<>();

for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : restaurantResults) {
String restaurantId = result.getContent().getName();

// 获取餐厅详细信息
Map<Object, Object> restaurantInfo = getRestaurantInfo(restaurantId);

// 检查餐厅状态
if (isRestaurantAvailable(restaurantInfo)) {
NearbyRestaurant restaurant = new NearbyRestaurant();
restaurant.setRestaurantId(restaurantId);
restaurant.setDistance(result.getDistance().getValue());
restaurant.setLongitude(result.getContent().getPoint().getX());
restaurant.setLatitude(result.getContent().getPoint().getY());
restaurant.setRestaurantInfo(restaurantInfo);

nearbyRestaurants.add(restaurant);
}
}

return nearbyRestaurants;
}

/**
* 查找附近的骑手
*/
public List<NearbyRider> findNearbyRiders(String userId, double radius, long limit) {
// 获取用户位置
List<Point> userLocation = advancedGeoService.getLocation("users", userId);
if (userLocation.isEmpty()) {
return Collections.emptyList();
}

Point userPoint = userLocation.get(0);

// 获取附近的骑手
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> riderResults =
advancedGeoService.getUsersInRange("riders", userPoint.getX(), userPoint.getY(),
radius, DistanceUnit.KILOMETERS, limit);

List<NearbyRider> nearbyRiders = new ArrayList<>();

for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : riderResults) {
String riderId = result.getContent().getName();

// 获取骑手详细信息
Map<Object, Object> riderInfo = getRiderInfo(riderId);

// 检查骑手状态
if (isRiderAvailable(riderInfo)) {
NearbyRider rider = new NearbyRider();
rider.setRiderId(riderId);
rider.setDistance(result.getDistance().getValue());
rider.setLongitude(result.getContent().getPoint().getX());
rider.setLatitude(result.getContent().getPoint().getY());
rider.setRiderInfo(riderInfo);

nearbyRiders.add(rider);
}
}

return nearbyRiders;
}

/**
* 更新骑手位置
*/
public void updateRiderLocation(String riderId, double longitude, double latitude,
Map<String, Object> riderInfo) {
String geoKey = DELIVERY_GEO_PREFIX + "riders";

// 更新地理位置
advancedGeoService.updateUserLocation(riderId, longitude, latitude, riderInfo);

// 更新骑手信息
updateRiderInfo(riderId, riderInfo);

// 记录骑手轨迹
advancedGeoService.recordUserTrack(riderId, longitude, latitude);
}

/**
* 更新餐厅信息
*/
private void updateRestaurantInfo(String restaurantId, Map<String, Object> restaurantInfo) {
String restaurantKey = RESTAURANT_PREFIX + restaurantId;
redisTemplate.opsForHash().putAll(restaurantKey, restaurantInfo);
redisTemplate.expire(restaurantKey, Duration.ofDays(1));
}

/**
* 获取餐厅信息
*/
private Map<Object, Object> getRestaurantInfo(String restaurantId) {
String restaurantKey = RESTAURANT_PREFIX + restaurantId;
return redisTemplate.opsForHash().entries(restaurantKey);
}

/**
* 检查餐厅是否可用
*/
private boolean isRestaurantAvailable(Map<Object, Object> restaurantInfo) {
if (restaurantInfo.isEmpty()) {
return false;
}

// 检查营业状态
String status = restaurantInfo.get("status").toString();
if (!"OPEN".equals(status)) {
return false;
}

// 检查营业时间
long currentTime = System.currentTimeMillis();
long openTime = Long.parseLong(restaurantInfo.get("openTime").toString());
long closeTime = Long.parseLong(restaurantInfo.get("closeTime").toString());

return currentTime >= openTime && currentTime <= closeTime;
}

/**
* 更新骑手信息
*/
private void updateRiderInfo(String riderId, Map<String, Object> riderInfo) {
String riderKey = "rider_info:" + riderId;
redisTemplate.opsForHash().putAll(riderKey, riderInfo);
redisTemplate.expire(riderKey, Duration.ofHours(1));
}

/**
* 获取骑手信息
*/
private Map<Object, Object> getRiderInfo(String riderId) {
String riderKey = "rider_info:" + riderId;
return redisTemplate.opsForHash().entries(riderKey);
}

/**
* 检查骑手是否可用
*/
private boolean isRiderAvailable(Map<Object, Object> riderInfo) {
if (riderInfo.isEmpty()) {
return false;
}

// 检查骑手状态
String status = riderInfo.get("status").toString();
if (!"AVAILABLE".equals(status)) {
return false;
}

// 检查最后更新时间(5分钟内)
long lastUpdateTime = Long.parseLong(riderInfo.get("lastUpdateTime").toString());
long currentTime = System.currentTimeMillis();

return currentTime - lastUpdateTime <= 5 * 60 * 1000;
}
}

3.3 打车应用附近的人

3.3.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/**
* 打车附近的人服务
*/
@Service
public class TaxiNearbyService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private AdvancedGeoService advancedGeoService;

@Autowired
private DriverService driverService;

private final String TAXI_GEO_PREFIX = "taxi_geo:";
private final String DRIVER_PREFIX = "driver:";

/**
* 更新司机位置
*/
public void updateDriverLocation(String driverId, double longitude, double latitude,
Map<String, Object> driverInfo) {
String geoKey = TAXI_GEO_PREFIX + "drivers";

// 更新地理位置
advancedGeoService.updateUserLocation(driverId, longitude, latitude, driverInfo);

// 更新司机信息
updateDriverInfo(driverId, driverInfo);

// 记录司机轨迹
advancedGeoService.recordUserTrack(driverId, longitude, latitude);
}

/**
* 查找附近的司机
*/
public List<NearbyDriver> findNearbyDrivers(String userId, double radius, long limit) {
// 获取用户位置
List<Point> userLocation = advancedGeoService.getLocation("users", userId);
if (userLocation.isEmpty()) {
return Collections.emptyList();
}

Point userPoint = userLocation.get(0);

// 获取附近的司机
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> driverResults =
advancedGeoService.getUsersInRange("drivers", userPoint.getX(), userPoint.getY(),
radius, DistanceUnit.KILOMETERS, limit);

List<NearbyDriver> nearbyDrivers = new ArrayList<>();

for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : driverResults) {
String driverId = result.getContent().getName();

// 获取司机详细信息
Map<Object, Object> driverInfo = getDriverInfo(driverId);

// 检查司机状态
if (isDriverAvailable(driverInfo)) {
NearbyDriver driver = new NearbyDriver();
driver.setDriverId(driverId);
driver.setDistance(result.getDistance().getValue());
driver.setLongitude(result.getContent().getPoint().getX());
driver.setLatitude(result.getContent().getPoint().getY());
driver.setDriverInfo(driverInfo);

nearbyDrivers.add(driver);
}
}

return nearbyDrivers;
}

/**
* 查找附近的乘客
*/
public List<NearbyPassenger> findNearbyPassengers(String driverId, double radius, long limit) {
// 获取司机位置
List<Point> driverLocation = advancedGeoService.getLocation("drivers", driverId);
if (driverLocation.isEmpty()) {
return Collections.emptyList();
}

Point driverPoint = driverLocation.get(0);

// 获取附近的乘客
List<GeoResult<RedisGeoCommands.GeoLocation<String>>> passengerResults =
advancedGeoService.getUsersInRange("passengers", driverPoint.getX(), driverPoint.getY(),
radius, DistanceUnit.KILOMETERS, limit);

List<NearbyPassenger> nearbyPassengers = new ArrayList<>();

for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : passengerResults) {
String passengerId = result.getContent().getName();

// 获取乘客详细信息
Map<Object, Object> passengerInfo = getPassengerInfo(passengerId);

// 检查乘客状态
if (isPassengerAvailable(passengerInfo)) {
NearbyPassenger passenger = new NearbyPassenger();
passenger.setPassengerId(passengerId);
passenger.setDistance(result.getDistance().getValue());
passenger.setLongitude(result.getContent().getPoint().getX());
passenger.setLatitude(result.getContent().getPoint().getY());
passenger.setPassengerInfo(passengerInfo);

nearbyPassengers.add(passenger);
}
}

return nearbyPassengers;
}

/**
* 更新乘客位置
*/
public void updatePassengerLocation(String passengerId, double longitude, double latitude,
Map<String, Object> passengerInfo) {
String geoKey = TAXI_GEO_PREFIX + "passengers";

// 更新地理位置
advancedGeoService.updateUserLocation(passengerId, longitude, latitude, passengerInfo);

// 更新乘客信息
updatePassengerInfo(passengerId, passengerInfo);
}

/**
* 更新司机信息
*/
private void updateDriverInfo(String driverId, Map<String, Object> driverInfo) {
String driverKey = DRIVER_PREFIX + driverId;
redisTemplate.opsForHash().putAll(driverKey, driverInfo);
redisTemplate.expire(driverKey, Duration.ofMinutes(30));
}

/**
* 获取司机信息
*/
private Map<Object, Object> getDriverInfo(String driverId) {
String driverKey = DRIVER_PREFIX + driverId;
return redisTemplate.opsForHash().entries(driverKey);
}

/**
* 检查司机是否可用
*/
private boolean isDriverAvailable(Map<Object, Object> driverInfo) {
if (driverInfo.isEmpty()) {
return false;
}

// 检查司机状态
String status = driverInfo.get("status").toString();
if (!"AVAILABLE".equals(status)) {
return false;
}

// 检查最后更新时间(2分钟内)
long lastUpdateTime = Long.parseLong(driverInfo.get("lastUpdateTime").toString());
long currentTime = System.currentTimeMillis();

return currentTime - lastUpdateTime <= 2 * 60 * 1000;
}

/**
* 更新乘客信息
*/
private void updatePassengerInfo(String passengerId, Map<String, Object> passengerInfo) {
String passengerKey = "passenger_info:" + passengerId;
redisTemplate.opsForHash().putAll(passengerKey, passengerInfo);
redisTemplate.expire(passengerKey, Duration.ofMinutes(10));
}

/**
* 获取乘客信息
*/
private Map<Object, Object> getPassengerInfo(String passengerId) {
String passengerKey = "passenger_info:" + passengerId;
return redisTemplate.opsForHash().entries(passengerKey);
}

/**
* 检查乘客是否可用
*/
private boolean isPassengerAvailable(Map<Object, Object> passengerInfo) {
if (passengerInfo.isEmpty()) {
return false;
}

// 检查乘客状态
String status = passengerInfo.get("status").toString();
if (!"WAITING".equals(status)) {
return false;
}

// 检查最后更新时间(5分钟内)
long lastUpdateTime = Long.parseLong(passengerInfo.get("lastUpdateTime").toString());
long currentTime = System.currentTimeMillis();

return currentTime - lastUpdateTime <= 5 * 60 * 1000;
}
}

四、性能优化与监控

4.1 性能优化

4.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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* 地理位置索引优化服务
*/
@Service
public class GeoIndexOptimizationService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private GeoHashAlgorithm geoHashAlgorithm;

private final String GEO_INDEX_PREFIX = "geo_index:";
private final int[] GEOHASH_PRECISIONS = {3, 4, 5, 6, 7}; // 不同精度的索引

/**
* 创建多级地理位置索引
*/
public void createMultiLevelGeoIndex(String userId, double latitude, double longitude) {
// 为不同精度创建索引
for (int precision : GEOHASH_PRECISIONS) {
String geohash = geoHashAlgorithm.encode(latitude, longitude, precision);
String indexKey = GEO_INDEX_PREFIX + precision + ":" + geohash;

// 添加到索引
redisTemplate.opsForSet().add(indexKey, userId);

// 设置过期时间
redisTemplate.expire(indexKey, Duration.ofDays(1));
}
}

/**
* 基于多级索引查找附近的人
*/
public List<String> findNearbyUsersWithMultiLevelIndex(double latitude, double longitude, double radius) {
Set<String> nearbyUsers = new HashSet<>();

// 根据半径选择合适的精度
int precision = selectPrecisionByRadius(radius);

// 生成GeoHash
String geohash = geoHashAlgorithm.encode(latitude, longitude, precision);

// 获取当前GeoHash的用户
String currentKey = GEO_INDEX_PREFIX + precision + ":" + geohash;
Set<Object> currentUsers = redisTemplate.opsForSet().members(currentKey);
if (currentUsers != null) {
currentUsers.forEach(user -> nearbyUsers.add(user.toString()));
}

// 获取邻居GeoHash的用户
List<String> neighbors = geoHashAlgorithm.getNeighbors(geohash);
for (String neighbor : neighbors) {
String neighborKey = GEO_INDEX_PREFIX + precision + ":" + neighbor;
Set<Object> neighborUsers = redisTemplate.opsForSet().members(neighborKey);
if (neighborUsers != null) {
neighborUsers.forEach(user -> nearbyUsers.add(user.toString()));
}
}

return new ArrayList<>(nearbyUsers);
}

/**
* 根据半径选择合适的精度
*/
private int selectPrecisionByRadius(double radius) {
if (radius <= 0.1) {
return 7; // 约12米
} else if (radius <= 0.5) {
return 6; // 约120米
} else if (radius <= 2.0) {
return 5; // 约1.2公里
} else if (radius <= 10.0) {
return 4; // 约12公里
} else {
return 3; // 约120公里
}
}

/**
* 清理过期索引
*/
@Scheduled(fixedRate = 300000) // 5分钟
public void cleanupExpiredIndexes() {
for (int precision : GEOHASH_PRECISIONS) {
String pattern = GEO_INDEX_PREFIX + precision + ":*";
Set<String> keys = redisTemplate.keys(pattern);

for (String key : keys) {
Long ttl = redisTemplate.getExpire(key);
if (ttl != null && ttl <= 0) {
redisTemplate.delete(key);
}
}
}
}
}

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
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* 地理位置缓存优化服务
*/
@Service
public class GeoCacheOptimizationService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private CaffeineCache localCache;

private final String GEO_CACHE_PREFIX = "geo_cache:";

/**
* 获取附近的人(带缓存)
*/
public List<NearbyUser> getNearbyUsersWithCache(String userId, double radius, long limit) {
String cacheKey = GEO_CACHE_PREFIX + userId + ":" + radius + ":" + limit;

// 1. 尝试从本地缓存获取
@SuppressWarnings("unchecked")
List<NearbyUser> cachedResult = (List<NearbyUser>) localCache.getIfPresent(cacheKey);
if (cachedResult != null) {
return cachedResult;
}

// 2. 从Redis获取
String redisCacheKey = "redis_cache:" + cacheKey;
@SuppressWarnings("unchecked")
List<NearbyUser> redisResult = (List<NearbyUser>) redisTemplate.opsForValue().get(redisCacheKey);
if (redisResult != null) {
localCache.put(cacheKey, redisResult);
return redisResult;
}

// 3. 从数据库获取
List<NearbyUser> result = getNearbyUsersFromDatabase(userId, radius, limit);

// 4. 写入缓存
localCache.put(cacheKey, result);
redisTemplate.opsForValue().set(redisCacheKey, result, Duration.ofMinutes(2));

return result;
}

/**
* 预热热门位置缓存
*/
@PostConstruct
public void warmupCache() {
// 预热热门城市的附近的人查询
List<String> hotCities = getHotCities();

for (String city : hotCities) {
// 预热前100名附近的人
getNearbyUsersWithCache(city, 5.0, 100);
}
}

/**
* 清理过期缓存
*/
@Scheduled(fixedRate = 300000) // 5分钟
public void cleanupExpiredCache() {
// 清理本地缓存
localCache.cleanUp();

// 清理Redis过期缓存
cleanupRedisExpiredCache();
}

/**
* 清理Redis过期缓存
*/
private void cleanupRedisExpiredCache() {
try {
Set<String> cacheKeys = redisTemplate.keys("redis_cache:" + GEO_CACHE_PREFIX + "*");

for (String key : cacheKeys) {
Long ttl = redisTemplate.getExpire(key);
if (ttl != null && ttl <= 0) {
redisTemplate.delete(key);
}
}
} catch (Exception e) {
log.error("清理Redis过期缓存失败", e);
}
}

/**
* 获取热门城市
*/
private List<String> getHotCities() {
// 实现获取热门城市的逻辑
return Arrays.asList("beijing", "shanghai", "guangzhou", "shenzhen");
}

/**
* 从数据库获取附近的人
*/
private List<NearbyUser> getNearbyUsersFromDatabase(String userId, double radius, long limit) {
// 实现从数据库获取附近的人的逻辑
return new ArrayList<>();
}
}

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* 地理位置监控指标
*/
@Component
public class GeoMetrics {

private final MeterRegistry meterRegistry;

public GeoMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}

/**
* 记录地理位置查询
*/
public void recordGeoQuery(String queryType) {
Counter.builder("geo.query.count")
.description("地理位置查询次数")
.tag("type", queryType)
.register(meterRegistry)
.increment();
}

/**
* 记录位置更新
*/
public void recordLocationUpdate(String updateType) {
Counter.builder("geo.location.update.count")
.description("位置更新次数")
.tag("type", updateType)
.register(meterRegistry)
.increment();
}

/**
* 记录附近的人查询响应时间
*/
public void recordNearbyQueryResponseTime(String queryType, long duration) {
Timer.builder("geo.nearby.query.response.time")
.description("附近的人查询响应时间")
.tag("type", queryType)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}

/**
* 记录位置更新响应时间
*/
public void recordLocationUpdateResponseTime(String updateType, long duration) {
Timer.builder("geo.location.update.response.time")
.description("位置更新响应时间")
.tag("type", updateType)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}

/**
* 记录地理位置数据量
*/
public void recordGeoDataSize(String dataType, long size) {
Gauge.builder("geo.data.size")
.description("地理位置数据量")
.tag("type", dataType)
.register(meterRegistry, size);
}
}

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
# prometheus-rules.yml
groups:
- name: geo_alerts
rules:
- alert: HighGeoQueryResponseTime
expr: geo_nearby_query_response_time{quantile="0.95"} > 1000
for: 2m
labels:
severity: warning
annotations:
summary: "地理位置查询响应时间过长"
description: "附近的人查询响应时间P95超过1秒,当前值: {{ $value }}ms"

- alert: HighLocationUpdateResponseTime
expr: geo_location_update_response_time{quantile="0.95"} > 500
for: 2m
labels:
severity: warning
annotations:
summary: "位置更新响应时间过长"
description: "位置更新响应时间P95超过500ms,当前值: {{ $value }}ms"

- alert: GeoDataSizeTooLarge
expr: geo_data_size > 10000000
for: 5m
labels:
severity: warning
annotations:
summary: "地理位置数据量过大"
description: "地理位置数据量超过1000万,当前值: {{ $value }}"

五、总结

Redis如何实现查找附近的人功能,通过合理的地理位置数据结构和算法设计,能够为企业提供高效、稳定的地理位置服务。本文从Redis Geo数据结构到GeoHash算法,从基础实现到企业级应用,系统梳理了基于Redis的查找附近的人功能完整解决方案。

5.1 关键要点

  1. 数据结构选择:Redis Geo和GeoHash是实现地理位置服务的核心数据结构
  2. 算法优化:GeoHash算法能够有效提高地理位置查询效率
  3. 应用场景:适用于社交、外卖、打车、旅游等多种业务场景
  4. 性能优化:通过索引优化、缓存优化等手段提高系统性能
  5. 监控告警:建立完善的监控体系,及时发现和处理问题

5.2 最佳实践

  1. 数据结构设计:合理选择Redis Geo和GeoHash数据结构
  2. 算法优化:使用GeoHash算法提高查询效率
  3. 缓存策略:使用多级缓存提高查询性能
  4. 索引优化:创建多级地理位置索引
  5. 监控告警:建立完善的监控体系,确保系统稳定运行

通过以上措施,可以构建一个高效、稳定、可扩展的地理位置服务系统,为企业的各种业务场景提供支持。