redis+lua实现分布式限流

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

yaml

server:port: 10086spring:redis:host: 127.0.0.1port: 6379database: 0lettuce:pool:max-active: 20max-idle: 10min-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>() {@Overridepublic 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 {@Autowiredprivate 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 {@Autowiredprivate 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 -> {// 用于存储的keybyte[] 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 thenredis.call('del', windowKey)currentCount = 0
end-- 检查是否超过限制
if currentCount + 1 <= maxRequests then-- 未超过,增加计数并返回成功,并设置键的过期时间为窗口剩余时间,以自动清理过期数据。如果超过最大请求次数,则拒绝请求redis.call('set', windowKey, currentCount + 1, 'EX', windowSize - (now - windowStart))return 1 -- 成功
elsereturn 0 -- 失败
end

Jmeter压测

在这里插入图片描述

在这里插入图片描述

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

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

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

相关文章

无人机RTMP推流EasyDSS直播平台推流成功,不显示直播按钮是什么原因?

互联网视频云平台/视频点播直播/视频推拉流EasyDSS支持HTTP、HLS、RTMP等播出协议&#xff0c;并且兼容多终端&#xff0c;如Windows、Android、iOS、Mac等。为了便于用户集成与二次开发&#xff0c;我们也提供了API接口供用户调用和集成。在无人机场景上&#xff0c;可以通过E…

[linux]基于Ubuntu24.04原内核6.8.0升级到6.9.0

物理机操作系统&#xff1a; 虚拟机操作系统&#xff1a; Ubuntu 24.04 下载地址&#xff1a; https://mirror.nju.edu.cn/ubuntu-releases/24.04/ubuntu-24.04-desktop-amd64.iso VM版本信息&#xff1a; 内核源代码来源&#xff1a; https://ftp.sjtu.edu.cn/sites/ftp.kern…

课设--学生成绩管理系统

欢迎来到 Papicatch的博客 文章目录 &#x1f349;技术核心 &#x1f349;引言 &#x1f348;标识 &#x1f348;背景 &#x1f348;项目概述 &#x1f348; 文档概述 &#x1f349;可行性分析的前提 &#x1f348;项目的要求 &#x1f348;项目的目标 &#x1f348;…

vue自定义一个回到顶部组件

1.首先创建一个backTop.vue页面&#xff1a; 页面有两个按钮&#xff0c;一个回到顶部按钮&#xff0c;一个刷新按钮(showRefresh:false将刷新按钮隐藏)&#xff0c;实现效果如下&#xff1a; 代码解析&#xff1a; domName:需要监听滚动的dom类名&#xff0c;不传默认监听bod…

搜维尔科技:特斯拉称工厂内有两台人形机器人开始自主工作

搜维尔科技消息&#xff0c;据外电报道&#xff0c;特斯拉声称&#xff0c;其目前拥有两台 Optimus 人形机器人在工厂内自主工作&#xff0c;这尚属首次。 如果目前这场薪酬方案混乱有什么好处的话&#xff0c;那就是特斯拉几乎看起来又有了一个公关部门。 当然&#xff0c;其…

记录open62541简单有效的编译生成.c和.h文件【OPCUA开源库】

一、下载和安装CMake 虽然说可以通过下面命令安装CMake,但是安装CMake时,通常会安装来自你的操作系统的软件仓库中的版本,这个版本可能不是最新的 sudo apt-get install cmake 如果安装后发现CMake版本低于CMake 3.13是没有办法进行编译的 接下来通过编译源码来升级高版本…

基数排序O(n)时间复杂度的实现

基数排序O(n)时间复杂度的实现 前言 之前写过一篇文章六种常见排序算法分析与实现&#xff0c;讲了六种常见的排序算法&#xff0c;但是没有了解到桶排序&#xff0c;基数排序这两种排序算法&#xff0c;今天刷LeetCode发现了这两种算法&#xff0c;本文先来聊聊基数排序的思…

06 SpringBoot 配置文件详解-application.yaml

Spring Boot 提供了大量的自动配置&#xff0c;极大地简化了spring 应用的开发过程&#xff0c;当用户创建了一个 Spring Boot 项目后&#xff0c;即使不进行任何配置&#xff0c;该项目也能顺利的运行起来。当然&#xff0c;用户也可以根据自身的需要使用配置文件修改 Spring …

vue3 vant4 仿京东分类功能实现

Ⅰ- 壹 - 功能展示和使用需求 需求描述 基于vant 实现,仿京东分类功能实现样式交互等基本实现,细节可能需要优化 地址 https://gitee.com/wswhq/vue3-vant-temp/tree/master/src/view/ClassIfication 功能展示 Ⅱ - 贰 - 封装思路 不表述了自己看代码吧 Ⅲ - 叁 - 使用 …

若依微服务Docker部署验证码出不来怎么办?

最近,有许多人反馈在使用 Docker 部署若依微服务项目时,遇到验证码无法显示的问题。本文将重点介绍解决该问题的注意事项以及整个项目的部署流程。之前我们也撰写过微服务部署教程,本文将在此基础上进行优化和补充。你也可以参考我之前写的部署教程:https://yang-roc.blog.…

AI来帮助我使用inno项目的配置打包win运行文件。

同时使用了chatgpt和文心一言。实测结果&#xff0c;chatgpt更好些&#xff0c;文心一言也有特点。贴图&#xff1a; ChatGpt: Chatgpt 感觉更了解你要的。 文心一言&#xff0c;要描述更清楚些。&#xff08;测试了几遍&#xff09; 最终我的结构是这样的&#xff1a; 具体怎…

【Java面试】十九、并发篇(下):线程池

文章目录 1、为什么要使用线程池2、线程池的执行原理2.1 七个核心参数2.2 线程池的执行原理 3、线程池用到的常见的阻塞队列有哪些4、如何确定核心线程数开多少个&#xff1f;5、线程池的种类有哪些&#xff1f;6、为什么不建议用Executors封装好的静态方法创建线程池7、线程池…

C++青少年简明教程:C++的指针入门

C青少年简明教程&#xff1a;C的指针入门 说到指针&#xff0c;就不可能脱离开内存。了解C的指针对于初学者来说可能有些复杂&#xff0c;我们可以试着以一种简单、形象且易于理解的方式来解释&#xff1a; 首先&#xff0c;我们可以将计算机内存想象成一个巨大的有许多格子的…

快速开发的UI框架:效率蹭蹭提高!!【送源码】

不知道各位用uniapp 开发移动端小程序或者网页&#xff0c;是否用UI框架。 我一般就用官方自带的&#xff0c;近期一个项目 用了uView, 感觉整体还不错&#xff0c;类似蚂蚁的风格。 特此推荐下&#xff0c;可以收藏一下&#xff0c;需要的时候记得来取哦&#xff01; 介绍 …

Linux 线程控制

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux初窥门径⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 前言 1.线程现象 2.线程等待 3.线程…

【APP移动端自动化测试】第四节.元素操作的API

文章目录 前言一、点击&输入&清空操作 1.1 点击元素 1.2 输入&清空元素二、获取文本内容&位置&大小操作 2.1 获取文本内容 2.2 获取位置&大小三、根据属性名获取属性值操作四、滑动和拖拽操作 4.1 _swipe 4.2 _scroll …

博瓦科技产品亮相湖北安博会啦!!!

6月12日&#xff0c;第二十三届2024中国&#xff08;武汉&#xff09;社会公共安全产品暨数字城市产业展览会&#xff08;简称&#xff1a;湖北安博会&#xff09;在武汉国际会展中心隆重开幕。作为行业内最具影响力的展会之一&#xff0c;此次盛会将汇聚来自全球的顶尖企业、专…

G6 - CycleGAN实战

&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/) 目录 理论知识CycleGAN能做什么 模型结构损失函数 模型效果总结与…

每天五分钟深度学习:逻辑回归算法完成m个样本的梯度下降

本文重点 上节课程我们学习了单样本逻辑回归算法的梯度下降,实际使用中我们肯定是m个样本的梯度下降,那么m个样本的如何完成梯度下降呢? m个样本的损失函数定义为: 我们定义第i个样本的dw、db为: dw和db为损失J对w和b的偏导数,因为m个样本的代价函数J是1到m个样本总损失…

适合各行各业在线预约的自定义小程序源码系统 前后端分离 带完整的安装代码包以及代搭建教程

系统概述 这款自定义小程序源码系统是为了适应不同行业的预约需求而设计的。它具有高度的灵活性和可扩展性&#xff0c;可以根据不同用户的需求进行定制化开发&#xff0c;满足各种复杂的业务场景。 系统的前端采用了先进的小程序技术&#xff0c;为用户提供了简洁、直观的操…