bboyjing's blog

Redis学习笔记十一【使用Redis构建支持程序】

之前学习了如何将Redis用作整个系统的一部分,这一章将来学习写如何使用Redis来构建辅助程序。代码示例位于redis-sample项目的support模块中。

日志

使用列表存储最新的日志记录:

1
2
3
4
5
6
7
8
9
10
11
12
public void logRecent(String name, String message, String severity) {
final byte[] destination = stringRedisTemplate
.getStringSerializer()
.serialize("recent_" + name + "_" + severity);
RedisCallback<Object> pipelineCallback = redisConnection -> {
redisConnection.lPush(destination,
(TIMESTAMP.format(new Date()) + ' ' + message).getBytes());
redisConnection.lTrim(destination, 0, 99);
return null;
};
stringRedisTemplate.executePipelined(pipelineCallback);
}

计数器

使用Redis来实现计数器,可以统计网站访问等信息。采用一个有序集合来存储需要记录的时间片信息,若干个散列来存储每个时间片在起始时间点的点击数。

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
//更新计数器
public void updateCounter(String name, int count){
long now = System.currentTimeMillis() / 1000;
SessionCallback<List<Object>> sessionCallback = new SessionCallback<List<Object>>() {
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
for(int i = 0; i < PRECISION.length; i++){
int prec = PRECISION[i];
//取得当前时间片的开始时间
long pnow = (now / prec) * prec;
String hash = String.valueOf(prec) + '_' + name;
//有序集合存储需要记录的时间片
operations.opsForZSet().add("known:", hash, i);
//时间片点击数散列
operations.opsForHash().increment("count_" + hash,String.valueOf(pnow), count);
}
return operations.exec();
}
};
stringRedisTemplate.execute(sessionCallback);
}
//获取点击数
public List<Pair<Integer,Integer>> getCounter(String name, int precision){
String hash = String.valueOf(precision) + '_' + name;
Map<Object, Object> data = stringRedisTemplate.opsForHash().entries("count_" + hash);
ArrayList<Pair<Integer,Integer>> results = new ArrayList<>();
data.forEach((k, v) ->
results.add(Pair.of(Integer.parseInt((String) k), Integer.parseInt((String) v))));
return results;
}

查找IP所属城市以及国家

我们使用maxmind提供的geoip2所属城市数据库作为测试数据,下载GeoLite2 City CSV文件,解压即可。
实现IP所属地查找程序会用到两个查找表,第一个查找表需要根据输入的IP地址来查找IP所属的geoname_id,第二个查找表则需要根据输入的geoname_id来查找ID对应的实际信息。

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
//将ip转换成整数
public long ipToScore(String ipAddress) {
int score = 0;
for (String v : ipAddress.split("\\.")){
score = score * 256 + Integer.parseInt(v, 10);
}
return score&0x0FFFFFFFFL;
}
//导入GeonameId
public void importIpsToRedis(File file) throws Exception{
FileReader reader = new FileReader(file);
CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT);
CSVRecord csvRecord;
Iterator<CSVRecord> recordIterator = parser.iterator();
while (recordIterator.hasNext()){
csvRecord = recordIterator.next();
if(csvRecord.getRecordNumber() > 1){
long score = ipToScore(csvRecord.get(0).split("/")[0]);
//多个IP地址范围可能会被映射至同一个城市ID,所以加上行号确保member的唯一性
String geonameId = csvRecord.get(1) + '_' + csvRecord.getRecordNumber();
stringRedisTemplate.opsForZSet().add("ip_geonameId:", geonameId, score);
}
}
}
//导入城市实际信息
public void importGeonameToRedis(File file) throws Exception {
FileReader reader = new FileReader(file);
CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT);
CSVRecord csvRecord;
Iterator<CSVRecord> recordIterator = parser.iterator();
Gson gson = new Gson();
while (recordIterator.hasNext()){
csvRecord = recordIterator.next();
String geonameId = csvRecord.get(0);
String country = csvRecord.get(5);
String region = csvRecord.get(3);
String city = !StringUtils.isEmpty(csvRecord.get(10)) ? csvRecord.get(10) : csvRecord.get(7);
String json = gson.toJson(new String[]{city, region, country});
stringRedisTemplate.opsForHash().put("geonameId_city:", geonameId, json);
}
}
//通过ip查找城市信息
public String[] findCityByIp(String ipAddress) {
long score = ipToScore(ipAddress);
Set<String> results = stringRedisTemplate.opsForZSet()
.reverseRangeByScore("ip_geonameId:", 0, score, 0, 1);
if (results.size() == 0) {
return null;
}
String cityId = results.iterator().next();
cityId = cityId.substring(0, cityId.indexOf('_'));
return new Gson().fromJson(
(String) stringRedisTemplate.opsForHash().get("geonameId_city:", cityId), String[].class);
}

项目需要导入的jar包:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>

另外,maxmind提供了mmdb文件,可以直接使用,maxmind gitbub有api使用说明。本人测试了下,通过api查询的结果和导入Redis再查询出来的结果一致。但是有些ip和maxmind官网查出来的结果不一样,这个这里就不深究了。

能使用Redis构建的支持程序远远不止上述案例,这里只是挑几个讲讲,更多的需要使用者按照Redis的特性自行挖掘。