使用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();// 手动确认
    }
}
相关推荐
m0_646429978 分钟前
RabbitMQ 核心知识总结(生产实战版)
分布式·rabbitmq
DBA_G2 小时前
GBase 8c分布式数据库客户端工具 gsql
数据库·分布式
chunyublog2 小时前
Spark 词频统计练习
大数据·分布式·spark
ClouGence3 小时前
开源数据库管理工具 CloudDM 4.4.0 发布,新增 Kafka、SAP HANA 数据源
数据库·kafka
acrel74 小时前
高速公路分布式光伏并网电能质量与安全保护方案应用
分布式·安全
RobinXYuan4 小时前
【Kafka 源码手记】01 从消息队列到 Event Streaming
kafka·源码阅读
IT小小猪5 小时前
# Hadoop 3.3.6 完全分布式集群部署知识库
hadoop·分布式·eclipse
此时不提桶,更待何时6 小时前
06-24-B-RabbitMQ面试与生产事故实战
分布式·面试·rabbitmq
来福猿6 小时前
分布式锁 Redis 实现源码分析:SETNX、过期时间与可重入设计
数据库·redis·分布式
Pniubi21 小时前
分布式锁分类讲解
分布式