├── README.md ├── pics ├── atomicInteger │ ├── result_1.png │ ├── result_2.png │ └── result_3.png ├── data-config.png ├── origin.png ├── pessLockInMySQL │ ├── result_1.png │ ├── result_2.png │ └── result_3.png ├── posiLockInMySQL │ ├── result_1.png │ ├── result_2.png │ └── result_3.png └── posiLockInRedis │ ├── result_1.png │ ├── result_2.png │ └── result_3.png ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── lc │ │ └── seckill │ │ ├── SecKillApp.java │ │ ├── cache │ │ ├── RedisCacheHandle.java │ │ └── RedisLoaderListener.java │ │ ├── common │ │ ├── GlobalExceptionHandler.java │ │ ├── Head.java │ │ ├── Message.java │ │ └── SecKillEnum.java │ │ ├── concurrent │ │ └── AtomicStock.java │ │ ├── config │ │ ├── MyBatisConfig.java │ │ ├── RabbitmqCallBackConfig.java │ │ └── RedisCacheConfig.java │ │ ├── constant │ │ ├── RabbitMQPropertyConst.java │ │ ├── RedisCacheConst.java │ │ └── SecKillStateConst.java │ │ ├── controller │ │ └── SecKillController.java │ │ ├── entity │ │ ├── DataObject.java │ │ ├── Product.java │ │ ├── Record.java │ │ └── User.java │ │ ├── exception │ │ └── SecKillException.java │ │ ├── mapper │ │ └── SecKillMapper.java │ │ ├── mq │ │ ├── RabbitMQReceiver.java │ │ └── RabbitMQSender.java │ │ ├── service │ │ └── SecKillService.java │ │ ├── utils │ │ └── SecKillUtils.java │ │ └── web │ │ ├── req │ │ └── SecKillRequest.java │ │ └── vo │ │ └── SecKillResponse.java └── resources │ ├── application.properties │ └── dao │ └── SecKillMapper.xml └── test └── java └── JUnitTest.java /README.md: -------------------------------------------------------------------------------- 1 | # SeckillSystem 2 | Java 秒杀设计方案 3 | 4 | ## 具体内容 5 | 对高并发高负载情形下的应用场景进行分析,以高效地处理资源竞争为目的,设计一个秒杀与抢购模型。 本项目提供了四种解决方案来比较系统的性能: 6 | 1.利用MySQL的update行锁实现悲观锁。 7 | 2.MySQL加字段version实现乐观锁。 8 | 3.使用Redis作为原子计数器(watch事务+decr操作),RabbitMQ作为消息队列记录用户抢购行为,MySQL做异步存储。 9 | 4.基于AtomicInteger的CAS机制 10 | 11 | ## 压测图片   12 | ![origin](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/origin.png) 13 | ![config](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/origin.png) 14 | *** 15 | 16 | ## 实验结果 17 | ### MySQL悲观锁 18 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/pessLockInMySQL/result_1.png) 19 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/pessLockInMySQL/result_2.png) 20 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/pessLockInMySQL/result_3.png) 21 | *** 22 | 23 | ### MySQL乐观锁 24 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/posiLockInMySQL/result_1.png) 25 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/posiLockInMySQL/result_2.png) 26 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/posiLockInMySQL/result_3.png) 27 | *** 28 | ### AtomicInteger实现CAS 29 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/atomicInteger/result_1.png) 30 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/atomicInteger/result_2.png) 31 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/atomicInteger/result_3.png) 32 | *** 33 | ### Redis的watch监控 34 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/posiLockInRedis/result_1.png) 35 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/posiLockInRedis/result_2.png) 36 | ![](https://github.com/GoldenLiang/SeckillSystem/blob/master/pics/posiLockInRedis/result_3.png) 37 | *** 38 | -------------------------------------------------------------------------------- /pics/atomicInteger/result_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/atomicInteger/result_1.png -------------------------------------------------------------------------------- /pics/atomicInteger/result_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/atomicInteger/result_2.png -------------------------------------------------------------------------------- /pics/atomicInteger/result_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/atomicInteger/result_3.png -------------------------------------------------------------------------------- /pics/data-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/data-config.png -------------------------------------------------------------------------------- /pics/origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/origin.png -------------------------------------------------------------------------------- /pics/pessLockInMySQL/result_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/pessLockInMySQL/result_1.png -------------------------------------------------------------------------------- /pics/pessLockInMySQL/result_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/pessLockInMySQL/result_2.png -------------------------------------------------------------------------------- /pics/pessLockInMySQL/result_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/pessLockInMySQL/result_3.png -------------------------------------------------------------------------------- /pics/posiLockInMySQL/result_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/posiLockInMySQL/result_1.png -------------------------------------------------------------------------------- /pics/posiLockInMySQL/result_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/posiLockInMySQL/result_2.png -------------------------------------------------------------------------------- /pics/posiLockInMySQL/result_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/posiLockInMySQL/result_3.png -------------------------------------------------------------------------------- /pics/posiLockInRedis/result_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/posiLockInRedis/result_1.png -------------------------------------------------------------------------------- /pics/posiLockInRedis/result_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/posiLockInRedis/result_2.png -------------------------------------------------------------------------------- /pics/posiLockInRedis/result_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/SeckillSystem/3d5ca490d30ef1732af2124bae3d213ec7ad2275/pics/posiLockInRedis/result_3.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | groupId 8 | SecKillSystem 9 | 1.0-SNAPSHOT 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 1.4.1.RELEASE 15 | 16 | 17 | 18 | 19 | com.alibaba 20 | fastjson 21 | 1.2.31 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-redis 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | 38 | 39 | 40 | junit 41 | junit 42 | 4.12 43 | 44 | 45 | 46 | org.projectlombok 47 | lombok 48 | 1.16.6 49 | 50 | 51 | 52 | org.slf4j 53 | slf4j-api 54 | 1.7.21 55 | 56 | 57 | 58 | com.alibaba 59 | druid 60 | 1.0.18 61 | 62 | 63 | 64 | mysql 65 | mysql-connector-java 66 | 5.1.39 67 | 68 | 69 | 70 | org.mybatis.spring.boot 71 | mybatis-spring-boot-starter 72 | 1.1.1 73 | 74 | 75 | 76 | commons-beanutils 77 | commons-beanutils 78 | 1.9.2 79 | 80 | 81 | 82 | com.rabbitmq 83 | amqp-client 84 | 3.6.4 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | src/main/java 93 | 94 | **/*.properties 95 | **/*.xml 96 | 97 | false 98 | 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-compiler-plugin 104 | 105 | 1.7 106 | 1.7 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/SecKillApp.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @MapperScan("") 9 | public class SecKillApp { 10 | public static void main(String[] args) { 11 | SpringApplication.run(SecKillApp.class,args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/cache/RedisCacheHandle.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.cache; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | import redis.clients.jedis.Jedis; 6 | import redis.clients.jedis.JedisPool; 7 | 8 | @Component 9 | public class RedisCacheHandle { 10 | 11 | @Autowired 12 | private JedisPool jedisPool; 13 | 14 | public Jedis getJedis(){ 15 | return jedisPool.getResource(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/cache/RedisLoaderListener.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.cache; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.TypeReference; 5 | import com.lc.seckill.constant.RedisCacheConst; 6 | import com.lc.seckill.entity.Product; 7 | import com.lc.seckill.entity.User; 8 | import com.lc.seckill.mapper.SecKillMapper; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | import redis.clients.jedis.Jedis; 13 | 14 | import javax.annotation.PostConstruct; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | @Slf4j 19 | @Component 20 | public class RedisLoaderListener { 21 | 22 | @Autowired 23 | private RedisCacheHandle redisCacheHandle; 24 | 25 | @Autowired 26 | private SecKillMapper secKillMapper; 27 | 28 | @PostConstruct 29 | public void initRedis(){ 30 | Jedis jedis = redisCacheHandle.getJedis(); 31 | List productList = secKillMapper.getAllProduct(); 32 | for (Product product:productList) { 33 | Map map = JSON.parseObject(JSON.toJSONString(product), new TypeReference>(){}); 34 | jedis.hmset("product_"+product.getId(),map); 35 | } 36 | log.info("Redis数据初始化完毕!"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/common/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.common; 2 | 3 | import com.lc.seckill.exception.SecKillException; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.ControllerAdvice; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | 9 | @ControllerAdvice 10 | @Slf4j 11 | public class GlobalExceptionHandler { 12 | 13 | @ExceptionHandler(value = SecKillException.class) 14 | @ResponseBody 15 | public Message handleSecKillException(SecKillException secKillException){ 16 | log.info(secKillException.getSecKillEnum().getMessage()); 17 | return new Message(secKillException.getSecKillEnum()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/common/Head.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.common; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author lc 7 | */ 8 | @Data 9 | public class Head { 10 | 11 | /** 12 | * 状态码,0成功,1系统异常,2参数异常 13 | */ 14 | private String statusCode; 15 | 16 | /** 17 | * 状态信息 18 | */ 19 | private String statusMessage; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/common/Message.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.common; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class Message { 11 | 12 | private Head head; 13 | 14 | private T body; 15 | 16 | public Message(SecKillEnum resultEnum, T body){ 17 | this.head = new Head(); 18 | this.head.setStatusCode(resultEnum.getCode()); 19 | this.head.setStatusMessage(resultEnum.getMessage()); 20 | this.body = body; 21 | } 22 | 23 | public Message(SecKillEnum resultEnum){ 24 | this.head = new Head(); 25 | this.head.setStatusCode(resultEnum.getCode()); 26 | this.head.setStatusMessage(resultEnum.getMessage()); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/common/SecKillEnum.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.common; 2 | 3 | 4 | import com.lc.seckill.constant.SecKillStateConst; 5 | 6 | /** 7 | * @author lc 8 | */ 9 | 10 | public enum SecKillEnum { 11 | /** 12 | * 服务级错误 13 | */ 14 | SUCCESS(SecKillStateConst.SUCCESS,"秒杀成功!"), 15 | FAIL(SecKillStateConst.FAIL, "秒杀失败!"), 16 | REPEAT(SecKillStateConst.REPEAT, "重复秒杀!"), 17 | SYSTEM_EXCEPTION(SecKillStateConst.SYSTEM_EXCEPTION, "系统错误!"), 18 | ; 19 | 20 | private String code; 21 | 22 | private String msg; 23 | 24 | SecKillEnum(String code, String msg){ 25 | this.code = code; 26 | this.msg = msg; 27 | } 28 | 29 | public String getCode() { 30 | return code; 31 | } 32 | 33 | public String getMsg() { 34 | return msg; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/concurrent/AtomicStock.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.concurrent; 2 | 3 | import com.lc.seckill.entity.Product; 4 | import com.lc.seckill.mapper.SecKillMapper; 5 | import lombok.Data; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.util.List; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | @Data 14 | @Component 15 | public class AtomicStock { 16 | 17 | private AtomicInteger samsungInteger = new AtomicInteger(); 18 | 19 | private AtomicInteger huaweiInteger = new AtomicInteger(); 20 | 21 | private AtomicInteger xiaomiInteger = new AtomicInteger(); 22 | 23 | private AtomicInteger iphoneInteger = new AtomicInteger(); 24 | 25 | @Autowired 26 | private SecKillMapper secKillMapper; 27 | 28 | @PostConstruct 29 | public void initAtomicInteger() { 30 | List productList = secKillMapper.getAllProduct(); 31 | for (Product product : productList) { 32 | getAtomicInteger(product.getProductName()).set(product.getStock()); 33 | 34 | } 35 | } 36 | 37 | public AtomicInteger getAtomicInteger(String productName) { 38 | AtomicInteger ai = null; 39 | if (productName != null && !productName.isEmpty()){ 40 | switch (productName){ 41 | case "iphone": 42 | ai = iphoneInteger; 43 | break; 44 | case "huawei": 45 | ai = huaweiInteger; 46 | break; 47 | case "samsung": 48 | ai = samsungInteger; 49 | break; 50 | case "xiaomi": 51 | ai = xiaomiInteger; 52 | break; 53 | } 54 | } 55 | return ai; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/config/MyBatisConfig.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.config; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.mybatis.spring.SqlSessionFactoryBean; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 11 | import org.springframework.core.io.support.ResourcePatternResolver; 12 | 13 | import java.io.IOException; 14 | import java.sql.SQLException; 15 | 16 | @Configuration 17 | @Slf4j 18 | public class MyBatisConfig { 19 | 20 | /** 21 | * datasource 22 | */ 23 | @Value("${spring.datasource.url}") 24 | private String dbUrl; 25 | 26 | @Value("${spring.datasource.username}") 27 | private String username; 28 | 29 | @Value("${spring.datasource.password}") 30 | private String password; 31 | 32 | @Value("${spring.datasource.driver-class-name}") 33 | private String driverClassName; 34 | 35 | @Value("${spring.datasource.initialSize}") 36 | private int initialSize; 37 | 38 | @Value("${spring.datasource.minIdle}") 39 | private int minIdle; 40 | 41 | @Value("${spring.datasource.maxActive}") 42 | private int maxActive; 43 | 44 | @Value("${spring.datasource.maxWait}") 45 | private int maxWait; 46 | 47 | @Value("${spring.datasource.timeBetweenEvictionRunsMillis}") 48 | private int timeBetweenEvictionRunsMillis; 49 | 50 | @Value("${spring.datasource.minEvictableIdleTimeMillis}") 51 | private int minEvictableIdleTimeMillis; 52 | 53 | @Value("${spring.datasource.validationQuery}") 54 | private String validationQuery; 55 | 56 | @Value("${spring.datasource.testWhileIdle}") 57 | private boolean testWhileIdle; 58 | 59 | @Value("${spring.datasource.testOnBorrow}") 60 | private boolean testOnBorrow; 61 | 62 | @Value("${spring.datasource.testOnReturn}") 63 | private boolean testOnReturn; 64 | 65 | @Value("${spring.datasource.poolPreparedStatements}") 66 | private boolean poolPreparedStatements; 67 | 68 | @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}") 69 | private int maxPoolPreparedStatementPerConnectionSize; 70 | 71 | @Value("${spring.datasource.filters}") 72 | private String filters; 73 | 74 | @Value("${spring.datasource.connectionProperties}") 75 | private String connectionProperties; 76 | 77 | /** 78 | * mybatis 79 | */ 80 | @Value("${mybatis.type-aliases-package}") 81 | private String typeAliasesPackage; 82 | 83 | @Value("${mybatis.mapper-locations}") 84 | private String mapperLocations; 85 | 86 | @Bean(name = "druidDataSource") 87 | public DruidDataSource dataSource(){ 88 | DruidDataSource dataSource = new DruidDataSource(); 89 | 90 | dataSource.setUrl(dbUrl); 91 | dataSource.setUsername(username); 92 | dataSource.setPassword(password); 93 | dataSource.setDriverClassName(driverClassName); 94 | 95 | //configuration 96 | dataSource.setInitialSize(initialSize); 97 | dataSource.setMinIdle(minIdle); 98 | dataSource.setMaxActive(maxActive); 99 | dataSource.setMaxWait(maxWait); 100 | dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); 101 | dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); 102 | dataSource.setValidationQuery(validationQuery); 103 | dataSource.setTestWhileIdle(testWhileIdle); 104 | dataSource.setTestOnBorrow(testOnBorrow); 105 | dataSource.setTestOnReturn(testOnReturn); 106 | dataSource.setPoolPreparedStatements(poolPreparedStatements); 107 | dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); 108 | try { 109 | dataSource.setFilters(filters); 110 | } catch (SQLException e) { 111 | log.error("druid configuration initialization filter", e); 112 | } 113 | dataSource.setConnectionProperties(connectionProperties); 114 | 115 | return dataSource; 116 | } 117 | 118 | @Bean 119 | public SqlSessionFactoryBean initSqlSessionFactoryBean(@Qualifier("druidDataSource") DruidDataSource dataSource) throws IOException { 120 | SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); 121 | sessionFactoryBean.setDataSource(dataSource); 122 | sessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage); 123 | //添加XML目录 124 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 125 | sessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations)); 126 | return sessionFactoryBean; 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/config/RabbitmqCallBackConfig.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.config; 2 | 3 | import org.springframework.amqp.core.*; 4 | import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; 5 | import org.springframework.amqp.rabbit.connection.ConnectionFactory; 6 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 7 | import org.springframework.beans.factory.annotation.Qualifier; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.Primary; 13 | import org.springframework.context.annotation.Scope; 14 | 15 | @Configuration 16 | public class RabbitmqCallBackConfig { 17 | 18 | @Value("${spring.rabbitmq.host}") 19 | private String address; 20 | 21 | @Value("${spring.rabbitmq.port}") 22 | private String port; 23 | 24 | @Value("${spring.rabbitmq.username}") 25 | private String username; 26 | 27 | @Value("${spring.rabbitmq.password}") 28 | private String password; 29 | 30 | @Value("${spring.rabbitmq.virtual-host}") 31 | private String virtualHost; 32 | 33 | @Value("${spring.rabbitmq.publisher-confirms}") 34 | private boolean publisherConfirms; 35 | 36 | @Value("${rabbitmq.config.exchangeName}") 37 | private String exchangeName; 38 | 39 | @Value("${rabbitmq.config.queueName}") 40 | private String queueName; 41 | 42 | @Value("${rabbitmq.config.routingKey}") 43 | private String routingKey; 44 | 45 | @Bean 46 | public ConnectionFactory initConnectionFactory(){ 47 | CachingConnectionFactory factory = new CachingConnectionFactory(); 48 | factory.setAddresses(address+":"+port); 49 | factory.setUsername(username); 50 | factory.setPassword(password); 51 | factory.setVirtualHost(virtualHost); 52 | factory.setPublisherConfirms(publisherConfirms); 53 | return factory; 54 | } 55 | 56 | //必须是prototype类型 57 | @Bean 58 | @Primary 59 | @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 60 | public RabbitTemplate initRabbitTemplate(@Qualifier("initConnectionFactory") ConnectionFactory connectionFactory){ 61 | return new RabbitTemplate(connectionFactory); 62 | } 63 | 64 | /** 65 | * 针对消费者配置 66 | * 1. 设置交换机类型 67 | * 2. 将队列绑定到交换机 68 | FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念 69 | HeadersExchange :通过添加属性key-value匹配 70 | DirectExchange:按照routingkey分发到指定队列 71 | TopicExchange:多关键字匹配 72 | */ 73 | @Bean 74 | public DirectExchange defaultExchange() { 75 | return new DirectExchange(exchangeName); 76 | } 77 | @Bean 78 | public Queue queue() { 79 | return new Queue(queueName); 80 | } 81 | @Bean 82 | public Binding binding() { 83 | return BindingBuilder.bind(queue()).to(defaultExchange()).with(routingKey); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/config/RedisCacheConfig.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.factory.annotation.Qualifier; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import redis.clients.jedis.JedisPool; 9 | import redis.clients.jedis.JedisPoolConfig; 10 | 11 | /** 12 | * @author lc 13 | */ 14 | @Configuration 15 | @Slf4j 16 | public class RedisCacheConfig { 17 | 18 | @Value("${spring.redis.host}") 19 | private String host; 20 | 21 | @Value("${spring.redis.port}") 22 | private int port; 23 | 24 | @Value("${spring.redis.pool.max-idle}") 25 | private int maxIdle; 26 | 27 | @Value("${spring.redis.pool.min-idle}") 28 | private int minIdle; 29 | 30 | @Value("${spring.redis.pool.max-wait}") 31 | private long maxWaitMillis; 32 | 33 | @Value("${spring.redis.pool.max-active}") 34 | private int maxActive; 35 | 36 | @Value("${spring.redis.timeout}") 37 | private int timeout; 38 | 39 | @Value("${spring.redis.database}") 40 | private int database; 41 | 42 | @Bean(name = "poolConfig") 43 | public JedisPoolConfig initJedisPoolConfig(){ 44 | log.info("JedisPoolConfig注入开始:"); 45 | JedisPoolConfig poolConfig = new JedisPoolConfig(); 46 | poolConfig.setMaxTotal(maxActive); 47 | poolConfig.setMaxIdle(maxIdle); 48 | poolConfig.setMaxWaitMillis(maxWaitMillis); 49 | poolConfig.setMinIdle(minIdle); 50 | poolConfig.setTestOnBorrow(true); 51 | poolConfig.setTestOnReturn(true); 52 | poolConfig.setBlockWhenExhausted(true); 53 | return poolConfig; 54 | } 55 | 56 | @Bean 57 | public JedisPool initJedisPool(@Qualifier("poolConfig") JedisPoolConfig poolConfig){ 58 | log.info("JedisPool注入开始:"); 59 | JedisPool jedisPool = new JedisPool(poolConfig,host,port,timeout); 60 | return jedisPool; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/constant/RabbitMQPropertyConst.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.constant; 2 | 3 | public final class RabbitMQPropertyConst { 4 | /** 5 | * 用户已买集合 6 | */ 7 | public final static String exchangeName = "iphone_has_bought_set"; 8 | 9 | public final static String HUAWEI_HAS_BOUGHT_SET = "huawei_has_bought_set"; 10 | 11 | public final static String SAMSUNG_HAS_BOUGHT_SET = "samsung_has_bought_set"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/constant/RedisCacheConst.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.constant; 2 | 3 | public final class RedisCacheConst { 4 | /** 5 | * 用户集合 6 | */ 7 | public final static String IPHONE_HAS_BUY_SET = "iphone_has_buy_set"; 8 | 9 | public final static String HUAWEI_HAS_BUY_SET = "huawei_has_buy_set"; 10 | 11 | public final static String SAMSUNG_HAS_BUY_SET = "samsung_has_buy_set"; 12 | 13 | public final static String XIAOMI_HAS_BUY_SET = "xiaomi_has_buy_set"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/constant/SecKillStateConst.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.constant; 2 | 3 | /** 4 | * 返回常量 5 | */ 6 | public final class SecKillStateConst { 7 | /** 8 | * 秒杀成功 9 | */ 10 | public final static String SUCCESS = "1"; 11 | /** 12 | * 秒杀失败 13 | */ 14 | public final static String FAIL = "0"; 15 | /** 16 | * 重复秒杀 17 | */ 18 | public final static String REPEAT = "-1"; 19 | /** 20 | * 系统错误 21 | */ 22 | public final static String SYSTEM_EXCEPTION = "-2"; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/controller/SecKillController.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.controller; 2 | 3 | import com.lc.seckill.cache.RedisCacheHandle; 4 | import com.lc.seckill.common.Message; 5 | import com.lc.seckill.common.SecKillEnum; 6 | import com.lc.seckill.service.SecKillService; 7 | import com.lc.seckill.web.req.SecKillRequest; 8 | import com.lc.seckill.web.vo.SecKillResponse; 9 | import org.junit.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.*; 12 | import redis.clients.jedis.Jedis; 13 | 14 | import java.util.HashMap; 15 | import java.util.HashSet; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | @RequestMapping("/seckill") 20 | @RestController 21 | public class SecKillController { 22 | 23 | @Autowired 24 | private SecKillService secKillService; 25 | 26 | /** 27 | * MySQL悲观锁 28 | * @param requestMessage 29 | * @return 30 | */ 31 | @RequestMapping(value = "/pessLockInMySQL",method = RequestMethod.POST) 32 | public Message pessLockInMySQL(@RequestBody Message requestMessage){ 33 | Map paramMap = new HashMap<>(); 34 | paramMap.put("userId",requestMessage.getBody().getUserId()); 35 | paramMap.put("productId",requestMessage.getBody().getProductId()); 36 | SecKillEnum secKillEnum = secKillService.handleByPessLockInMySQL(paramMap); 37 | Message responseMessage = new Message<>(secKillEnum,null); 38 | return responseMessage; 39 | } 40 | 41 | /** 42 | * MySQL乐观锁 43 | * @param requestMessage 44 | * @return 45 | */ 46 | @RequestMapping(value = "/posiLockInMySQL",method = RequestMethod.POST) 47 | public Message posiLockInMySQL(@RequestBody Message requestMessage){ 48 | Map paramMap = new HashMap<>(); 49 | paramMap.put("userId",requestMessage.getBody().getUserId()); 50 | paramMap.put("productId",requestMessage.getBody().getProductId()); 51 | SecKillEnum secKillEnum = secKillService.handleByPosiLockInMySQL(paramMap); 52 | Message responseMessage = new Message<>(secKillEnum,null); 53 | return responseMessage; 54 | } 55 | 56 | /** 57 | * 利用redis的watch监控的特性 58 | * @throws InterruptedException 59 | */ 60 | @RequestMapping(value = "/baseOnRedisWatch",method = RequestMethod.POST) 61 | public Message baseOnRedisWatch(@RequestBody Message requestMessage) throws InterruptedException { 62 | Map paramMap = new HashMap<>(); 63 | paramMap.put("userId",requestMessage.getBody().getUserId()); 64 | paramMap.put("productId",requestMessage.getBody().getProductId()); 65 | SecKillEnum secKillEnum = secKillService.handleByRedisWatch(paramMap); 66 | Message responseMessage = new Message<>(secKillEnum,null); 67 | return responseMessage; 68 | } 69 | 70 | /** 71 | * 利用AtomicInteger的CAS机制特性 72 | * @param requestMessage 73 | * @return 74 | */ 75 | @RequestMapping(value = "/baseOnAtomicInteger",method = RequestMethod.POST) 76 | public Message baseOnAtomicInteger(@RequestBody Message requestMessage){ 77 | Map paramMap = new HashMap<>(); 78 | paramMap.put("userId",requestMessage.getBody().getUserId()); 79 | paramMap.put("productId",requestMessage.getBody().getProductId()); 80 | SecKillEnum secKillEnum = secKillService.handleByAtomicInteger(paramMap); 81 | Message responseMessage = new Message<>(secKillEnum,null); 82 | return responseMessage; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/entity/DataObject.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.entity; 2 | 3 | import lombok.Data; 4 | 5 | public @Data class DataObject { 6 | private String id; 7 | private String name; 8 | private String userId; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/entity/Product.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.math.BigDecimal; 8 | import java.util.Date; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class Product { 14 | 15 | /** 16 | * id 17 | */ 18 | private Integer id; 19 | /** 20 | * 产品名称 21 | */ 22 | private String name; 23 | /** 24 | * 价格 25 | */ 26 | private BigDecimal price; 27 | /** 28 | * 库存 29 | */ 30 | private Integer stock; 31 | 32 | public Product(Integer id){ 33 | this.id = id; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/entity/Record.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * 购买明细记录 11 | */ 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class Record { 16 | /** 17 | * id 18 | */ 19 | private Integer id; 20 | /** 21 | * 用户 22 | */ 23 | private User user; 24 | /** 25 | * 产品 26 | */ 27 | private Product product; 28 | /** 29 | * 1秒杀成功,0秒杀失败,-1重复秒杀,-2系统异常 30 | */ 31 | private String state; 32 | /** 33 | * 状态的明文标识 34 | */ 35 | private String stateInfo; 36 | /** 37 | * 创建时间 38 | */ 39 | private Date createTime; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.math.BigDecimal; 8 | import java.util.Date; 9 | 10 | /** 11 | * @author lc 12 | */ 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class User { 17 | /** 18 | * 主键 19 | */ 20 | private Integer id; 21 | /** 22 | * 用户名 23 | */ 24 | private String username; 25 | /** 26 | * 手机号码 27 | */ 28 | private String phone; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/exception/SecKillException.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.exception; 2 | 3 | import com.lc.seckill.common.SecKillEnum; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class SecKillException extends RuntimeException { 8 | 9 | private SecKillEnum secKillEnum; 10 | 11 | public SecKillException(SecKillEnum secKillEnum){ 12 | this.secKillEnum = secKillEnum; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/mapper/SecKillMapper.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.mapper; 2 | 3 | import com.lc.seckill.entity.Product; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public interface SecKillMapper{ 8 | 9 | boolean updatePessLockInMySQL(Product product); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/mq/RabbitMQReceiver.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.mq; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.TypeReference; 5 | import com.lc.seckill.entity.Record; 6 | import com.lc.seckill.mapper.SecKillMapper; 7 | import org.springframework.amqp.rabbit.annotation.RabbitHandler; 8 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | @RabbitListener(queues = "seckillQueue") 14 | public class RabbitMQReceiver { 15 | 16 | @Autowired 17 | private SecKillMapper secKillMapper; 18 | 19 | @RabbitHandler 20 | public void process(String message) throws Exception { 21 | Record record = JSON.parseObject(message, new TypeReference(){}); 22 | //插入record 23 | secKillMapper.insertRecord(record); 24 | //更改物品库存 25 | secKillMapper.updateByAsynPattern(record.getProduct()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/mq/RabbitMQSender.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.mq; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 5 | import org.springframework.amqp.rabbit.support.CorrelationData; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.UUID; 10 | 11 | /** 12 | * 可靠确认模式 13 | */ 14 | @Slf4j 15 | @Component 16 | public class RabbitMQSender implements RabbitTemplate.ConfirmCallback{ 17 | 18 | @Autowired 19 | private RabbitTemplate rabbitTemplate; 20 | 21 | public void send(String message) { 22 | rabbitTemplate.setConfirmCallback(this); 23 | CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString()); 24 | rabbitTemplate.convertAndSend("seckillExchange", "seckillRoutingKey", message, correlationData); 25 | } 26 | 27 | @Override 28 | public void confirm(CorrelationData correlationData, boolean ack, String cause) { 29 | log.info("callbakck confirm: " + correlationData.getId()); 30 | if (ack){ 31 | log.info("插入record成功"); 32 | }else{ 33 | log.info("cause:"+cause); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/service/SecKillService.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.TypeReference; 5 | import com.lc.seckill.common.SecKillEnum; 6 | import com.lc.seckill.entity.Product; 7 | import com.lc.seckill.entity.Record; 8 | import com.lc.seckill.entity.User; 9 | import com.lc.seckill.mapper.SecKillMapper; 10 | import com.lc.seckill.exception.SecKillException; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | import redis.clients.jedis.Jedis; 14 | import redis.clients.jedis.JedisPool; 15 | 16 | import java.util.Date; 17 | import java.util.Map; 18 | 19 | @Service 20 | public class SecKillService { 21 | 22 | @Autowired 23 | private JedisPool jedisPool; 24 | 25 | @Autowired 26 | private SecKillMapper secKillMapper; 27 | 28 | public SecKillEnum handleByPessLockInMySQL(Map paramMap) { 29 | Jedis jedis = jedisPool.getResource(); 30 | Record record = null; 31 | String username = (String) paramMap.get("username"); 32 | String productId = (String) paramMap.get("productId"); 33 | Map map = jedis.hgetAll(username); 34 | User user = JSON.parseObject(JSON.toJSONString(map),new TypeReference(){}); 35 | Product product = new Product(Integer.valueOf(productId)); 36 | 37 | boolean isBuy = jedis.sismember("usernameSet", username); 38 | if (isBuy){ 39 | record = new Record(null,user,product,SecKillEnum.REPEAT.getCode(),SecKillEnum.REPEAT.getMsg(),new Date()); 40 | //todo 添加record到消息队列rabbitMq 41 | throw new SecKillException(SecKillEnum.REPEAT); 42 | } 43 | boolean secKillSuccess = secKillMapper.updatePessLockInMySQL(product); 44 | if (secKillSuccess){ 45 | record = new Record(null,user,product,SecKillEnum.SUCCESS.getCode(),SecKillEnum.SUCCESS.getMsg(),new Date()); 46 | //todo 添加record到消息队列rabbitMq 47 | return SecKillEnum.SUCCESS; 48 | }else { 49 | record = new Record(null,user,product,SecKillEnum.FAIL.getCode(),SecKillEnum.FAIL.getMsg(),new Date()); 50 | //todo 添加record到消息队列rabbitMq 51 | throw new SecKillException(SecKillEnum.FAIL); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/utils/SecKillUtils.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.utils; 2 | 3 | import com.lc.seckill.constant.RedisCacheConst; 4 | 5 | public class SecKillUtils { 6 | public static String getRedisHasBySetKey(String productName){ 7 | String hasBySet = ""; 8 | if (productName!=null && !productName.isEmpty()){ 9 | switch (productName){ 10 | case "iphone": 11 | hasBySet = RedisCacheConst.IPHONE_HAS_BUY_SET; 12 | break; 13 | case "huawei": 14 | hasBySet = RedisCacheConst.HUAWEI_HAS_BUY_SET; 15 | break; 16 | case "samsung": 17 | hasBySet = RedisCacheConst.SAMSUNG_HAS_BUY_SET; 18 | break; 19 | case "xiaomi": 20 | hasBySet = RedisCacheConst.XIAOMI_HAS_BUY_SET; 21 | break; 22 | } 23 | } 24 | return hasBySet; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/web/req/SecKillRequest.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.web.req; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class SecKillRequest { 11 | 12 | private String username; 13 | 14 | private Integer productId; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/lc/seckill/web/vo/SecKillResponse.java: -------------------------------------------------------------------------------- 1 | package com.lc.seckill.web.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class SecKillResponse { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #上下文配置 2 | server.port=8081 3 | server.context-path=/SecKillDesign 4 | 5 | # 数据库访问配置 6 | # 主数据源,默认的 7 | spring.datasource.type=com.alibaba.druid.pool.DruidDataSource 8 | spring.datasource.url=jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf8&useSSL=false 9 | spring.datasource.username=root 10 | spring.datasource.password=lc19971225 11 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 12 | 13 | # 下面为连接池的补充设置,应用到上面所有数据源中 14 | # 初始化大小,最小,最大 15 | spring.datasource.initialSize=5 16 | #最小空闲数 17 | spring.datasource.minIdle=5 18 | #最大连接数 19 | spring.datasource.maxActive=40000 20 | # 配置获取连接等待超时的时间 21 | spring.datasource.maxWait=60000 22 | # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 23 | spring.datasource.timeBetweenEvictionRunsMillis=2000 24 | # 配置一个连接在池中最小生存的时间,单位是毫秒 25 | spring.datasource.minEvictableIdleTimeMillis=300000 26 | spring.datasource.validationQuery=SELECT 1 FROM DUAL 27 | spring.datasource.testWhileIdle=true 28 | spring.datasource.testOnBorrow=true 29 | spring.datasource.testOnReturn=true 30 | # 打开PSCache,并且指定每个连接上PSCache的大小 31 | spring.datasource.poolPreparedStatements=true 32 | spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 33 | # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 34 | spring.datasource.filters=stat,wall,log4j 35 | # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 36 | spring.datasource.connectionProperties=druid.stat.mergeSql=true; 37 | # 合并多个DruidDataSource的监控数据 38 | #spring.datasource.useGlobalDataSourceStat=true 39 | 40 | # REDIS (RedisProperties) 41 | # Redis数据库索引(默认为0) 42 | spring.redis.database=0 43 | # Redis服务器地址 44 | spring.redis.host=127.0.0.1 45 | # Redis服务器连接端口 46 | spring.redis.port=6379 47 | # Redis服务器连接密码(默认为空) 48 | #spring.redis.password=redis 49 | # 连接池最大连接数(使用负值表示没有限制) 50 | spring.redis.pool.max-active=3000 51 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 52 | spring.redis.pool.max-wait=-1 53 | # 连接池中的最大空闲连接 54 | spring.redis.pool.max-idle=8 55 | # 连接池中的最小空闲连接 56 | spring.redis.pool.min-idle=0 57 | # 连接超时时间(毫秒) 58 | spring.redis.timeout=0 59 | 60 | #rabbitmq上下文配置 61 | spring.rabbitmq.host=127.0.0.1 62 | spring.rabbitmq.port=5672 63 | spring.rabbitmq.username=guest 64 | spring.rabbitmq.password=guest 65 | spring.rabbitmq.publisher-confirms=true 66 | spring.rabbitmq.virtual-host=/ 67 | 68 | rabbitmq.config.exchangeName=seckillExchange 69 | rabbitmq.config.queueName=seckillQueue 70 | rabbitmq.config.routingKey=seckillRoutingKey 71 | 72 | #为entity包下的类起别名 73 | mybatis.type-aliases-package=com.lc.seckill.entity 74 | #mapper扫描位置 75 | mybatis.mapper-locations = classpath:dao/*.xml 76 | -------------------------------------------------------------------------------- /src/main/resources/dao/SecKillMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | insert into tbl_concurrent(name, amount) values(#{name}, #{amount}) 10 | 11 | 12 | 13 | 14 | update product set stock=stock-1 15 | where id=#{id} and stock>0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | update tbl_concurrent set amount=#{amount},version=version+1 27 | where id=#{id} AND version=#{version} 28 | 29 | 30 | 31 | 32 | update tbl_concurrent set amount=#{amount},version=#{version} 33 | where id=#{id} AND #{version}>version 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/test/java/JUnitTest.java: -------------------------------------------------------------------------------- 1 | import com.alibaba.druid.sql.visitor.functions.Char; 2 | import org.junit.Test; 3 | 4 | //@RunWith(SpringRunner.class) 5 | //@SpringBootTest() 6 | public class JUnitTest { 7 | 8 | @Test 9 | public void test2() throws InterruptedException { 10 | String message = "HELLO world!"; 11 | byte[] b = message.getBytes(); 12 | for (int i = 0; i