一个完整的秒杀系统
加入缓存
package com.ckq.seckill.config;
import com.ckq.entity.Specification;
import com.ckq.seckill.global.Constants;
import com.ckq.seckill.service.SeckillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
public class InitProject implements ApplicationRunner {
@Autowired
private SeckillService seckillService;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void run(ApplicationArguments args) throws Exception {
Map<String,Object> map=new HashMap<String, Object>();
List<Specification> goods=seckillService.getgoodsById(map);
for (int i=0;goods.size()>i;i++){
stringRedisTemplate.opsForValue().set(Constants.PRODUCT_STOCK_PREFIX+goods.get(i).getId(),goods.get(i).getStock()+"",2, TimeUnit.HOURS);
}
System.out.println("缓存已就绪");
}
}
rabbitmq配置
package com.ckq.seckill.config;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class MyMqconfig {
//@RabbitListener(queues = "order.release.order.queue")
public void listener(Object o, Channel channel, Message message) throws IOException {
System.out.println("过期的消息 ===> " + new String(message.getBody()) + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
/**
* 死信队列
*/
@Bean
public Queue orderDelayQueue() {
//死信队列的参数
Map<String, Object> params = new HashMap<>();
params.put("x-dead-letter-exchange", "order-event-exchange");//死信路由名称
params.put("x-dead-letter-routing-key", "order.release.order");//死信路由的路由键
params.put("x-message-ttl", 60000);//消息过期时间(单位:毫秒)
return new Queue("order.delay.queue", true, false, false, params);
}
@Bean
public Queue orderReleaseOrder() {
return new Queue("order.release.order.queue", true, false, false);
}
@Bean
public Exchange orderEventExchange() {
return new TopicExchange("order-event-exchange", true, false);
}
@Bean
public Binding orderCreateOrderBinding() {
return new Binding("order.delay.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.create.order", null);
}
@Bean
public Binding orderReleaseOrderBinding() {
return new Binding("order.release.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.order", null);
}
}
redis 重写序列化
package com.ckq.seckill.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<String, Object>();
//更换key的序列化方式,针对字符串类型
redisTemplate.setKeySerializer(new GenericJackson2JsonRedisSerializer());
//更换value的序列化方式,针对字符串类型
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//更换key的序列化方式,针对hash类型
redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
//更换value的序列化方式,针对hash类型
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
zookeeper配置
package com.ckq.seckill.config;
import com.ckq.seckill.service.impl.ZookeeperWatcher;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZookeeperConfig {
// 服务端 zookeeper 地址
private static final String serverZookeeperAddress = "localhost:2181";
@Bean
public ZooKeeper initZookeeper() throws Exception {
// 创建观察者
ZookeeperWatcher watcher = new ZookeeperWatcher();
// 创建 Zookeeper 客户端
ZooKeeper zooKeeper = new ZooKeeper(serverZookeeperAddress, 30000, watcher);
// 将客户端注册给观察者
watcher.setZooKeeper(zooKeeper);
// 将配置好的 zookeeper 返回
return zooKeeper;
}
}
zookeeper 调用
package com.ckq.seckill.config;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.util.concurrent.CountDownLatch;
public class ZookeeperSync implements Watcher {
private static CountDownLatch connectSemaphore = new CountDownLatch(1);
private static ZooKeeper zooKeeperClient = null;
private static Stat stat = new Stat();
public static void main(String[] args) throws Exception {
// 文件存放路径
String path = "/username";
// 连接 zookeeper 并注册一个默认监听器
zooKeeperClient = new ZooKeeper("192.168.0.12:2181", 5000, new com.ckq.seckill.config.ZookeeperSync());
// 等待连接 zookeeper 成功通知
connectSemaphore.await();
// 获取 path 目录节点下的配置数据,并注册默认的监听器
System.out.println(new String(zooKeeperClient.getData(path, true, stat)));
Thread.sleep(Integer.MAX_VALUE);
}
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType() == Event.EventType.None && watchedEvent.getPath() == null){
connectSemaphore.countDown();
}else if (watchedEvent.getType() == Event.EventType.NodeDataChanged){
try {
System.out.println("新值为:" + new String(zooKeeperClient.getData(watchedEvent.getPath(), true, stat)));
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
主控制层入口
package com.ckq.seckill.controller;
import com.ckq.config.JsonUtil;
import com.ckq.entity.InOrder;
import com.ckq.entity.Specification;
import com.ckq.seckill.global.Constants;
import com.ckq.seckill.service.SeckillService;
import com.rabbitmq.client.Channel;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName OrdersController
* @Description TODO
* @Author XYM
* @Date 2021/5/13 10:34
* @Version 1.0
**/
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
private SeckillService seckillService;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ZooKeeper zooKeeper;
private static final ConcurrentHashMap<Integer, Boolean> productSoldMap=new ConcurrentHashMap<>();
@PostMapping("goods")
public String getgoods(){
Map<String,Object> map=new HashMap<String, Object>();
List<Specification> goods=seckillService.getgoodsById(map);
return JsonUtil.JsonSerializer(goods);
}
@PostMapping("seckillgoods")
public String getsecgoods(Specification specification){
Map<String,Object> map=new HashMap<String, Object>();
map.put("id",specification.getId());
List<Specification> goods=seckillService.getgoodsById(map);
Specification seckikkgood=goods.get(0);
seckikkgood.setStarttime(seckikkgood.getStarttime());
System.out.println(seckikkgood);
return JsonUtil.JsonSerializer(seckikkgood);
}
@PostMapping("goodsbyId")
public String getgoodsById(Integer id,Integer sellerId,Integer deliveryAddId) throws KeeperException, InterruptedException {
if(productSoldMap.get(id) != null){
System.out.println("直接返回");
return "卖完了";
}
//String lock="lock";
// RLock rLock = redisson.getLock(lock);
InOrder inOrder;
try {
//rLock.lock();
Long stock = stringRedisTemplate.opsForValue().decrement(Constants.PRODUCT_STOCK_PREFIX+id);
System.out.println(stock);
if(stock<0){
productSoldMap.put(id,true);
stringRedisTemplate.opsForValue().increment(Constants.PRODUCT_STOCK_PREFIX + id);
String productPath ="/"+Constants.PRODUCT_STOCK_PREFIX+"/"+id;
if (zooKeeper.exists(productPath, true) == null) {
zooKeeper.create(productPath, "true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 监听 zookeeper 售完节点
zooKeeper.exists(productPath, true);
return "商品已售空";
//throw new RuntimeException("商品已售空");
}
inOrder=seckillService.order(id,sellerId,deliveryAddId);
}catch (Exception e){
// 数据库减库存失败回滚已售罄列表记录
if (productSoldMap.get(id) != null) {
productSoldMap.remove(id);
}
// 通过 zookeeper 回滚其他服务器的 JVM 缓存中的商品售完标记
String path = "/"+Constants.PRODUCT_STOCK_PREFIX+"/" + id;
if (zooKeeper.exists(path, true) != null){
zooKeeper.setData(path, "false".getBytes(), -1);
}
stringRedisTemplate.opsForValue().increment( Constants.PRODUCT_STOCK_PREFIX+ id);
// 回滚 Redis 中的库存
return "回滚";
}
return JsonUtil.JsonSerializer(inOrder);
}
@RabbitListener(queues = "order.release.order.queue")
public void listener(InOrder inOrder, Channel channel, Message message) throws IOException {
System.out.println(inOrder.toString());
InOrder s= (InOrder) redisTemplate.opsForValue().get(inOrder.getOrderId());
if(s.getOrderStep()==2){
System.out.println("已支付,订单生效");
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
else{
System.out.println("订单超时"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
stringRedisTemplate.opsForValue().increment( Constants.PRODUCT_STOCK_PREFIX+inOrder.getSpid());
inOrder.setOrderStep(1);
inOrder.setPayType(0);
inOrder.setMark("超时订单");
seckillService.buygodos(inOrder);
seckillService.justock(inOrder.getSpid());
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
@PostMapping("buy")
public String buygoods(InOrder inOrder){
inOrder.setMark("订单已付款");
redisTemplate.opsForValue().set(inOrder.getOrderId(),inOrder);
Integer count=seckillService.buygodos(inOrder);
return JsonUtil.JsonSerializer(count);
}
public static ConcurrentHashMap<Integer,Boolean> getProductSoldMap() {
return productSoldMap;
}
}
逻辑层接口
package com.ckq.seckill.service;
import com.ckq.entity.InOrder;
import com.ckq.entity.Inorderlist;
import com.ckq.entity.Order;
import com.ckq.entity.Specification;
import java.util.List;
import java.util.Map;
public interface SeckillService {
List<Specification> getgoodsById(Map<String,Object> map);
InOrder order(Integer id,Integer sellerId,Integer deliveryAddId);
Integer buygodos(InOrder inOrder);
Integer justock(Integer id);
}
逻辑层实现
package com.ckq.seckill.service.impl;
import com.baomidou.mybatisplus.extension.api.R;
import com.ckq.entity.*;
import com.ckq.seckill.config.SnowFlakeId;
import com.ckq.seckill.dao.InOrderDao;
import com.ckq.seckill.dao.InOrderGoodsDao;
import com.ckq.seckill.dao.SeckillDao;
import com.ckq.seckill.service.SeckillService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @ClassName OrdersServiceImpl
* @Description TODO
* @Author XYM
* @Date 2021/5/13 10:54
* @Version 1.0
**/
@Service
public class SeckillServiceImpl implements SeckillService {
@Autowired
private SeckillDao seckillDao;
@Autowired
private InOrderDao inOrderDao;
@Autowired
private InOrderGoodsDao inOrderGoodsDao;
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public List<Specification> getgoodsById(Map<String,Object> map) {
return seckillDao.getgoodsById(map);
}
@Transactional
public InOrder order(Integer id,Integer sellerId,Integer deliveryAddId){
Map<String,Object> map=new HashMap<String, Object>();
map.put("id",id);
List<Specification> list=seckillDao.getgoodsById(map);
if (list.get(0).getStock() <= 0){
throw new RuntimeException("The product has been sold out.");
}
//创建订单
InOrder inOrder=new InOrder();
InOrderGoodsList inOrderGoodsList=new InOrderGoodsList();
inOrderGoodsList.setGoodsId(list.get(0).getGoodsId());
inOrderGoodsList.setGoodsImg(list.get(0).getGoodsImgUrl());
inOrderGoodsList.setSpId(id);
inOrderGoodsList.setBuyNum(1);
inOrderGoodsList.setSname(list.get(0).getSname());
inOrderGoodsList.setPrice(list.get(0).getPrice());
inOrderGoodsList.setOrderId(SnowFlakeId.getSnowId());
inOrderGoodsList.setGoodsName(list.get(0).getSname());
seckillDao.addInOrderGoodsList(inOrderGoodsList);
inOrder.setOrderId(inOrderGoodsList.getOrderId());
inOrder.setSellerId(sellerId);
inOrder.setAmount(list.get(0).getPrice());
inOrder.setOrderStep(1);
inOrder.setOrderState(1);
inOrder.setPayType(2);
inOrder.setSpid(id);
inOrder.setDeliveryAddId(deliveryAddId);
seckillDao.addInOrderlist(inOrder);
createOrder(inOrder);
System.out.println("+缓存");
redisTemplate.opsForValue().set(inOrder.getOrderId(),inOrder,30, TimeUnit.MINUTES);
System.out.println("+缓存");
//减库存
Integer count=seckillDao.jstock(id);
System.out.println("减库存");
if (count <= 0){
System.out.println("减库存错误");
throw new RuntimeException("The product has been sold out.");
}
return inOrder;
}
public R createOrder(InOrder inOrder) {
System.out.println("创建订单===> " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
// 给MQ发消息
rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", inOrder);
return R.ok(new Date());
}
@Override
public Integer buygodos(InOrder inOrder) {
return seckillDao.buygodos(inOrder);
}
public Integer justock(Integer id){
return seckillDao.ujstock(id);
}
}
常量
package com.ckq.seckill.global;
public class Constants {
public static final String PRODUCT_STOCK_PREFIX = "product";
}
持久层接口
package com.ckq.seckill.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ckq.entity.InOrder;
import org.springframework.stereotype.Repository;
@Repository
public interface InOrderDao extends BaseMapper<InOrder> {
}
package com.ckq.seckill.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ckq.entity.InOrderGoodsList;
import org.springframework.stereotype.Repository;
@Repository
public interface InOrderGoodsDao extends BaseMapper<InOrderGoodsList> {
}
package com.ckq.seckill.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ckq.entity.*;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* @ClassName InformationDao
* @Description TODO
* @Author XYM
* @Date 2021/5/13 10:58
* @Version 1.0
**/
@Repository
public interface SeckillDao extends BaseMapper<Specification> {
//查数据
List<Specification> getgoodsById(Map<String,Object> map);
//生成订单
Integer order(Order order);
//付款
Integer buygodos(InOrder inOrder);
Integer jstock(Integer id);
Integer ujstock(Integer id);
Integer addInOrderlist(InOrder inOrder);
Integer addInOrderGoodsList(InOrderGoodsList inOrderGoodsList);
}
yml配置
server:
port: 8008
spring:
application:
name: supply-seller-seckill
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/supply-admin?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root
password:
druid:
initial-size: 1
max-active: 10
min-idle: 1
max-wait: 30000
redis:
host: localhost
port: 6379
lettuce:
pool:
max-active: 10
min-idle: 1
max-idle: 10
max-wait: 30000
password: 123456
sleuth:
sampler:
probability: 1 #取样器
zipkin:
sender:
type: rabbit
# 连接rabbitMQ
rabbitmq:
listener:
direct:
retry:
enabled: true
simple:
retry:
enabled: true
max-attempts: 3 #重试几次
initial-interval: 5000 #次数间隔5S
acknowledge-mode: manual
host: localhost
port: 5672
username: guest
password: guest
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
logging:
level:
com.ckq.product.dao: debug
#feign超时设置
feign:
client:
config:
default:
connect-timeout: 30000
read-tmout: 30000
#表示暴露所有的节点,才能在sentienl控制台中展示实时监控信息
management:
endpoints:
web:
exposure:
include: '*'
mapping
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ckq.seckill.dao.SeckillDao">
<select id="getgoodsById" resultType="com.ckq.entity.Specification" parameterType="Map">
select * from seckill_goods
<where>
<if test="id!=null and id!=''">and id = #{id}</if>
<if test="goodsId!=null and goodsId!=''">and goodsId = #{goodsId}</if>
</where>
</select>
<insert id="order" parameterType="com.ckq.entity.Order">
insert into gorder (goods,price) values (#{goods},#{price})
</insert>
<update id="buygodos" parameterType="map">
update bm_inorderlist set payType=#{payType},orderStep=#{orderStep},mark=#{mark} where orderId=#{orderId}
</update>
<update id="jstock" parameterType="map">
Update seckill_goods set stock=stock-1 where id=#{id} and stock>0
</update>
<update id="ujstock" parameterType="map">
Update seckill_goods set stock=stock+1 where id=#{id} and stock>0
</update>
<insert id="addInOrderlist" parameterType="com.ckq.entity.InOrder">
insert into bm_inorderlist (orderId,sellerId,amount,payType,orderStep,orderState,spid,deliveryAddId,createTime) values (
#{orderId},#{sellerId},#{amount},#{payType},#{orderStep},#{orderState},#{spid},#{deliveryAddId},default
)
</insert>
<insert id="addInOrderGoodsList" parameterType="com.ckq.entity.InOrderGoodsList">
insert into bm_inordergoodslist (id,orderId,goodsId,spId,sname,goodsName,goodsImg,price,buyNum) values (
null,#{orderId},#{goodsId},#{spId},#{sname},#{goodsName},#{goodsImg},#{price},#{buyNum}
)
</insert>
</mapper>