Flink实战| Flink+Redis实时防刷接口作弊

  • 2019 年 10 月 25 日
  • 筆記

随着人口红利的慢慢削减,互联网产品的厮杀愈加激烈,大家开始看好下沉市场的潜力,拼多多,趣头条等厂商通过拉新奖励,购物优惠等政策率先抢占用户,壮大起来。其他各厂商也紧随其后,纷纷推出自己产品的极速版,如今日头条极速版,腾讯新闻极速版等,也通过拉新奖励,阅读奖励等政策来吸引用户。

对于这类APP,实时风控是必不可少的,一个比较常见的实时风控场景就是防刷接口作弊。刷接口是黑产的一种作弊手段,APP上的各种操作,一般都会对应后台的某个接口,用户操作APP数据就会通过接口上报到后台,但如果黑产通过破解获取到了APP的新增用户接口,那他们就能跳过登陆APP步骤直接调后台接口构造虚假数据牟利了。对于这类业务,我们可以通过Flink + Redis来实现实时防刷接口的功能。数据流图如下所示:

刷接口作弊一般是越过登陆APP操作,直接调Server端的接口发数据,这些用户在APP的上报日志里面就不存在,那我们可以通过Flink将APP实时上报上来的新增用户写入Redis中,然后Server端将接口上报上来的用户与Redis里的用户进行比对,如果不在Redis里面则判为刷接口用户。

对于这个需求,得要求实时计算引擎能达到毫秒级延迟,否则会造成用户的误判和影响用户体验。为此我们选择了Flink作为实时计算引擎。

主要代码逻辑如下:

//配置flink运行环境  val env = StreamExecutionEnvironment.getExecutionEnvironment  //val env = StreamExecutionEnvironment.createLocalEnvironment()  env.enableCheckpointing(1000 * 60 * 5)  env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.AT_LEAST_ONCE)  env.getCheckpointConfig.setMinPauseBetweenCheckpoints(1000 * 60 * 3)  env.getCheckpointConfig.setMaxConcurrentCheckpoints(1)  env.setStateBackend(new FsStateBackend(checkPointPath))  env.getConfig.setLatencyTrackingInterval(1000)  env.getConfig.registerTypeWithKryoSerializer(classOf[Log], classOf[ProtobufSerializer])  env.setStreamTimeCharacteristic(EventTime)  env.setParallelism(parallel)  env.getConfig.setLatencyTrackingInterval(1000)    //kafka source,实时消费kafka中日志解析出用户id  val stream = env.addSource(new FlinkKafkaConsumer010[Array[Log]](topic, new LogDeserializationSchema(), properties))  val data = stream.flatMap(x => x)    .map(log =>{      val userid = log.getUid.getUuid      val current_time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())      (userid,current_time)    }).filter(record=>{    val userid = record._1    var flag = false    if(userid != null && !"".equals(userid)){      flag = true    }    flag  })    //redis sink,将APP上报日志的用户id写入redis供server端匹配  data.addSink(new RedisSink[(String, String)](getJedisClusterConfig, new RedisSinkMapper))  env.execute("newsinfo_active_userid_to_redis")

其中比较重要的几点:

1 构造kafka source

val stream = env.addSource(new FlinkKafkaConsumer010[Array[Log]](topic, new LogDeserializationSchema(), properties))

一般APP上报的都是序列化的数据,我们需要定义反序列化方法,LogDeserializationSchema 是一个protobuf类型的反序列化方法。

//将kafka中的数据解析为google protobuf 的Log,一个message可能包含多条Log  class LogDeserializationSchema extends AbstractDeserializationSchema[Array[Log]] {    override def deserialize(message: Array[Byte]): Array[Log] = {      val data = ArrayBuffer[Log]()      val input = new ByteArrayInputStream(message)      while (input.available() > 0) {        try {          data += Log.parseDelimitedFrom(input)        } catch {          case _: Throwable =>        }      }      input.close()      data.toArray    }  }

2 redis sink

这里用的是网上开源的flink-connector-redis依赖库。
更多相关内容见:http://bahir.apache.org/docs/flink/current/flink-streaming-redis

Maven依赖如下

<dependency>      <groupId>org.apache.bahir</groupId>      <artifactId>flink-connector-redis_2.11</artifactId>      <version>1.1-SNAPSHOT</version>  </dependency>

Redis Sink 提供用于向Redis发送数据的接口的类。接收器可以使用三种不同的方法与不同类型的Redis环境进行通信:

  • 单Redis服务器
  • Redis集群
  • Redis Sentinel

Redis Sink 核心类是 RedisMappe 是一个接口,使用时我们要编写自己的redis操作类实现这个接口中的三个方法,如下所示:

class RedisExampleMapper extends RedisMapper[(String, String)]{    override def getCommandDescription: RedisCommandDescription = {      new RedisCommandDescription(RedisCommand.HSET, "HASH_NAME")    }      override def getKeyFromData(data: (String, String)): String = data._1      override def getValueFromData(data: (String, String)): String = data._2  }  val conf = new FlinkJedisPoolConfig.Builder().setHost("127.0.0.1").build()  stream.addSink(new RedisSink[(String, String)](conf, new RedisExampleMapper))

使用RedisCommand设置数据结构类型时和redis结构对应关系。

以上我们利用 Flink + Redis 实时了一个基本的实时防刷接口模型。关注微信公众号《大数据技术进阶》,观看更多大数据实战文章。