购物车大家都习惯用过,添加购物车,删除购物车等等操作。
看了很多github或者码云都仓库代码,购物车的实现一般都是直接操作数据库,进行增删改查。
个人认为是不对的!
你可以说,数据库可以分库分表或者其他操作。但是对于频繁操作数据库的,会造成数据库io崩掉,然后直接导致系统挂掉。这就是为什么很多数据刚开始会用缓存处理的原因之一。
最后,数据肯定是要沉淀到数据库中去的,但是我们不想过多的浪费业务逻辑去操作数据库,这就可以采用异步方式,这里我使用了rocketmq去实现。
下面是一个简单实现(业务逻辑也相对简单,没有sql处理,看注释即可)
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.2.0</version>
</dependency>
记得加上这个。
@RestController
@RequestMapping("/cart")
public class CartController {
@Autowired
private CartService cartService;
/**
*
* @return
*/
@PostMapping("")
public Cart addCart(@RequestBody Cart cart, String userId) {
return cartService.addCart(cart, userId);
}
}
rest中有一个添加cart的接口。
我们看下service中实现
@Slf4j
@Service
public class CartService {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
private Producer producer;
/**
* 添加商品到购物车
* @param cart
* @return
*/
public Cart addCart(Cart cart, String userId) {
//并发量大的话 其实不要连mysql
//添加购物车 就是直接往redis中去添加
//直接操作redis 所以不用注解Cache
//把信息带到MQ 服务从MQ 中去消费 添加到数据库 才行
//查询是否有相关productid
//用hash去存储
Object hash = redisTemplate.opsForHash().get("cart:" + userId,"cart:" + cart.getProductId());
if(hash != null) {
Integer newNum = Integer.parseInt(hash.toString()) + cart.getNum();
redisTemplate.opsForHash().put("cart:" + userId,"cart:" + cart.getProductId(), newNum.toString());
} else {
redisTemplate.opsForHash().put("cart:" + userId,"cart:" + cart.getProductId(), cart.getNum().toString());
}
//异步传入到消息
try{
Message message = new Message(RocketConfig.CART_TOPIC, JSON.toJSONBytes(cart));
SendResult result = producer.getProducer().send(message);
log.info("生产者传入消息到消费者---{}", result);
}catch (Exception e) {
log.error(e.getMessage());
}
return cart;
}
}
@Data
public class Cart {
private String id;
private String shopId;
private String userId;
private String productId;
private Integer num;
}
public class RocketConfig {
public static final String NAMESRV = "192.168.100.108:9876";
public static final String CART_TOPIC = "cart_topic";
}
@Slf4j
@Component
public class Producer {
private String proupName = "cart_group";
private DefaultMQProducer producer;
public Producer() {
producer = new DefaultMQProducer(proupName);
producer.setNamesrvAddr(RocketConfig.NAMESRV);
producer.setVipChannelEnabled(false);
start();
}
public void start() {
try {
this.producer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
public void shutdown() {
this.producer.shutdown();
}
public DefaultMQProducer getProducer() {
return this.producer;
}
}
@Slf4j
@Component
public class Cousmer {
private DefaultMQPushConsumer consumer;
private String groupName = "cart_group";
public Cousmer() throws MQClientException {
consumer = new DefaultMQPushConsumer(groupName);
consumer.setNamesrvAddr(RocketConfig.NAMESRV);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
consumer.subscribe(RocketConfig.CART_TOPIC, "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list
, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt message : list) {
try {
String body = new String(message.getBody(), "utf-8");
log.info("Consumer-获取消息-主题topic为={}, 消费消息为={}", message.getTopic(), body);
//添加数据到数据库中逻辑
log.info("添加到数据库中" + message.getMsgId());
} catch (UnsupportedEncodingException e) {
//e.printStackTrace();
log.error("消费者异常");
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("消费者 启动成功=======");
}
}
ok 这是几个处理类
首先说下我自己遇到的问题
1、在设计过程中,购物车在redis中的存储结构应该是hash
可以看到,这就是用户id为22的这个用户所添加的购物车以及数量,qw12313为产品id。
可能业务代码写的比较凌乱,但是基本思想都差不多。
其次,我的redis以及mq 都是搭建在虚拟机中。
ok,那么问题来了,redis访问没问题,一直都可以。
但是mq都访问遇到坑了,要不就是route not info......要不就是脸上namesrv但是消息发送失败!
这里比较好多方式是
rocketmq-console,大家部署这个,然后看效果就知道了。
具体看每一个topic的路由,你会发现并不是你的虚拟机中ens33的网络,而是docker0的网络!
其实在官网,rocketmq是有介绍的,大家仔细一点可以发现,我们需要在broker.conf中去配置ip
这样,访问就没问题。
看到没有!!
请求时间,测试了一下,在100ms以内(大家设计接口时候,最后接口访问返回时间控制在200ms以内,这样才是优秀的接口!)
额。。。写的并不是很完善,包括rocketmq-console的部署也没说,其实网上一大堆,大家都可以参考都用!
如果上述你都redis,mq都是在本地启动都话,其实没必要了,访问是成功的,我这里因为在虚拟机中搭建比较坑,所以大家还是仁者见仁,智者见智!
从购物车设计引发的一系列问题(rocketMQ在虚拟机中启动注意事项)
原文:https://www.cnblogs.com/lzphu/p/12571454.html