gateway Redisson接口级别限流解决方案

文章目录

  • 前言
    • 1. 计数器算法(固定窗口限流器)
    • 2. 滑动窗口日志限流器
    • 3. 漏桶算法(Leaky Bucket)
    • 4. 令牌桶算法(Token Bucket)
    • 5. 限流队列
    • 应用场景
    • 实现工具
  • 一、Redisson简介
  • 二、Redisson限流器的原理
  • 三、Redisson限流器技术的应用
  • 四、gateway自定义接口限流实现
    • 1. maven
    • 2. 接口限流数据结构定义RateLimitProperties
    • 3. RedissonClient 注入
    • 4. redis 初始化写入限流配置
    • 5. 接口过滤限流规则
    • 6. nacos配置热更新redis配置
    • 7. 接口匹配工具类
  • 总结


前言

在当今互联网时代,面对快速增长的用户流量和不断扩大的系统规模,合理的限流策略变得愈发重要。针对这一问题,Redisson作为一款高性能的分布式应用开发框架,在限流方面拥有出色的技术。本文将深入探讨Redisson的限流器技术,为读者详细介绍其原理和应用。

在软件架构中,限流器(Rate Limiter)是一种用于控制资源利用、维持服务质量和防止系统过载的重要组件。限流器可以按照不同的策略和算法来实现,主要包括以下几种类型:

1. 计数器算法(固定窗口限流器)

原理:在一个固定的时间窗口内计算请求的数量,如果请求超过了预设的最大阈值,则拒绝后续的请求,直到下一个时间窗口开始。

优点:简单易理解和实现。

缺点:在窗口切换的时刻可能会发生两倍于阈值的请求量,即“窗口边缘效应”。

2. 滑动窗口日志限流器

原理:改进了固定窗口算法的边缘效应。它记录了每个请求的时间戳,将时间窗口分成多个小窗口,滑动时只统计当前时间窗口内的请求量。

优点:比固定窗口算法更平滑,减少了窗口切换时的峰值。

缺点:需要记录更多的请求信息,可能会增加系统开销。

3. 漏桶算法(Leaky Bucket)

原理:所有的请求都被放入到一个固定容量的桶里,请求按照固定的速率从桶中“漏出”(被处理)。如果桶满了,则会丢弃进来的请求。

优点:输出流量比较平滑,即使短时间内有大量请求到达,输出速率也是恒定的。

缺点:不具备突发流量的处理能力,对于突发请求仍然可能导致请求被丢弃。
请添加图片描述

4. 令牌桶算法(Token Bucket)

原理:令牌以固定速率被添加到桶中,每个请求必须消耗一个令牌才能被处理。如果桶中没有令牌,则请求被排队或丢弃,直到有令牌可用。

优点:允许一定程度的突发流量,因为桶中可以累积一些令牌。

缺点:如果突发流量持续时间较长,超过了桶的容量,那么超出的请求仍然会被丢弃或延迟处理。
请添加图片描述

5. 限流队列

原理:请求首先进入一个队列,队列根据系统的处理能力和预设的规则控制请求的进入和退出。

优点:可以根据系统负载动态调整请求的进出,对系统影响较小。

缺点:实现相对复杂,需要精确控制队列的大小和请求的处理速率。

应用场景

  • API网关:在API网关层面进行限流,可以防止过多的请求直接压垮后端服务。
  • 分布式系统:在分布式服务之间进行限流,可以协调不同服务的负载,防止某些服务成为瓶颈。
  • 微服务架构:对单个微服务的接口进行限流,保证服务的稳定性。

实现工具

  • Guava RateLimiter:基于令牌桶算法,是Java中一个广泛使用的限流实现。
  • Nginx:使用漏桶算法进行HTTP请求的限流。
  • Redis:可以使用Redis的原子性操作和脚本来实现复杂的限流策略。

选择合适的限流策略应当基于系统的具体需求,比如是否需要应对突发流量、系统的可用性要求、实现的复杂度等因素。在实际应用中,很可能会结合多种策略来实现更加精细化的流量控制。

一、Redisson简介

Redisson是一个基于Redis的Java驻留内存(In-Memory)数据网格中间件,提供了丰富的分布式对象和服务,可用于开发高性能、可扩展的分布式应用。其中,Redisson的限流器技术是其重要的功能之一。

二、Redisson限流器的原理

Redisson限流器采用了一种基于令牌桶算法的策略。令牌桶算法通过维护一个固定容量的令牌桶,在每个时间段内产生若干个令牌,请求在获取令牌后才能进行处理。这种算法可以有效控制流量,并在一定程度上保护系统免受突然的高并发压力。

三、Redisson限流器技术的应用

  1. 系统流控
    Redisson的限流器技术可以用于对系统进行流量控制,确保系统在高负载情况下依然能够提供稳定可靠的服务。通过限制请求的速率和并发数,可以避免系统因为过载而崩溃或变得不可用。

  2. 接口保护
    对于一些重要的接口或某些敏感资源,我们可能希望限制用户的访问频率,以避免恶意的使用或攻击。Redisson的限流器技术可以很好地实现这一目的,通过限制请求的速率和次数,有效保护接口的安全性和可用性。

  3. 基于用户的限流
    对于一些特定用户或特定用户组,我们可能需要设置不同的限流策略。Redisson提供了基于用户的限流管理能力,可以根据用户身份信息或其他特征进行流量控制,满足个性化的限流需求。

四、gateway自定义接口限流实现

1. maven

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.1</version></dependency>

2. 接口限流数据结构定义RateLimitProperties

gateway.yaml

gateway:rate-limit:configs:- name: 服务1option:- uri: 接口1/*matchType: 1size: 200microSecond: 1000- name: 服务2option:- uri: 接口1/*matchType: 1size: 1second: 1- uri: 接口2matchType: 0size: 1microSecond: 1000- name: 服务3option:- uri: 接口1matchType: 0size: 10microSecond: 1000consumer: 1waitTimer: 1000

限流配置类 RateLimitProperties

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;/*** 限流配置获取,支持热更新*/@Data
@Component
@JsonIgnoreProperties(ignoreUnknown = true)
@ConfigurationProperties(prefix = "gateway.rate-limit")
public class RateLimitProperties {private List<Configs> configs;@Data@Component@ConfigurationProperties(prefix = "gateway.rate-limit.configs")public static class Configs {private String name;private List<Option> option = new ArrayList<>();@Data@Component@JsonIgnoreProperties(ignoreUnknown = true)@ConfigurationProperties(prefix = "gateway.rate-limit.configs.option")public static class Option {private String uri; //api uriprivate int matchType; // 匹配类型 0 完整匹配 1 模糊匹配private int size; //令牌桶 大小private int microSecond = 1000; //生成令牌的周期private int consumer = 1; //一个请求消耗令牌数量private int waitTimer = 0; // 等待最多xx s后再拿令牌}}
}

3. RedissonClient 注入

RedisConfig

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.TransportMode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Value("${spring.redis.host}")private String redisAddress;@Value("${spring.redis.port}")private String redisPort;@Value("${spring.redis.password}")private String redisPas;@Value("${spring.redis.database:1}")private Integer redisDb;@Beanpublic RedissonClient redissonClient(RedisProperties redisProperties) {Config config = new Config();config.setTransportMode(TransportMode.NIO);config.useSingleServer().setAddress("redis://" + redisAddress + ":" + redisPort).setPassword(redisPas).setDatabase(redisDb);return Redisson.create(config);}public static void redisSerialize(RedisTemplate redisTemplate) {// 2.设置统一序列化规则RedisSerializer stringRedisSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringRedisSerializer);redisTemplate.setHashKeySerializer(stringRedisSerializer);//Json序列化配置Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = serializer();redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setEnableTransactionSupport(false);}private static Jackson2JsonRedisSerializer serializer() {//Json序列化配置Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL,JsonTypeInfo.As.WRAPPER_ARRAY);jackson2JsonRedisSerializer.setObjectMapper(om);return jackson2JsonRedisSerializer;}
}

4. redis 初始化写入限流配置

ApiRateLimitInit

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;import javax.annotation.Resource;
import java.util.List;@Configuration
@Slf4j
public class ApiRateLimitInit {@Resourceprivate RateLimitProperties properties;@Resourceprivate RedisTemplate<String, RateLimitProperties.Configs> redisTemplate;@Resourceprivate RedissonClient redissonClient;@Beanpublic void initApis() {List<RateLimitProperties.Configs> configs = properties.getConfigs();//将最新的配置放到redis中,在做限流判断的代码中,直接从redis中读取最新的配置redisLimitConfig(configs);}public void redisLimitConfig(List<RateLimitProperties.Configs> configs) {RedisConfig.redisSerialize(redisTemplate);redisTemplate.delete("ApiRateLimitFilter");RKeys keys = redissonClient.getKeys();keys.deleteByPattern("*buslimiters*");if (ObjectUtil.isNotNull(configs)) {redisTemplate.opsForList().leftPushAll("ApiRateLimitFilter", configs);//重新初始化所有limiterfor (RateLimitProperties.Configs temp : configs) {temp.getOption().forEach(o -> {RRateLimiter limiter = redissonClient.getRateLimiter(StrUtil.format("buslimiters:{}:{}", temp.getName(), o.getUri()));limiter.trySetRate(RateType.OVERALL, o.getSize(), o.getMicroSecond(), RateIntervalUnit.MILLISECONDS);});}}}
}

5. 接口过滤限流规则

ApiRateLimitFilter

import cn.hutool.core.util.StrUtil;
import com.gsafety.bg.gsdss.gateway.contants.CommonConstant;
import com.gsafety.bg.gsdss.gateway.utils.ResponseUtils;
import com.gsafety.bg.gsdss.gateway.utils.WebUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;@Slf4j
@Component
public class ApiRateLimitFilter implements GlobalFilter, Ordered {@Resourceprivate RedissonClient redissonClient;@Resourceprivate RedisTemplate<String, RateLimitProperties.Configs> redisTemplate;@Overridepublic int getOrder() {return 0;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String uri = exchange.getRequest().getURI().getPath().substring(1);int index = uri.indexOf("/");String microServiceName = StringUtils.substringBefore(uri, "/");String apiPath = uri.substring(index);List<RateLimitProperties.Configs> configs = redisTemplate.opsForList().range("ApiRateLimitFilter", 0, -1);List<RateLimitProperties.Configs.Option> option = configs.stream().filter(s -> microServiceName.equals(s.getName())).findFirst().orElse(new RateLimitProperties.Configs()).getOption();RateLimitProperties.Configs.Option thisOption = WebUtils.matchesRateLimit(apiPath, option);//不在限流配置中if (thisOption == null) {return chain.filter(exchange);}RRateLimiter limiter = redissonClient.getRateLimiter(StrUtil.format("buslimiters:{}:{}", microServiceName, thisOption.getUri()));boolean acquire = limiter.tryAcquire(thisOption.getConsumer(), thisOption.getWaitTimer(), TimeUnit.MICROSECONDS);if (acquire) {return chain.filter(exchange);}log.info(uri + "限流");//exchange 信息放入kafka中,等待消费return ResponseUtils.error(exchange.getResponse(), CommonConstant.ErrorResp.BUSY_ERROR);}
}

6. nacos配置热更新redis配置

RateLimitNacosListener

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.listener.Listener;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.Executor;@Slf4j
@Configuration
public class RateLimitNacosListener implements InitializingBean {@Value("${spring.application.name}")private String appName;@Resourceprivate NacosConfigManager nacosConfigManager;@Resourceprivate NacosConfigProperties configProperties;@Resourceprivate ApiRateLimitInit rateLimitInit;@Overridepublic void afterPropertiesSet() throws Exception {nacosConfigManager.getConfigService().addListener(appName + ".yaml", configProperties.getGroup(),new Listener() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic void receiveConfigInfo(String configInfo) {YAMLMapper yamlMapper = new YAMLMapper();try {JsonNode jsonNode = yamlMapper.readTree(configInfo);String configStr = jsonNode.get("gateway").get("rate-limit").get("configs").toPrettyString();List<RateLimitProperties.Configs> configs = JSONUtil.toList(configStr, RateLimitProperties.Configs.class);//将最新的配置放到redis中,在做限流判断的代码中,直接从redis中读取最新的配置rateLimitInit.redisLimitConfig(configs);log.info("config更新:" + configStr);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}});}
}

7. 接口匹配工具类

WebUtils

/*** WebUtils 工具类**/
public class WebUtils {/*** 获取限流接口对象*/public static RateLimitProperties.Configs.Option matchesRateLimit(String uri, List<RateLimitProperties.Configs.Option> all) {PathMatcher matcher = new AntPathMatcher();List<RateLimitProperties.Configs.Option> allMatch = all.stream().filter(o -> !o.getUri().endsWith("*")).collect(Collectors.toList());for (RateLimitProperties.Configs.Option option : allMatch) {if (StringUtils.equals(option.getUri(), uri)) {return option;}}List<RateLimitProperties.Configs.Option> fuzzyMatch = all.stream().filter(o -> o.getUri().endsWith("*")).collect(Collectors.toList());for (RateLimitProperties.Configs.Option option : fuzzyMatch) {if (matcher.match(option.getUri(), uri)) {return option;}}return null;}
}

总结

其他方案尝试:
gateway自带限流器:令牌桶算法实现,支持自定义key-resolver,即限流规则(用户、ip、请求地址等),但是只支持服务级别限流,无法限制到具体接口

Redisson的限流器技术是一项用于控制系统流量并保护重要接口的强大工具。它通过令牌桶算法,提供了灵活的流控策略和个性化的限流管理,帮助开发者有效应对高负载和突发流量的挑战。在实际应用中,合理利用Redisson的限流器技术,可以提高系统的稳定性、安全性和可用性,为用户提供更好的体验。


在这里插入图片描述

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

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

相关文章

面向对象三大特征之三:多态--java学习笔记

什么是多态 多态是在继承/实现情况下的一种现象&#xff0c;表现为&#xff1a;对象多态、行为多态 对象多态&#xff1a;举个栗子&#xff0c;比如一个人&#xff0c;他可以是一个老师&#xff0c;也可以是一个歌手&#xff0c;也可以是一个丈夫...... 行为多态&#xff1a;举…

PHP反序列化总结4--原生类总结

原生类的简要介绍以及原生类和反序列化的关系 PHP 原生类指的是 PHP 内置的类&#xff0c;它们可以直接在 PHP 代码中使用且无需安装或导入任何库&#xff0c;相当于代码中的内置方法例如echo &#xff0c;print等等可以直接调用&#xff0c;但是原生类就是可以就直接php中直接…

jmeter分布式服务搭建

目录 一、环境准备 二、 安装包下载 三 、安装jdk 四 、控制机安装 4.1 解压压缩包 4.2 修改 bin/jmeter.properties 4.3 修改 bin/system.properties 五、执行机安装 5.1 解压安装包 5.2 修改 bin/jmeter.properties 5.3 修改 bin/system.properties 5.4 启动执行机 …

关于如何禁用、暂停或退出OneDrive等操作,看这篇文件就够了

​想知道如何禁用OneDrive?你可以暂停OneDrive的文件同步,退出应用程序,阻止它在启动时打开,或者永远从你的机器上删除该应用程序。我们将向你展示如何在Windows计算机上完成所有这些操作。 如何在Windows上关闭OneDrive 有多种方法可以防止OneDrive在你的电脑上妨碍你。…

堆排序——高效解决TOP-K问题

. 个人主页&#xff1a;晓风飞 专栏&#xff1a;数据结构|Linux|C语言 路漫漫其修远兮&#xff0c;吾将上下而求索 文章目录 引言什么是堆&#xff1f;建堆堆排序&#xff1a;排序的最终结果 堆排序实现函数声明交换函数 Swap下沉调整 DnAdd堆排序函数 HeapSort主函数 文件中找…

SpringBoot+Vue实现对称加密和非对称加密

我们先来了解一下什么是对称加密和非对称加密&#xff0c;以及两者的优缺点 对称加密 使用同一个密钥对消息进行加密解密 优点&#xff1a;加密和解密的速度快&#xff0c;适合于数据量大的加解密 缺点&#xff1a;密钥在网络传输中可能被泄露&#xff0c;因此安全性相对较低…

C++核心编程三:函数提高(持续更新)

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;C从基础到进阶 &#x1f319;C核心编程&#x1f30f;1 函数提高&#x1f384;1.1 函数默认参数&#x1f384;1.2 函数占位参数&#x1f384;1.3 函数重载&#x1f349;1.3.1 函数重载概述&…

护眼灯有蓝光吗?防蓝光护眼台灯推荐

护眼台灯是家长为孩子购买的常见用品之一&#xff0c;但对于它的了解却不够深入&#xff0c;很多人购买之后反而容易出现眼睛疲劳、不适的情况&#xff01;据了解&#xff0c;主要的原因是因为在选择护眼台灯时&#xff0c;大多数人没有专业知识&#xff0c;没有买到合适的护眼…

012集:三目运算符实例讲解(if else)及for、while循环—python基础入门实例

Python也有自己的三目运算符&#xff1a; 条件为真时的结果 if 判段的条件 else 条件为假时的结果 即&#xff1a;Python可以通过if语句来实现三目运算符的功能&#xff0c;因此可以把这种if语句当做三目运算符&#xff0c;具体语法格式如下&#xff1a; 返回True执行 if 表达…

Github搭建图床 github搭建静态资源库 免费CDN加速 github搭建图床使用 jsdelivr CDN免费加速访问

Github搭建图床 github搭建静态资源库 免费CDN加速 github搭建图床使用 jsdelivr CDN免费加速访问 前言1、创建仓库2、开启 gh-pages页面功能3、访问测试 前言 写博客文章时&#xff0c;图片的上传和存放是一个问题&#xff0c;使用小众第三方图床&#xff0c;怕不稳定和倒闭&…

.net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别

//全局过滤器 builder.Services.AddMvc(m > { m.Filters.Add<AllResultFilter>(); }); 1、实现过滤器 public class AllResultFilter : IResultFilter {/// <summary>/// 结果执行后方法/// 不可更改结果/// </summary>/// <param name"con…

springboot+mysql大学生就业推荐系统-计算机毕业设计源码01535

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对学生就业管理等问题&#xff0c;对学生就业…

扒开MySQL的源码,探索MVCC实现方式

下载MySQL源码 没有什么比源码更靠谱的了&#xff0c;所以我们先把源码下载下来&#xff0c;后期验证使用MySQL源码下载 MVCC是什么 mvvc全称是multi-version concurrency control&#xff08;多版本并发控制&#xff09;&#xff0c;主要用于处理读写并发冲突的问题。 MVC…

大数据开发之Hive(企业级调优)

第 10 章&#xff1a;企业级调优 创建测试用例 1、建大表、小表和JOIN后表的语句 // 创建大表 create table bigtable(id bigint, t bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by \t; //…

Asp .Net Core 系列:基于 Swashbuckle.AspNetCore 包 集成 Swagger

什么是 Swagger? Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。它提供了一种规范的方式来定义、构建和文档化 RESTful Web 服务&#xff0c;使客户端能够发现和理解各种服务的功能。Swagger 的目标是使部署管理和使用功…

Halcon滤波器 laplace 算子

Halcon滤波器 laplace 算子 使用laplace 算子对图像进行二次求导&#xff0c;会在边缘产生零点&#xff0c;因此该算子常常与zero_crossing算子配合使用。求出这些零点&#xff0c;也就得到了图像的边缘。同时&#xff0c;由于laplace算子对孤立像素的响应要比对边缘或线的响应…

【SpringBoot系列】JDK动态代理

🤵‍♂️ 个人主页:@香菜的个人主页,加 ischongxin ,备注csdn ✍🏻作者简介:csdn 认证博客专家,游戏开发领域优质创作者,华为云享专家,2021年度华为云年度十佳博主 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收…

kafka除了作为消息队列还能做什么?

Kafka 最初是为大规模处理日志而构建的。它可以保留消息直到过期&#xff0c;并让各个消费者按照自己的节奏提取消息。 与其之前的竞品不同&#xff0c;Kafka 不仅仅是一个消息队列&#xff0c;它还是一个适用于各种情况的开源事件流平台。 让我们回顾一下流行的 Kafka 用例。 …

C语言经典算法之冒泡排序算法

目录 前言 建议&#xff1a; 简介&#xff1a; 一、代码实现 二、时空复杂度 时间复杂度&#xff1a; 空间复杂度&#xff1a; 总结&#xff1a; 前言 建议&#xff1a; 1.学习算法最重要的是理解算法的每一步&#xff0c;而不是记住算法。 2.建议读者学习算法的时候…

CNN:Convolutional Neural Network(下)

目录 1 CNN 学到的是什么 1.1 Convolution 中的参数 1.2 FFN 中的参数 1.3 Output 2 Deep Dream 3 Deep Style 4 More Application 4.1 AlphaGo 4.2 Speech 4.3 Text 原视频&#xff1a;李宏毅 2020&#xff1a;Convolutional Neural Network 本博客属于学…