概述
JAVA构建高并发商城秒杀系统——架构分析:
https://blog.csdn.net/lkp1603645756/article/details/81744558
未看理论知识的可以点击上方链接查看。
前面说了那么多理论,接下来自己写代码:
不清楚如何用IDEA创建Spring Boot项目的童鞋,可以点击该链接查看:
https://blog.csdn.net/lkp1603645756/article/details/81872249
首先,创建数据库,建立seckill_goods和seckill_order表
配置项目application.properties文件,设置数据库连接
spring.datasource.url = jdbc:mysql://localhost:3306/databaseset?useUnicode=true&characterEncoding=utf-8
spring.datasource.username = root
spring.datasource.password = 123456
spring.datasource.driverClassName = com.mysql.jdbc.Driver
#redis
spring.redis.hostName=127.0.0.1
spring.redis.password=
spring.redis.port=6379
spring.redis.jedis.pool.max-active=9
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=10000ms
创建一个Spring Boot项目
分别创建两张表的实体类
/**
* 作者:LKP
* 时间:2018/8/20
*/
public class Goods {
private int id;
private String name;
private int count;
private int sale;
private int version;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getSale() {
return sale;
}
public void setSale(int sale) {
this.sale = sale;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
}
/**
* 作者:LKP
* 时间:2018/8/20
*/
public class Order {
private int id;
private String custname;
private String createTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCustname() {
return custname;
}
public void setCustname(String custname) {
this.custname = custname;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
}
分别创建两张表的mapper映射文件,这里我们采取纯注解的方法配置,不太了解的童鞋自行百度一下。
商品mapper创建三个方法,分别是构建扣减库存的两个方法,一个是悲观锁,一个是乐观锁调用的,在构建一个查询商品的方法。
/**
* 作者:LKP
* 时间:2018/8/2
*/
public interface GoodsMapper {
/**
* 减掉商品库存——悲观锁
* @return
*/
@Update("UPDATE `databaseset`.`seckill_goods` SET `name` = 'iphone X', `count` = #{goods.count}, `sale` = #{goods.sale}, `version` = 0 WHERE `id` = 1 ;") //for update
int updateGoodsCount(@Param("goods")Goods goods);
/**
* 减掉商品库存——乐观锁
* @return
*/
@Update("UPDATE `databaseset`.`seckill_goods` SET `name` = 'iphone X', `count` = #{goods.count}, `sale` = #{goods.sale}, `version` = #{goods.version}+1 WHERE `id` = #{goods.id} and version = #{updateVersion};")
int updateGoodsCountOptimisticLock(@Param("goods")Goods goods, @Param("updateVersion")int version);
/**
* 查询商品
* @return
*/
@Select("select `id`, `name`, `count`, `sale`, `version` from seckill_goods where id = 1 for update;")
Goods getGoods();
}
订单mapper,创建一个生成订单的方法
/**
* 作者:LKP
* 时间:2018/8/2
*/
public interface OrderMapper {
/**
* 生成订单
* @param name
* @param createTime
* @return
*/
@Insert("INSERT INTO `databaseset`.`seckill_order`(`custname`, `create_time`) VALUES (#{name}, #{createTime});")
int insertOrder(@Param("name") String name, @Param("createTime") String createTime);
}
创建商品Service接口
/**
* 作者:LKP
* 时间:2018/8/2
*/
public interface GoodsService {
/**
* 减掉商品库存——悲观锁
* @return
*/
int updateGoodsCount(Goods goods);
/**
* 减掉商品库存——乐观锁
* @return
*/
int updateGoodsCountOptimisticLock(Goods goods,int version);
/**
* 查询商品
* @return
*/
Goods getGoods();
}
实现它
/**
* 作者:LKP
* 时间:2018/8/2
*/
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
private GoodsMapper userMapper;
@Override
public int updateGoodsCount(Goods goods) {
return userMapper.updateGoodsCount(goods);
}
@Override
public int updateGoodsCountOptimisticLock(Goods goods,int version) {
return userMapper.updateGoodsCountOptimisticLock(goods,version);
}
@Override
public Goods getGoods() {
return userMapper.getGoods();
}
}
创建订单Service接口
/**
* 作者:LKP
* 时间:2018/8/21
*/
public interface OrderService {
/**
* 生成订单
* @param name
* @param createTime
* @return
*/
int insertOrder(String name, String createTime);
/**
* 悲观锁
* @return
*/
void seckillPessimism() throws Exception;
/**
* 不重试乐观锁
* @return
*/
void seckillOptimistic();
/**
* 会重试的乐观锁
* @return
*/
int seckillWithOptimistic();
/**
* 无锁
*/
void seckill();
/**
* 使用redis原子操作保障原子性
*/
void seckillwithRedis();
}
实现它
/**
* 作者:LKP
* 时间:2018/8/21
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Override
public int insertOrder(String name, String createTime) {
return orderMapper.insertOrder(name,createTime);
}
@Autowired
private GoodsService goodsService;
@Resource
private SqlSessionFactory sqlSessionFactory;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 悲观锁
* @return
*/
@Override
public void seckillPessimism() throws Exception {
//悲观锁begin
SqlSession sqlSession = sqlSessionFactory.openSession(false);
sqlSession.getConnection().setAutoCommit(false);
//查询库存,如果库存大于0,则继续秒杀逻辑
Goods goods = goodsService.getGoods();
if (null != goods && goods.getCount() <= 0) {
System.out.println(Thread.currentThread().getName() + "悲观锁方式商品卖光了!!!当前时间:" + System.currentTimeMillis());
return;
}
//库存-1,销量+1
Goods goodsForUpdate = new Goods();
goodsForUpdate.setCount(goods.getCount()-1);
goodsForUpdate.setSale(goods.getSale()+1);
goodsForUpdate.setId(1);
int i = goodsService.updateGoodsCount(goodsForUpdate);
//当库存更新成功后创建订单
if(1>0){
//创建订单
String time = System.currentTimeMillis()+"";
String custname = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(custname,createTime);
}
sqlSession.getConnection().commit();
}
@Override
public void seckillOptimistic() {
//查询库存,如果库存大于0,则继续秒杀逻辑
Goods goods = goodsService.getGoods();
if (null != goods && goods.getCount() <= 0) {
System.out.println(Thread.currentThread().getName() + "乐观锁方式商品卖光了!!!当前时间:" + System.currentTimeMillis());
return;
}
int currentVersion = goods.getVersion();
Goods goodsForUpdate = new Goods();
goodsForUpdate.setVersion(currentVersion);
goodsForUpdate.setCount(goods.getCount()-1);
goodsForUpdate.setSale(goods.getSale()+1);
goodsForUpdate.setId(1);
int i = goodsService.updateGoodsCountOptimisticLock(goodsForUpdate,currentVersion);
//当库存更新成功后创建订单
if(1>0){
String time = System.currentTimeMillis()+"";
String custname = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(custname,createTime);
}
}
/**
* 会重试的乐观锁
* @return
*/
@Override
public int seckillWithOptimistic() {
//查询库存,如果库存大于0,则继续秒杀逻辑
Goods goods = goodsService.getGoods();
if (null != goods && goods.getCount() <= 0) {
System.out.println(Thread.currentThread().getName() + "乐观锁方式商品卖光了!!!当前时间:" + System.currentTimeMillis());
return -1;
}
int currentVersion = goods.getVersion();
Goods goodsForUpdate = new Goods();
goodsForUpdate.setVersion(currentVersion);
goodsForUpdate.setCount(goods.getCount()-1);
goodsForUpdate.setSale(goods.getSale()+1);
goodsForUpdate.setId(1);
int i = goodsService.updateGoodsCountOptimisticLock(goodsForUpdate,currentVersion);
//当库存更新成功后创建订单
if(1>0){
String time = System.currentTimeMillis()+"";
String custname = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(custname,createTime);
return 1;
}else{ //乐观锁如何重试呢?
return 0;
}
}
/**
* 无锁
*/
@Override
public void seckill() {
//查询库存,如果库存大于0,则继续秒杀逻辑
Goods goods = goodsService.getGoods();
if (null != goods && goods.getCount() <= 0) {
System.out.println(Thread.currentThread().getName() + "无锁方式商品卖光了!!!当前时间:" + System.currentTimeMillis());
return;
}
//库存-1,销量+1
Goods goodsForUpdate = new Goods();
goodsForUpdate.setCount(goods.getCount()-1);
goodsForUpdate.setSale(goods.getSale()+1);
goodsForUpdate.setId(1);
int i = goodsService.updateGoodsCount(goodsForUpdate);
//当库存更新成功后创建订单
if(1>0){
//创建订单
String time = System.currentTimeMillis()+"";
String name = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(name,createTime);
}
}
@Override
public void seckillwithRedis() {
String key = "seckill"; //定义一个key,key的值就是商品的数量
long count = stringRedisTemplate.opsForValue().increment(key,-1l);
if(count >=0 ){
//创建订单
String time = System.currentTimeMillis()+"";
String name = "zhangsan"+time.substring(8,time.length());
String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
insertOrder(name,createTime);
}else{
System.out.println("卖光了"+System.currentTimeMillis());
}
}
}
接下来创建我们的Controller
/**
* 作者:LKP
* 时间:2018/8/2
*/
@Controller
@EnableAutoConfiguration
public class DemoController {
@Autowired
private OrderService orderService;
/**
* 访问nginx
*/
@RequestMapping("/nginx")
@ResponseBody
public String nginx(){
RestTemplate restTemplate = new RestTemplate();
String conent = restTemplate.getForObject("http://127.0.0.1/",String.class);
if(conent.contains("Welcome to nginx!")){
return "success";
}
return null;
}
/**
* 无锁
* @return
*/
@RequestMapping(value = "/seckill")
@ResponseBody
public void seckill(){
orderService.seckill();
}
/**
* 悲观锁
* @return
*/
@RequestMapping(value = "/seckillPessimisticLock")
@ResponseBody
public void seckillPessimisticLock(){
try {
orderService.seckillPessimism();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 乐观锁
* @return
*/
@RequestMapping(value = "/seckillOptimisticLock")
@ResponseBody
public void OptimisticLock(){
orderService.seckillOptimistic();
}
/**
* 失败会重试乐观锁
* @return
*/
@RequestMapping(value = "/seckillOptimisticLockretry")
@ResponseBody
public void OptimisticLockRetry(){
while (true){
int i = orderService.seckillWithOptimistic();
//如果卖光了 或者卖出成功跳出循环,否者一直循环,直到卖出去位置
if(i==-1 || i>0){
break;
}
}
}
/**
* 使用redis原子操作保障原子性
*/
@RequestMapping(value = "/seckillRedis")
@ResponseBody
public void seckillRedis(){
orderService.seckillwithRedis();
}
}
到这里所有的功能都已经写好了,接下来我们就来测试一下我们的秒杀系统。
新建测试用例
/**
* 作者:LKP
* 时间:2018/8/21
*/
@RunWith(SpringRunner.class)
@Component
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests {
RestTemplate restTemplate = new RestTemplate();
/**
* @LocalServerPort 提供了 @Value("${local.server.port}") 的代替
*/
@LocalServerPort
private int port;
private URL base;
@Before
public void setUp() throws Exception {
String url = String.format("http://localhost:%d/", port);
System.out.println(String.format("port is : [%d]", port));
this.base = new URL(url);
//测试nginx的正常请求和限流请求
url_nginx = "http://127.0.0.1:"+port+"/nginx";
//测试数据库-无锁
url_nolock = "http://127.0.0.1:"+port+"/seckill";
//测试乐观锁
url_optimistic = "http://127.0.0.1:"+port+"/seckillOptimisticLock";
//测试带重试的乐观锁
url_optimisticWithRetry = "http://127.0.0.1:"+port+"/seckillOptimisticLockretry";
//测试悲观锁
url_pessimistic = "http://127.0.0.1:"+port+"/seckillPessimisticLock";
//使用redis原子操作保障原子性
url_redis = "http://127.0.0.1:"+port+"/seckillRedis";
}
//测试nginx的正常请求和限流请求
String url_nginx = "http://127.0.0.1:8080/nginx";
//测试数据库-无锁
String url_nolock = "http://127.0.0.1:8080/seckill";
//测试乐观锁
String url_optimistic = "http://127.0.0.1:8080/seckillOptimisticLock";
//测试带重试的乐观锁
String url_optimisticWithRetry = "http://127.0.0.1:8080/seckillOptimisticLockretry";
//测试悲观锁
String url_pessimistic = "http://127.0.0.1:8080/seckillPessimisticLock";
//使用redis原子操作保障原子性
String url_redis = "http://127.0.0.1:8080/seckillRedis";
//测试nginx 使用20个并发,测试购买商品使用200个并发
private static final int amount = 200;
//发令枪,目的是模拟真正的并发,等所有线程都准备好一起请求
private CountDownLatch countDownLatch = new CountDownLatch(amount);
@Test
public void contextLoads() throws InterruptedException {
System.out.println("开始卖:"+System.currentTimeMillis());
for (int i = 0; i < amount; i++) {
new Thread(new Request()).start();
countDownLatch.countDown();
}
Thread.currentThread().sleep(100000);
}
public class Request implements Runnable{
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println(restTemplate.getForObject(url_nginx,String.class));
restTemplate.getForObject(url_redis,String.class);
}
}
}
记得预先在本机上安装nginx,配置好并能正确访问。
首先我们来测试一下我们的nginx无限流,我们用20个并发来请求nginx
启动本机的nginx
启动我们的测试用例
输出结果如下:
打印了20success,证明20个请求都通过了。
接下来我们这是nginx的漏桶限流,来限制通过的流量。
找到你nginx的安装目录,进入config目录下面的nginx.conf文件
增加一行该配置,$binary_remote_addr,限流维度,表示对每一个ip进行限流,1r/s表示1秒一个
在这里引用它
记得重启nginx哦,不然修改不会生效
接下来我们在运行代码
童鞋们猜猜看能通过多少请求呢,也就是打印多少个success?
总共只有一个请求通过了拦截。
因为设置1秒钟一个请求的限流,当20请求同时过来的时候,只有一个请求能成功通过拦截,剩下的都被拦截掉了。
PS:限流运行的时候,会有报错,不过这是正常现象,因为剩下的请求被限流了,没有被处理。
前面我们所了,限流桶,刚刚我们只是设置了限流,但是没有用上桶,现在我们设置一个漏桶为5的容量,它会慢慢处理掉桶里的请求。这里童鞋们猜猜会正常多少并发请求呢?
修改完配置记得重启nginx
运行结果:
桶的容量只有5个,为什么处理了6个请求呢?
因为当第一个请求过来的时候,它直接被处理掉了,之后过来的请求,就被装在了漏桶里面,直到5个空间被装满,之后会就被慢慢处理掉,所以加上第一次处理的请求,和漏桶里面的请求,总共就处理了6个请求。
还可以在集群的tomcat去限流,总共接受500,超过的请求就掉请求,nginx还有很多其他的限流方式,感兴趣的小伙伴们可以去试试。
Java还可以引用 guawa 做令牌桶限流,这里不演示了,很简单,自己可以去百度查查
其他前端限流,nginx限流,java限流,分布式限流之后,到达数据库的流量已经很小了,就相当于100个并发抢100个商品,这里我们在用乐观锁和悲观锁进行控制既可以了。
首先我们演示一下无锁的情况下,200个并发抢购100个商品,看看会出现什么情况。
设置好两个表的值,然后注释掉上一条代码,设置url_nolock
修改并发为200个,然后点击运行
程序运行完
我们去看一下数据库的数据
订单表有200个订单
商品表,还剩24个商品,是不是很神奇,凭空卖出了那么多订单。
这样肯定是不行的,怎么预防这种情况呢?这时候乐观锁和悲观锁就登场了。
把商品表数据恢复,清空订单表的数据
接下来测试悲观锁
运行程序,查看运行结果:
我们再去看看数据库的数据
商品表:
订单表:
我们通过sql统计来更直观来查看
获得执行时间
下面我们用不重试的乐观锁来测试
启动程序,查看运行结果:
查看数据库的数据:
计算出不重试乐观锁的时间:
这里可能出现200个并发秒杀商品,抢购不完的,可以加到并发。
下面我们在测试一下重试乐观锁
计算出它的实际
虽然三种情况测试出来的时间与前面讲的不符
但是,高并发情况下两个锁的结论:悲观锁速度更快!!!有时乐观锁偶然会比悲观锁低,但是在大数据的情况下,悲观锁会比乐观锁低!
有兴趣的童鞋可以自己去操作一边,如果是不一样的时间,可以在下放评论。
接下来通过redis的原子性来实现,因为redis是单线程的
修改访问url
在redis里面预先存入一个key为seckill,值为100的数据
然后启动程序:
200个线程,只有100个得到了处理。
到这里就结束了,有兴趣的童鞋可以自己动手试试。
秒杀系统代码托管在GitHub:https://github.com/gdjkmax/SpeedKillSystem 有需要的童鞋可自行下载。
最后
以上就是欢喜花卷为你收集整理的JAVA构建高并发商城秒杀系统——操作实践的全部内容,希望文章能够帮你解决JAVA构建高并发商城秒杀系统——操作实践所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复