使用kafka改造分布式事务

文章目录

1、kafka确保消息不丢失?

1.1、生产者端确保消息不丢失

  1. 发送模式:发后即忘、同步阻塞确认、异步非阻塞确认
  2. 生产者acks模式:props.put("acks", "all")、acks: all(-1)
  3. 配置重试:props.put("retries", 3)、retries: 3

1.2、kafka服务端确保消息不丢失

  1. kafka是文件型的消息中间件,不会单纯的因为服务器宕机导致消息丢失
  2. 消息的log日志文件损坏:搭建kafka集群(副本)

1.3、消费者确保正确无误的消费

  1. 偏移量提交
     自动提交:enable-auto-commit: true
     手动提交:ack-mode: manual_immediate:同步提交 异步提交(推荐)
  2. 偏移量重置:
     auto-offset-reset: earliest -> 如果有偏移量则继续消费,如果偏移量没了,从头重新进行消费,可能会存在幂等性问题
     auto-offset-reset: latest -> 如果有偏移量则继续消费,如果偏移量不存在,只消费新消息,旧消息没消费完就丢掉了
     auto-offset-reset: none -> 如果有偏移量则继续消费,如果偏移量不存在,抛出异常
  3. 消费者重试:重试主题和死信主题, @RetryableTopic()

2、生产者发送消息 KafkaService

java 复制代码
package com.atguigu.tingshu.common.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

@Service
public class KafkaService {

    private static final Logger logger = LoggerFactory.getLogger(KafkaService.class);

    @Autowired
    private KafkaTemplate kafkaTemplate;

    /**
     * 向指定主题发送消息
     * 此方法通过调用重载的sendMsg方法,向指定主题发送消息,使用默认的消息标签和消息键
     *
     * @param topic 发送消息的主题
     * @param msg   需要发送的消息内容
     */
    public void sendMsg(String topic, String msg){
        // 调用重载的sendMsg方法,传入默认值以简化调用
        this.sendMsg(topic, null, null, msg);
    }

    /**
     * 发送消息到指定的Kafka主题
     *
     * @param topic 消息主题
     * @param partition 分区编号
     * @param key 消息键值
     * @param msg 消息内容
     */
    public void sendMsg(String topic, Integer partition, String key, String msg){
        // 发生消息并返回异步结果
        CompletableFuture<SendResult> future = this.kafkaTemplate.send(topic, partition, key, msg);

        // 异步处理发送结果
        future.whenCompleteAsync((result, ex) -> {
            if (ex != null){
                // 如果发送过程中出现异常
                logger.error("生产者发送消息失败!原因:{}", ex.getMessage());
            }
        });
    }

}
  • whenCompleteAsync:异步完成时的处理、当异步操作完成时

3、UserInfoServiceImpl -> login()

  • 此时 service-user 是生产者 发送消息
java 复制代码
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

	@Autowired
	private WxMaService wxMaService;

	@Autowired
	private RedisTemplate redisTemplate;

	@Autowired
	private UserAccountFeignClient userAccountFeignClient;

	@Autowired
	private KafkaService kafkaService;


	/**
	 * 根据微信返回的code进行用户登录
	 * @param code 微信登录凭证
	 * @return 返回包含登录令牌的Map对象
	 */
	//@GlobalTransactional
	//@Transactional
	@Override
	public Map<String, Object> login(String code) {
	    // 创建一个HashMap对象用于存放返回的数据
	    HashMap<String, Object> map = new HashMap<>();

	    try {
	        // 通过微信服务获取用户的会话信息
	        WxMaJscode2SessionResult sessionInfo = this.wxMaService.getUserService().getSessionInfo(code);
	        // 获取用户的openid
	        String openid = sessionInfo.getOpenid();

	        // 查询数据库中是否存在该openid对应的用户信息
	        UserInfo userInfo = this.getOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getWxOpenId, openid));
	        if (userInfo == null) {
	            // 如果用户不存在,则创建一个新的UserInfo对象
	            userInfo = new UserInfo();
	            // 设置用户的openid
	            userInfo.setWxOpenId(openid);
	            // 设置用户的昵称,其中包含一个随机生成的ID
	            userInfo.setNickname("这家伙太懒"+ IdWorker.getIdStr());
	            // 设置用户的头像URL
	            userInfo.setAvatarUrl("https://img0.baidu.com/it/u=1633409170,3159960019&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500");
	            // 保存用户信息到数据库
	            this.save(userInfo);

	            // 初始化用户账号信息
	            //userAccountFeignClient.initAccount(userInfo.getId());
				this.kafkaService.sendMsg(KafkaConstant.QUEUE_USER_REGISTER,userInfo.getId().toString());


				//int i = 1 / 0;
	        }
	        // 生成一个随机的登录令牌
	        String token = UUID.randomUUID().toString();
	        // 创建一个UserInfoVo对象,用于存放用户信息
	        UserInfoVo userInfoVo = new UserInfoVo();
	        // 将UserInfo对象的属性复制到UserInfoVo对象中
	        BeanUtils.copyProperties(userInfo, userInfoVo);
	        // 将用户信息存储到Redis中,设置过期时间为30分钟
	        this.redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_KEY_PREFIX + token, userInfoVo,RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);

	        // 将生成的登录令牌放入Map对象中
	        map.put("token", token);

	        // 返回包含登录令牌的Map对象
	        return map;
	    } catch (WxErrorException e) {
	        // 如果发生微信错误异常,抛出自定义的异常
	        throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
	    }
	}

}

4、service-account - > AccountListener.java

  • 此时 service-account 是消费者 接收消息
java 复制代码
@Slf4j
@Component
public class AccountListener {

    @Autowired
    private UserAccountService userAccountService;

    @RetryableTopic(backoff = @Backoff(2000))
    @KafkaListener(topics = KafkaConstant.QUEUE_USER_REGISTER)
    public void listen(String userId, Acknowledgment ack){

        // 如果是空消息直接确认掉,后续不用再执行
        if (StringUtils.isBlank(userId)) {
            ack.acknowledge();
            return;
        }

        // 初始化账户
        this.userAccountService.saveAccount(Long.valueOf(userId));

        ack.acknowledge();// 手动确认
    }
}
相关推荐
向夏威夷 梦断明暄19 小时前
从架构特点到功能缺陷,重新认识分析型分布式数据库
数据库·分布式·架构
Wang's Blog20 小时前
Go-Zero 项目开发19:基于 Kafka 的异步消息存储与转发实战
golang·kafka
Devin~Y21 小时前
互联网大厂 Java 面试实录:Spring Boot、MyBatis、Redis、Kafka、Spring Security、RAG 与 MCP 全链路问答
java·redis·kafka·mybatis·spring security·spring mvc·sprint boot
AAA@峥21 小时前
Ceph 集群配置管理完整指南
运维·数据库·分布式·ceph
心如鉄补1 天前
分布式链路追踪系统之docker-compose安装skywalking
分布式·docker·skywalking
努力努力再努力wz1 天前
【分布式系统与 RPC 框架系列】从单机瓶颈到远程调用:一文理解分布式架构与 RPC 原理
linux·网络·c++·分布式·网络协议·rpc·架构
ACP广源盛139246256731 天前
此芯 P1 AI BOX / AI NAS@ACP#IX8008、IX8024 应用场景与市场机会
大数据·人工智能·分布式·单片机·嵌入式硬件
网络工程小王2 天前
【HCIE-AI】12.deepspeed分布式并行训练进阶版
人工智能·pytorch·分布式·学习·昇腾·deepspeed
ACP广源盛139246256732 天前
国产算力互联IX8024@ACP#Kimi K3 开源后的端侧部署硬件架构分析
大数据·人工智能·分布式·单片机·嵌入式硬件
花生了什么事o2 天前
分布式 ID 生成方案:从数据库自增到雪花算法
数据库·分布式·算法