SpringBoot实现Java高并发秒杀系统之Service层开发(二)

SpringBoot实现Java高并发秒杀系统之Service层开发 Service接口的设计 之前我们写好了DAO层的接口,这里我们要开始着手编写业务层接口,然后编写业务层接口的实现类并编写业务层的核心逻辑。 设计业务层接口,应该站在使用者角度上设计,如我们应该做到: 1.定义业务方法的颗粒度要细。 2.方法的参数要明确简练,不建议使用类似Map这种类型,让使用者可以封装进Map中一堆参数而传递进来,尽量精确到哪些参数。 3.方法的return返回值... 阅读详情

继上一篇文章:SpringBoot实现Java高并发秒杀系统之DAO层开发 我们创建了SpringBoot项目并熟悉了秒杀系统的表设计,下面我们将讲解一下秒杀系统的核心部分:Service业务层的开发。

Service层又称为业务层,在Spring阶段主要是由@Service注解标记的一层,包含Service业务接口的开发和业务接口实现类的开发,这里我们将讲解如何优雅的设计业务层接口以及针对秒杀系统业务层的优化技术等和针对高并发的解决方案。

本项目的源码请参看:springboot-seckill 如果觉得不错可以star一下哦(#.#)

本项目一共分为四个模块来讲解,具体的开发教程请看我的博客文章:

Service接口的设计

之前我们写好了DAO层的接口,这里我们要开始着手编写业务层接口,然后编写业务层接口的实现类并编写业务层的核心逻辑。

设计业务层接口,应该站在使用者角度上设计,如我们应该做到:

  • 1.定义业务方法的颗粒度要细。

  • 2.方法的参数要明确简练,不建议使用类似Map这种类型,让使用者可以封装进Map中一堆参数而传递进来,尽量精确到哪些参数。

  • 3.方法的return返回值,除了应该明确返回值类型,还应该指明方法执行可能产生的异常(RuntimeException),并应该手动封装一些通用的异常处理机制。

类比DAO层接口的定义,我这里先给出完整的SeckillService.java的定义(注意:在DAO层(Mapper)中我们定义了两个接口SeckillMapper和SeckillOrderMapper,但是Service层接口为1个):

public interface SeckillService {

    /**
     * 获取所有的秒杀商品列表
     *
     * @return
     */
    List<Seckill> findAll();

    /**
     * 获取某一条商品秒杀信息
     *
     * @param seckillId
     * @return
     */
    Seckill findById(long seckillId);

    /**
     * 秒杀开始时输出暴露秒杀的地址
     * 否者输出系统时间和秒杀时间
     *
     * @param seckillId
     */
    Exposer exportSeckillUrl(long seckillId);

    /**
     * 执行秒杀的操作
     *
     * @param seckillId
     * @param userPhone
     * @param money
     * @param md5
     */
    SeckillExecution executeSeckill(long seckillId, BigDecimal money, long userPhone, String md5)
            throws SeckillException, RepeatKillException, SeckillCloseException;
}

这里我将依次讲解一下为什么接口会这样设计?接口方法的返回值是怎样定义的?

findById和findAll方法

这两个方法就简单很多:

  • findById(): 顾名思义根据ID主键查询。按照接口的设计我们需要指定参数是seckillId(秒杀商品的ID值。注意:这里定义为long类型,不要定义为包装类类型,因为包装类类型不能直接进行大小比较,必须转换为基本类型才能进行值大小比较);返回值自然是查询到的商品表数据级Seckill实体类了。

  • findAll(): 顾名思义是查询数据库中所有的秒杀商品表的数据,因为记录数不止一条,所以一般就用List集合接收,并制定泛型是List<Seckill>,表示从数据库中查询到的列表数据都是Seckill实体类对应的数据,并以Seckill实体类的结构将列表数据封装到List集合中。

exportSeckillUrl方法

exportSeckillUrl()方法可有的讲了,他是暴露接口用到的方法,目的就是获取秒杀商品抢购的地址。

1.为什么要单独创建一个方法来获取秒杀地址?

在之前我们做的后端项目中,跳转到某个详情页一般都是:根据ID查询该详情数据,然后将页面跳转到详情页并将数据直接渲染到页面上。但是秒杀系统不同,它也不能就这样简单的定义,要知道秒杀技术的难点就是如何应对高并发?同一件商品,比如瞬间有十万的用户访问,而还存在各种黄牛,有各种工具去抢购这个商品,那么此时肯定不止10万的访问量的,并且开发者要尽量的保证每个用户抢购的公平性,也就是不能让一个用户抢购一堆数量的此商品。

这就是我们常说的接口防刷问题。因此单独定义一个获取秒杀接口的方法是有必要的。

2.如何做到接口防刷?

接口方法:Exposer exportSeckillUrl(long seckillId);从参数列表中很易明白:就是根据该商品的ID获取到这个商品的秒杀url地址;但是返回值类型Exposer是什么呢?

思考一下如何做到接口防刷?

  1. 首先要保证该商品处于秒杀状态。也就是1.秒杀开始时间要<当前时间;2.秒杀截止时间要>当前时间。

  2. 要保证一个用户只能抢购到一件该商品,应做到商品秒杀接口对应同一用户只能有唯一的一个URL秒杀地址,不同用户间秒杀地址应是不同的,且配合订单表seckill_order中联合主键的配置实现。

针对上面的两条分析,我们给出Exposer的设计(要注意此类定义在/dto/路径下表明此类是我们手动封装的结果属性,它类似JavaBean但又不属于,仅用来封装秒杀状态的结果,目的是提高代码的重用率):

此例源码请看:GitHub

public class Exposer {

    //是否开启秒杀
    private boolean exposed;

    //加密措施,避免用户通过抓包拿到秒杀地址
    private String md5;

    //ID
    private long seckillId;

    //系统当前时间(毫秒)
    private long now;

    //秒杀开启时间
    private long start;

    //秒杀结束时间
    private long end;

    public Exposer(boolean exposed, String md5, long seckillId) {
        this.exposed = exposed;
        this.md5 = md5;
        this.seckillId = seckillId;
    }

    public Exposer(boolean exposed, Long seckillId, long now, long start, long end) {
        this.exposed = exposed;
        this.seckillId = seckillId;
        this.now = now;
        this.start = start;
        this.end = end;
    }

    public Exposer(boolean exposed, long seckillId) {
        this.exposed = exposed;
        this.seckillId = seckillId;
    }
}

如上我们封装的结果类可以满足我们的需求:1.首先指明商品当前秒杀状态:秒杀未开始、秒杀进行中、秒杀已结束;2.如果秒杀未开始返回false和相关时间用于前端展示秒杀倒计时;3。如果秒杀已经结束就返回false和当前商品的ID;3.如果秒杀正在进行中就返回该商品的秒杀地址(md5混合值,避免用户抓包拿到秒杀地址)。

executeSeckill方法

这里我们再回顾一下秒杀系统的业务分析:

可以看到,秒杀的业务逻辑很清晰,用户抢购了商品业务层需要完成:1.减库存;2.储存用户秒杀订单明细。而因为储存订单明细应该是在用户成功秒杀到订单后才执行的操作,所以并不需要定义在Service接口中。那么我们就看一下用户针对库存的业务分析:

可以看到针对库存业务其实还是两个操作:1.减库存;2.记录购买明细。但是其中涉及到很多事物操作和性能优化问题我们放在后面讲。这里我们将这两个操作合并为一个接口方法:执行秒杀的操作。

所以再看一下我们对exexuteSeckill()方法的定义:

    SeckillExecution executeSeckill(long seckillId, BigDecimal money, long userPhone, String md5)
            throws SeckillException, RepeatKillException, SeckillCloseException;

1.分析参数列表

由于executeSeckill()方法涉及:1.减库存;2.记录购买明细。因为我们的项目不涉及复杂的数据,所以没有太多的明细参数(用money替代)。那么当前参数分别有何作用?

  • seckillId和userPhone用于在insert订单明细时进行防重复秒杀;只要有相同的seckillId和userPhone就一定主键冲突报错。

  • seckillId和md5用于组成秒杀接口地址的一部分,当用户点击抢购时获取到之前暴露的秒杀地址中的md5值和当前传入的md5值进行比较,如果匹配再进行下一步操作。

2.分析返回值类型

和在设计exportSeckillUrl接口方法时一样,针对秒杀操作也应该包含很多返回数据,比如:秒杀结束、秒杀成功、秒杀系统异常…信息,我们也将这些信息用类封装在dto文件夹中。于是我们的返回值SeckillExecution类定义如下:

public class SeckillExecution {

    private Long seckillId;

    //秒杀执行结果状态
    private int state;

    //状态表示
    private String stateInfo;

    //秒杀成功的订单对象
    private SeckillOrder seckillOrder;

    public SeckillExecution(Long seckillId, int state, String stateInfo, SeckillOrder seckillOrder) {
        this.seckillId = seckillId;
        this.state = state;
        this.stateInfo = stateInfo;
        this.seckillOrder = seckillOrder;
    }

    public SeckillExecution(Long seckillId, int state, String stateInfo) {
        this.seckillId = seckillId;
        this.state = state;
        this.stateInfo = stateInfo;
    }
}

state用于-1,0,1这种状态的表示,这些数字分别被赋予不同的含义,后面讲到。stateInfo表示state状态数字的中文解释,比如:秒杀成功、秒杀结束、秒杀系统异常等信息。

3.分析异常

减库存操作和插入购买明细操作都会产生很多未知异常(RuntimeException),比如秒杀结束、重复秒杀等。除了要返回这些异常信息,还有一个非常重要的操作就是捕获这些RuntimeException,从而避免系统直接报错。

针对秒杀关闭的异常,我们定义SeckillCloseException.java:

public class SeckillCloseException extends SeckillException {

    public SeckillCloseException(String message) {
        super(message);
    }

    public SeckillCloseException(String message, Throwable cause) {
        super(message, cause);
    }
}

针对重复秒杀的异常,我们定义RepeatKillException.java:

public class RepeatKillException extends SeckillException {

    public RepeatKillException(String message) {
        super(message);
    }

    public RepeatKillException(String message, Throwable cause) {
        super(message, cause);
    }
}

同时,系统还可能出现其他位置异常,所以我们还需要定义一个异常继承所有异常的父类Exception:

public class SeckillException extends RuntimeException {

    public SeckillException(String message) {
        super(message);
    }

    public SeckillException(String message, Throwable cause) {
        super(message, cause);
    }
}

ServiceImpl实现类的设计

我们在src/cn/tycoding/service/impl下创建Service接口的实现类: SeckillServiceImpl.java

在开始讲解之前我们先理解几个概念:

1.为什么我们的系统需要事务?

举个栗子:比如a在购买商品A的同时,售卖该商品的商家突然调低了A商品的价格,但此瞬时价格调整还没有更新到数据库用户购买的订单就已经提交了,那么用户不就多掏了钱吗?又比如a购买的商品后库存数量减少的sql还没有更新到数据库,此时瞬间b用户看到还有商品就点击购买了,而此时商品的库存数量其实已经为0了,这样就造成了超卖。

针对上面两个栗子,我们必须要给出解决方案,不然就太坑了。

2.什么是事务?

在软件开发领域,**全有或全无的操作称为事务(transaction)。**事务有四个特性,即ACID:

  • 原子性:原子性确保事务中所有操作全部发生或全部不发生。
  • 一致性:一旦事务完成(不管成功还是失败),系统必须却把它所建模的业务处于一致的状态。
  • 隔离性:事务允许多个用户对相同的数据进行操作,每个用户的操作不会与其他用户纠缠在一起。
  • 持久性:一旦事务完成,事务的结果应该持久化,这样就能从任何的系统崩溃中恢复过来。

事务常见的问题:

  • 更新丢失:当多个事务选择同一行操作,并且都是基于最初的选定的值,由于每个事务都不知道其他事务的存在,就会发生更新覆盖的问题。
  • 脏读:事务A读取了事务B已经修改但为提交的数据。若事务B回滚数据,事务A的数据存在不一致的问题。
  • 不可重复读:书屋A第一次读取最初数据,第二次读取事务B已经提交的修改或删除的数据。导致两次数据读取不一致。不符合事务的隔离性。
  • 幻读:事务A根据相同条件第二次查询到的事务B提交的新增数据,两次数据结果不一致,不符合事务的隔离性。

3.Spring对事务的控制

Spring框架针对事务提供了很多事务管理解决方案。我们这里只说常用的:声明式事务。声明式事务通过传播行为、隔离级别、只读提示、事务超时及回滚规则来进行定义。我们这里讲用Spring提供的注解式事务方法:@Transaction。

使用注解式事务的优点:开发团队达到一致的约定,明确标注事务方法的编程风格。

使用事务控制需要注意:

  1. 保证事务方法的执行时间尽可能短,不要穿插其他的网络操作PRC/HTTP请求(可以将这些请求剥离出来)。
  2. 不是所有的放阿飞都需要事务控制,如只有一条修改操作、只读操作等是不需要事务控制的。

注意

Spring默认只对运行期异常(RuntimeException)进行事务回滚操作,对于编译异常Spring是不进行回滚的,所以对于需要进行事务控制的方法尽量将可能抛出的异常都转换成运行期异常。这也是我们我什么要在Service接口中手动封装一些RuntimeException信息的一个重要原因。

exportSeckillUrl方法

@Service
public class SeckillServiceImpl implements SeckillService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    //设置盐值字符串,随便定义,用于混淆MD5值
    private final String salt = "sjajaspu-i-2jrfm;sd";
    @Autowired
    private SeckillMapper seckillMapper;

    @Autowired
    private SeckillOrderMapper seckillOrderMapper;

    @Override
    public Exposer exportSeckillUrl(long seckillId) {
        Seckill seckill = seckillMapper.findById(seckillId);
        if (seckill == null) {
            //说明没有查询到
            return new Exposer(false, seckillId);
        }
        Date startTime = seckill.getStartTime();
        Date endTime = seckill.getEndTime();
        //获取系统时间
        Date nowTime = new Date();
        if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) {
            return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());
        }
        //转换特定字符串的过程,不可逆的算法
        String md5 = getMD5(seckillId);
        return new Exposer(true, md5, seckillId);
    }

    //生成MD5值
    private String getMD5(Long seckillId) {
        String base = seckillId + "/" + salt;
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }

exportSeckillUrl()还是比较清晰的,主要逻辑:根据传进来的seckillId查询seckill表中对应数据,如果没有查询到就直接返回Exposer(false,seckillId)标识没有查询到该商品的秒杀接口信息,可能是用户非法输入的数据;如果查询到了,就获取秒杀开始时间和秒杀结束时间以及new一个当前系统时间进行判断当前秒杀商品是否正在进行秒杀活动,还没有开始或已经结束都直接返回Exposer;如果上面两个条件都符合了就证明该商品存在且正在秒杀活动中,那么我们需要暴露秒杀接口地址。

因为我们要做到接口防刷的功能,所以需要生成一串md5值作为秒杀接口中一部分。而Spring提供了一个工具类DigestUtils用于生成MD5值,且又由于要做到更安全所以我们采用md5+盐的加密方式生成一传md5加密数据作为秒杀URL地址的一部分发送给Controller。

executeSeckill方法

    @Override
    @Transactional
    public SeckillExecution executeSeckill(long seckillId, BigDecimal money, long userPhone, String md5)
            throws SeckillException, RepeatKillException, SeckillCloseException {
        if (md5 == null || !md5.equals(getMD5(seckillId))) {
            throw new SeckillException("seckill data rewrite");
        }
        //执行秒杀逻辑:1.减库存;2.储存秒杀订单
        Date nowTime = new Date();

        try {
            //记录秒杀订单信息
            int insertCount = seckillOrderMapper.insertOrder(seckillId, money, userPhone);
            //唯一性:seckillId,userPhone,保证一个用户只能秒杀一件商品
            if (insertCount <= 0) {
                //重复秒杀
                throw new RepeatKillException("seckill repeated");
            } else {
                //减库存
                int updateCount = seckillMapper.reduceStock(seckillId, nowTime);
                if (updateCount <= 0) {
                    //没有更新记录,秒杀结束
                    throw new SeckillCloseException("seckill is closed");
                } else {
                    //秒杀成功
                    SeckillOrder seckillOrder = seckillOrderMapper.findById(seckillId);
                    return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, seckillOrder);
                }
            }
        } catch (SeckillCloseException e) {
            throw e;
        } catch (RepeatKillException e) {
            throw e;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            //所有编译期异常,转换为运行期异常
            throw new SeckillException("seckill inner error:" + e.getMessage());
        }
    }

executeSeckill方法相对复杂一些,主要涉及两个业务操作:1.减库存(调用reduceStock());2.记录订单明细(调用insertOrder())。我们以一张图来描述一下主要逻辑:

由此我抛出以下问答:

1.insertCount和updateCount哪来?

在之前我们写项目中可能对于insert和update的操作直接设置返回值类型为void,虽然Mybatis的<insert>和<update>语句都没有resultType属性,但是并不带表其没有返回值,默认的返回值是0或1…表示该条SQL影响的行数,如果为0就表示该SQL没有影响数据库,但是为了避免系统遇到错误的SQL返回错误信息而不是直接报错,我们可以在书写SQL时:insert ignore into xxx即用ignore参数,当Mybatis执行该SQL发生异常时直接返回0表示更新失败而不是系统报错。

2.为什么先记录秒杀订单信息操作再执行减库存操作?

这里涉及了一个简单的Java并发优化操作,详细内容优化方式请看:SpringBoot实现Java高并发秒杀系统之系统优化

3.上例中用到的SeckillStatEnum是什么?

之前我们讲exportSeckillUrl时在/dto/中创建了类Exposer;在讲executeSeckill的时候创建了SeckillExecution类,他们都是用来封装返回的结果信息的,不是说他们是必须的,而是用这种方式会更规范且代码看起来更加整洁,而且我们的代码的重用率会更高。

于是,当用户秒杀成功后其实需要返回一句话秒杀成功即可,但是我们单独提取到了一个枚举类中:

public enum SeckillStatEnum {
    SUCCESS(1, "秒杀成功"),
    END(0, "秒杀结束"),
    REPEAT_KILL(-1,"重复秒杀"),
    INNER_ERROR(-2, "系统异常"),
    DATA_REWRITE(-3, "数据串改");

    private int state;
    private String stateInfo;

    SeckillStatEnum(int state, String stateInfo) {
        this.state = state;
        this.stateInfo = stateInfo;
    }

    public int getState() {
        return state;
    }

    public String getStateInfo() {
        return stateInfo;
    }

    public static SeckillStatEnum stateOf(int index){
        for (SeckillStatEnum state : values()){
            if (state.getState() == index){
                return state;
            }
        }
        return null;
    }
}

具体枚举的语法不再讲,简单来说就是将这些通用的返回结果提取出来,且枚举这种类型更适合当前方法的返回值特点。除了创建这个枚举对象,还需要修改SeckillExecution的源代码,这里不再贴出。

4.为什么要cache这么多异常?

前面我们已经提到了Spring默认只对运行期异常进行事务回滚操作,对于编译期异常时不进行回滚的,所以这也是我们为什么一直强调要手动创建异常类。

这里就是要将所有编译期异常转换为运行期异常,因为我们定义的所有异常最终都是继承RuntimeException。

此例具体代码请看:GitHub


交流

如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!


联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

利用Java-SpringBoot+Redis+Jmeter实现高并发限时秒杀 前言 ​ “商品秒杀”功能模块是建立在“商品详情”功能模块的基础之上,对 于这一功能模块而言,其主要的核心流程在于:前端发起抢购请 求,该请求将携带着一些请求数据:待秒杀Id跟当前用户Id等数 据;后端接口在接收到请求之后,将执行一系列的判断与秒杀处 理逻辑,最终将处理结果返回给到前端。 本文将使用SpringBoot+Mysql+Redis+RabbitMQ+tkmybatis实现限... 阅读详情

相关推荐

基于Springboot秒杀系统的实现

秒杀系统业务分析 在秒杀系统当中有两个核心的表:秒杀商品(kill_product)与秒杀明细(kill_item),具体的逻辑是一个用户秒杀商品的库存减一,秒杀明细的记录增加一条。这两步作是处于同一事务之中。 当秒杀日期尚未达到会提示用户秒杀尚未开始; 当用户多次秒杀同一商品会提示用户重复秒杀; 当秒杀日期过期或者秒杀商品的库存为零会提示用户秒杀结束。 Entity设计 import l...

Cherry_Amy的博客 632

微服务Springboot实战大揭秘/高并发/高可用/高负载/互联网技术

Java架构师系列课程是针对有志向架构师发展的广大学员而设置,不管你是工作一到三年,还是三到五年,本课程都会对你产生巨大的提升,让你的薪资大幅翻倍,走向BAT架构师的伟大梦想。

springboot-:记录实现高并发的解决方案

springboot-demo 记录实现高并发的解决方案 day01 1.springboot整合druid多数据源和log4j2 (1)实现数据源,主数据库(写),从数据库(读),读写分离的方案之一 (2)mysql主从复制,保持数据一致性 (3)使用druid的监控功能 (4)log4j2日志介绍和配置说明 2.redis哨兵模式的实现,实现高可用 主库写,从库读 day02 (1)两次md5,第一次客户端明文+固定盐值 第二次客户端第一次前端传过来的+随机盐值 项目里面模拟的是登录查询的过程 整个过程分析如下:首先,第一次用户注册的时候输入框输入明文,然后前端再加固定盐值, 第二次前端传过来第一次加密的+后端随机生成的盐值,该盐值要存入数据库 查询的时候,第一次客户端明文+固定盐值,传入后端,第二次后端去查询数据库注册时候后端随机生成的盐值, 把第一次前端传过来的加密的+数据库查询的

Spring Boot接口实现百万并发

随着互联网的发展,越来越多的应用需要支持高并发。在这种情况下,如何实现高并发成为了一个重要的问题。Spring Boot是一个非常流行的Java框架,它提供了很多方便的功能来支持高并发。本文将介绍如何使用Spring Boot来实现百万并发。Spring Boot是一个基于Spring框架的快速开发框架,它可以帮助开发人员快速构建Spring应用程序。Spring Boot提供了很多默认的配置,可以减少开发人员的工作量。同时,Spring Boot也提供了很多扩展功能,可以帮助开发人员更好地处理业务逻辑。

果酱 の 博客 1万+

Springboot + vue 实现导出word

Springboot + vue 实现导出word 后端代码 使用word先创建好已知的模板 使用word工具另存为.xml格式的文件 使用freemarker ftl模板 将修改后的xml文件复制到开发工具中,将文件后缀修改为ftl文件,并且将里面的动态值使用${}包裹例如:${zyjn} word工具类 /*** * * Project Name:maventest * <p>生成word工具类<br> * * @ClassName: WordUtil *

qq_45542373的博客 2895

VS2019配置Qt5.14.2开发环境全流程(含MSVC2017 64-bit选择指南)

本文详细介绍了在VS2019中配置Qt5.14.2开发环境的完整流程,特别针对MSVC2017 64-bit编译器的选择提供了专业建议。从环境准备、Qt安装配置到VS2019集成设置,逐步指导开发者完成开发环境的搭建,并提供了常见问题的解决方案和高级优化技巧,帮助开发者高效进行Qt开发。

weixin_29243279的博客 511

@Transactional 事务不生效原因

1.方法不是public的 2.事务被try() catch{} 吃掉了 3.数据库引擎不支持事务 4.没有被 Spring 管理 类上面么有@service 或者@compent 5. 自身调用的问题 6. 数据源没有配置事务管理器 @Transactional 实现原理: 1) 事务开始时,通过AOP机制,生成一个代理connection对象, 并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。 在接下来的整个事

yanziyu0721730的博客 796

VASP计算层错能(SFE)全攻略2

本文介绍了使用VASP计算纯镍(Ni)层错能(SFE)的具体步骤。首先构建Ni的FCC单胞并扩展为超胞,通过修改原子位置引入堆垛层错;随后分别计算完美晶体和含层错结构的能量;最后通过能量差与层错面积之比求得SFE值。计算需注意k点网格、截断能和超胞尺寸的收敛性测试,并推荐使用PAW赝势和ASE等工具辅助结构构建。

weixin_50519490的博客 1986

【三十】springboot项目上高并发解决示例

springboot项目上高并发解决示例

weixin_56995925的博客 5896

从Halcon到数学原理:3D点云平面拟合与距离计算的5个关键陷阱

本文深入剖析了3D点云平面拟合与点到平面距离计算中的五个关键陷阱,包括无限与有限平面的距离歧义、法向量方向随机性、拟合算法选择、点云密度不均以及实际部署中的性能衰减。文章穿透Halcon等工具的操作层面,回归数学原理与工程实践,为工业视觉和三维测量领域的高精度应用提供关键指导。

fire9的博客 449

BUUCTF reverse题解汇总

BUUCTF平台刷题过程中做的一些逆向题解的总结归类

于高山之巅,方见大河奔涌;于群峰之上,更觉长风浩荡。 1万+

SpringBoot接口高并发处理策略

在分布式系统中,多个节点可能同时访问共享资源,此时需要使用分布式锁来确保操作的原子性。命令可以用于实现简单的计数器,通过它可以在一定时间内限制接口的访问次数。:在高并发场景下,可以使用读写分离架构,将读操作和写操作分别在不同的数据库实例上执行。:将频繁查询的数据存储在Redis中,减少对数据库的直接访问。:对于大数据量的查询,使用分页查询减少单次查询的数据量。:合理设计数据库查询,避免全表扫描,使用索引加速查询。:对于一些不经常变化的数据,可以使用本地缓存(如。:对于耗时的操作,可以使用异步编程模型(如。

m0_73366975的博客 2440

ESP32网络开发实例-开发环境搭建

NodeMCU-ESP-32S开发环境搭建 NodeMCU-32S 最强的ESP32 开发板非盗版或副厂的CH340。WiFi 蓝牙ESP-32 可用Arduino IDE ESP32-S 是一款通用型WiFi-BT-BLE MCU模组,功能强大,用途广泛,可以用于低功耗传感器网络和要求极高的任务,例如语音编码、音频流和MP3解码等。 此款模组的核心是ESP32芯片,具有可扩展、自适应的特点。两个CPU核可以被单独控制或上电。时钟频率的调节范围为80 MHz到240 MHz。用户可以切断CPU的电源,利用低

视觉与物联智能 3781

SpringBoot实现Java高并发秒杀系统

SpringBoot实现Java高并发秒杀系统pom依赖JavaBean实体类pojo配置表设计DAO层开发XML映射Service接口的设计前端交互流程设计 首先创建springboot项目; pom依赖 <parent> <groupId>org.springframework.boot</groupId> <a...

Ronin_He的博客 874

利用大模型和聚类算法找出 Excel 文件中重复或相似度高的数据,并使用 FastAPI 进行封装的详细方案

tokenizer将输入的文本列表texts转换为 PyTorch 张量,同时进行填充和截断操作,以确保所有输入具有相同的长度。对输入进行推理,得到模型的输出。对模型的最后一层隐藏状态求平均,得到每个文本的向量表示。.numpy()将 PyTorch 张量转换为 NumPy 数组。使用DBSCAN算法对文本向量进行聚类。eps是邻域半径,是形成核心点所需的最小样本数。fit方法对输入的向量进行聚类,并返回每个样本所属的簇标签。try:定义了一个 POST 请求的 API 路由,路径为。

AI的内部世界的博客 1069

利用插件扩展 Qt 应用程序

不仅 Qt 本身可以通过插件进行扩展,而且 Qt 应用程序也可以通过插件来扩展,这需要应用程序使用 `QPluginLoader` 检测和加载插件。

青春不老,奋斗不止! 1万+

(二)基于SpringBoot实战Java秒杀高并发系统Service开发(超详细)

根据上篇博客接下来我们继续讲,SpringBoot实现Java高并发秒杀系统之DAO层开发 我们创建了SpringBoot项目并熟悉了秒杀系统的表设计,下面我们将讲解一下秒杀系统的核心部分:Service业务层的开发。 Service层又称为业务层,在Spring阶段主要是由@Service注解标记的一层,包含Service业务接口的开发和业务接口实现类的开发,这里我们将讲解如何优雅的设计业务层接口以及针对秒杀系统业务层的优化技术等和针对高并发的解决方案。 Service接口的设计 之前我们写好了DAO层的

weixin_45593950的博客 849

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

SpringBoot实现Java高并发秒杀系统之DAO层开发(一)

秒杀系统在如今电商项目中是很常见的,最近在学习电商项目时讲到了秒杀系统的实现,于是打算使用SpringBoot框架学习一下秒杀系统(本项目基于慕课网的一套免费视频教程:Java高并发秒杀API,视频教程中讲解的很详细,非常感谢这位讲师)。也是因为最近学习了SpringBoot框架(GitHub教程:SpringBoot入门之CRUD ),觉得SpringBoot框架确实比传统SSM框架方便了很多,...

TyCoding的博客 4636

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Springboot+mybatis+redis实现java秒杀系统

Springboot+mybatis+redis实现java秒杀系统1.效果图标题 1.效果图 标题

sbw17608463386的博客 890

Springboot+redis实现商品秒杀

SpringBoot+redis实现秒杀系统 更加详细的原理分析 代码实现原理请点此此处 下面是一个Springboot 整合redis yml配置文件 启动项目时运行redis 本地服务 server: port: 8080 spring: datasource: name: springboot type: com.alibaba.druid.pool.DruidDa...

weixin_44422324的博客 3万+

SpringBoot实现Java秒杀系统

项目是有地址的,我会放到文章的最后面 直接service,我们会介绍两种秒杀模式 public interface GoodsService { /** * 通过lua脚本实现的秒杀 * @param skuCode 商品编码 * @param buyNum 购买数量 * @return 购买数量 */ Long flashS...

weixin_44172680的博客 279

基于springboot、Redis搭建秒杀系统(一)

秒杀系统 前言 最近把redis基本的东西学的差不多了,但是没有做过什么具体的项目来实践,于是计划做一个项目来巩固知识。 本项目是一个整合springboot、mybatis、redis来做的一个秒杀系统。 之后的笔记部分内容来自上述up的笔记。 1. 秒杀系统 1.1 秒杀场景 电商抢购限量商品 卖周董演唱会的门票 火车票抢座 12306 … 1.2 为什么要做个系统 如果你的项目流量非常小,完全不用担心有并发的购买请求,那么做这样一个系统意义不大。但如果你的系统要像12306那样,接受高并发访问和下

KJ_Incrediable 962
上一篇: SpringBoot实现Java高并发秒杀系统之Web层开发(三)
下一篇: SpringBoot实现Java高并发秒杀系统之DAO层开发(一)
TyCoding
博客等级 码龄9年 316粉丝 49原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值