Redis+Lua限流的四种算法

news2024/10/25 12:08:57

在这里插入图片描述

1. 固定窗口(Fixed Window)

原理:
  • 固定窗口算法将时间划分为固定的时间段(窗口),比如 1 秒、1 分钟等。在每个时间段内,允许最多一定数量的请求。如果请求超出配额,则拒绝。
优点:
  • 实现简单,能够快速处理请求限流。
缺点:
  • 在窗口边界处可能出现流量突增的情况(称为“边界效应”),比如两个窗口交界处可能短时间内允许通过的请求数量翻倍。
Lua脚本:
-- KEYS[1]: 限流的键(通常为用户ID或者API)
-- ARGV[1]: 最大允许请求数
-- ARGV[2]: 窗口时间(以秒为单位)

local current = redis.call('GET', KEYS[1])

if current and tonumber(current) >= tonumber(ARGV[1]) then
    return 0  -- 返回0表示超出限流
else
    current = redis.call('INCR', KEYS[1])
    if tonumber(current) == 1 then
        redis.call('EXPIRE', KEYS[1], ARGV[2])  -- 设置窗口时间
    end
    return 1  -- 返回1表示未超限
end
Java模拟限流:
package com.strap.common.redis.demo;

import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;


/**
 * 固定窗口lua限流Demo
 *
 * @author strap
 */
public class FixedWindowExample {

    private static final String LIMIT_SCRIPT =
            "-- KEYS[1]: 限流的键(通常为用户ID或者API)\n" +
                    "-- ARGV[1]: 最大允许请求数\n" +
                    "-- ARGV[2]: 窗口时间(以秒为单位)\n" +
                    "\n" +
                    "local current = redis.call('GET', KEYS[1])\n" +
                    "\n" +
                    "if current and tonumber(current) >= tonumber(ARGV[1]) then\n" +
                    "    return 0  -- 返回0表示超出限流\n" +
                    "else\n" +
                    "    current = redis.call('INCR', KEYS[1])\n" +
                    "    if tonumber(current) == 1 then\n" +
                    "        redis.call('EXPIRE', KEYS[1], ARGV[2])  -- 设置窗口时间\n" +
                    "    end\n" +
                    "    return 1  -- 返回1表示未超限\n" +
                    "end";

    @SneakyThrows
    public static void main(String[] args) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setBlockWhenExhausted(true);
        try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
            for (int i = 0; i < 100; i++) {
                Thread.sleep(100);
                Object o = jedis.eval(LIMIT_SCRIPT, 1, "FixedWindowExample", "10", "5");
                if (Long.valueOf(1).equals(o)) {
                    System.out.println(i + "=============================放行");
                } else {
                    System.out.println(i + "拦截=============================");
                }
            }
        }
    }

}


2. 滑动窗口(Sliding Window)

原理:
  • 滑动窗口改进了固定窗口的“边界效应”问题,它通过更细粒度的时间单位来平滑地控制请求。滑动窗口可以在较短的时间窗口内动态调整请求计数,防止瞬时流量激增。
优点:
  • 能平滑地限制请求,减少流量的突增问题。
缺点:
  • 相对固定窗口来说,滑动窗口实现复杂度更高。
Lua脚本:
-- KEYS[1]: 限流的键(通常为用户ID或者API)
-- ARGV[1]: 时间窗口(秒)
-- ARGV[2]: 最大允许请求数
-- ARGV[3]: 当前时间戳(毫秒)

-- 移除窗口外的请求
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[3] - ARGV[1] * 1000)

local count = redis.call('ZCARD', KEYS[1])

if tonumber(count) >= tonumber(ARGV[2]) then
    return 0  -- 请求被限制
else
    redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])  -- 添加当前请求的时间戳
    redis.call('EXPIRE', KEYS[1], ARGV[1])  -- 设置过期时间
    return 1  -- 请求允许
end
Java模拟限流:
package com.strap.common.redis.demo;

import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;


/**
 * 滑动窗口lua限流Demo
 *
 * @author strap
 */
public class SlidingWindowExample {

    private static final String LIMIT_SCRIPT =
            "-- KEYS[1]: 限流的键(通常为用户ID或者API)\n" +
                    "-- ARGV[1]: 时间窗口(秒)\n" +
                    "-- ARGV[2]: 最大允许请求数\n" +
                    "-- ARGV[3]: 当前时间戳(毫秒)\n" +
                    "\n" +
                    "-- 移除窗口外的请求\n" +
                    "redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[3] - ARGV[1] * 1000)\n" +
                    "\n" +
                    "local count = redis.call('ZCARD', KEYS[1])\n" +
                    "\n" +
                    "if tonumber(count) >= tonumber(ARGV[2]) then\n" +
                    "    return 0  -- 请求被限制\n" +
                    "else\n" +
                    "    redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])  -- 添加当前请求的时间戳\n" +
                    "    redis.call('EXPIRE', KEYS[1], ARGV[1])  -- 设置过期时间\n" +
                    "    return 1  -- 请求允许\n" +
                    "end";


    @SneakyThrows
    public static void main(String[] args) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setBlockWhenExhausted(true);
        try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
            for (int i = 0; i < 100; i++) {
                Thread.sleep(100);
                long now = System.currentTimeMillis();
                Object o = jedis.eval(LIMIT_SCRIPT, 1, "SlidingWindowExample", "5", "10", now + "");
                if (Long.valueOf(1).equals(o)){
                    System.out.println(i + "=============================放行");
                }else {
                    System.out.println(i + "拦截=============================");
                }
            }

        }
    }


}

3. 令牌桶(Token Bucket)

原理:
  • 令牌桶算法以恒定速率向桶中添加令牌。每次请求需要消耗一定数量的令牌,如果桶内有足够的令牌,允许请求通过;否则拒绝请求。令牌可以积累,从而允许短时间内的流量突发。
优点:
  • 允许短时间的流量突发,适用于需要应对高峰流量的场景。
缺点:
  • 如果高峰流量持续时间较长,可能导致后续请求被大量拒绝。
Lua脚本:
-- 当前的键
local key = KEYS[1]
-- 令牌桶的容量
local capacity = tonumber(ARGV[1])
-- 令牌的生成速率(个/秒)
local rate = tonumber(ARGV[2])
-- 当前时间戳(毫秒)
local now = tonumber(ARGV[3])
-- 请求的令牌数量
local requestedTokens = tonumber(ARGV[4])
-- 键的最大生命周期
local expire = math.ceil(capacity / rate)

-- 获取当前桶内的令牌数量,默认为capacity
local currentTokens = tonumber(redis.call('HGET', key, 'currentTokens') or capacity)
-- 获取上次令牌更新的时间
local lastUpdate = tonumber(redis.call('HGET', key, 'last_update') or 0)

-- 首次进来初始化令牌数量
if lastUpdate == 0 then
    redis.call('HSET', key, 'last_update', now)
    redis.call('HSET', key, 'currentTokens', currentTokens)
    redis.call('EXPIRE', key, expire)
else
    -- 计算在当前时间段内生成的令牌数量
    local tokensToAdd = math.floor((now - lastUpdate) / 1000 * rate)
    currentTokens = math.min(capacity, currentTokens + tokensToAdd)
end

-- 计算当前是否能提供请求的令牌数量
local isAllow = 0
if currentTokens >= requestedTokens then
    isAllow = 1
    redis.call('HSET', key, 'last_update', now)
    redis.call('HSET', key, 'currentTokens', currentTokens - requestedTokens)
    redis.call('EXPIRE', key, expire)
end

return {isAllow, currentTokens}
Java模拟限流:
package com.strap.common.redis.demo;

import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;

import java.util.List;

/**
 * 令牌桶lua限流Demo
 *
 * @author strap
 */
public class TokenBucketExample {

    private static final String LIMIT_SCRIPT = "-- 当前的键\n" +
            "local key = KEYS[1]\n" +
            "-- 令牌桶的容量\n" +
            "local capacity = tonumber(ARGV[1])\n" +
            "-- 令牌的生成速率(个/秒)\n" +
            "local rate = tonumber(ARGV[2])\n" +
            "-- 当前时间戳(毫秒)\n" +
            "local now = tonumber(ARGV[3])\n" +
            "-- 请求的令牌数量\n" +
            "local requestedTokens = tonumber(ARGV[4])\n" +
            "-- 键的最大生命周期\n" +
            "local expire = math.ceil(capacity / rate)\n" +
            "\n" +
            "-- 获取当前桶内的令牌数量,默认为capacity\n" +
            "local currentTokens = tonumber(redis.call('HGET', key, 'currentTokens') or capacity)\n" +
            "-- 获取上次令牌更新的时间\n" +
            "local lastUpdate = tonumber(redis.call('HGET', key, 'last_update') or 0)\n" +
            "\n" +
            "-- 首次进来初始化令牌数量\n" +
            "if lastUpdate == 0 then\n" +
            "    redis.call('HSET', key, 'last_update', now)\n" +
            "    redis.call('HSET', key, 'currentTokens', currentTokens)\n" +
            "    redis.call('EXPIRE', key, expire)\n" +
            "else\n" +
            "    -- 计算在当前时间段内生成的令牌数量\n" +
            "    local tokensToAdd = math.floor((now - lastUpdate) / 1000 * rate)\n" +
            "    currentTokens = math.min(capacity, currentTokens + tokensToAdd)\n" +
            "end\n" +
            "\n" +
            "-- 计算当前是否能提供请求的令牌数量\n" +
            "local isAllow = 0\n" +
            "if currentTokens >= requestedTokens then\n" +
            "    isAllow = 1\n" +
            "    redis.call('HSET', key, 'last_update', now)\n" +
            "    redis.call('HSET', key, 'currentTokens', currentTokens - requestedTokens)\n" +
            "    redis.call('EXPIRE', key, expire)\n" +
            "end\n" +
            "\n" +
            "return {isAllow, currentTokens}";

    @SneakyThrows
    public static void main(String[] args) {

        JedisPoolConfig config = new JedisPoolConfig();
        config.setBlockWhenExhausted(true);
        try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
            int rate = 1;       // 速率(令牌的生成速率(个/秒))
            int capacity = 10;  // 桶容量(令牌桶的容量)
            int everyTime = 1;  // 每次请求需要拿走的令牌数量
            for (int i = 0; i < 100; i++) {
                Thread.sleep(100);
                long now = System.currentTimeMillis();
                Object o = jedis.eval(LIMIT_SCRIPT, 1, "TokenBucketExample", String.valueOf(capacity), String.valueOf(rate), String.valueOf(now), String.valueOf(everyTime));
                List<Object> resutl = (List)o;
                if (Long.valueOf(1).equals(resutl.get(0))){
                    System.out.println(i + "请求前桶内剩余令牌数:" + resutl.get(1) + "==============================放行");
                }else {
                    System.out.println(i + "请求前桶内剩余令牌数:" + resutl.get(1) + "=拦截=============================");
                }
            }
        }

    }


}

4.漏桶(Leaky Bucket)

原理:
  • 漏桶算法将请求流量放入一个“漏桶”中,桶以固定速率漏水(处理请求)。如果流量超过桶的容量,多余的请求将被拒绝。漏桶严格控制输出速率,因此不会出现流量突发。
优点:
  • 严格限制请求速率,适用于要求平滑流量的场景。
缺点:
  • 不允许流量突发,处理效率可能不及令牌桶。
Lua脚本:
-- 当前的键
local key = KEYS[1]
-- 漏桶的容量
local capacity = tonumber(ARGV[1])
-- 漏水速率(个/秒)
local rate = tonumber(ARGV[2])
-- 当前时间戳
local now = tonumber(ARGV[3])
-- 请求计数(进来的tokens数量)
local requestedTokens = tonumber(ARGV[4])
-- 键的最大生命周期
local expire = math.ceil(capacity / rate)

-- 获取当前漏桶内的令牌数量,默认为0
local currentTokens = tonumber(redis.call('HGET', key, 'tokens') or 0)
-- 获取上次漏桶令牌数量的更新时间
local lastUpdate = tonumber(redis.call('HGET', key, 'last_update') or now)
-- 漏桶在当前时间范围内已流出的令牌数
local leaks = math.floor((now - lastUpdate) / 1000 * rate)
-- 重新计算当前漏桶内的令牌数量 math.min(capacity, currentTokens + deltaTokens)
currentTokens = math.max(currentTokens - leaks + requestedTokens, 0)
-- 是否允许通过,默认不允许
local isAllow = 0
if currentTokens <= capacity then
    -- 当前令牌数量还能放进去
    isAllow = 1
    redis.call('HSET', key, 'tokens', currentTokens)
    redis.call('HSET', key, 'last_update', now)
    redis.call('EXPIRE', key, expire);
end
return {isAllow, currentTokens}
Java模拟限流:
package com.strap.common.redis.demo;

import lombok.SneakyThrows;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;

import java.util.List;


/**
 * 漏桶lua限流Demo
 *
 * @author strap
 */
public class LeakyBucketExample {

    private static final String LIMIT_SCRIPT =
            "-- 当前的键\n" +
                    "local key = KEYS[1]\n" +
                    "-- 漏桶的容量\n" +
                    "local capacity = tonumber(ARGV[1])\n" +
                    "-- 漏水速率(个/秒)\n" +
                    "local rate = tonumber(ARGV[2])\n" +
                    "-- 当前时间戳\n" +
                    "local now = tonumber(ARGV[3])\n" +
                    "-- 请求计数(进来的tokens数量)\n" +
                    "local requestedTokens = tonumber(ARGV[4])\n" +
                    "-- 键的最大生命周期\n" +
                    "local expire = math.ceil(capacity / rate)\n" +
                    "\n" +
                    "-- 获取当前漏桶内的令牌数量,默认为0\n" +
                    "local currentTokens = tonumber(redis.call('HGET', key, 'tokens') or 0)\n" +
                    "-- 获取上次漏桶令牌数量的更新时间\n" +
                    "local lastUpdate = tonumber(redis.call('HGET', key, 'last_update') or now)\n" +
                    "-- 漏桶在当前时间范围内已流出的令牌数\n" +
                    "local leaks = math.floor((now - lastUpdate) / 1000 * rate)\n" +
                    "-- 重新计算当前漏桶内的令牌数量 math.min(capacity, currentTokens + deltaTokens)\n" +
                    "currentTokens = math.max(currentTokens - leaks + requestedTokens, 0)\n" +
                    "-- 是否允许通过,默认不允许\n" +
                    "local isAllow = 0\n" +
                    "if currentTokens <= capacity then\n" +
                    "    -- 当前令牌数量还能放进去\n" +
                    "    isAllow = 1\n" +
                    "    redis.call('HSET', key, 'tokens', currentTokens)\n" +
                    "    redis.call('HSET', key, 'last_update', now)\n" +
                    "    redis.call('EXPIRE', key, expire);\n" +
                    "end\n" +
                    "return {isAllow, currentTokens}";


    @SneakyThrows
    public static void main(String[] args) {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setBlockWhenExhausted(true);
        try (Jedis jedis = new Jedis("127.0.0.1", 6379)) {
            int rate = 1;       // 速率(每秒恒定流出的token数量)
            int capacity = 10;  // 桶容量(可存放token数量)
            int everyTime = 1;  // 每次进来的token数量
            for (int i = 0; i < 100; i++) {
                Thread.sleep(100);
                long now = System.currentTimeMillis();
                Object o = jedis.eval(LIMIT_SCRIPT, 1, "LeakyBucketExample", String.valueOf(capacity), String.valueOf(rate), String.valueOf(now), String.valueOf(everyTime));
                List<Object> resutl = (List)o;
                if (Long.valueOf(1).equals(resutl.get(0))){
                    System.out.println(i + "当前桶内令牌数:" + resutl.get(1) + "==============================放行");
                }else {
                    System.out.println(i + "当前桶内令牌数:" + resutl.get(1) + "=拦截=============================");
                }
            }
        }
    }

}

算法对比

算法工作机制优点缺点使用场景
固定窗口固定时间窗口内计数实现简单,快速判断窗口边界可能导致流量突发(边界效应)简单的 API 限流,低要求的场景
滑动窗口滑动时间窗口内计数更精确地控制流量,减少流量突发实现较复杂,较高的性能开销动态限流场景,减少流量突增,如 API 网关
令牌桶令牌以固定速率生成,请求消耗令牌支持流量突发,且易于实现和理解如果高峰流量持续时间过长,会导致后续请求被拒绝适合支持突发流量的场景,如限速下载、API 限流
漏桶固定速率处理请求,严格控制输出流量严格控制流量,平滑输出不允许流量突发严格控制请求速率,如网络流量控制,负载均衡等

总结

  • 固定窗口简单易用,适合对流量要求不高的场景。
  • 滑动窗口平滑控制流量,适合对流量突发有一定需求但又希望平稳控制的场景。
  • 令牌桶允许突发流量,适合需要高效处理短时流量高峰的应用。
  • 漏桶严格控制请求速率,适合对平稳处理请求要求很高的场景。

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

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

相关文章

解锁流量密码:TikTok常见引流方式分享

在这个TikTok这个竞争激烈但又遍布商机的平台上&#xff0c;如何有效地引流&#xff0c;尤其是对于新手来说&#xff0c;是一个重要的课题。本文将详细介绍TikTok的几种常见引流方式&#xff0c;并为新手提供切实可行的引流策略&#xff0c;以帮助他们在平台上获得更高的曝光率…

Java 字节流:高效处理二进制数据

前言 字节流是 Java I/O 系统的一部分&#xff0c;专门用于处理字节数据。由于所有数据在计算机中最终都以字节形式存在&#xff0c;这意味着字节流可以操作任何类型的数据&#xff0c;包括文本、图片、视频等。 Java 提供了多种字节流类&#xff0c;这些类继承自 InputStrea…

Python 爬虫项目实战:爬取某云热歌榜歌曲

一、网络爬虫的定义 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;也成为网页蜘蛛或者网页机器人&#xff0c;是一种按照既定规则自动浏览网络并提取信息的程序。爬虫的主要用途包括数据采集、网络索以及内容抓取等。 二、爬虫基本原理 1、种子URL&#xff1a;爬…

使用Python循环地画一个简单的爱心 - 烂漫教程

运行软件&#xff1a;pycharm 画一个爱心的代码&#xff1a; import turtle def print_love1():# 设置画笔颜色和粗细turtle.pencolor("red")turtle.pensize(6)# 绘制爱心turtle.fillcolor("pink")turtle.begin_fill()turtle.left(45)turtle.forward(100…

新鲜出炉面试题之【说说spring spring MVC spring boot的区别】

Spring MVC 和 Spring Boot 是 Spring 框架的一部分&#xff0c;但它们的目的和用途有所不同。下面详细阐述这两者之间的区别。 1. 概念 Spring MVC&#xff1a; Spring MVC 是一种基于请求-响应模式的 Web 框架&#xff0c;属于 Spring 框架的一部分。它提供了一种分离的方式…

【Jenkins】解决在Jenkins Agent节点容器内无法访问物理机的docker和docker compose的问题

解决在Jenkins Agent节点容器内无法访问物理机的docker和docker compose的问题 1. 确定物理机docker和docker compose已经安装2. 编写Jenkins Agent结点docker-compose.yaml配置文件3. 修改docker运行时文件权限4. 启动容器并验证 最近接触到一个发布产物是一个 docker镜像的项…

ORACLE 的SCHEDULER创建JOB

JOB_NAME&#xff1a;指定任务的名称&#xff0c;必选值&#xff0c;注意要确保指定的名称唯一。 JOB_TYPE&#xff1a;任务执行的操作类型&#xff0c;必选值&#xff0c;STORED_PROCEDURE&#xff1a;表示任务执行的是 ORACLE 存储过程。 JOB_ACTION&#xff1a;任务执行的操…

Pendle protocol

道阻且长&#xff0c;行而不辍&#xff0c;未来可期 我身边多是聪明的人&我一直认为我不是那个会学习的人&#xff0c;以往看官方文档&#xff0c;总有许多理解不透的地方&#xff0c;需要靠众多博主的白话分析才能理解&#xff0c;but,在学习Pendle protocol的时候&#x…

进行FMEA时需要考虑哪些历史数据?

在进行FMEA&#xff08;Failure Modes and Effects Analysis&#xff0c;即故障模式与影响分析&#xff09;时&#xff0c;历史数据扮演着至关重要的角色。这些数据不仅为分析提供了坚实的基础&#xff0c;还帮助团队更准确地预测潜在故障模式&#xff0c;评估其影响&#xff0…

uni-app 开发微信小程序,实现图片预览和保存

1.使用 uni.previewImage() 预览图片 1.1 图片列表 1.2 预览 1.2.1 样式无法调整 1.2.2 微信小程序不支持预览本地文件路径图片&#xff08;图片上传到小程序的临时文件存储或云服务存储&#xff09; 1.3 无法绑定 longpress"saveImage(item)" 长按保存图片事件 …

C++,STL 044(24.10.24)

内容 1.set容器的构造函数。 2.set容器的赋值操作。 运行代码 #include <iostream> #include <set>using namespace std;void printSet(set<int> &s) {for (set<int>::iterator it s.begin(); it ! s.end(); it){cout << *it << &…

充电宝目前什么牌子的质量好耐用?盘点2024年五款高性价充电宝!

充电宝目前什么牌子的质量好耐用&#xff1f;这是许多人在选购充电宝时最为关心的问题。随着移动设备的普及和技术的进步&#xff0c;充电宝已经成为日常生活中不可或缺的伴侣。为了帮助大家在众多品牌中做出明智的选择&#xff0c;我特地盘点了2024年五款具有高性价比的充电宝…

跨境科技公司如何借助 NocoBase 升级内部系统并实现外部业务增长?

关于政元软件 深圳市政元软件有限公司&#xff0c;作为国家级高新技术企业和专精特新企业&#xff0c;自 2006 年成立以来&#xff0c;一直走在跨境企业服务的前沿。近年来&#xff0c;政元软件将人工智能技术与跨境业务场景结合&#xff0c;为客户提供创新的解决方案&#xff…

数据结构——树、二叉树和森林间的转换

前言 介绍 &#x1f343;数据结构专区&#xff1a;数据结构 参考 该部分知识参考于《数据结构&#xff08;C语言版 第2版&#xff09;》129~130页 &#x1f308;每一个清晨&#xff0c;都是世界对你说的最温柔的早安&#xff1a;ૢ(≧▽≦)و✨ 目录 前言 1、基础知识 2…

现场总是发生急停,很可能是PLC和设置间网络中断

如果你的现场总是发生急停&#xff0c;很可能是PLC和设置间网络中断&#xff0c;本文用一个真实案例告诉你问题背后的原因和解决方法&#xff01; 这是一台生产汽车配件的机器&#xff0c;使用1500F的控制器连接机器人控制器&#xff0c;现场装置总会莫名其妙的发生急停故障。…

Linux运维_搭建smb服务

Samba&#xff08;SMB&#xff09;是一个开源软件&#xff0c;允许Linux和Unix系统与Windows系统共享文件和打印机。以下是一些关于Samba和SMB的基本信息和操作步骤&#xff1a; Samba 和 SMB 基本概念 Samba&#xff1a;实现了SMB&#xff08;Server Message Block&#xff…

程序员中后期,靠什么和其他人拉开差距?

有人说程序员是吃青春饭的&#xff0c;到了35岁会被淘汰。但和身边几十位程序员聊完后&#xff0c;发现实际情况不尽如此&#xff01; 工作两三年的时候&#xff0c;有些程序员技术优势明显&#xff0c;看起来职业前途很好&#xff1b;工作四五年后&#xff0c;该学的技术都熟…

Vue学习记录之十七 css中样式穿透及新特征介绍

一、scoped原理 在vue页面的css中,有一个设置为scoped,使用以后dom的节点会出现下面的规则。其实我们打完包就是一个html页面,如果不做处理,将会导致css混乱。 给HTML的DOM节点加一个不重复data属性(形如:data-v-123)来表示他的唯一性在每句css选择器的末尾(编译后的生成的…

Java每日面试题(前端Vue拓展)(day20)

目录 Vue是什么&#xff1f;v-if 和 v-show的区别&#xff1f;watch与compute的区别&#xff1f;使用过哪些前端组件&#xff1f;Vue父子组件如何进行交互&#xff1f;Vue的生命周期v-for指令中的key属性有什么用&#xff1f; Vue是什么&#xff1f; Vue是一个渐进式 JavaScrip…

平时实战知识(混合版)map.keyset+stream+maven打包

为这个博主点赞!!!!!!!!!!!!!!!! Java stream 使用样例_哪些类可以使用stream-CSDN博客 IDEA使用maven命令打包_idea 打包maven-CSDN博客