当前位置: 首页 > news >正文

RedisTemplate自增时保证原子性的lua脚本限制接口请求频率

场景:限制请求后端接口的频率,例如1秒钟只能请求次数不能超过10次,通常的写法是:

1.先去从redis里面拿到当前请求次数

2.判断当前次数是否大于或等于限制次数

3.当前请求次数小于限制次数时进行自增

这三步在请求不是很密集的时候,程序执行很快,可能不会产生问题,如果两个请求几乎在同一时刻到来,我们第1步和第2步的判断是无法保证原子性的。

改进方式:使用redis的lua脚本,将"读取值、判断大小、自增"放到redis的一次操作中,redis底层所有的操作请求都是串行的,也就是一个请求执行完,才会执行下一个请求。

自增的lua脚本如下

    /*** 自增过期时间的原子性脚本*/private String maxCountScriptText() {return "local key = KEYS[1]\n" +"local count = tonumber(ARGV[1])\n" +"local time = tonumber(ARGV[2])\n" +"local current = redis.call('get', key);\n" +"if current and tonumber(current) > count then\n" +"    return tonumber(current);\n" +"end\n" +"current = redis.call('incr', key)\n" +"if tonumber(current) == 1 then\n" +"    redis.call('expire', key, time)\n" +"end\n" +"return tonumber(current);";}

 将接口限流功能封装成一个注解@RateLimiter,在接口方法上面加上@RateLimiter就可以实现限流:

 redis工具类:

package com.zhou.redis.util;import com.zhou.redis.dto.MyRedisMessage;
import com.zhou.redis.exception.LockException;
import com.zhou.redis.script.MaxCountQueryScript;
import com.zhou.redis.script.MaxCountScript;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;@Configuration
@Slf4j
public class RedisUtil {public RedisTemplate<String, Object> redisTemplate;private MaxCountScript maxCountScript;private MaxCountQueryScript maxCountQueryScript;public RedisUtil(RedisTemplate redisTemplate, MaxCountScript maxCountScript, MaxCountQueryScript maxCountQueryScript) {this.redisTemplate = redisTemplate;this.maxCountScript = maxCountScript;this.maxCountQueryScript = maxCountQueryScript;}/***  尝试加锁,返回加锁成功或者失败* @param time 秒**/public boolean tryLock(String key,Object value,Long time){if(time == null || time <= 0){time = 30L;}Boolean b = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(time));return b == null ? false : b;}/***  释放锁(拿到锁之后才能调用释放锁)**/public boolean unLock(String key){Boolean b = redisTemplate.delete(key);return b == null ? false : b;}/*** 对key进行自增1* @param maxCount  最大值* @param time      增加次数* @return          自增后的值*/public Long incr(String key,int maxCount, int time){List<String> keys = Collections.singletonList(key);return  redisTemplate.execute(maxCountScript, keys, maxCount, time);}/*** 获得当前值*/public Long incrNow(String key){List<String> keys = Collections.singletonList(key);return  redisTemplate.execute(maxCountQueryScript, keys);}
}

 redis配置类:

package com.zhou.redis.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhou.redis.listener.MyRedisListener;
import com.zhou.redis.script.MaxCountQueryScript;
import com.zhou.redis.script.MaxCountScript;
import com.zhou.redis.util.RedisTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.util.Arrays;
import java.util.List;@Configuration
public class RedisConfig {@SuppressWarnings("all")@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);//Json序列化配置Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);//String的序列化StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();//key采用string的序列化template.setKeySerializer(stringRedisSerializer);//hash的key采用string的序列化template.setHashKeySerializer(stringRedisSerializer);//value序列化采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);//hash的value序列化方式采用jacksontemplate.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}/*** Redis消息监听器容器* 这个容器加载了RedisConnectionFactory和消息监听器* 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理** @param redisConnectionFactory 连接工厂* @param adapter                适配器* @return redis消息监听容器*/@Bean@SuppressWarnings("all")public RedisMessageListenerContainer container(RedisConnectionFactory redisConnectionFactory,FuncUpdateListener listener,MessageListenerAdapter adapter) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();// 监听所有库的key过期事件container.setConnectionFactory(redisConnectionFactory);// 所有的订阅消息,都需要在这里进行注册绑定,new PatternTopic(TOPIC_NAME1)表示发布的主题信息// 可以添加多个 messageListener,配置不同的通道List<Topic> topicList = Arrays.asList(new PatternTopic(RedisTopic.TOPIC1),new PatternTopic(RedisTopic.TOPIC2));container.addMessageListener(listener, topicList);/*** 设置序列化对象* 特别注意:1. 发布的时候需要设置序列化;订阅方也需要设置序列化*         2. 设置序列化对象必须放在[加入消息监听器]这一步后面,否则会导致接收器接收不到消息*/Jackson2JsonRedisSerializer seria = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);seria.setObjectMapper(objectMapper);container.setTopicSerializer(seria);return container;}/*** 这个地方是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”* 也有好几个重载方法,这边默认调用处理器的方法 叫OnMessage*/@SuppressWarnings("all")@Beanpublic MessageListenerAdapter listenerAdapter() {//MessageListenerAdapter receiveMessage = new MessageListenerAdapter(printMessageReceiver, "receiveMessage");MessageListenerAdapter receiveMessage = new MessageListenerAdapter();Jackson2JsonRedisSerializer seria = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);seria.setObjectMapper(objectMapper);receiveMessage.setSerializer(seria);return receiveMessage;}@Beanpublic MaxCountScript maxCountScript() {return new MaxCountScript(maxCountScriptText());}@Beanpublic MaxCountQueryScript maxCountQueryScript() {return new MaxCountQueryScript(maxCountQueryScriptText());}/*** 自增过期时间的原子性脚本*/private String maxCountScriptText() {return "local key = KEYS[1]\n" +"local count = tonumber(ARGV[1])\n" +"local time = tonumber(ARGV[2])\n" +"local current = redis.call('get', key);\n" +"if current and tonumber(current) > count then\n" +"    return tonumber(current);\n" +"end\n" +"current = redis.call('incr', key)\n" +"if tonumber(current) == 1 then\n" +"    redis.call('expire', key, time)\n" +"end\n" +"return tonumber(current);";/*return "local limitMaxCount = tonumber(ARGV[1])\n" +"local limitSecond = tonumber(ARGV[2])\n" +"local num = tonumber(redis.call('get', KEYS[1]) or '-1')\n" +"if limitMaxCount then\n" +"   return -1\n" +"end\n" +"if num == -1 then\n" +"    redis.call('incr', KEYS[1])\n" +"    redis.call('expire', KEYS[1], limitSecond)\n" +"    return 1\n" +"else\n" +"    if num >= limitMaxCount then\n" +"        return 0\n" +"    else\n" +"        redis.call('incr', KEYS[1])\n" +"        return 1\n" +"    end\n" +"end";*/}/*** 查询当前值脚本*/private String maxCountQueryScriptText() {return "local key = KEYS[1]\n" +"local current = redis.call('get', key);\n" +"if current then\n" +"    return tonumber(current);\n" +"else\n" +"    return current\n" +"end\n";}
}

 拦截模式枚举类:根据ip拦截或者方法拦截

package com.zhou.aop;/*** @author lang.zhou* @since 2023/1/31 17:56*/
public enum LimitType {IP,DEFAULT
}

 封装自定义注解:@RateLimiter

package com.zhou.aop;import java.lang.annotation.*;/*** @author lang.zhou* @since 2023/1/31 17:49*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {/*** 限流key*/String key() default "RateLimiter";/*** 限流时间,单位秒*/int time() default 60;/*** 限流次数*/int count() default 100;/*** 限流类型*/LimitType limitType() default LimitType.DEFAULT;/*** 限流后返回的文字*/String limitMsg() default "访问过于频繁,请稍候再试";
}

 注解的切面逻辑:

package com.zhou.aop;import com.zhou.redis.util.RedisUtil;
import com.zhou.common.utils.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;/*** 接口限流切面* @author lang.zhou* @since 2023/1/31 17:50*/
@Aspect
@Slf4j
@Component
public class RateLimiterAspect {@Autowiredprivate RedisUtil redisUtils;@Before("@annotation(rateLimiter)")public void doBefore(JoinPoint point, RateLimiter rateLimiter) {int time = rateLimiter.time();int count = rateLimiter.count();String combineKey = getCombineKey(rateLimiter, point);try {Long number = redisUtils.incr(combineKey, count, time);if (number == null || number.intValue() > count){log.info("请求【{}】被拦截,{}秒内请求次数{}",combineKey,time,number);throw new RuntimeException(rateLimiter.limitMsg());}} catch (ServiceRuntimeException e) {throw e;} catch (Exception e) {throw new RuntimeException("网络繁忙,请稍候再试");}}/*** 获取限流key*/public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {StringBuilder s = new StringBuilder(rateLimiter.key());ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if(requestAttributes != null){HttpServletRequest request = requestAttributes.getRequest();if (rateLimiter.limitType() == LimitType.IP) {s.append(IpUtil.getIpAddr(request)).append("-");}}MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();Class<?> targetClass = method.getDeclaringClass();s.append(targetClass.getName()).append(".").append(method.getName());return s.toString();}}

 lua自增脚本类:

package com.zhou.redis.script;import org.springframework.data.redis.core.script.DefaultRedisScript;/*** @author lang.zhou* @since 2023/2/25*/
public class MaxCountScript extends DefaultRedisScript<Long> {public MaxCountScript(String script) {super(script,Long.class);}
}

 lua查询当前值的脚本类:

package com.zhou.redis.script;import org.springframework.data.redis.core.script.DefaultRedisScript;/*** @author lang.zhou* @since 2023/2/25*/
public class MaxCountQueryScript extends DefaultRedisScript<Long> {public MaxCountQueryScript(String script) {super(script,Long.class);}
}

 订阅消息通道的枚举:

package com.zhou.redis.util;public class RedisTopic {public static final String TOPIC1 = "TOPIC1";public static final String TOPIC2 = "TOPIC2";
}

消息实体类: 

package com.zhou.redis.dto;import lombok.Data;import java.io.Serializable;/*** redis订阅消息实体* @since 2022/11/11 17:34*/
@Data
public class MyRedisMessage implements Serializable {private String msg;
}

订阅消息监听器: 

package com.zhou.redis.listener;import com.zhou.redis.dto.MyRedisMessage;
import com.zhou.redis.util.RedisTopic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import javax.script.ScriptException;/*** @author lang.zhou*/
@Slf4j
@Component
public class MyRedisListener implements MessageListener {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Overridepublic void onMessage(Message message, byte[] pattern) {String topic = new String(pattern);// 接收的topiclog.info("channel:{}" , topic);if(RedisTopic.TOPIC1.equals(topic)){//}else if(RedisTopic.TOPIC2.equals(topic)){//序列化对象(特别注意:发布的时候需要设置序列化;订阅方也需要设置序列化)MyRedisMessage msg = (MyRedisMessage) redisTemplate.getValueSerializer().deserialize(message.getBody());log.info("message:{}",msg);}}
}

注解使用方式:1秒内一个ip最多只能请求10次

@RestController
@RequestMapping("/test/api")
public class CheckController{@PostMapping("/limit")@RateLimiter(time = 1, count = 10, limitType = LimitType.IP, limitMsg = "请求过于频繁,请稍后重试")public void limit(HttpServletRequest request){//执行业务代码}}

相关文章:

RedisTemplate自增时保证原子性的lua脚本限制接口请求频率

场景&#xff1a;限制请求后端接口的频率&#xff0c;例如1秒钟只能请求次数不能超过10次&#xff0c;通常的写法是&#xff1a; 1.先去从redis里面拿到当前请求次数 2.判断当前次数是否大于或等于限制次数 3.当前请求次数小于限制次数时进行自增 这三步在请求不是很密集的时…...

《通信基站绿色低碳服务评价技术要求》团体标准顺利通过技术审查

2023年12月14日团体标准《通信基站绿色低碳服务评价技术要求》召开了技术审查视频会议。来自节能权威机构、科研院校、通信行业企业的专家以及标准编制组代表参加了本次会议。 技术审查专家组由郑州大学能动学院教授赵金辉、国家节能中心节能技术推广处处长辛升、中国标准化研…...

堆排序(C语言版)

一.堆排序 堆排序即利用堆的思想来进行排序&#xff0c;总共分为两个步骤&#xff1a; 1. 建堆 升序&#xff1a;建大堆 降序&#xff1a;建小堆 2. 利用堆删除思想来进行排序 1.1.利用上下调整法实现堆排序 第一步&#xff1a;建堆 好了&#xff0c;每次建堆都要问自己…...

实现区域地图散点图效果,vue+echart地图+散点图

需求&#xff1a;根据后端返回的定位坐标数据实现定位渲染 1.效果图 2.准备工作,在main.js和index.js文件中添加以下内容 main.js app.use(BaiduMap, {// ak 是在百度地图开发者平台申请的密钥 详见 http://lbsyun.baidu.com/apiconsole/key */ak: sRDDfAKpCSG5iF1rvwph4Q95M…...

Kubernetes 学习总结(41)—— 云原生容器网络详解

背景 随着网络技术的发展&#xff0c;网络的虚拟化程度越来越高&#xff0c;特别是云原生网络&#xff0c;叠加了物理网络、虚机网络和容器网络&#xff0c;数据包在网络 OSI 七层网络模型、TCP/IP 五层网络模型的不同网络层进行封包、转发和解包。网络数据包跨主机网络、容器…...

多人协同开发git flow,创建初始化项目版本

文章目录 多人协同开发git flow&#xff0c;创建初始化项目版本1.gitee创建组织模拟多人协同开发2.git tag 打标签3.git push origin --tags 多人协同开发git flow&#xff0c;创建初始化项目版本 1.gitee创建组织模拟多人协同开发 组织中新建仓库 推送代码到我们组织的仓库 2…...

「Kafka」入门篇

「Kafka」入门篇 基础架构 Kafka 快速入门 集群规划 集群部署 官方下载地址&#xff1a;http://kafka.apache.org/downloads.html 解压安装包&#xff1a; [atguiguhadoop102 software]$ tar -zxvf kafka_2.12-3.0.0.tgz -C /opt/module/修改解压后的文件名称&#xff1a; [a…...

PHP8的JIT(Just-In-Time)编译器是什么?

PHP8的JIT&#xff08;Just-In-Time&#xff09;编译器是什么&#xff1f; PHP8是最新的PHP版本&#xff0c;引入了JIT&#xff08;Just-In-Time&#xff09;编译器&#xff0c;以进一步提高性能和执行速度。 JIT编译器是一种在运行时将解释性语言转化为机器码的技术。在过去…...

【C++对于C语言的扩充】C++与C语言的联系,命名空间、C++中的输入输出以及缺省参数

文章目录 &#x1f680;前言&#x1f680;C有何过C之处&#xff1f;&#x1f680;C中的关键字&#x1f680;命名空间✈️为什么要引入命名空间&#xff1f;✈️命名空间的定义✈️如何使用命名空间中的内容呢&#xff1f; &#x1f680;C中的输入和输出✈️C标准库的命名空间✈…...

Excel中部分sheet页隐藏并设置访问密码

1、新建sheet1 2、新建sheet2 3、隐藏sheet2 4、保护工作簿、输密码 5、密码二次确认 6、隐藏的sheet2已经查看不了 7、想要查看时&#xff0c;按图示输入原密码即可 8、查看sheet2内容...

从零开始配置pwn环境:CTF PWN 做题环境

前期在kali2023环境安装的pwndocker使用发现不好用&#xff0c;so找了网上配置好pwn环境的虚拟机。 GitHub - giantbranch/pwn-env-init: CTF PWN 做题环境一键搭建脚本 可以直接下载我配置好的Ubuntu 16.04&#xff0c;为VMware导出的ovf格式 链接&#xff1a;百度网盘 请输…...

Vue3复习笔记

目录 挂载全局属性和方法 v-bind一次绑定多个值 v-bind用在样式中 Vue指令绑定值 Vue指令绑定属性 动态属性的约束 Dom更新时机 ”可写的“计算属性 v-if与v-for不建议同时使用 v-for遍历对象 数组变化检测 事件修饰符 v-model用在表单类标签上 v-model还可以绑定…...

【OpenCV】OpenCV:计算机视觉的强大工具库

摘要   OpenCV是一个广泛应用于计算机视觉领域的开源工具库&#xff0c;为开发者提供了丰富的图像处理和计算机视觉算法。本文将介绍OpenCV的功能和应用领域&#xff0c;并探讨它在实践中的重要性和前景。 计算机视觉的强大工具库 一、什么是OpenCV&#xff1f;二、OpenCV的功…...

spring-boot-autoconfigure误引入spring-boot-starter-data-jpa而导致数据源初始化异常

一、现状描述 某个Grade类引入了jpa的注解&#xff1a; import javax.persistence.Column; import javax.persistence.Embeddable;/*** 年级*/ Embeddable public class Grade {Column(name "code")private int code; }并且pom.xml中引入该jar包&#xff1a;sprin…...

工程(十六)——自己数据集跑Fast_livo

一、基础环境 Ubuntu20.04 ROS noetic PCL 1.8 Eigen 3.3.4 Sophus git clone https://github.com/strasdat/Sophus.git cd Sophus git checkout a621ff mkdir build && cd build && cmake .. make sudo make install 下面两个直接把包下载下来一起编译…...

PostgreSQL数据库的json操作

1.操作符 select json字段::json->key值 from order -- 对象域 select json字段::json->>key值 from order -- 文本 select json字段::json#>{key值} from order -- 对象域 select json字段::json#>>{key值} from order -- 文本对象域表示还能继续操作&#…...

gradio-osprey-demo

创建需要的dockerfle ################### # 使用 Ubuntu 作为基础镜像 FROM nvcr.io/nvidia/cuda:11.8.0-devel-ubuntu22.04 # 更新软件包列表并安装依赖项 RUN apt update && \ apt install -y python3 python3-pip git ffmpeg libsm6 libxext6 curl wget …...

从仿写持久层框架到MyBatis核心源码阅读

接上篇手写持久层框架&#xff1a;https://blog.csdn.net/liwenyang1992/article/details/134884703 MyBatis源码 MyBatis架构原理&主要组件 MyBatis架构设计 MyBatis架构四层作用是什么呢&#xff1f; API接口层&#xff1a;提供API&#xff0c;增加、删除、修改、查询…...

浏览器常用基本操作之python3+selenium4自动化测试

1、打开指定的网页地址 我们使用selenium进行自动化测试时&#xff0c;打开浏览器之后&#xff0c;第一步就是让浏览器访问我们指定的地址&#xff0c;可使用get方法实现 1 2 3 from selenium import webdriver driver webdriver.Edge() driver.get(https://www.baidu.com/)…...

在MySQL中使用VARCHAR字段进行日期筛选

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

日常一水C

多态 言简意赅&#xff1a;就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过&#xff0c;当子类和父类的函数名相同时&#xff0c;会隐藏父类的同名函数转而调用子类的同名函数&#xff0c;如果要调用父类的同名函数&#xff0c;那么就需要对父类进行引用&#…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

WEB3全栈开发——面试专业技能点P7前端与链上集成

一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染&#xff08;SSR&#xff09;与静态网站生成&#xff08;SSG&#xff09; 框架&#xff0c;由 Vercel 开发。它简化了构建生产级 React 应用的过程&#xff0c;并内置了很多特性&#xff1a; ✅ 文件系…...