【Redis】4、全局唯一 ID生成、单机(非分布式)情况下的秒杀和一人一单

news2024/10/7 14:24:09

目录

  • 一、利用 Redis 实现全局唯一 ID 生成
    • (1) 为啥要用全局唯一 ID 生成
    • (2) 全局唯一 ID 生成器
    • (3) 全局 ID 的结构
    • (4) 代码实现
      • ① RedisIdWorker
      • ② Test
    • (5) 全局唯一 ID 其他生成策略
  • 二、添加优惠券
    • (1) 数据库
    • (2) 添加优惠券接口
  • 三、优惠券秒杀下单功能
    • (1) 超卖问题
    • (2) 乐观锁(版本号和 CAS)
    • (3) 乐观锁解决超卖问题
  • 四、一人一单功能【☆】
  • 五、并发情况下的线程安全问题

一、利用 Redis 实现全局唯一 ID 生成

(1) 为啥要用全局唯一 ID 生成

CREATE TABLE `tb_voucher_order` (
  `id` bigint(20) NOT NULL COMMENT '主键',
  `user_id` bigint(20) unsigned NOT NULL COMMENT '下单的用户id',
  `voucher_id` bigint(20) unsigned NOT NULL COMMENT '购买的代金券id',
  `pay_type` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '支付方式 1:余额支付;2:支付宝;3:微信',
  `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '订单状态,1:未支付;2:已支付;3:已核销;4:已取消;5:退款中;6:已退款',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
  `pay_time` timestamp NULL DEFAULT NULL COMMENT '支付时间',
  `use_time` timestamp NULL DEFAULT NULL COMMENT '核销时间',
  `refund_time` timestamp NULL DEFAULT NULL COMMENT '退款时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT

🍀 id 字段不是自增 AUTO_INCREMENT


  • 每个店铺都可以发布优惠券:

在这里插入图片描述

  • 用户抢购的时候会生成订单并保存到 tb_voucher_order 这张表中
  • 如订单 id 使用数据库自增 ID 会出现以下问题:

🍀 id 规律性太明显(可能会被用户猜测到优惠券的 id)
🍀 受单表数据量的限制(优惠券订单可能很多,当分库分表的时候,每张表的 id 各自递增)

(2) 全局唯一 ID 生成器

🍀 全局 ID 生成器:一种在分布式系统下用来生成全局唯一 ID 的工具。一般要满足下列特性:

在这里插入图片描述

🍀 ① 唯一性:一个 ID 只能对应数据库中的一条记录
🍀 ② 高可用:生成 ID 的功能在高并发情况下也要能够提供服务
🍀 ③ 高性能:生成 ID 的速度要足够快(否则会影响其他业务的功能)
🍀 ④ 递增性:ID 必须递增才能让 MySQL 为表创建索引(提高数据库表查询效率的实现)
🍀 ⑤ 安全性:ID 不能过于简单,让用户猜测到

(3) 全局 ID 的结构

🍀 可使用 Redis 的 incr 实现自增
🍀 为了增加 ID 的安全性,不直接使用 Redis 自增的数值,而是拼接一些其它信息

在这里插入图片描述

ID 的组成部分:
🍀 符号位:1bit,永远为 0【ID 永远是正数】
🍀 时间戳:31bit,以为单位,可以使用69年
🍀 序列号:32bit,秒内的计数器,支持每秒产生 2^32 个不同 ID

(4) 代码实现

① RedisIdWorker

@Component
@SuppressWarnings("all")
public class RedisIdWorker {
    // 开始时间(秒)
    private static final long BEGIN_DAY_SECONDS;
    // 序列号的长度
    private static final long NO_BITS = 32;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    static {
        BEGIN_DAY_SECONDS = getSecondsOfDate(2020, 5, 20, 5, 20, 20);
    }

    /**
     * @param idPrefix 标识 ID 是哪个业务的
     * @return ID 值
     */
    public long newId(String idPrefix) {
        // 1.生成时间戳
        long seconds = getNowSeconds() - BEGIN_DAY_SECONDS;

        // 2.生成序列号
        // 2.1 当前日期
        String ymd = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2 使用 Redis 生成序列号
        Long no = stringRedisTemplate.opsForValue().increment("icrId:" + idPrefix + ":" + ymd);

        // 把时间戳左移32位, 空出序列号的位置
        return seconds << NO_BITS | no;
    }

    /**
     * 获取某个日期的秒数
     */
    private static long getSecondsOfDate(int y, int month, int d, int h, int min, int sec) {
        LocalDateTime time = LocalDateTime.of(y, month, d, h, min, sec);
        return time.toEpochSecond(ZoneOffset.UTC);
    }

    /**
     * 获取此时此刻的秒数
     */
    private static long getNowSeconds() {
        LocalDateTime curTime = LocalDateTime.now();
        return curTime.toEpochSecond(ZoneOffset.UTC);
    }
}

② Test

@SpringBootTest
class HmDianPingApplicationTests {

    @Resource
    private RedisIdWorker redisIdWorker;

    // 线程池
    private ExecutorService executorService = Executors.newFixedThreadPool(520);

    // 计数器
    private CountDownLatch latch = new CountDownLatch(300);

    @Test
    public void testReidIdWorker() throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 100; i++) {
                long orderId = redisIdWorker.newId("order");
                System.out.println("orderId = " + orderId);
            }
            latch.countDown(); // 任务执行完就递减
        };

        long begin = System.currentTimeMillis();

        for (int i = 0; i < 300; i++) {
            executorService.submit(task);
        }

        latch.await();
        long end = System.currentTimeMillis();
        System.out.println("duration: " + (end - begin));
    }

}

(5) 全局唯一 ID 其他生成策略

全局唯一ID生成策略:
🍀 UUID
🍀 Redis 自增
🍀 snowflake 算法
🍀 数据库自增(专门用一张表自增 ID)

Redis 自增 ID 策略:
🍀 每天一个 key,方便统计订单量
🍀 ID 结构:时间戳 + 计数器

二、添加优惠券

(1) 数据库

普通券表:

CREATE TABLE `tb_voucher` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `shop_id` bigint(20) unsigned DEFAULT NULL COMMENT '商铺id',
  `title` varchar(255) NOT NULL COMMENT '代金券标题',
  `sub_title` varchar(255) DEFAULT NULL COMMENT '副标题',
  `rules` varchar(1024) DEFAULT NULL COMMENT '使用规则',
  `pay_value` bigint(10) unsigned NOT NULL COMMENT '支付金额,单位是分。例如200代表2元',
  `actual_value` bigint(10) NOT NULL COMMENT '抵扣金额,单位是分。例如200代表2元',
  `type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '0,普通券;1,秒杀券',
  `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '1,上架; 2,下架; 3,过期',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT

```秒杀券表:`

CREATE TABLE `tb_seckill_voucher` (
  `voucher_id` bigint(20) unsigned NOT NULL COMMENT '关联的优惠券的id',
  `stock` int(8) NOT NULL COMMENT '库存',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `begin_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生效时间',
  `end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '失效时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`voucher_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='秒杀优惠券表,与优惠券是一对一关系'

(2) 添加优惠券接口

📗 优惠券(或秒杀券)增加完毕后会返回券的 ID

    @Override
    @Transactional
    public void addSeckillVoucher(Voucher voucher) {
        // 保存优惠券
        save(voucher);
        // 保存秒杀信息
        SeckillVoucher seckillVoucher = new SeckillVoucher();
        seckillVoucher.setVoucherId(voucher.getId());
        seckillVoucher.setStock(voucher.getStock());
        seckillVoucher.setBeginTime(voucher.getBeginTime());
        seckillVoucher.setEndTime(voucher.getEndTime());
        seckillVoucherService.save(seckillVoucher);
    }
{
	"actualValue": 10000,
	"rules": "全场通用\\n无需预约\\n可无限叠加\\n不兑现、不找零七\\n仅堂食",
	"updateTime": "2022-05-02T10:10:10",
	"title": "100元代金券(大优惠)",
	"type": 1,
	"payValue": 8000,
	"subTitle": "错过再等一年",
	"createTime": "2022-05-10T10:10:10",
	"id": 1,
	"shopId": 1,
	"beginTime": "2023-08-20T10:10:10",
	"endTime": "2023-08-21T23:10:10",
	"stock": 100,
	"status": 1
}

三、优惠券秒杀下单功能

📖 ① 秒杀是否开始或结束,如果尚未开始或已经结束则无法下单
📖 ② 库存是否充足,库存不足则无法下单

在这里插入图片描述

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    @Transactional // 事务
    public Result seckillVoucher(Long voucherId) {
        SeckillVoucher voucherById = seckillVoucherService.getById(voucherId);

        // 判断秒杀是否开始或结束
        LocalDateTime beginTime = voucherById.getBeginTime();
        LocalDateTime endTime = voucherById.getEndTime();
        LocalDateTime nowTime = LocalDateTime.now();

        if (nowTime.isBefore(beginTime)) {
            return Result.fail("秒杀未开始(no start)");
        }

        if (nowTime.isAfter(endTime)) {
            return Result.fail("秒杀已结束(finish)");
        }

        // 判断库存是否充足
        Integer stock = voucherById.getStock();

        if (stock < 1) {
            return Result.fail("库存不足");
        }

        // 扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId).update();

        if (success) {
            VoucherOrder voucherOrder = new VoucherOrder();
            long seckillOrderId = redisIdWorker.newId("seckillOrder");
            voucherOrder.setId(seckillOrderId); // 订单 ID
            voucherOrder.setUserId(UserHolder.getUser().getId()); // 用户 ID
            voucherOrder.setVoucherId(voucherId); // 优惠券 ID

            if (save(voucherOrder)) {
                return Result.ok(seckillOrderId);
            }

            return Result.fail("服务器忙, 请稍后再秒杀下单");
        }

        return Result.fail("库存不足");
    }

}

(1) 超卖问题

超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁: 超卖问题:

悲观锁
📖 认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行
📖 如 Synchronized、Lock 都属于悲观锁

乐观锁
📖 认为线程安全问题不一定会发生,因此不加锁
📖 在更新数据时去判断有没有其它线程对数据做了修改。
📖 如果没有修改则认为是安全的,自己才更新数据
📖 如果已经被其它线程修改,说明发生了安全问题,此时可以重试或异常

(2) 乐观锁(版本号和 CAS)

乐观锁的关键是判断之前查询得到的数据是否有被修改过

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

(3) 乐观锁解决超卖问题

在这里插入图片描述

四、一人一单功能【☆】

📖 修改秒杀业务,要求同一个优惠券,同一个用户只能下一单

在这里插入图片描述

在这里插入图片描述

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    public Result seckillVoucher(Long voucherId) {
        SeckillVoucher voucherById = seckillVoucherService.getById(voucherId);

        // 判断秒杀是否开始或结束
        LocalDateTime beginTime = voucherById.getBeginTime();
        LocalDateTime endTime = voucherById.getEndTime();
        LocalDateTime nowTime = LocalDateTime.now();

        if (nowTime.isBefore(beginTime)) {
            return Result.fail("秒杀未开始(no start)");
        }

        if (nowTime.isAfter(endTime)) {
            return Result.fail("秒杀已结束(finish)");
        }

        // 判断库存是否充足
        Integer stock = voucherById.getStock();

        if (stock < 1) {
            return Result.fail("库存不足");
        }

        // 一人一单
        Long userId = UserHolder.getUser().getId();

        synchronized (userId.toString().intern()) {
            // 获取事务的代理对象
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(userId, voucherId);
        }
    }

    @Transactional // 事务
    public Result createVoucherOrder(Long userId, Long voucherId) {

        Integer count = query().eq("user_id", userId)
                .eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("一人只能下一单");
        }

        // 扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .ge("stock", 0) // 保证库存大于零(CAS 乐观锁)
                .update();

        if (success) {
            VoucherOrder voucherOrder = new VoucherOrder();
            long seckillOrderId = redisIdWorker.newId("seckillOrder");
            voucherOrder.setId(seckillOrderId); // 订单 ID
            voucherOrder.setUserId(userId); // 用户 ID
            voucherOrder.setVoucherId(voucherId); // 优惠券 ID

            if (save(voucherOrder)) {
                return Result.ok(seckillOrderId);
            }

            return Result.fail("服务器忙, 请稍后再秒杀下单");
        }

        return Result.fail("库存不足");
    }

}

五、并发情况下的线程安全问题

在这里插入图片描述

在这里插入图片描述



worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/json;

    sendfile        on;
    
    keepalive_timeout  65;

    server {
        listen       8080;
        server_name  localhost;
        # 指定前端项目所在的位置
        location / {
            root   html/hmdp;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }


        location /api {  
            default_type  application/json;
            #internal;  
            keepalive_timeout   30s;  
            keepalive_requests  1000;  
            #支持keep-alive  
            proxy_http_version 1.1;  
            rewrite /api(/.*) $1 break;  
            proxy_pass_request_headers on;
            #more_clear_input_headers Accept-Encoding;  
            proxy_next_upstream error timeout;  
            #proxy_pass http://127.0.0.1:8081;
            proxy_pass http://backend;
        }
    }

    upstream backend {
        server 127.0.0.1:8081 max_fails=5 fail_timeout=10s weight=1;
        server 127.0.0.1:8082 max_fails=5 fail_timeout=10s weight=1;
    }  
}


在这里插入图片描述

在这里插入图片描述

集群模式下,每个 JVM 都有自己的锁监视器
每个 JVM 的锁监视器互相不可见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/743778.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目上线“G”速报 | GBASE助力四川银行反洗钱系统上线运行

随着金融机构资管业务的不断发展&#xff0c;藉由以银行为代表的金融机构建设反洗钱系统&#xff0c;向执法机构报送可疑活动&#xff0c;成为侦测潜在金融犯罪、打击腐败的重要防线。为更好助力反洗钱工作&#xff0c;四川银行着手构建新一代的反洗钱系统。作为信创第二期的重…

重磅预告丨Fortinet Demo Day系列实战攻防演练来袭!

随着网络安全形势的日趋严峻&#xff0c;越来越多的企业遭受了勒索、欺诈等危害。在高昂的赎金、生产损失&#xff0c;以及名誉损害的恐惧中&#xff0c;企业已经谈“黑”色变。黑客是如何悄无声息的“越过”重重高墙、道道壁垒进入到生产环境、办公空间&#xff0c;并在内网疯…

Android各种支持裤的最新依赖以及用户文档

https://developer.android.com/jetpack/androidx/versions 链接截图如下&#xff1a; 点击“Release Notes”中的链接&#xff0c;如果对应支持库有用户指南还能看到对应链接&#xff0c;还有如何添加依赖等&#xff0c;比如支持库中的actviity&#xff0c;如下&#xff1a;截…

【漂移-扩散通量重建 FV 方案】用于半导体和气体放电模拟的电子传输的更准确的 Sharfetter-Gummel 算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Perl 7 - 使用 Perlbrew 管理perl 版本

文章目录 关于 Perlbrew安装 Perlbrew使用 perlbrew 安装/管理 perl 版本 关于 Perlbrew 官网&#xff1a;https://perlbrew.pl 相关文档&#xff1a; App::perlbrew https://metacpan.org/pod/App::perlbrew Perlbrew 是一个工具&#xff0c;用于管理您$HOME 目录(或您指定的…

Flink基于信用值的流量控制

背景 flink内部实现了一个类似于tcp滑动窗口概念的流量控制功能&#xff0c;以满足其内部的流量控制功能&#xff0c;本文就来讲解下flink实现的基于信用值的流量控制的原理 实现原理 首先&#xff0c;我们先来看一下在flink中是如何实现数据传输的&#xff0c; 从上图可知&…

css animation 鼠标移入暂停会抖动

如图 实现一个赞助商横向滚动列表墙&#xff0c; 上下两排向右滚动&#xff0c;中间向左滚动&#xff0c;鼠标移入暂停当前行。 实现&#xff1a; // 使用animation.moving {animation: move 20s linear infinite; }keyframes move {0% {}100% {transform: translateX(-50%);…

可靠的手机问题修复工具分享 - 修复各种 Android 系统问题

一般来说&#xff0c;安卓手机都可以流畅运行。但不幸的是&#xff0c;有时您的Android手机可能无法正常运行&#xff0c;例如无响应、突然重启等。在这种情况下&#xff0c;您将需要Android手机维修软件。这些 Android 修复工具可以帮助您轻松解决此类问题&#xff0c;并还给您…

QEMU源码全解析4 —— QEMU参数解析(4)

接前一篇文章&#xff1a;QEMU源码全解析3 —— QEMU参数解析&#xff08;3&#xff09; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; 本回讲解Q…

JavaWeb学生考勤签到请假系统

一、项目简介 本项目是一套JavaWeb学生考勤签到请假系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;ecl…

生成图片验证码-Google Kaptcha

CaptchaImage生成 验证码 图片 captchaProducerMath.createText() 类似 captchaProducer.createText() 混合带字符的char如下 从若依学的&#xff0c;先看他的引用方式 package com.ruoyi.web.controller.common;import java.awt.image.BufferedImage; import java.io.IOExcept…

【导航算路(RP)模块功能】

什么是RP Route Production/Route Planning 就是在给定自车位置和目的地的情况下&#xff0c;按照用户设定的不同条件&#xff0c;计算出一条或多条从自车位置到目的地的花费(根据用户的设定&#xff0c;可能是指时间&#xff0c;费用等)最少的最优路以供用户使用。 为什么要…

【新版系统架构】第十七章-通信系统架构设计理论与实践

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 第一章-绪论第二章-计算机系统基础知识&#xff08;一&#xff09;第二章-计算机系统基础知识&#xff08;二&#xff09;第三章-信息系统基础知识第四章-信息安全技术基础知识第五章-软件工程…

ColddBoxEasy_EN靶机详解

ColddBoxEasy_EN靶机详解 上来扫描ip&#xff0c;找到ip后对这个ip进行一个单独的扫描。发现ssh开到4512端口上了&#xff0c;这里其实没用上&#xff0c;给我们挖的坑。 打开网页左下角有个login登陆&#xff0c;是一个wordpress搭建的网站&#xff0c;扫描一下用户名&#x…

U盘文件恢复,简单4步快速恢复文件!

U盘中删除的文件还能恢复吗&#xff1f;从理论上来看&#xff0c;u盘删除的文件其实还没有永久的被删除&#xff0c;但是这取决于多种因素。如果我们及时在新数据写入前对u盘中的数据进行恢复&#xff0c;那么恢复的可能性还是比较大的。 那么可能有朋友会好奇&#xff1a;u盘文…

开源LLM大模型微调简明教程

我相信你们大多数人都听说过 ChatGPT 并尝试过它来回答你的问题&#xff01; 有没有想过幕后发生了什么&#xff1f; 它由 Open AI 开发的大型语言模型 GPT-3 提供支持。 这些大型语言模型&#xff08;通常称为LLM&#xff09;开启了自然语言处理的许多可能性。 推荐&#xff1…

datagrip 更改表名报错

我就想改个表名报错 报错提示 Error Refactoring cannot be performed File D:\datagrip\lib\platform-impl.jar!\standardSchemas\xhtml1-frameset.xsd is located in a read-only container. 后面还有一大堆的错误&#xff0c;当时没有截图 试探: 注:再次之前我已经用use选择…

VMWare在Ubuntu系统下无法启动问题

项目场景&#xff1a; 在Ubuntu系统安装了VMWare虚拟机&#xff0c;已开始还能打开VMWare&#xff0c;能进入正常VM界面。最近怎么进入不了VM主界面。启动虚拟机发现报错&#xff1a;Unable to install all modules. See ****; 问题描述 1、启动VMware&#xff1a;提示内核需…

c++中的时间处理(1)localtime、localtime_r和localtime_s

c中对时间的处理有好几个函数&#xff0c;很多C程序员可能用过&#xff0c;但不一定完全搞得清楚。这里&#xff0c;我先讲解下&#xff1a;localtime、localtime_r和localtime_s的使用 1、localtime localtime用来获取系统时间&#xff0c;精度为秒。 struct tm *localtime…

路径规划算法:基于鹰栖息优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于鹰栖息优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于鹰栖息优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法…