基于Redis实现限流的几种方式

限流尽可能在满足需求的情况下越简单越好!

分布式限流是指在分布式系统中对请求进行限制,以防止系统过载或滥用资源。以下是常见的分布式限流策略及其实现方式:

1、基于 Redis 的固定窗口限流

原理

  • 设定一个时间窗口(如 1 秒)
  • 使用 Redis 维护一个计数器,存储当前窗口的请求数
  • 当请求到来时,INCR 计数器,如果超过阈值则拒绝
  • 过期后自动删除键,进入下一个窗口

优缺点: ✅ 简单易实现
❌ 在窗口交界处可能会出现短时间的突发流量("临界突增")

public class RedisRateLimiter {private final StringRedisTemplate redisTemplate;// 命令前缀private final String key;private final int rate;private final int window;public RedisRateLimiter(StringRedisTemplate redisTemplate, String key, int rate, int window) {this.redisTemplate = redisTemplate;this.key = key;this.rate = rate;this.window = window;}// 检查并获取令牌public boolean acquire() {String currentKey = key + "_" + (getCurrentSeconds() / window);Long currentCount = redisTemplate.opsForValue().increment(currentKey);redisTemplate.expire(currentKey, window, TimeUnit.SECONDS);log.info("当前获取到的令牌数 key {}  count {} result {} ",currentKey,currentCount,currentCount > rate);if (currentCount > rate){return false;}return true;}private long getCurrentSeconds() {return System.currentTimeMillis()/1000;}public void acquireSleep()  {int count = 0;while (!acquire()){sleep(1);count++;}}private void sleep(int second) {try {TimeUnit.SECONDS.sleep(second);} catch (InterruptedException e) {e.printStackTrace();}}public boolean acquireSleep(int waitSecond) {int count = 0;while (!acquire()){if (count >= waitSecond){return false;}sleep(1);count++;log.info("RedisRateLimiter[{}] try acquire sleep {}",key,count);}return true;}public static void main(String[] args) throws InterruptedException {ch.qos.logback.classic.Logger logger=(ch.qos.logback.classic.Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);logger.setLevel(Level.OFF);StringRedisTemplate stringRedisTemplate=getStringRedisTemplate();RedisRateLimiter redisRateLimiter = new RedisRateLimiter(stringRedisTemplate,"request_interface",16,10);// 模拟 50 个并发线程,每个线程尝试获取 10 次令牌final int threadCount = 50;ExecutorService executor = Executors.newFixedThreadPool(threadCount);CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {executor.submit(() -> {// 每个线程尝试多次调用限流方法for (int j = 0; j < 10; j++) {redisRateLimiter.acquireSleep();System.out.println("当前线程:"+Thread.currentThread().getName()+",获取到令牌,时间"+ DateFormatUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"));// 模拟每次请求间隔 100 毫秒redisRateLimiter.milliseconds(100);}latch.countDown();});}latch.await();executor.shutdown();}private static StringRedisTemplate getStringRedisTemplate() {// 1. 创建单机模式的配置RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();redisStandaloneConfiguration.setHostName("127.0.0.1");redisStandaloneConfiguration.setPort(6379);// 2. 构造 LettuceConnectionFactory,并初始化LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration);factory.afterPropertiesSet();  // 初始化连接工厂// 3. 创建 StringRedisTemplate 并设置连接工厂StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();stringRedisTemplate.setConnectionFactory(factory);stringRedisTemplate.afterPropertiesSet();  // 初始化模板return stringRedisTemplate;}private void milliseconds(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {e.printStackTrace();}}}

main方法中的计算结果可以看到在并发环境下严格的执行10s16次请求(也就是1分钟96次请求),这个就有个弊端,在并发环境下他们一拿到令牌同一秒就执行请求了。这个就是突发流量。

我的业务就是1分钟允许请求100次对方接口,像这种虽然严格按照1分钟不超过100次请求但是有突发流量对方还是返回了频率过高,可能对方计算频率方式不一样吧。所以这种方式不太可取。

当前线程:pool-1-thread-30,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-6,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-18,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-35,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-38,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-37,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-33,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-44,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-3,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-45,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-5,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-20,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-11,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-43,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-15,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-29,获取到令牌,时间2025-03-15 00:17:11
当前线程:pool-1-thread-4,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-16,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-12,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-24,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-26,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-8,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-14,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-49,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-42,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-21,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-1,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-10,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-31,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-50,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-36,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-48,获取到令牌,时间2025-03-15 00:17:20
当前线程:pool-1-thread-9,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-19,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-47,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-2,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-34,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-46,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-41,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-22,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-17,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-27,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-28,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-32,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-25,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-13,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-40,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-23,获取到令牌,时间2025-03-15 00:17:30
当前线程:pool-1-thread-39,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-7,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-34,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-13,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-2,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-22,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-28,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-46,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-25,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-19,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-9,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-32,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-39,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-17,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-47,获取到令牌,时间2025-03-15 00:17:40
当前线程:pool-1-thread-41,获取到令牌,时间2025-03-15 00:17:40

2. 基于 Redis 的滑动窗口限流

原理

  • 维护一个基于时间的列表(ZSET,有序集合)
  • 每次请求时,记录当前时间戳到 ZSET
  • 删除超出窗口时间范围的请求
  • 统计 ZSET 中当前窗口内的请求数,超出阈值则拒绝

优缺点: ✅ 解决了固定窗口的临界突增问题
❌ 存储和计算成本比固定窗口稍高

原理说明

  • 利用 Redis 的有序集合(ZSet),以请求的时间戳作为 score,每个请求入队一个唯一的 member(例如时间戳+UUID)。
  • 每次请求时,先移除时间窗口外的记录(score 小于当前时间减去窗口长度)。
  • 统计当前窗口内的请求数量,若数量超过设定阈值,则拒绝请求。
@Slf4j
public class RedisSlidingWindowRateLimiter {private final StringRedisTemplate redisTemplate;private final String key;private final int rate;private final int window; // 窗口长度,单位秒public RedisSlidingWindowRateLimiter(StringRedisTemplate redisTemplate, String key, int rate, int window) {this.redisTemplate = redisTemplate;this.key = key;this.rate = rate;// 限制窗口长度在 1 分钟以内Assert.isTrue(window > 0 && window <= 60, "窗口只支持一分钟内");this.window = window;}// 检查并获取令牌public boolean acquire() {long now = System.currentTimeMillis();// 计算窗口起始时间(单位毫秒)long windowStart = now - window * 1000;// 移除过期的请求记录redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);// 添加当前请求记录,member 用当前时间戳加 UUID 保证唯一性,score 为当前时间String member = now + "_" + UUID.randomUUID().toString();redisTemplate.opsForZSet().add(key, member, now);// 统计当前窗口内的请求数量Long count = redisTemplate.opsForZSet().count(key, windowStart, now);// 为了避免 key 永不过期,设置一个过期时间(窗口长度)redisTemplate.expire(key, window, TimeUnit.SECONDS);if (count != null && count > rate) {return false;}return true;}// 采用轮询方式等待获取令牌public void acquireSleep() {int count = 0;while (!acquire()){ThreadUtil.sleep(1, TimeUnit.SECONDS);count++;log.info("RedisSlidingWindowRateLimiter[{}] try acquire sleep {}", key, count);}}public boolean acquireSleep(int waitSecond) {int count = 0;while (!acquire()){if (count >= waitSecond){return false;}ThreadUtil.sleep(1, TimeUnit.SECONDS);count++;log.info("RedisSlidingWindowRateLimiter[{}] try acquire sleep {}", key, count);}return true;}
}

代码说明

  • 移除过期记录:调用 removeRangeByScore 清理掉窗口外的请求数据。
  • 添加当前请求:将当前请求的时间戳与 UUID 组合后添加到 ZSet 中,score 为当前时间,确保在滑动窗口内计数。
  • 统计计数:通过 count 方法统计当前窗口内的请求数,如果超出限制则返回 false。

3. 基于 Redis 的令牌桶限流

原理

  • 设定一个容量为 max_tokens 的令牌桶,初始装满
  • 以固定速率向桶中添加令牌(如每秒 10 个)
  • 每次请求需要消耗一个令牌,没有令牌时拒绝请求
  • 通常使用 Redis 的 Lua 脚本实现原子操作

优缺点: ✅ 更加平滑,支持突发流量
❌ 需要额外的定时任务或后台线程补充令牌

原理说明

  • 令牌桶算法中,设定一个桶最大容量 capacity,同时以一定速率 refillRate 补充令牌。
  • 每次请求需要消耗一个令牌,若当前桶内令牌不足,则拒绝请求。
  • 为保证原子性,利用 Redis 的 Lua 脚本将令牌获取和补充过程封装为原子操作。
@Slf4j
public class RedisTokenBucketRateLimiter {private final StringRedisTemplate redisTemplate;private final String key;// 桶的容量(最大令牌数)private final int capacity;// 令牌补充速率,单位:个/秒private final double refillRate;// Lua 脚本,用于原子化处理令牌桶逻辑private static final String LUA_SCRIPT = "local tokens_key = KEYS[1] .. ':tokens' \n" +"local timestamp_key = KEYS[1] .. ':ts' \n" +"local capacity = tonumber(ARGV[1]) \n" +"local refill_rate = tonumber(ARGV[2]) \n" +"local current_time = tonumber(ARGV[3]) \n" +"local requested = tonumber(ARGV[4]) \n" +"local tokens = tonumber(redis.call('get', tokens_key) or capacity) \n" +"local last_refill = tonumber(redis.call('get', timestamp_key) or current_time) \n" +"local delta = current_time - last_refill \n" +"local tokens_to_add = delta * refill_rate \n" +"tokens = math.min(capacity, tokens + tokens_to_add) \n" +"if tokens < requested then \n" +"   return 0 \n" +"else \n" +"   tokens = tokens - requested \n" +"   redis.call('set', tokens_key, tokens) \n" +"   redis.call('set', timestamp_key, current_time) \n" +"   return 1 \n" +"end";public RedisTokenBucketRateLimiter(StringRedisTemplate redisTemplate, String key, int capacity, double refillRate) {this.redisTemplate = redisTemplate;this.key = key;this.capacity = capacity;this.refillRate = refillRate;}// 检查并获取令牌public boolean acquire() {// 当前时间(单位秒)long currentTime = System.currentTimeMillis() / 1000;// 请求消耗 1 个令牌Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {List<byte[]> keys = Collections.singletonList(key.getBytes());List<byte[]> args = Arrays.asList(String.valueOf(capacity).getBytes(),String.valueOf(refillRate).getBytes(),String.valueOf(currentTime).getBytes(),"1".getBytes());return connection.eval(LUA_SCRIPT.getBytes(), ReturnType.INTEGER, keys.size(), keys.toArray(new byte[0][]), args.toArray(new byte[0][]));});return result != null && result == 1;}
}

代码说明

  • Lua 脚本逻辑
    • 获取当前桶中剩余令牌数和上次补充时间,若不存在则默认初始化为满桶状态。
    • 根据当前时间与上次更新时间的差值计算应补充的令牌数,并更新桶内令牌。
    • 判断是否有足够令牌供本次请求(默认请求 1 个令牌),若不足返回 0,否则扣减令牌并更新上次补充时间,返回 1。
  • 原子执行:通过 redisTemplate 的 eval 方法保证 Lua 脚本的原子性,避免并发问题。

4. 基于 Redis 的漏桶限流

原理

  • 设定一个队列模拟漏桶
  • 按固定速率从队列取出请求执行
  • 请求过多时,超出队列长度的请求被丢弃

优缺点: ✅ 输出速率稳定,不受突发流量影响
❌ 可能会丢弃部分流量

原理说明

  • 漏桶算法中,将请求看作向桶中注入的“水”,桶以固定速率漏水(处理请求)。
  • 当桶中水量超过预设容量时,则拒绝新请求。
  • 同样利用 Lua 脚本保证原子操作。
@Slf4j
public class RedisLeakyBucketRateLimiter {private final StringRedisTemplate redisTemplate;private final String key;// 桶的容量(允许的最大突发请求数)private final int capacity;// 漏水速率,单位:个/秒,表示每秒可处理的请求数private final double leakRate;// Lua 脚本,用于原子化处理漏桶逻辑private static final String LUA_SCRIPT = "local level_key = KEYS[1] .. ':level' \n" +"local timestamp_key = KEYS[1] .. ':ts' \n" +"local capacity = tonumber(ARGV[1]) \n" +"local leak_rate = tonumber(ARGV[2]) \n" +"local current_time = tonumber(ARGV[3]) \n" +"local level = tonumber(redis.call('get', level_key) or '0') \n" +"local last_time = tonumber(redis.call('get', timestamp_key) or current_time) \n" +"local delta = current_time - last_time \n" +"local leaked = delta * leak_rate \n" +// 计算漏水后桶内水量,不能低于 0"level = math.max(0, level - leaked) \n" +"if level + 1 > capacity then \n" +"   return 0 \n" +"else \n" +"   level = level + 1 \n" +"   redis.call('set', level_key, level) \n" +"   redis.call('set', timestamp_key, current_time) \n" +"   return 1 \n" +"end";public RedisLeakyBucketRateLimiter(StringRedisTemplate redisTemplate, String key, int capacity, double leakRate) {this.redisTemplate = redisTemplate;this.key = key;this.capacity = capacity;this.leakRate = leakRate;}// 检查并获取请求处理资格public boolean acquire() {// 当前时间(单位秒)long currentTime = System.currentTimeMillis() / 1000;Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {List<byte[]> keys = Collections.singletonList(key.getBytes());List<byte[]> args = Arrays.asList(String.valueOf(capacity).getBytes(),String.valueOf(leakRate).getBytes(),String.valueOf(currentTime).getBytes());return connection.eval(LUA_SCRIPT.getBytes(), ReturnType.INTEGER, keys.size(), keys.toArray(new byte[0][]), args.toArray(new byte[0][]));});return result != null && result == 1;}
}

代码说明

  • Lua 脚本逻辑
    • 从 Redis 中获取当前桶内水量(即请求数量)和上次更新的时间。
    • 根据当前时间与上次更新时间的差值和设定的漏水速率计算“漏掉”的水量,并更新桶内水量(不能低于 0)。
    • 判断加入当前请求后是否超过桶的容量,超过则返回 0(拒绝),否则将水量加 1 并更新记录,返回 1 表示允许。
  • 原子执行:同样通过 eval 方法保证操作原子性,避免并发修改问题。

总结

  • 滑动窗口:使用 Redis ZSet 记录请求时间戳,动态统计窗口内请求数,平滑控制突发流量。
  • 令牌桶:通过 Lua 脚本实现令牌的自动补充和扣减,支持一定的突发请求。
  • 漏桶:用固定漏水速率保证请求以均匀的速率被处理,避免瞬间大量请求。

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

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

相关文章

【前端文件下载实现:多种表格导出方案的技术解析】

前端文件下载实现&#xff1a;多种表格导出方案的技术解析 背景介绍 在企业级应用中&#xff0c;数据导出是一个常见需求&#xff0c;特别是表格数据的导出。在我们的管理系统中&#xff0c;不仅需要支持用户数据的Excel导出&#xff0c;还需要处理多种格式的表格文件下载&am…

堆概念和结构

1. 二叉树的顺序结构 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中通常 把堆使用顺序结构的数组来存储 &#xff0c;需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事&#xff0c…

VUE的脚手架搭建引入类库

VUE的小白脚手架搭建 真的好久好久自己没有发布自己博客了,对于一直在做后端开发的我 ,由于社会卷啊卷只好学习下怎么搭建前端,一起学习成长吧~哈哈哈(最终目的,能够懂并简易开发) 文章目录 VUE的小白脚手架搭建1.下载node.js2.安装vue脚手架3.创建一个项目4.代码规范约束配置(…

使用 Arduino 和 ThingSpeak 通过互联网进行实时温度和湿度监测

使用 ThingSpeak 和 Arduino 通过 Internet 进行温度和湿度监控 湿度和温度是许多地方(如农场、温室、医疗、工业家庭和办公室)非常常见的测量参数。我们已经介绍了使用 Arduino 进行湿度和温度测量,并在 LCD 上显示数据。 在这个物联网项目中,我们将使用ThingSpeak在互联…

论文分享:PL-ALF框架实现无人机低纹理环境自主飞行

在室内仓库、地下隧道等低纹理复杂场景中&#xff0c;无人机依赖视觉传感器进行自主飞行时&#xff0c;往往会遇到定位精度低、路径规划不稳定等难题。针对这一问题&#xff0c;重庆邮电大学计算机学院雷大江教授团队在IEEE Trans期刊上提出了一种新型自主飞行框架&#xff1a;…

[Java实战]性能优化qps从1万到3万

一、问题背景 ​ 事情起因是项目上springboot项目提供的tps达不到客户要求,除了增加服务器提高tps之外,作为团队的技术总监,架构师,技术扛把子,本着我不入地狱谁入地狱的原则,决心从代码上优化,让客户享受到飞一般的感觉。虽然大多数编程工作在写下第一行代码时已经完成…

如何筛选能实现共享自助健身房“灵活性”的物联网框架?

共享自助健身房已经成为一种新兴的健身方式&#xff0c;这种模式方便快捷&#xff0c;尤其适合i人健身爱好者&#xff0c;市场接受度还是挺好的。对于无人自助式的健身房要想实现灵活性&#xff0c;要挑选什么样的物联网框架呢&#xff1f; 1. 支持多种通信协议 共享自助健身…

【后端】【django】抛弃 Django 自带用户管理后,能否使用 `simple-jwt`?

抛弃 Django 自带用户管理后&#xff0c;能否使用 simple-jwt&#xff1f; 一、结论 是的&#xff0c;即使抛弃了 Django 自带的用户管理&#xff08;AbstractUser 或 AbstractBaseUser&#xff09;&#xff0c;仍然可以使用 django-rest-framework-simplejwt&#xff08;简称…

【量化科普】Correlation,相关性

【量化科普】Correlation&#xff0c;相关性 &#x1f680;量化软件开通 &#x1f680;量化实战教程 在量化投资领域&#xff0c;相关性&#xff08;Correlation&#xff09;是一个核心概念&#xff0c;用于衡量两个变量之间的线性关系强度和方向。简单来说&#xff0c;它告…

大数据学习(68)- Flink和Spark Streaming

&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一…

MCU详解:嵌入式系统的“智慧之心”

在现代电子设备中&#xff0c; MCU&#xff08;Microcontroller Unit&#xff0c;微控制器&#xff09;扮演着至关重要的角色。从智能家居到工业控制&#xff0c;从汽车电子到医疗设备&#xff0c;MCU以其小巧、低功耗和高集成度的特点&#xff0c;成为嵌入式系统的核心组件。 …

(链表)24. 两两交换链表中的节点

给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xff1a;[2,1,4…

吴恩达机器学习笔记复盘(三)Jupyter NoteBook

Jupyter NoteBook Jupyter是一个开源的交互式计算环境&#xff1a; 特点 交互式编程&#xff1a;支持以单元格为单位编写和运行代码&#xff0c;用户可以实时看到代码的执行结果&#xff0c;便于逐步调试和理解代码逻辑。多语言支持&#xff1a;不仅支持Python&#xff0c;还…

【Linux】从互斥原理到C++ RAII封装实践

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

微服务无状态服务设计

微服务无状态服务设计是构建高可用、高扩展性系统的核心方法。 一、核心设计原则 请求独立性 每个请求必须携带完整的上下文信息&#xff0c;服务不依赖本地存储的会话或用户数据。例如用户认证通过JWT传递所有必要信息&#xff0c;而非依赖服务端Session。 状态外置化 将会话…

30、map 和 unordered_map的区别和实现机制【高频】

底层结构 map底层是红黑树结构&#xff0c;而unordered_map底层是哈希结构; 有序性 但是红黑树其实是一种二叉搜索树&#xff0c;插入删除时会自动排序hash因为是把数据映射到数组上的&#xff0c;而且存在哈希冲突&#xff0c;所以不能保证有序存储 所以有序存储使用map&a…

大数据-spark3.5安装部署之local模式

spark&#xff0c;一个数据处理框架和计算引擎。 下载 local模式即本地模式&#xff0c;就是不需要任何其他节点资源就可以在本地执行spark代码的环境。用于练习演示。 上传解压 使用PortX将文件上传至/opt 进入/opt目录&#xff0c;创建目录module&#xff0c;解压文件至/o…

Manus “Less structure,More intelligence ”独行云端处理器

根据市场调研机构Statista数据显示&#xff0c;全球的AR/AR的市场规模预计目前将达到2500亿美元&#xff0c;Manus作为VR手套领域的领军企业&#xff0c;足以颠覆你的认知。本篇文章将带你解读Manus产品&#xff0c;针对用户提出的种种问题&#xff0c;Manus又将如何解决且让使…

Oracle数据库存储结构--逻辑存储结构

数据库存储结构&#xff1a;分为物理存储结构和逻辑存储结构。 物理存储结构&#xff1a;操作系统层面如何组织和管理数据 逻辑存储结构&#xff1a;Oracle数据库内部数据组织和管理数据&#xff0c;数据库管理系统层面如何组织和管理数据 Oracle逻辑存储结构 数据库的逻…

芯驿电子 ALINX 亮相德国纽伦堡,Embedded World 2025 精彩回顾

2025年3月13日&#xff0c;全球规模最大的嵌入式行业盛会——德国纽伦堡国际嵌入式展&#xff08;embedded world 2025&#xff09;圆满落幕。 在这场汇聚全球 950 家展商、3 万余专业观众的科技盛宴中&#xff0c;芯驿电子 ALINX 展位人头攒动&#xff0c;多款尖端产品吸引客户…