|
@@ -27,6 +27,7 @@ public class RedisHotStore {
|
|
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(RedisHotStore.class);
|
|
private static final Logger log = LoggerFactory.getLogger(RedisHotStore.class);
|
|
|
public static final String BID_NOT_FOUND_MSG = "bid not found";
|
|
public static final String BID_NOT_FOUND_MSG = "bid not found";
|
|
|
|
|
+ private static final Duration IMPRESSION_BID_TTL = Duration.ofMinutes(10);
|
|
|
private static final Duration DEFAULT_CONVERSION_SEEN_TTL = Duration.ofDays(7);
|
|
private static final Duration DEFAULT_CONVERSION_SEEN_TTL = Duration.ofDays(7);
|
|
|
private static final Duration DEFAULT_DEDUCTION_COUNTER_TTL = Duration.ofDays(7);
|
|
private static final Duration DEFAULT_DEDUCTION_COUNTER_TTL = Duration.ofDays(7);
|
|
|
|
|
|
|
@@ -59,40 +60,39 @@ public class RedisHotStore {
|
|
|
// ─── EventRecorder ───────────────────────────────────────────────────────
|
|
// ─── EventRecorder ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 对应 Go RecordBid:曝光事件只写 Stream,其他事件写热缓存并写 Stream。
|
|
|
|
|
|
|
+ * 对应 Go RecordBid:
|
|
|
|
|
+ * Pipeline: SET bid:{qk} + SET media:{media}:{traceId} + XADD
|
|
|
*/
|
|
*/
|
|
|
public void recordBid(BidRecord record) {
|
|
public void recordBid(BidRecord record) {
|
|
|
if (record.getCreatedAt() == null) {
|
|
if (record.getCreatedAt() == null) {
|
|
|
record.setCreatedAt(Instant.now());
|
|
record.setCreatedAt(Instant.now());
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
- boolean cacheHot = !"impression".equalsIgnoreCase(record.getEventType());
|
|
|
|
|
- String hotPayload = cacheHot ? objectMapper.writeValueAsString(record.toHotRecord()) : null;
|
|
|
|
|
|
|
+ // Redis KEY 只存关键字段(瘦身),降低内存占用
|
|
|
|
|
+ String hotPayload = objectMapper.writeValueAsString(record.toHotRecord());
|
|
|
// Stream 存全量数据,供 ColdWorker 持久化到 TiDB
|
|
// Stream 存全量数据,供 ColdWorker 持久化到 TiDB
|
|
|
String fullPayload = objectMapper.writeValueAsString(record);
|
|
String fullPayload = objectMapper.writeValueAsString(record);
|
|
|
|
|
|
|
|
redis.executePipelined((org.springframework.data.redis.connection.RedisConnection conn) -> {
|
|
redis.executePipelined((org.springframework.data.redis.connection.RedisConnection conn) -> {
|
|
|
trimStreamIfNeeded(conn);
|
|
trimStreamIfNeeded(conn);
|
|
|
- if (cacheHot) {
|
|
|
|
|
- byte[] hotBytes = hotPayload.getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
- long ttlMillis = bidTtl.toMillis();
|
|
|
|
|
- if (record.getQk() != null && !record.getQk().isEmpty()) {
|
|
|
|
|
- conn.stringCommands().set(
|
|
|
|
|
- bidKey(record.getQk()).getBytes(StandardCharsets.UTF_8),
|
|
|
|
|
- hotBytes,
|
|
|
|
|
- org.springframework.data.redis.core.types.Expiration.milliseconds(ttlMillis),
|
|
|
|
|
- org.springframework.data.redis.connection.RedisStringCommands.SetOption.UPSERT
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- if (record.getMedia() != null && !record.getMedia().isEmpty()
|
|
|
|
|
- && record.getMediaTraceId() != null && !record.getMediaTraceId().isEmpty()) {
|
|
|
|
|
- conn.stringCommands().set(
|
|
|
|
|
- mediaTraceKey(record.getMedia(), record.getMediaTraceId()).getBytes(StandardCharsets.UTF_8),
|
|
|
|
|
- hotBytes,
|
|
|
|
|
- org.springframework.data.redis.core.types.Expiration.milliseconds(ttlMillis),
|
|
|
|
|
- org.springframework.data.redis.connection.RedisStringCommands.SetOption.UPSERT
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ byte[] hotBytes = hotPayload.getBytes(StandardCharsets.UTF_8);
|
|
|
|
|
+ long ttlMillis = bidTtlFor(record.getEventType()).toMillis();
|
|
|
|
|
+ if (record.getQk() != null && !record.getQk().isEmpty()) {
|
|
|
|
|
+ conn.stringCommands().set(
|
|
|
|
|
+ bidKey(record.getQk()).getBytes(StandardCharsets.UTF_8),
|
|
|
|
|
+ hotBytes,
|
|
|
|
|
+ org.springframework.data.redis.core.types.Expiration.milliseconds(ttlMillis),
|
|
|
|
|
+ org.springframework.data.redis.connection.RedisStringCommands.SetOption.UPSERT
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ if (record.getMedia() != null && !record.getMedia().isEmpty()
|
|
|
|
|
+ && record.getMediaTraceId() != null && !record.getMediaTraceId().isEmpty()) {
|
|
|
|
|
+ conn.stringCommands().set(
|
|
|
|
|
+ mediaTraceKey(record.getMedia(), record.getMediaTraceId()).getBytes(StandardCharsets.UTF_8),
|
|
|
|
|
+ hotBytes,
|
|
|
|
|
+ org.springframework.data.redis.core.types.Expiration.milliseconds(ttlMillis),
|
|
|
|
|
+ org.springframework.data.redis.connection.RedisStringCommands.SetOption.UPSERT
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
// XADD 全量数据到 Stream
|
|
// XADD 全量数据到 Stream
|
|
|
byte[] streamKey = stream.getBytes(StandardCharsets.UTF_8);
|
|
byte[] streamKey = stream.getBytes(StandardCharsets.UTF_8);
|
|
@@ -175,6 +175,10 @@ public class RedisHotStore {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private Duration bidTtlFor(String eventType) {
|
|
|
|
|
+ return "impression".equalsIgnoreCase(eventType) ? IMPRESSION_BID_TTL : bidTtl;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public Map<String, BidRecord> findBidsByQks(List<String> qks) {
|
|
public Map<String, BidRecord> findBidsByQks(List<String> qks) {
|
|
|
if (qks == null || qks.isEmpty()) return Collections.emptyMap();
|
|
if (qks == null || qks.isEmpty()) return Collections.emptyMap();
|
|
|
Map<String, BidRecord> result = new HashMap<>();
|
|
Map<String, BidRecord> result = new HashMap<>();
|