redis+lua实现分布式限流

news2024/11/29 5:37:21

redis+lua实现分布式限流

文章目录

  • redis+lua实现分布式限流
    • 为什么使用redis+lua实现分布式限流
    • 使用ZSET也可以实现限流,为什么选择lua的方式
    • 实现
      • 依赖
      • lua脚本
      • yaml
      • 代码实现
    • Jmeter压测

为什么使用redis+lua实现分布式限流

  1. 原子性:通过Lua脚本执行限流逻辑,所有操作在一个原子上下文中完成,避免了多步操作导致的并发问题。
  2. 灵活性:Lua脚本可以编写复杂的逻辑,比如滑动窗口限流,易于扩展和定制化。
  3. 性能:由于所有逻辑在Redis服务器端执行,减少了网络往返,提高了执行效率。

使用ZSET也可以实现限流,为什么选择lua的方式

使用zset需要额度解决这些问题

  1. 并发控制:需要额外的逻辑来保证操作的原子性和准确性,可能需要配合Lua脚本或Lua脚本+WATCH/MULTI/EXEC模式来实现。
  2. 资源消耗:长期存储请求记录可能导致Redis占用更多的内存资源。

为什么redis+zset不能保证原子性和准确性

  1. 多步骤操作:滑动窗口限流通常需要执行多个步骤,比如检查当前窗口的请求次数、添加新的请求记录、可能还需要删除过期的请求记录等。这些操作如果分开执行,就有可能在多线程或多进程环境下出现不一致的情况。
  2. 非原子性复合操作:虽然单个Redis命令是原子的,但当你需要执行一系列操作来维持限流状态时(例如,先检查计数、再增加计数、最后可能还要删除旧记录),没有一个单一的Redis命令能完成这些复合操作。如果在这系列操作之间有其他客户端修改了数据,就会导致限流不准确。
  3. 竞争条件:在高并发环境下,多个客户端可能几乎同时执行限流检查和增加请求的操作,如果没有适当的同步机制,可能会导致请求计数错误。

实现

依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.kang</groupId>
    <artifactId>rate-limiter-project</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rate-limiter-project</name>
    <description>rate-limiter-project</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.2</version>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.0.1-jre</version> <!-- 请检查最新版本 -->
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

lua脚本

-- KEYS[1] 是Redis中存储计数的key,,,
local key = KEYS[1]

-- ARGV[1]是当前时间戳-[当前时间戳]
local now = tonumber(ARGV[1])

-- ARGV[2]是最大请求次数-[最大请求次数]
local maxRequests = tonumber(ARGV[2])

-- ARGV[3]是时间窗口长度-[时间窗口长度]
local windowSize = tonumber(ARGV[3])

-- 获取当前时间窗口的起始时间
local windowStart = math.floor(now / windowSize) * windowSize

-- 构建时间窗口内的key,用于区分不同窗口的计数
local windowKey = key .. ':' .. tostring(windowStart)

-- 获取当前窗口的计数
local currentCount = tonumber(redis.call('get', windowKey) or '0')

-- 如果当前时间不在窗口内,重置计数
if now > windowStart + windowSize then
    redis.call('del', windowKey)
    currentCount = 0
end

-- 检查是否超过限制
if currentCount + 1 <= maxRequests then
    -- 未超过,增加计数并返回成功,并设置键的过期时间为窗口剩余时间,以自动清理过期数据。如果超过最大请求次数,则拒绝请求
    redis.call('set', windowKey, currentCount + 1, 'EX', windowSize - (now - windowStart))
    return 1 -- 成功
else
    return 0 -- 失败
end

yaml

server:
  port: 10086

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5

代码实现

在这里插入图片描述

启动类

package com.kang.limter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
public class RateLimiterProjectApplication {

    public static void main(String[] args) {
        SpringApplication.run(RateLimiterProjectApplication.class, args);
        log.info("RateLimiterProjectApplication start success");
    }

}

CacheConfig

package com.kang.limter.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.kang.limter.utils.LuaScriptUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.kang.limter.constant.SystemConstant.REDIS_RATE_LIMITER_LUA_SCRIPT_PATH;

/**
 * @Author Emperor Kang
 * @ClassName CacheConfig
 * @Description 缓存配置
 * @Date 2024/6/13 10:07
 * @Version 1.0
 * @Motto 让营地比你来时更干净
 */
@Slf4j
@Configuration
public class CacheConfig {

    /**
     * 缓存配置,加载lua脚本
     * @return
     */
    @Bean(name = "rateLimiterLuaCache")
    public LoadingCache<String, String> rateLimiterLuaCache() {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                // 设置缓存的最大容量,最多100个键值对
                .maximumSize(100)
                // 设置缓存项过期策略:写入后2小时过期
                .expireAfterWrite(2, TimeUnit.HOURS)
                // 缓存统计信息记录
                .recordStats()
                // 构建缓存加载器,用于加载缓存项的值
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String scriptPath) throws Exception {
                        try {
                            return LuaScriptUtils.loadLuaScript(scriptPath);
                        } catch (Exception e) {
                            log.error("加载lua脚本失败:{}", e.getMessage());
                            return null;
                        }
                    }
                });

        // 预热缓存
        warmUpCache(cache);

        return cache;
    }

    /**
     * 预热缓存
     */
    private void warmUpCache(LoadingCache<String, String> cache) {
        try {
            // 假设我们有一个已知的脚本列表需要预热
            List<String> knownScripts = Collections.singletonList(REDIS_RATE_LIMITER_LUA_SCRIPT_PATH);
            for (String script : knownScripts) {
                String luaScript = LuaScriptUtils.loadLuaScript(script);
                // 手动初始化缓存
                cache.put(script, luaScript);
                log.info("预加载Lua脚本成功: {}, length: {}", script, luaScript.length());
            }
        } catch (Exception e) {
            log.error("预加载Lua脚本失败: {}", e.getMessage(), e);
        }
    }
}
  • 这里使用缓存预热加快lua脚本的加载速度,基于JVM内存操作,所以很快

SystemConstant

package com.kang.limter.constant;

/**
 * @Author Emperor Kang
 * @ClassName SystemConstant
 * @Description 系统常量
 * @Date 2024/6/12 19:25
 * @Version 1.0
 * @Motto 让营地比你来时更干净
 */
public class SystemConstant {
    /**
     * 限流配置缓存key前缀
     */
    public static final String REDIS_RATE_LIMITER_KEY_PREFIX = "outreach:config:limiter:%s";

    /**
     * 限流lua脚本路径
     */
    public static final String REDIS_RATE_LIMITER_LUA_SCRIPT_PATH = "classpath:lua/rate_limiter.lua";
}

RateLimiterController

package com.kang.limter.controller;

import com.kang.limter.dto.RateLimiterRequestDto;
import com.kang.limter.utils.RateLimiterUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static java.lang.Thread.sleep;

/**
 * @Author Emperor Kang
 * @ClassName RateLimiterController
 * @Description TODO
 * @Date 2024/6/12 19:33
 * @Version 1.0
 * @Motto 让营地比你来时更干净
 */
@Slf4j
@RestController
@RequestMapping("/rate/limiter")
public class RateLimiterController {
    @Autowired
    private RateLimiterUtil rateLimiterUtil;

    @PostMapping("/test")
    public String test(@RequestBody RateLimiterRequestDto rateLimiterRequestDto) {
        // 是否限流
        if (!rateLimiterUtil.tryAcquire(rateLimiterRequestDto.getInterfaceCode(), 5, 1000)) {
            log.info("触发限流策略,InterfaceCode:{}", rateLimiterRequestDto.getInterfaceCode());
            return "我被限流了InterfaceCode:" + rateLimiterRequestDto.getInterfaceCode();
        }

        log.info("请求参数:{}", rateLimiterRequestDto);

        try {
            log.info("开始加工逻辑");
            sleep(1000);
        } catch (InterruptedException e) {
            log.error("休眠异常");
            Thread.currentThread().interrupt();
            return "加工异常";
        }

        return "加工成功,成功返回";
    }
}

RateLimiterRequestDto

package com.kang.limter.dto;

import lombok.Data;

/**
 * @Author Emperor Kang
 * @ClassName RateLimiterRequestDto
 * @Description TODO
 * @Date 2024/6/12 19:39
 * @Version 1.0
 * @Motto 让营地比你来时更干净
 */
@Data
public class RateLimiterRequestDto {
    /**
     * 接口编码
     */
    private String interfaceCode;
}

ResourceLoaderException

package com.kang.limter.exception;

/**
 * @Author Emperor Kang
 * @ClassName ResourceLoaderException
 * @Description 自定义资源加载异常
 * @Date 2024/6/12 18:10
 * @Version 1.0
 * @Motto 让营地比你来时更干净
 */
public class ResourceLoaderException extends Exception{
    public ResourceLoaderException() {
        super();
    }

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

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

    public ResourceLoaderException(Throwable cause) {
        super(cause);
    }

    protected ResourceLoaderException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

LuaScriptUtils

package com.kang.limter.utils;

import com.kang.limter.exception.ResourceLoaderException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

@Slf4j
public class LuaScriptUtils {

    /**
     * 从类路径下读取Lua脚本内容。
     * @param scriptPath 类路径下的Lua脚本文件路径
     * @return Lua脚本的文本内容
     */
    public static String loadLuaScript(String scriptPath) throws ResourceLoaderException {
        Assert.notNull(scriptPath, "script path must not be null");
        try {
            // 读取lua脚本
            ResourceLoader resourceLoader = new DefaultResourceLoader();
            Resource resource = resourceLoader.getResource(scriptPath);
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
                StringBuilder scriptBuilder = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    scriptBuilder.append(line).append("\n");
                }
                String lua = scriptBuilder.toString();
                log.debug("读取的lua脚本为: {}", lua);
                return lua;
            }
        } catch (Exception e) {
            log.error("Failed to load Lua script from path: {}", scriptPath, e);
            throw new ResourceLoaderException("Failed to load Lua script from path: " + scriptPath, e);
        }
    }
}

RateLimiterUtil

package com.kang.limter.utils;

import com.google.common.cache.LoadingCache;
import com.kang.limter.exception.ResourceLoaderException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.nio.charset.StandardCharsets;

import static com.kang.limter.constant.SystemConstant.REDIS_RATE_LIMITER_KEY_PREFIX;
import static com.kang.limter.constant.SystemConstant.REDIS_RATE_LIMITER_LUA_SCRIPT_PATH;

/**
 * @Author Emperor Kang
 * @ClassName RateLimiterUtil
 * @Description 限流工具类
 * @Date 2024/6/12 17:56
 * @Version 1.0
 * @Motto 让营地比你来时更干净
 */
@Slf4j
@Component
public class RateLimiterUtil {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    @Qualifier("rateLimiterLuaCache")
    private LoadingCache<String, String> rateLimiterLuaCache;


    /**
     * @param interfaceCode 接口标识
     * @param maxRequests   最大请求数
     * @param windowSizeMs  窗口大小
     * @return boolean
     * @Description 尝试获取令牌
     * @Author Emperor Kang
     * @Date 2024/6/12 17:57
     * @Version 1.0
     */
    public boolean tryAcquire(String interfaceCode, int maxRequests, long windowSizeMs) {
        try {
            long currentTimeMillis = System.currentTimeMillis();

            String luaScript = rateLimiterLuaCache.get(REDIS_RATE_LIMITER_LUA_SCRIPT_PATH);
            log.info("缓存查询lua,length={}", luaScript.length());

            if(StringUtils.isBlank(luaScript)){
                log.info("从缓存中未获取到lua脚本,尝试手动读取");
                luaScript = LuaScriptUtils.loadLuaScript(REDIS_RATE_LIMITER_LUA_SCRIPT_PATH);
            }

            // 二次确认
            if(StringUtils.isBlank(luaScript)){
                log.info("lua脚本加载失败,暂时放弃获取许可,不再限流");
                return true;
            }

            // 限流核心逻辑
            String finalLuaScript = luaScript;
            Long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
                // 用于存储的key
                byte[] key = String.format(REDIS_RATE_LIMITER_KEY_PREFIX, interfaceCode).getBytes(StandardCharsets.UTF_8);
                // 当前时间(毫秒)
                byte[] now = String.valueOf(currentTimeMillis).getBytes(StandardCharsets.UTF_8);
                // 最大请求数
                byte[] maxRequestsBytes = String.valueOf(maxRequests).getBytes(StandardCharsets.UTF_8);
                // 窗口大小
                byte[] windowSizeBytes = String.valueOf(windowSizeMs).getBytes(StandardCharsets.UTF_8);
                // 执行lua脚本
                return connection.eval(finalLuaScript.getBytes(StandardCharsets.UTF_8), ReturnType.INTEGER, 1, key, now, maxRequestsBytes, windowSizeBytes);
            });

            Assert.notNull(result, "执行lua脚本响应结果为null");

            // 获取结果
            return result == 1L;
        } catch (ResourceLoaderException e) {
            log.error("加载lua脚本失败", e);
        } catch (Exception e){
            log.error("执行限流逻辑异常", e);
        }
        return true;
    }
}

lua脚本

-- KEYS[1] 是Redis中存储计数的key,,,
local key = KEYS[1]

-- ARGV[1]是当前时间戳-[当前时间戳]
local now = tonumber(ARGV[1])

-- ARGV[2]是最大请求次数-[最大请求次数]
local maxRequests = tonumber(ARGV[2])

-- ARGV[3]是时间窗口长度-[时间窗口长度]
local windowSize = tonumber(ARGV[3])

-- 获取当前时间窗口的起始时间
local windowStart = math.floor(now / windowSize) * windowSize

-- 构建时间窗口内的key,用于区分不同窗口的计数
local windowKey = key .. ':' .. tostring(windowStart)

-- 获取当前窗口的计数
local currentCount = tonumber(redis.call('get', windowKey) or '0')

-- 如果当前时间不在窗口内,重置计数
if now > windowStart + windowSize then
    redis.call('del', windowKey)
    currentCount = 0
end

-- 检查是否超过限制
if currentCount + 1 <= maxRequests then
    -- 未超过,增加计数并返回成功,并设置键的过期时间为窗口剩余时间,以自动清理过期数据。如果超过最大请求次数,则拒绝请求
    redis.call('set', windowKey, currentCount + 1, 'EX', windowSize - (now - windowStart))
    return 1 -- 成功
else
    return 0 -- 失败
end

Jmeter压测

在这里插入图片描述

在这里插入图片描述

  • 200次请求/s,限流了195,而我们设置的最大令牌数就是5

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

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

相关文章

融合心血管系统(CVS)多视角信号的新架构新策略

随着深度学习的发展和传感器的广泛采用&#xff0c;自动多视角融合&#xff08;MVF&#xff09;在心血管系统&#xff08;CVS&#xff09;信号处理方面取得了进展。然而&#xff0c;普遍的MVF模型架构通常将同一时间步骤但不同视角的CVS信号混合成统一的表示形式&#xff0c;忽…

RFID技术在农产品管理中的应用

使用RFID技术对农产品生产、加工、存储和销售的全过程进行跟踪&#xff0c;追溯食品的生产和加工过程&#xff0c;能够有效加强农产品的管理&#xff0c;如图7—10所示。 将RFID技术应用于农业食品安全&#xff0c;首先是建立完整、准确的食品供应链信息记录。借助RFID 对物体…

unity简单数字拼图小游戏(源码)

代码&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement;public class DragImage : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler {pub…

1_常见指令【Linux中常见30个指令的学习和使用】【万字长文】

常见指令以及权限理解 开始学习linux前的注意事项 在学习linux之前&#xff0c;我们要知道linux是一个操作系统。 那操作系统是什么呢&#xff1f;&#xff08;这里只做大概了解&#xff09; 操作系统就是一个管理软硬件的软件。 它对上提供良好&#xff08;稳定、高效、安…

大模型Prompt-Tuning技术入门

Prompt-Tuning方法 1 NLP任务四种范式 目前学术界一般将NLP任务的发展分为四个阶段&#xff0c;即NLP四范式&#xff1a; 第一范式&#xff1a;基于「传统机器学习模型」的范式&#xff0c;如TF-IDF特征朴素贝叶斯等机器算法&#xff1b;第二范式&#xff1a;基于「深度学习模…

Echarts图表: 矩形树图都有哪些配置项,一文告诉你

Hello&#xff0c;本期和大家分享矩形树图的配置项&#xff0c;欢迎评论区和贝格前端工场互动交流。 一、矩形树图是什么 矩形树图是ECharts中的一种图表类型&#xff0c;用于展示树形结构的数据。矩形树图通过矩形的大小和位置来表示树形结构中节点的层次关系和数据的大小。…

C端设计师去做B端界面设计易犯哪些?要不要给他提个醒。

2024-03-29 17:03大美B端工场 很多C端设计师初涉B端设计&#xff0c;思路转变不过来&#xff0c;还用C端思想指导B端设计&#xff0c;结果就是总感觉不舒服&#xff0c;大美B端工场&#xff0c;为大家详细解读一下。 当C端设计师去设计B端界面时&#xff0c;可能会经常犯以下…

【Spine学习08】之短飘,人物头发动效制作思路

上一节说完了跑步的&#xff0c; 这节说头发发型。 基础过程总结&#xff1a; 1.创建骨骼&#xff08;头发需要在上方加一个总骨骼&#xff09; 2.创建网格&#xff08;并绑定黄线&#xff09; 3.绑定权重&#xff08;发根位置的顶点赋予更多总骨骼的权重&#xff09; 4.切换到…

使用星鸾云GPU云服务器搭配Jupyter Lab,创建个人AI大模型

最近我们公司IT部门宣布了一个大事情&#xff0c;他们开发了一款内部用的大模型&#xff0c;叫作一号AI员工&#xff08;其实就是一个聊天机器人&#xff09;&#xff0c;这个一号员工可以回答所有关于公司财务、人事、制度、产品方面的问题。 我问了句&#xff1a;公司加班有…

【服务器硬件由 CPU、RAM、硬盘等组成,选购时需考虑应用需求、预算等。散热、安全、监控与维护亦重要,未来发展趋势包括高性能、低能耗和智能化。】

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

蚁剑webshell实验

GitHub - AntSwordProject/AwesomeScript: AntSword Shell 脚本分享/示例 命令行和文件管理 https 基本上请求一次响应一次就断开。

Opencv数一数有多少个水晶贴纸?

1.目标-数出有多少个贴纸 好久没更新博客了&#xff0c;最近家里小朋友在一张A3纸上贴了很多水晶贴纸&#xff0c;要让我帮他数有多少个&#xff0c;看上去有点多&#xff0c;贴的也比较随意&#xff0c;于是想着使用Opencv来识别一下有多少个。 原图如下&#xff1a; 代码…

如何应对pcdn的流量攻击?

面对PCDN的流量攻击&#xff0c;可以采取以下措施来应对&#xff1a; 一&#xff0e;配置防火墙&#xff1a; 1.禁止未授权的PCDN域名访问&#xff1a;根据网络需求&#xff0c;配置防火墙规则&#xff0c;只允许特定的PCDN域名进行访问&#xff0c;从而防止未经授权的PCDN节…

这份简历让一位程序员在谷歌获得30万美元

大家好&#xff0c;我是弗雷。每次都在让大家尝试走出职场&#xff0c;但今天我们却选择再次走进职场。 作为曾经的职场人&#xff0c;我深深明白简历的敲门砖作用&#xff0c;制作一份优质的简历至关重要。 你所见过的最优秀的简历是什么样子&#xff1f; 或者你想象中最优秀…

十大成长型思维:定位思维、商业思维、时间管理思维、学习成长思维、精力管理思维、逻辑表达思维、聚焦思维、金字塔原理、目标思维、反思思维

一、定位思维 定位思维是一种在商业和管理领域中至关重要的思维模式&#xff0c;它涉及到如何在顾客心智中确立品牌的独特位置&#xff0c;并使其与竞争对手区分开来。以下是关于定位思维的清晰介绍&#xff1a; 1、定义 定位思维是一种从潜在顾客的心理认知出发&#xff0c;通…

TrueNAS系统在ARM平台上的移植

随着家庭及中小型企业对存储和共享需求的日益增长&#xff0c;高效、可靠的文件存储系统成为支撑各类应用的关键。 在众多存储系统中&#xff0c;TrueNAS以其卓越的数据完整性与可靠性、简洁高效的应用程序部署和管理、灵活的虚拟化应用添加能力&#xff0c;以及出色的可用性&a…

汽车IVI中控开发入门及进阶(二十六):视频解码芯片ADV7180

前言: ADV7180芯片的功能框图如下: ADV7180自动检测并将兼容全球NTSC、PAL和SECAM标准的标准模拟基带电视信号转换为兼容8位ITU-R BT.656接口标准的4:2:2分量视频数据。 ADV7180芯片介绍: 简单的数字输出接口与各种MPEG编码器、编解码器、移动视频处理器以及Analog Devic…

Stable Diffusion 3 Medium 模型

开源SD3&#xff0c;中型版本&#xff0c;20亿参数&#xff0c;Stable Diffusion 3 Medium&#xff0c;系统内存要求32G&#xff0c;显卡6G。 a female character with long, flowing hair that appears to be made of ethereal, swirling patterns resembling the Northern Li…

嵌入式操作系统_3.操作系统内核架构

内核是操作系统的核心部分&#xff0c;它管理着系统的各种资源。内核可以看成连接应用程序和硬件的一座桥梁&#xff0c;是直接运行在硬件上的最基础的软件实体。目前从内核架构来划分&#xff0c;可分为宏内核&#xff08;Monolithic Kernel&#xff09;和微内核&#xff08;M…

AI图书下载:《ChatGPT打造赚钱机器》

这本书《ChatGPT打造赚钱机器》&#xff08;ChatGPT Money Machine 2024 The Ultimate Chatbot Cheat Sheet&#xff09;是一本全面的指南&#xff0c;旨在帮助读者快速掌握如何利用ChatGPT等人工智能技术创造收益。 以下是各章节内容的总结&#xff1a; **引言** 介绍了人工智…