Kafka Stream实现热点文章【实时更新】:根据用户行为点赞、浏览、喜欢、收藏计算文章对应Score实现文章实时更新。【黑马头条Day11】Kafka和Kafka Stream的对比

目录

定时计算和实时计算

Kafka Strem相关概念

Kafka Stream应用执行逻辑

Kafka Stream和Kafka的简单对比

项目中如何使用Kafka Strem实现实时更新热点数据


定时计算和实时计算

        定时计算:在Day10中,使用xxl-job工具实现热点文章的定时更新,每天凌晨2点读取MySQL数据库中数据并根据加权计算对应文章的热度,即分值score,根据分值排序选取对应的文章将其缓存到Redis数据库中,对App端用户进行数据推荐。

        实时计算:在用户访问浏览的同时根据用户对应的行为更新响应文章的score,从而实现更加精准的向用户推荐相关文章。

定时计算(批量计算)与实时计算(流式计算)的对比图示:

Kafka Strem相关概念

        源处理器(Source Processor):源处理器是一个没有任何上游处理器的特殊类型的流处理器。它从一个或多个kafka主题生成输入流。通过消费这些主题的消息并将它们转发到下游处理器。

        Sink处理器:sink处理器是一个没有下游流处理器的特殊类型的流处理器。它接收上游流处理器的消息发送到一个指定的Kafka主题

        KStream:Kafka Stream主要依赖的数据结构。

相关概念链接:Kafka入门实战教程(7):Kafka Streams-腾讯云开发者社区-腾讯云 (tencent.com)

Kafka Stream应用执行逻辑

        Kafka Streams宣称自己实现了精确一次处理语义(Exactly Once Semantics, EOS,以下使用EOS简称),所谓EOS,是指消息或事件对应用状态的影响有且只有一次。其实,对于Kafka Streams而言,它天然支持端到端的EOS,因为它本来就是和Kafka紧密相连的。下图展示了一个典型的Kafka Streams应用的执行逻辑:

        一个Kafka Strem需要执行5个步骤:

  • 读取最新处理消息的位移。
  • 读取消息数据。
  • 执行处理逻辑。
  • 将处理结果写回Kafka。
  • 保存位置信息。

        这五步的执行必须是原子性的,否则无法实现精确一次处理语义。而在设计上,Kafka Streams在底层大量使用了Kafka事务机制和幂等性Producer来实现多分区的写入,又因为它只能读写Kafka,因此Kafka Streams很easy地就实现了端到端的EOS。

Kafka Stream和Kafka的简单对比

        Kafka使用图示:

        Kafka Stream使用图示:

        Kafka使用只需要一个Topic,一个Producer生产消息发送到Topic对应的分片,随后Consumer根据Topic消费对应的消息。

        Kafka Strem在Kafka的基础上多了一个Topic相当于两个Kafka使用的顺序集成。多个Producer向第一个Topic发送任务,任务在Kafka Stream中进行流式处理,随后将处理后的消息发送给第二个Topic,Comsumer订阅第二个Topic进行相关任务的消费。

项目中如何使用Kafka Strem实现实时更新热点数据

发送微服务创建KafkaTemplate对象并发送对应数据到相关Topic


@Service
@Transactional
@Slf4j
public class ApLikesBehaviorServiceImpl implements ApLikesBehaviorService {

    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @Override
    public ResponseResult like(LikesBehaviorDto dto) {

        
        // 创建kafka发送对象
        UpdateArticleMess mess = new UpdateArticleMess();
        mess.setArticleId(dto.getArticleId());
        mess.setType(UpdateArticleMess.UpdateArticleType.LIKES);
        //......
        //......
            mess.setAdd(1);
        kafkaTemplate.send(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC, JSON.toJSONString(mess));

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);

    }

    
}

接收微服务配置KafkaStreamConfig配置类对象

@Setter
@Getter
@Configuration
@EnableKafkaStreams
@ConfigurationProperties(prefix="kafka")
public class KafkaStreamConfig {
    private static final int MAX_MESSAGE_SIZE = 16* 1024 * 1024;
    private String hosts;
    private String group;
    @Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
    public KafkaStreamsConfiguration defaultKafkaStreamsConfig() {
        Map<String, Object> props = new HashMap<>();
        props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, hosts);
        props.put(StreamsConfig.APPLICATION_ID_CONFIG, this.getGroup()+"_stream_aid");
        props.put(StreamsConfig.CLIENT_ID_CONFIG, this.getGroup()+"_stream_cid");
        props.put(StreamsConfig.RETRIES_CONFIG, 10);
        props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        return new KafkaStreamsConfiguration(props);
    }
}

需要在配置文件中指定hosts和group

kafka:
  hosts: 192.168.200.130:9092
  group: ${spring.application.name}

 创建流式处理模式对数据进行处理并发送

@Configuration
@Slf4j
public class HotArticleStreamHandler {

    @Bean
    public KStream<String, String> kStream(StreamsBuilder streamsBuilder){

        // 接收消息
        KStream<String, String> stream = streamsBuilder.stream(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC);
        // 聚合流式处理
        stream.map((key, value) -> {
            // 解析JSON字符串获得对象
            UpdateArticleMess mess = JSON.parseObject(value, UpdateArticleMess.class);
            // 重置消息的key和value key:1234343434   和  value: likes:1
            return new KeyValue<String, String>(mess.getArticleId().toString(), mess.getType().name() + ":" + mess.getAdd());
        })
                // 按照文章Id进行聚合
                .groupBy((key, value) -> key)
                // //时间窗口
                .windowedBy(TimeWindows.of(Duration.ofSeconds(10)))
                // 自行进行聚合的运算
                .aggregate(new Initializer<String>() {
                    /**
                     * 初始方法,返回值消息的value
                     * @return
                     */
                    @Override
                    public String apply() {
                        return "COLLECTION:0,COMMENT:0,LIKES:0,VIEWS:0";
                    }
                    /**
                     * 真正的聚合计算,返回的是消息的value
                     */
                }, new Aggregator<String, String, String>() {
                    @Override
                    public String apply(String key, String value, String aggValue) {
                        if(StringUtils.isBlank(value)){
                            return aggValue;
                        }
                        String[] aggAry = aggValue.split(",");
                        int col = 0,com=0,lik=0,vie=0;
                        for (String agg : aggAry) {
                            String[] split = agg.split(":");
                            /**
                             * 获得初始值,也是时间窗口内计算之后的值
                             */
                            switch (UpdateArticleMess.UpdateArticleType.valueOf(split[0])){
                                case COLLECTION:
                                    col = Integer.parseInt(split[1]);
                                    break;
                                case COMMENT:
                                    com = Integer.parseInt(split[1]);
                                    break;
                                case LIKES:
                                    lik = Integer.parseInt(split[1]);
                                    break;
                                case VIEWS:
                                    vie = Integer.parseInt(split[1]);
                                    break;
                            }
                        }
                        /**
                         * 累加操作
                         */
                        String[] valAry = value.split(":");
                        switch (UpdateArticleMess.UpdateArticleType.valueOf(valAry[0])){
                            case COLLECTION:
                                col += Integer.parseInt(valAry[1]);
                                break;
                            case COMMENT:
                                com += Integer.parseInt(valAry[1]);
                                break;
                            case LIKES:
                                lik += Integer.parseInt(valAry[1]);
                                break;
                            case VIEWS:
                                vie += Integer.parseInt(valAry[1]);
                                break;
                        }

                        String formatStr = String.format("COLLECTION:%d,COMMENT:%d,LIKES:%d,VIEWS:%d", col, com, lik, vie);
                        System.out.println("文章的id:"+key);
                        System.out.println("当前时间窗口内的消息处理结果:"+formatStr);
                        return formatStr;
                    }
                }, Materialized.as("hot-atricle-stream-count-001"))
                .toStream()
                 // 发送消息
                .map((key,value)->{
                    return new KeyValue<>(key.key().toString(),formatObj(key.key().toString(),value));
                })
                .to(HotArticleConstants.HOT_ARTICLE_INCR_HANDLE_TOPIC);

        return stream;
    }
    
}
创建KafkaListener监听Stream处理后的数据并执行后续操作
@Component
@Slf4j
public class ArticleIncrHandleListener {
    @Autowired
    private ApArticleService apArticleService;

    @KafkaListener(topics = HotArticleConstants.HOT_ARTICLE_INCR_HANDLE_TOPIC)
    public void onMessage(String mess){
        if(StringUtils.isNotBlank(mess)){
            ArticleVisitStreamMess articleVisitStreamMess = JSON.parseObject(mess, ArticleVisitStreamMess.class);
            log.info("监听到的消息为:{}", articleVisitStreamMess);
            apArticleService.updateScore(articleVisitStreamMess);
        }
    }
}

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KafkaStream实时计算是指通过Kafka Stream这个功能,对存储在Kafka中的数据进行流式处理和分析的过程。在流式计算模型中,输入数据是持续的,可以认为在时间上是无界的,因此永远无法拿到全量数据进行计算。同时,计算结果也是持续输出的,因此在时间上也是无界的。流式计算通常对实时性要求较高,通过在数据到达时将计算逻辑应用于数据,实现实时计算和分析。而为了提高计算效率,流式计算通常采用增量计算的方式,而不是全量计算Kafka Stream的应用场景非常广泛。它可以用于实时监控和报警,通过对流数据进行实时计算和分析,可以及时发现异常情况并进行相应的处理。比如,可以对实时交易数据进行监控,及时发现异常交易并触发报警。此外,Kafka Stream还可以用于实时推荐系统,通过对用户行为数据进行实时计算和分析,实现个性化推荐功能。另外,Kafka Stream还可以用于实时统计和聚合,比如对网站访问日志进行实时计算和分析,实时统计访问量、用户活跃度等指标。总之,Kafka Stream可以在需要对流数据进行实时计算和分析的场景中发挥重要作用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [KafkaStream分布式流式处理的新贵-Kafka设计解析(七)](https://download.csdn.net/download/weixin_38514805/14944855)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [kafka stream实现实时流式计算以及springboot集成kafka stream](https://blog.csdn.net/m0_45806184/article/details/126398614)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值