0%

实现频控

实现频控

在广告行业中,经常会有频控的限制,来防止同一个广告被同一个用户多次的访问

使用redis实现

可以使用redis来实现频控,首先想一下redis存储的key是什么?我们要限制的是一波广告投放下同一个用户的访问次数,那么key可以设置为uid:campid,也就是用户id拼上投放id作为频控。

那么value存储什么内容呢?如果只是单纯的整个投放周期下的频控的话,那其实设置为访问次数就可以了。但是有的时候周期不是整个投放周期(如按天频控、按周频控、按月频控等)

使用以下逻辑

1
2
3
4
5
// 添加曝光
private void addExpose(String uid, String campaignId){
String key = uid+":"+campaignId;
jedis.lpush(key, String.valueOf(System.currentTimeMillis()));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 是否超频
private boolean isOver(String uid, String campaignId, long range,int limit) {
String key = uid+":"+campaignId;
long cur = System.currentTimeMillis();

List<String> exposes = jedis.lrange(key, 0, 10);//最大为10

int count = 0;
if (exposes == null || exposes.isEmpty()) {
return false;
} else {
for (String expose: exposes) {
long time = Long.parseLong(expose);
if (time > cur - range) {
count++;
} else {
break;
}
}
}
return count > limit;
}

使用hbase

由于广告投放中的数据量是很大的,存在redis中内存扛不住,所以后来使用了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
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
// 是否超频
private boolean isOver(String uid, String campId, long range,int limit) {

long cur = System.currentTimeMillis();
List<Long> exposes = getExposeFromHbase(uid, campId);

int count = 0;
if (exposes == null || exposes.isEmpty()) {
return false;
} else {
for (long time: exposes) {
if (time > cur - range) {
count++;
} else {
break;
}
}
}
return count > limit;
}

/**
* 从Hbase中获取到指定Camp的曝光列表
*
* @param uid
* @param campId
* @return
*/
public List<Long> getExposeFromHbase(String uid, String campId) {
if (StringUtils.isBlank(uid) || StringUtils.isBlank(campId)) {
return null;
}

Table htable = null;
try {
// 对应的表
htable = connection.getTable(TableName.valueOf("expose"));
// 用户id作为rowkey
Get get = new Get(Bytes.toBytes(uid));
// 列族 列名
get.addColumn(Bytes.toBytes("f"),Bytes.toBytes(campId));

Result result = htable.get(get);
if (result != null && !result.isEmpty()) {
// 内容
byte[] value = result.getValue(Bytes.toBytes("f"),Bytes.toBytes(campId));

String s = new String(value);
Gson gson = new Gson();
// 历史曝光的时间
List<Long> tsList = gson.fromJson(s, new TypeToken<ArrayList<Long>>() {
}.getType());
return tsList;
}
} catch (IOException e) {
LOGGER.error("get camp list failed: {}", uid);
}finally {
if (htable != null) {
try {
htable.close();
} catch (Exception closeExcption) {
LOGGER.error("close connection failed: ", closeExcption);
}
}
}
return null;
}

添加曝光

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
public void addExpose(String uid, String campId){
long ts = System.currentTimeMillis();
List<Long> tsList;
try {
tsList = getExposeFromHbase(uid, campId);
} catch (Exception e) {
LOGGER.error(e.toString());
return;
}
if (tsList == null) { // 之前没有
tsList = new ArrayList<>();
}
tsList.add(ts);
// 添加曝光到hbase中
addExposeInHbase(uid, campId,tsList);
}

/**
* 添加一次曝光到Hbase
*
* @param uid
* @param campId
* @param tsList
*/
public void addExposeInHbase(String uid, String campId,List<Long> tsList) {

Table htable = null;
try {
htable = connection.getTable(TableName.valueOf("expose"));
Gson gson = new Gson();
Put put = new Put(Bytes.toBytes(uid));
put.addColumn(Bytes.toBytes("f"), Bytes.toBytes(campId), Bytes.toBytes(gson.toJson(tsList)));
//增加过期时间 90天
put.setTTL(3600 * 24 * 90 * 1000L);
htable.put(put);
} catch (IOException e) {
LOGGER.error("write to hbase failed: {}", uid + ":" + e.getMessage());
} finally {
if (htable != null) {
try {
htable.close();
} catch (Exception closeExcption) {
LOGGER.error("close connection failed: ", closeExcption);
}
}
}
}

欢迎关注我的其它发布渠道