借助Nacos配置中心实现一个动态线程池

news2024/10/5 19:16:57

目录

一、实现思路

二、实现说明概览

三、代码实现

DynamicThreadPool

RejectedProxyInvocationHandler

DynamicThreadPoolRegister

 DynamicThreadPoolRefresher 

测试动态线程池


平常我们系统中定义的一些线程池如果要想修改的话,需要修改配置重启服务才能生效,这样使用起来很不方便,而且对线程池也没有一个监控告警,基于以上原因,我们可以自定义一个可以动态修改线程池配置、对线程池有监控的动态线程池。

美团有一篇技术文章Java线程池实现原理及其在美团业务中的实践,本文的思路也是来源于这篇文章的,有兴趣的同学可以自己看一下。

一、实现思路

我们平常用的线程池ThreadPoolExecutor提供的有修改线程池配置的方法:

  • setCorePoolSize(int corePoolSize) 设置线程池核心线程数量
  • setMaximumPoolSize(int maximumPoolSize) 设置线程池最大线程数量
  • setKeepAliveTime(long time, TimeUnit unit) 设置线程池非核心线程空闲时间
  • setRejectedExecutionHandler(RejectedExecutionHandler handler) 设置线程池拒绝策略

        通过这几个set方法可以在系统运行时动态修改线程池实例的参数

        那么如果动态修改配置呢?

        可以借助Nacos配置中心来完成,我们将线程池的参数配置在Nacos配置中心,我们修改配置文件时Nacos监听器会监听到线程池配置的变动,我们调用线程池的set方法即可动态修改线程池参数。

        如果有多个线程池,怎么知道修改的哪个线程池的配置参数呢?

        可以给线程池设置一个id,将线程池注册到一个Map中,Map中的key和value分别是线程池id和对应的线程池实例,我们根据配置中的线程池id去找到对应的线程池实例进行修改。

        示例如下:threadPoolId1和threadPoolId2是线程池的唯一标识

dynamic.threadpool.threadPoolId1.coreSize=3
dynamic.threadpool.threadPoolId1.maxSize=8
dynamic.threadpool.threadPoolId2.coreSize=12
dynamic.threadpool.threadPoolId2.maxSize=20

二、实现说明概览

自定义线程池包含如下四个类:

  • DynamicThreadPool 自定义动态线程池:继承了ThreadPoolExecutor,定义了一个唯一的线程池id
  • RejectedProxyInvocationHandler 线程池拒绝策略代理类:线程池拒绝策略动态代理类,可以在任务被线程池拒绝的时候执行一些自定义的逻辑,例如告警通知
  • DynamicThreadPoolRegister 动态线程池注册器&运行监听器:构建获取DynamicThreadPool实例并将该实例注册到全部Map中,Map中的key和value分别是线程池id和对应的线程池实例,通过Nacos配置变更动态刷新线程池参数配置,另外还有一个后台监听预警任务
  • DynamicThreadPoolRefresher 动态线程池动态刷新处理器:注册Nacos配置监听,调用DynamicThreadPoolRegister的refreshThreadPool方法动态刷新线程池

三、代码实现

DynamicThreadPool

  • 继承了ThreadPoolExecutor,定义了一个唯一的线程池id-threadPoolId
  • 定义了AtomicLong rejectCount用于统计线程池任务拒绝的数量
  • executeTimeOut:通过重写beforeExecute和afterExecute方法,在线程池任务执行前后计算时间差值,即任务的执行时间,可以判断是否大于executeTimeOut任务执行超时时间,超时告警通知
  • RejectedProxyInvocationHandler:线程池拒绝策略动态代理类,可以在任务被线程池拒绝的时候执行一些自定义的逻辑,例如告警通知

        代码如下所示

import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Proxy;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author wkp
 * @description 自定义动态线程池
 * @create 2023-04-13
 */
@Getter
@Setter
@Slf4j
public class DynamicThreadPool extends ThreadPoolExecutor {
    private String threadPoolId;
    private final AtomicLong rejectCount = new AtomicLong();
    private Long executeTimeOut;
    private final ThreadLocal<Long> executeTimeThreadLocal = new ThreadLocal<>();

    /**
     * 动态线程池构造方法
     * @param threadPoolId 动态线程池唯一标识
     * @param executeTimeOut 线程任务执行超时时间
     * @param corePoolSize 核心线程池数量
     * @param maximumPoolSize 最大线程池数量
     * @param keepAliveTime 空闲线程活跃时间
     * @param unit  时间单位
     * @param workQueue 任务队列
     * @param threadFactory 线程工厂
     * @param redundancyHandler 线程池拒绝策略
     */
    public DynamicThreadPool(String threadPoolId,long executeTimeOut,int corePoolSize, int maximumPoolSize,
                             long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,
                             ThreadFactory threadFactory,RejectedExecutionHandler redundancyHandler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, redundancyHandler);
        this.threadPoolId=threadPoolId;
        this.executeTimeOut = executeTimeOut;
        //对拒绝策略创建动态代理,在代理里面可以发送预警通知
        RejectedExecutionHandler rejectedProxy = createProxy(redundancyHandler, threadPoolId, rejectCount);
        setRejectedExecutionHandler(rejectedProxy);
    }

    public static RejectedExecutionHandler createProxy(RejectedExecutionHandler rejectedExecutionHandler, String threadPoolId, AtomicLong rejectedNum) {
        RejectedExecutionHandler rejectedProxy = (RejectedExecutionHandler) Proxy
                .newProxyInstance(
                        rejectedExecutionHandler.getClass().getClassLoader(),
                        new Class[]{RejectedExecutionHandler.class},
                        new RejectedProxyInvocationHandler(rejectedExecutionHandler, threadPoolId, rejectedNum));
        return rejectedProxy;
    }

    @Override
    public void execute(@NonNull Runnable command) {
        super.execute(command);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        if (executeTimeOut == null) {
            return;
        }
        //线程任务执行之前设置当前时间
        executeTimeThreadLocal.set(System.currentTimeMillis());
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        Long startTime=executeTimeThreadLocal.get();
        if (startTime == null) {
            return;
        }
        try {
            //线程任务执行之后计算时间差值,获取线程任务执行时间是否超时
            long endTime = System.currentTimeMillis();
            long executeTime;
            boolean executeTimeAlarm = (executeTime = (endTime - startTime)) > executeTimeOut;
            if (executeTimeAlarm) {
                log.info("线程任务执行超时了,threadPoolId:{},executeTime:{}",threadPoolId,executeTime);
                //TODO 发送钉钉飞书预警
            }
        } finally {
            executeTimeThreadLocal.remove();
        }
    }

    @Override
    public void setCorePoolSize(int corePoolSize){
        super.setCorePoolSize(corePoolSize);
    }

    @Override
    public void setMaximumPoolSize(int maximumPoolSize) {
        super.setMaximumPoolSize(maximumPoolSize);
    }
}

RejectedProxyInvocationHandler

RejectedProxyInvocationHandler 线程池拒绝策略代理类:可以在任务被线程池拒绝的时候执行一些自定义的逻辑,例如拒绝任务数量统计、告警通知等

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @description 线程池拒绝策略代理类
 * @author wkp
 * @create 2023/4/13
 */
@Slf4j
@AllArgsConstructor
public class RejectedProxyInvocationHandler implements InvocationHandler {

    private final Object target;

    private final String threadPoolId;

    private final AtomicLong rejectCount;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //线程池拒绝次数统计
        rejectCount.incrementAndGet();
        log.info("线程任务执行触发拒绝策略了,threadPoolId:{},rejectCount:{}",threadPoolId,rejectCount.get());
        //TODO 发送钉钉飞书预警
        try {
            return method.invoke(target, args);
        } catch (InvocationTargetException ex) {
            throw ex.getCause();
        }
    }
}

DynamicThreadPoolRegister

动态线程池注册器&运行监听器:构建获取DynamicThreadPool实例并将该实例注册到全部Map中,Map中的key和value分别是线程池id和对应的线程池实例,通过Nacos配置变更动态刷新线程池参数配置,另外还有一个后台监听预警任务

  • buildThreadPool:该方法创建一个动态线程池实例,并且将该实例放入threadPoolMap中
  • Map<String, DynamicThreadPool> threadPoolMap:Map中的key和value分别是线程池id和对应的线程池实例
  • refreshThreadPool(Map<String, Map<String, Integer>> threadPoolConfig):根据Nacos配置中心的配置动态刷新线程池参数配置,threadPoolConfig参数的key是threadPoolId,value是该线程池的参数配置,例如{"threadPool1":{"coreSize":2,"maxSize":10}}
  • scheduledExecutorService:一个后台定时任务,定时遍历扫描threadPoolMap中的所有线程池实例,获取其运行状态,可以根据活跃的线程数或者队列堆积任务达到一定阈值时进行预警通知
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;

import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.*;

/**
 * @author wkp
 * @description 动态线程池注册器&运行监听器
 * @create 2023-04-13
 */
@Slf4j
public class DynamicThreadPoolRegister {
    public static final String coreSize = "coreSize";
    public static final String maxSize = "maxSize";
    private static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    //存放动态线程池实例,key为threadPoolId
    private static Map<String, DynamicThreadPool> threadPoolMap = new ConcurrentHashMap<>();

    static {
        //后台定时任务监控线程池运行状态,可以进行预警
        scheduledExecutorService.scheduleAtFixedRate(()->{
            Set<String> set = threadPoolMap.keySet();
            for(String threadPoolId:set){
                DynamicThreadPool pool = threadPoolMap.get(threadPoolId);
                //线程池最大线程数
                int maximumPoolSize = pool.getMaximumPoolSize();
                //线程池当前线程数
                int poolSize = pool.getPoolSize();
                //活跃线程数
                int activeCount = pool.getActiveCount();
                BlockingQueue<Runnable> queue = pool.getQueue();
                //队列元素个数
                int queueSize = queue.size();
                //队列剩余容量
                int remainingCapacity = queue.remainingCapacity();
                //队列容量
                int queueCapacity = queueSize + remainingCapacity;
                //TODO 根据配置的活跃线程数或者队列任务数量阈值进行预警
                //TODO 预警阈值可以改成动态配置
                if(poolSize>maximumPoolSize*0.5){
                    log.info("线程池负载过高了,poolSize:{},maximumPoolSize{}",threadPoolId,poolSize,maximumPoolSize);
                }
                if(queueSize>queueCapacity*0.5){
                    log.info("线程池任务堆积了,queueSize:{},queueCapacity{}",threadPoolId,queueSize,queueCapacity);
                }
            }
            log.info("线程池监控定时任务,threadPoolSize:{}",threadPoolMap.size());
        },5,10,TimeUnit.SECONDS);
    }

    public static ThreadPoolExecutor buildThreadPool(String threadPoolId, long executeTimeOut, int corePoolSize, int maximumPoolSize) {
        return buildThreadPool(threadPoolId, executeTimeOut, corePoolSize, maximumPoolSize, 60,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }

    public static ThreadPoolExecutor buildThreadPool(String threadPoolId, long executeTimeOut, int corePoolSize, int maximumPoolSize,
                                                     long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
                                                     ThreadFactory threadFactory, RejectedExecutionHandler redundancyHandler) {
        DynamicThreadPool dynamicThreadPool = new DynamicThreadPool(threadPoolId, executeTimeOut, corePoolSize,
                maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, redundancyHandler);
        threadPoolMap.put(threadPoolId, dynamicThreadPool);
        return dynamicThreadPool;
    }

    /**
     * 刷新线程池配置
     * @param threadPoolConfig
     */
    public static void refreshThreadPool(Map<String, Map<String, Integer>> threadPoolConfig) {
        if (MapUtils.isEmpty(threadPoolConfig)) {
            return;
        }
        Set<String> set = threadPoolConfig.keySet();
        for (String threadPoolId : set) {
            DynamicThreadPool dynamicThreadPool = threadPoolMap.get(threadPoolId);
            if (Objects.isNull(dynamicThreadPool)) {
                continue;
            }
            Map<String, Integer> propertyMap = threadPoolConfig.get(threadPoolId);
            //根据配置修改线程池的线程数,队列,拒绝策略等等
            dynamicThreadPool.setCorePoolSize(propertyMap.get(coreSize));
            dynamicThreadPool.setMaximumPoolSize(propertyMap.get(maxSize));
        }
    }
}

 DynamicThreadPoolRefresher 

        动态线程池动态刷新处理器:注册Nacos配置监听,调用DynamicThreadPoolRegister的refreshThreadPool方法动态刷新线程池

        前提是项目中要引入Nacos配置中心,Nacos的pom依赖我就不写了,项目中的bootstrap.properties配置如下

#nacos注册中心、配置中心地址
spring.cloud.nacos.discovery.server-addr=nacos-host:80
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.group=DEFAULT_GROUP
spring.cloud.nacos.config.server-addr=nacos-host:80

#动态线程池配置文件
spring.cloud.nacos.config.extension-configs[0].data-id=dynamic-threadpool.properties
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true

 nacos配置文件dynamic-threadpool.properties如下所示:

dynamic.threadpool.threadPoolId1.coreSize=3
dynamic.threadpool.threadPoolId1.maxSize=8

DynamicThreadPoolRefresher代码如下: 

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * @author wkp
 * @description 动态线程池动态刷新处理器
 * @create 2023-04-13
 */
@Slf4j
@Component
public class DynamicThreadPoolRefresher implements InitializingBean {
    @Resource
    private NacosConfigManager nacosConfigManager;

    @Override
    public void afterPropertiesSet() throws Exception {
        //对nacos中动态线程池的配置文件dynamic-threadpool.properties注册监听
        nacosConfigManager.getConfigService().addListener("dynamic-threadpool.properties", "DEFAULT_GROUP",
            new Listener() {

                @Override
                public Executor getExecutor() {
                    return Executors.newSingleThreadExecutor();
                }

                @Override
                public void receiveConfigInfo(String configInfo) {
                    dynamicRefresh(configInfo);
                }
        });
    }

    private void dynamicRefresh(String configInfo) {
        log.info(configInfo);
        Properties properties = new Properties();
        try {
            properties.load(new StringReader(configInfo));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //配置示例
        //dynamic.threadpool.threadPoolId1.coreSize=5
        //dynamic.threadpool.threadPoolId1.maxSize=10
        //dynamic.threadpool.threadPoolId2.coreSize=12
        //dynamic.threadpool.threadPoolId2.maxSize=20
        Set<Object> set = properties.keySet();
        //解析线程池配置,key是threadPoolId,例如{"threadPool1":{"coreSize":2,"maxSize":10}}
        Map<String,Map<String,Integer>> threadPoolConfig=new HashMap<>();
        for(Object key:set){
            String s = key.toString();
            String[] keyArr = s.split("\\.");
            String threadPoolId=keyArr[2];
            String threadProperty=keyArr[3];
            Integer threadPropertyValue= Integer.valueOf(properties.get(key).toString());
            Map<String, Integer> map = threadPoolConfig.getOrDefault(threadPoolId,new HashMap<>());
            map.put(threadProperty,threadPropertyValue);
            threadPoolConfig.put(threadPoolId,map);
            log.info("线程池配置变更了,threadPoolId:{},{}:{}",threadPoolId,threadProperty,threadPropertyValue);
        }
        //TODO 发送钉钉飞书预警
        //根据线程池配置刷新线程池实例
        DynamicThreadPoolRegister.refreshThreadPool(threadPoolConfig);
    }
}

测试动态线程池

        测试类如下所示,构造了一个动态线程池实例,其执行的任务为每5秒打印自己的核心线程数和最大线程数。

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author wkp
 * @description 测试动态线程池
 * @create 2023-04-13
 */
@Component
@Slf4j
public class TestDynamicThreadPool  implements InitializingBean {

    private ThreadPoolExecutor threadPoolExecutor;

    @Override
    public void afterPropertiesSet() throws Exception {
        threadPoolExecutor=DynamicThreadPoolRegister.buildThreadPool("threadPoolId1",1000,2,5);

        threadPoolExecutor.execute(()->{
            while(true) {
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("threadPoolId1执行后台任务coreSize:{},maxSize:{}", threadPoolExecutor.getCorePoolSize(), threadPoolExecutor.getMaximumPoolSize());
            }
        });
    }
}

        启动服务,观察控制台输出的日志:定时监听器每10秒执行一次监听,自定义的任务每5秒输出一次线程池的线程参数

2023-04-13 19:25:14.451 +0800 [TID: N/A] [pool-9-thread-1] INFO  c.b.t.u.i.t.TestDynamicThreadPool:31 - threadPoolId1执行后台任务coreSize:2,maxSize:5
2023-04-13 19:25:19.447 +0800 [TID: N/A] [pool-8-thread-1] INFO  c.b.t.u.i.t.DynamicThreadPoolRegister:48 - 线程池监控定时任务,threadPoolSize:1
2023-04-13 19:25:19.452 +0800 [TID: N/A] [pool-9-thread-1] INFO  c.b.t.u.i.t.TestDynamicThreadPool:31 - threadPoolId1执行后台任务coreSize:2,maxSize:5
2023-04-13 19:25:24.453 +0800 [TID: N/A] [pool-9-thread-1] INFO  c.b.t.u.i.t.TestDynamicThreadPool:31 - threadPoolId1执行后台任务coreSize:2,maxSize:5
2023-04-13 19:25:29.448 +0800 [TID: N/A] [pool-8-thread-1] INFO  c.b.t.u.i.t.DynamicThreadPoolRegister:48 - 线程池监控定时任务,threadPoolSize:1

        修改Nacos中的线程池配置后(从3和8改成了4和9),再观察控制台日志:可以看到线程池的参数已经发生了变化。

 2023-04-13 19:25:48.135 +0800 [TID: N/A] [pool-14-thread-1] INFO  c.b.t.u.i.t.DynamicThreadPoolRefresher:71 - 线程池配置变更了,threadPoolId:threadPoolId1,maxSize:9
2023-04-13 19:25:49.550 +0800 [TID: N/A] [pool-14-thread-1] INFO  c.b.t.u.i.t.DynamicThreadPoolRefresher:71 - 线程池配置变更了,threadPoolId:threadPoolId1,coreSize:4

2023-04-13 19:25:53.136 +0800 [TID: N/A] [pool-9-thread-1] INFO  c.b.t.u.i.t.TestDynamicThreadPool:31 - threadPoolId1执行后台任务coreSize:4,maxSize:9

至此,我们自定义的动态线程池实现完成了。

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

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

相关文章

『pyqt5 从0基础开始项目实战』05. 按钮点击事件之添加新数据 (保姆级图文)

目录导包和框架代码给按钮绑定一个点击事件获取输入框的数据多线程与界面更新&#xff08;新线程与UI更新的数据交互&#xff09;代码结构完整代码main文件Threads.py总结欢迎关注 『pyqt5 从0基础开始项目实战』 专栏&#xff0c;持续更新中 欢迎关注 『pyqt5 从0基础开始项目…

上海亚商投顾:沪指创年内新高 大金融、中字头集体走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 沪指今日低开高走&#xff0c;午后涨超1%&#xff0c;创出近10个月以来新高&#xff0c;创业板指走势较弱&#xf…

不走弯路,AI真的能提高生产效率

AI应用虽然取得了令人瞩目的成果&#xff0c;但是在实际应用中仍存在不少困境。市面上不乏有AI绘画、AI写作、AI聊天的相关产品&#xff0c;即使Chatgpt可以写代码、写论文&#xff0c;但由于技术的有限性&#xff0c;还需要不断地优化完善才能给出更精准的答复&#xff0c;也少…

契约锁与多家软件行业伙伴达成战略合作,携手助力组织数字化转型

近日&#xff0c;契约锁电子签章与天翼云、神州数码、同望科技、宏灿软件、甄零科技、正量科技等多家软件行业伙伴达成战略合作&#xff0c;充分发挥各自专业与资源优势&#xff0c;从产品、市场、销售、技术等多方面展开深度合作&#xff0c;共同为客户提供全程数字化解决方案…

zabbix创建自定义监控模板

目录 第一章先行配置zabbix 第二章配置自定义 2.1.案列&#xff1a;自定义监控客户端服务器登录的人数需求&#xff1a;限制登录人数不超过 3 个&#xff0c;超过 3 个就发出报警信息 2.2.在 Web 页面创建自定义监控项模板 2.3.zabbix 自动发现与自动注册 总结 自定义监控…

【论文精度(李沐老师)】Generative Adversarial Nets

Abstract 我们提出了一个新的framework&#xff0c;通过一个对抗的过程来估计生成模型&#xff0c;其中会同时训练两个模型&#xff1a;生成模型G来获取整个数据的分布&#xff0c;辨别模型D来分辨数据是来自于训练样本还是生成模型G。生成模型G的任务是尽量的让辨别模型D犯错…

DI依赖注入

DI依赖注入Setter注入setter注入引用类型setter注入简单类型&#xff08;基本数据类型和字符串&#xff09;构造器注入构造器注入引用类型自动装配集合注入首先我们明确一些观点1、注入的Bean的数据包括引用类型与简单类型&#xff08;基本数据类型和字符串&#xff09;2、通过…

HTML5 地理定位

HTML5 Geolocation&#xff08;地理定位&#xff09; HTML5 Geolocation&#xff08;地理定位&#xff09;用于定位用户的位置。 Geolocation 通过请求一个位置信息&#xff0c;用户同意后&#xff0c;浏览器会返回一个包含经度和维度的位置信息&#xff01; 定位用户的位置 …

【C语言数组部分】

数组部分综述引入&#xff1a;数组概念&#xff1a;一、一维数组1.1一维数组的创建&#xff1a;1.2一维数组的初始化&#xff1a;1.2.1初始化概念&#xff1a;1.2.2完全初始化&#xff1a;1.2.3不完全初始化&#xff1a;1.3字符数组的初始化&#xff1a;1.3.1用字符初始化&…

如何解决spring的循环依赖问题?

前言 昨天我们说了什么是spring的循环依赖&#xff0c;以及产生的原因&#xff0c;今天那我们就来说说如何解决spring的循环依赖问题。 上篇文章说到过&#xff0c;只有通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题是被解决的&#xff1f; Spring是怎样解决…

rhce第二次作业

配置ssh免密登陆&#xff0c;能够通过客户端主机通过redhat用户和服务端主机基于公钥验证方式进行远程连接 [root456 ~]# hostname host [root456 ~]# bash \\更改名称[roothost ~]# su redhat …

2023年腾讯云S5云服务器性能网络收发包PPS、连接数、内网带宽能力等性能测评

腾讯云服务器标准型S5实例CPU采用Intel Xeon Cascade Lake或者Intel Xeon Cooper Lake处理器&#xff0c;主频2.5GHz&#xff0c;睿频3.1GHz&#xff0c;标准型S5云服务器基于全新优化虚拟化平台&#xff0c;配有全新的Intel Advanced Vector Extension (AVX-512) 指令集&#…

微服务+springcloud+springcloud alibaba学习笔记【Spring Cloud服务网关】(7/9)

Spring Cloud服务网关 7/91、GateWay概述2、GateWay的特性:3、GateWay与zuul的区别:4、zuul1.x的模型:5、什么是webflux:6、GateWay三大概念:6.1,路由:6.2,断言:6.3,过滤:7、GateWay的工作原理:8、使用GateWay:8.1,建module8.2,修改pom文件8.3,写配置文件8.4,主启动类8.5,针对p…

Springboot 整合 Redis 进行基本操作

SpringBoot整合Redis 首先创建 Springboot 项目。 spring-data-redis针对jedis提供了如下功能&#xff1a;1.连接池自动管理&#xff0c;并提供了一个高度封装的“RedisTemplate”类2.针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口ValueOperat…

计网第六章.应用层各协议概览

以下来自湖科大计算机网络公开课笔记及个人所搜集资料 目录一. C/S方式和P2P对等方式二. 动态主机配置协议DHCP三.域名系统DNS域名解析过程四.文件传输协议FTP基本工作原理&#xff1a;五.电子邮件六.万维网www万维网的文档6.1 HTTP&#xff08;HyperText Transfer Protocol&am…

左手Python 右手R —— 最大公约数和最小公倍数

左手Python 右手R —— 最大公约数和最小公倍数前言1、 最大公约数1.1 约数1.2 最大公约数1.3 求解方法2、 最小公倍数2.1 倍数2.2 最小公倍数2.3 求解方法3、程序实现3.1 python 代码实现3.2 R语言代码实现小结创作不易&#xff0c;都浏览到这儿了&#xff0c;看官可否将下面的…

【Linux系统创建,修改用户和组和修改目录文件的权限以及实验的心得体会】

实验过程 1.创建一个新用户nick,设置其主目录为home/nick。 &#xff08;1&#xff09;添加新用户&#xff1a; sudo useradd -m 用户名 sudo passwd 新用户名 &#xff08;2&#xff09;给新用户可以执行的root权限 sudo vi /etc/sudoers #User privilege specification roo…

Spring Security实战(一)——基于内存和数据库模型的认证与授权

目录 简介 一、初识Spring Security&#xff08;入门案例&#xff09; &#xff08;1&#xff09;新建project &#xff08;2&#xff09;选择依赖 &#xff08;3&#xff09;编写一个 HelloController &#xff08;4&#xff09;启动项目&#xff0c;访问localhost:8080…

手动清除gitlab中prometheus的数据

背景&#xff1a; gitlab服务器上&#xff0c; 磁盘经常爆满。后来通过 du -sh ./* 查出prometheus下的data占了绝大多数磁盘空间。 因此&#xff0c;准备删除prometheus的数据。 思路 由于prometheus的数据占用的空间较大&#xff0c;因此在实际使用时&#xff0c;可以关闭…

【 Spring MVC 核心功能(一) - 使用注解实现 URL 路由映射】

文章目录引言一、RequestMapping 注解介绍1.1 RequestMapping 是 post 还是 get 请求&#xff1f;1.2 RequestMapping 指定一种请求方式二、GetMapping三、PostMapping四、总结引言 前面我们讲到&#xff0c;学习 Spring MVC 需要掌握三个核心功能即连接&#xff0c;获取参数&…