springboot 缓存框架Cache整合redis组成二级缓存

news2024/11/24 14:44:43

springboot 缓存框架Cache整合redis组成二级缓存
项目性能优化的解决方案除开硬件外的方案无非就是优化sql,减少sql 的执行时间,合理运用缓存让同样的请求和数据库之间的连接尽量减少,内存的处理速度肯定比直接查询数据库来的要快一些。今天就记录一下spring的缓存框架和redis形成二级缓存来优化查询效率,废话不多说直接上代码:
整体目录:
在这里插入图片描述
首先定义注解:缓存注解和删除注解

package com.example.test1.cache.aspect.annoation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 11:12
 * @Description: 数据缓存注解
 * @Version 1.0
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DateCachePut {
    /**
     * 模块名:区分模块下的不同功能key,key可能重复
     */
    String module() default "";

    /**
     * 缓存的数据key
     */
    String key() default "";

    /**
     * key生成
     */
    String keyGenerator() default "DefaultKeyGenerate";

    /**
     * 过期时间,默认30
     */
    long passTime() default 30;

    /**
     * 过期时间单位,默认:秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * 是否出发条件,支持springEl表达式
     */
    String condition() default "true";
}

package com.example.test1.cache.aspect.annoation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 11:13
 * @Description: 缓存删除注解
 * @Version 1.0
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataCacheEvict {
    /**
     * 模块名:区分模块下的不同功能key,key可能重复
     */
    String module() default "";

    /**
     * 缓存的数据key
     */
    String key() default "";

    /**
     * key生成
     */
    String keyGenerator() default "DefaultKeyGenerate";

    /**
     * 删除时间,默认1
     */
    long delay() default 1;

    /**
     * 过期时间单位,默认:秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * 是否出发条件,支持springEl表达式
     */
    String condition() default "true";
}

注解切面类

package com.example.test1.cache.aspect;

import com.example.test1.cache.aspect.annoation.DataCacheEvict;
import com.example.test1.cache.aspect.annoation.DateCachePut;
import com.example.test1.cache.generate.IKeyGenerate;
import com.example.test1.cache.handle.CacheHandle;
import com.example.test1.util.SpElUtils;
import com.example.test1.util.SpiUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Optional;
import java.util.StringJoiner;

/**
 * @Author xx
 * @Date 2024/6/27 11:34
 * @Description:
 * @Version 1.0
 */
@Slf4j
@Aspect
@Component
public class CacheAspect {
    /**
     * 缓存前缀
     */
    private static final String CHAR_PREFIX = "cache";

    @Resource
    private CacheHandle cacheHandle;

    @Value(value = "${spring.application.name}")
    private String applicationName;


    @SneakyThrows
    @Around(value = "@annotation(dateCachePut)")
    public Object cachePut(ProceedingJoinPoint joinPoint, DateCachePut dateCachePut){
        String applicationName = StringUtils.isBlank(dateCachePut.module()) ?
                this.applicationName : dateCachePut.module();

        //解析key并查询缓存
        String key = buildCacheKey(applicationName,
                dateCachePut.module(),
                joinPoint,
                dateCachePut.key(),
                dateCachePut.keyGenerator());

        Object result = cacheHandle.get(key);

        if(result == null){
            result = joinPoint.proceed();
            if(result != null){
                Boolean condition = SpElUtils.getValue(joinPoint,dateCachePut.condition(),Boolean.class,result);
                if(condition){
                    cacheHandle.put(key,result,dateCachePut.passTime(),dateCachePut.timeUnit());
                }
            }
        }
        return result;
    }

    /**
     * 删除缓存
     * @param joinPoint 连接点
     * @param dataCacheEvict 删除注解
     * @return
     */
    @SneakyThrows
    @Around(value = "@annotation(dataCacheEvict)")
    public Object cacheRemove(ProceedingJoinPoint joinPoint, DataCacheEvict dataCacheEvict){
        String applicationName = StringUtils.isBlank(dataCacheEvict.module()) ?
                this.applicationName : dataCacheEvict.module();

        //解析key并查询缓存
        String key = buildCacheKey(applicationName,
                dataCacheEvict.module(),
                joinPoint,
                dataCacheEvict.key(),
                dataCacheEvict.keyGenerator());

        cacheHandle.evict(key);

        //执行目标方法
        Object result = joinPoint.proceed();

        // 条件成立则异步删除
        Boolean condition = SpElUtils.getValue(joinPoint, dataCacheEvict.condition(), Boolean.class, result);
        if(condition){
            cacheHandle.asyEvict(key,dataCacheEvict.delay(),dataCacheEvict.timeUnit());
        }

        return result;
    }

    /**
     * 构建缓存key
     * @param applicationName 服务名
     * @param module 模块名
     * @param joinPoint 链接点
     * @param key  编写的key表达式
     * @param keyGenerator key生成器实现类名称
     * @return
     */
    private String buildCacheKey(String applicationName,String module,
                                 ProceedingJoinPoint joinPoint,String key,String keyGenerator){
        return new StringJoiner("::")
                .add(CHAR_PREFIX)
                .add(applicationName)
                .add(module)
                .add(generateKey(joinPoint,key,keyGenerator))
                .toString();
    }

    /**
     * 生成key
     * 1:key为空的情况下将会是方法参数列表中的toString集合
     * 2:将表达式传递的key生成器实现类生成
     *
     * @param joinPoint 连接点
     * @param key 编写的key表达式
     * @param keyGenerator key生成器实现类名
     * @return
     */
    private CharSequence generateKey(ProceedingJoinPoint joinPoint, String key, String keyGenerator) {
        return StringUtils.isEmpty(keyGenerator) ? Arrays.toString(joinPoint.getArgs()) :
                Optional.ofNullable(SpiUtils.getServiceImpl(keyGenerator, IKeyGenerate.class))
                        .map(keyGenerate ->{
                            Assert.notNull(keyGenerate,String.format("%s找不到keyGenerate实现类", keyGenerator));
                            return keyGenerate.generateKey(joinPoint,key);
                        }).orElse(null);
    }
}

工具类:

package com.example.test1.util;

import lombok.SneakyThrows;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**
 * @Author xx
 * @Date 2024/6/27 15:10
 * @Description:
 * @Version 1.0
 */
public class SpElUtils {

    private final static String RESULT = "result";

    /**
     * 用于springEL表达式的解析
     */
    private static SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    /**
     * 用于获取方法参数定义的名字
     */
    private static DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    /**
     * 根据EL表达式获取值
     *
     * @param joinPoint 连接点
     * @param key       springEL表达式
     * @param classes   返回对象的class
     * @return 获取值
     */
    public static <T> T getValue(ProceedingJoinPoint joinPoint, String key, Class<T> classes) {
        // 解析springEL表达式
        EvaluationContext evaluationContext = getEvaluationContext(joinPoint, null);
        return spelExpressionParser.parseExpression(key).getValue(evaluationContext, classes);
    }

    /**
     * 根据EL表达式获取值
     *
     * @param joinPoint  连接点
     * @param expression springEL表达式
     * @param classes    返回对象的class
     * @param result     result
     * @return 获取值
     */
    public static <T> T getValue(JoinPoint joinPoint, String expression, Class<T> classes, Object result) throws NoSuchMethodException {
        // 解析springEL表达式
        EvaluationContext evaluationContext = getEvaluationContext(joinPoint, result);
        return spelExpressionParser.parseExpression(expression).getValue(evaluationContext, classes);
    }

    /**
     * 获取参数上下文
     *
     * @param joinPoint 连接点
     * @return 参数上下文
     */
    @SneakyThrows
    private static EvaluationContext getEvaluationContext(JoinPoint joinPoint, Object result) {
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        String[] parameterNames = defaultParameterNameDiscoverer.getParameterNames(getMethod(joinPoint));
        for (int i = 0; i < parameterNames.length; i++) {
            evaluationContext.setVariable(parameterNames[i], joinPoint.getArgs()[i]);
        }
        evaluationContext.setVariable(RESULT, result);
        return evaluationContext;
    }

    /**
     * 获取目标方法
     *
     * @param joinPoint 连接点
     * @return 目标方法
     */
    private static Method getMethod(JoinPoint joinPoint) throws NoSuchMethodException {
        Signature signature = joinPoint.getSignature();
        return joinPoint.getTarget().getClass()
                .getMethod(signature.getName(), ((MethodSignature) signature).getParameterTypes());
    }
}

package com.example.test1.util;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;

import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 14:19
 * @Description:
 * @Version 1.0
 */
public class SpiUtils {


    /**
     * SPI缓存key
     *
     * @param <T>
     */
    private static final class SpiCacheKeyEntity<T> {
        /**
         * 实现的接口class
         */
        private Class<T> classType;

        /**
         * 实现类的名称
         */
        private String serviceName;

        public SpiCacheKeyEntity(Class<T> classType, String serviceName) {
            this.classType = classType;
            this.serviceName = serviceName;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            SpiCacheKeyEntity<?> spiCacheKeyEntity = (SpiCacheKeyEntity<?>) o;
            return Objects.equals(classType, spiCacheKeyEntity.classType) && Objects.equals(serviceName, spiCacheKeyEntity.serviceName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(classType, serviceName);
        }
    }

    private SpiUtils() {
    }

    /**
     * 单例
     * 根据接口实现类的名称以及接口获取实现类
     *
     * @param serviceName 实现类的名称
     * @param classType   实现的接口class
     * @return 具体的实现类
     */
    public static <T> T getServiceImpl(String serviceName, Class<T> classType) {
        return (T) SERVICE_IMPL_CACHE.get(new SpiCacheKeyEntity(classType, serviceName));
    }

    /**
     * SPI接口实现类 Caffeine软引用同步加载缓存(其内部做了同步处理)
     */
    public final static LoadingCache<SpiCacheKeyEntity, Object> SERVICE_IMPL_CACHE = Caffeine.newBuilder()
            .expireAfterAccess(24, TimeUnit.HOURS)
            .maximumSize(100)
            .softValues()
            .build(spiCacheKeyEntity -> getServiceImplByPrototype(spiCacheKeyEntity.serviceName, spiCacheKeyEntity.classType));

    /**
     * 多例
     * 根据接口实现类的名称以及接口获取实现类
     *
     * @param serviceName 实现类的名称
     * @param classType   实现的接口class
     * @return 具体的实现类
     */
    public static <T> T getServiceImplByPrototype(String serviceName, Class<T> classType) {
        ServiceLoader<T> services = ServiceLoader.load(classType, Thread.currentThread().getContextClassLoader());
        for (T s : services) {
            if (s.getClass().getSimpleName().equals(serviceName)) {
                return s;
            }
        }
        return null;
    }
}

缓存key生成接口

package com.example.test1.cache.generate;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * @Author xx
 * @Date 2024/6/27 15:05`在这里插入代码片`
 * @Description: 缓存key 接口生成器
 * @Version 1.0
 */
public interface IKeyGenerate {
    /**
     *生成key
     * @param joinPoint 连接点
     * @param key 编写的key表达式
     * @return
     */
    String generateKey(ProceedingJoinPoint joinPoint,String key);
}

实现

package com.example.test1.cache.generate.impl;

import com.example.test1.cache.generate.IKeyGenerate;
import com.example.test1.util.SpElUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * @Author xx
 * @Date 2024/6/27 15:08
 * @Description: 默认key生成器
 * @Version 1.0
 */
@Slf4j
public class DefaultKeyGenerate implements IKeyGenerate {

    @Override
    public String generateKey(ProceedingJoinPoint joinPoint, String key) {
        try {
            return SpElUtils.getValue(joinPoint,key,String.class);
        } catch (Exception e) {
            log.error("DefaultKeyGenerate 抛出异常:{}", e.getMessage(), e);
            throw new RuntimeException("DefaultKeyGenerate 生成key出现异常", e);
        }
    }
}

缓存处理

package com.example.test1.cache.handle;

import com.example.test1.cache.schema.ICacheSchema;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 15:26
 * @Description: 缓存处理
 * @Version 1.0
 */
@Component
public class CacheHandle {

    @Resource
    private ICacheSchema cacheSchema;

    @Resource
    private ScheduledThreadPoolExecutor asyScheduledThreadPoolExecutor;

    /**
     *查询缓存
     * @param key 缓存key
     * @return 缓存值
     */
    public Object get(String key){
        return cacheSchema.get(key);
    }

    /**
     *存入缓存
     * @param key  缓存key
     * @param value 缓存值
     * @param expirationTime 缓存过期时间
     * @param timeUnit 时间单位
     */
    public void put(String key, Object value, long expirationTime, TimeUnit timeUnit){
        cacheSchema.put(key,value,expirationTime,timeUnit);
    }

    /**
     * 移除缓存
     * @param key 缓存key
     * @return
     */
    public boolean evict(String key){
        boolean evict = cacheSchema.evict(key);
        if(evict){

        }
        return evict;
    }

    /**
     * 异步定时删除缓存
     * @param key 缓存key
     * @param passTime 定时删除时间
     * @param timeUnit 定时删除单位
     */
    public void asyEvict(String key, long passTime, TimeUnit timeUnit) {
        asyScheduledThreadPoolExecutor.schedule(()->this.evict(key),passTime,timeUnit);
    }
}

缓存公共接口,和多级缓存接口

package com.example.test1.cache.schema;

import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 15:28
 * @Description: 缓存公共接口
 * @Version 1.0
 */
public interface ICacheSchema {

    /**
     * 查询缓存
     *
     * @param key 缓存的key
     * @return 缓存的值
     */
    Object get(String key);

    /**
     * 存入缓存
     *
     * @param key            缓存的key
     * @param value          缓存的值
     * @param expirationTime 缓存的过期时间
     * @param timeUnit       缓存过期时间的单位
     */
    void put(String key, Object value, long expirationTime, TimeUnit timeUnit);

    /**
     * 移除缓存
     *
     * @param key 缓存的key
     * @return 移除操作结果
     */
    boolean evict(String key);
}

package com.example.test1.cache.schema;

/**
 * @Author xx
 * @Date 2024/6/27 16:25
 * @Description: 多级缓存
 * @Version 1.0
 */
public interface IMultipleCache extends ICacheSchema{

    /**
     * 移除一级缓存
     *
     * @param key 缓存的key
     * @return 移除状态
     */
    boolean evictHeadCache(String key);
}

本地缓存实现类

package com.example.test1.cache.schema.caffeien;

import com.example.test1.cache.schema.ICacheSchema;
import com.github.benmanes.caffeine.cache.Cache;

import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 15:30
 * @Description: Caffeine 本地缓存
 * @Version 1.0
 */
public class CaffeineCache implements ICacheSchema {

    private final Cache cache;

    public CaffeineCache(Cache cache) {
        this.cache = cache;
    }

    @Override
    public Object get(String key) {
        return cache.getIfPresent(key);
    }

    @Override
    public void put(String key, Object value, long expirationTime, TimeUnit timeUnit) {
        cache.put(key,value);
    }

    @Override
    public boolean evict(String key) {
         cache.invalidate(key);
         return true;
    }
}

redis缓存实现类

package com.example.test1.cache.schema.redis;

import com.example.test1.cache.schema.ICacheSchema;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 15:51
 * @Description: redis分布式缓存
 * @Version 1.0
 */
public class RedisCache implements ICacheSchema {

    private final RedisTemplate redisTemplate;

    public RedisCache(RedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
    }

    @Override
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    @Override
    public void put(String key, Object value, long expirationTime, TimeUnit timeUnit) {
        if(expirationTime == -1){
            redisTemplate.opsForValue().set(key,value);
        }else {
            redisTemplate.opsForValue().set(key,value.toString(),expirationTime,timeUnit);
        }
    }

    @Override
    public boolean evict(String key) {
        return redisTemplate.delete(key);
    }
}

多级缓存实现类

package com.example.test1.cache.schema.multiple;

import com.example.test1.cache.config.CacheConfig;
import com.example.test1.cache.schema.ICacheSchema;
import com.example.test1.cache.schema.IMultipleCache;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 16:23
 * @Description: 多级缓存实现
 * @Version 1.0
 */
@Slf4j
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class MultipleCache implements IMultipleCache {

    /**
     * 一级缓存
     */
    private ICacheSchema head;
    /**
     *下级缓存实现
     */
    private MultipleCache next;

    private CacheConfig cacheConfig;

    public MultipleCache(ICacheSchema head){
        this.head = head;
    }


    @Override
    public Object get(String key) {
        Object value = head.get(key);
        if(value == null && next != null){
             value = next.get(key);
             if(value != null && cacheConfig != null){
                 head.put(key,value,cacheConfig.getCaffeineDuration(),TimeUnit.SECONDS);
             }
        }
        return value;
    }

    @Override
    public void put(String key, Object value, long expirationTime, TimeUnit timeUnit) {
        head.put(key,value,expirationTime,timeUnit);
        if(next != null){
            next.put(key,value,expirationTime,timeUnit);
        }
    }

    @Override
    public boolean evict(String key) {
        head.evict(key);
        if(next != null){
            next.evict(key);
        }
        return true;
    }

    @Override
    public boolean evictHeadCache(String key) {
        log.debug("移除一级缓存key={}", key);
        return head.evict(key);
    }
}

配置类:

package com.example.test1.cache.config;

import com.example.test1.cache.schema.ICacheSchema;
import com.example.test1.cache.schema.caffeien.CaffeineCache;
import com.example.test1.cache.schema.multiple.MultipleCache;
import com.example.test1.cache.schema.redis.RedisCache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import javax.annotation.Resource;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author xx
 * @Date 2024/6/27 16:12
 * @Description: 缓存管理实例注册
 * @Version 1.0
 */
@Slf4j
@EnableCaching
@Configuration
@ComponentScan("com.example.test1.cache")
@EnableConfigurationProperties(CacheConfig.class)
public class CacheManagerAutoConfiguration {

    @Resource
    private CacheConfig cacheConfig;

    @Resource
    private RedisTemplate redisTemplate;


    @Bean
    public ICacheSchema cacheSchema(){
        //构建多级缓存
        CaffeineCache caffeineCache = buildCaffeineCache();

        RedisCache redisCache = buildRedisCache();

        //构建组合多级缓存
        return new MultipleCache(caffeineCache)
                .setNext(new MultipleCache(redisCache))
                .setCacheConfig(cacheConfig);
    }

    @Bean
    public ScheduledThreadPoolExecutor asyScheduledEvictCachePool() {
        return new ScheduledThreadPoolExecutor(cacheConfig.getAsyScheduledEvictCachePoolSize(),
                new CustomizableThreadFactory("asy-evict-cache-pool-%d"));
    }

    private RedisCache buildRedisCache() {
        return new RedisCache(redisTemplate);
    }

    /**
     * 构建Caffeine缓存
     *
     * @return Caffeine缓存
     */
    private CaffeineCache buildCaffeineCache() {
        Cache<String, Object> caffeineCache = Caffeine.newBuilder()
                .expireAfterWrite(cacheConfig.getCaffeineDuration(), TimeUnit.SECONDS)
                .maximumSize(cacheConfig.getCaffeineMaximumSize())
                .removalListener((RemovalListener) (k, v, removalCause) -> log.info("caffeine缓存移除:key={},cause={}", k, removalCause))
                .build();
        return new CaffeineCache(caffeineCache);
    }
}

package com.example.test1.cache.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @Author xx
 * @Date 2024/6/27 16:10
 * @Description:
 * @Version 1.0
 */
@Data
@ConfigurationProperties(prefix = "test-cache", ignoreInvalidFields = true)
public class CacheConfig {
    /**
     * 缓存模式(默认多级缓存)
     */
//    private SchemaEnum schemaEnum = SchemaEnum.MULTIPLE;

    /**
     * 定时异步清理缓存线程池大小(默认50)
     */
    private int asyScheduledEvictCachePoolSize = 50;

    /**
     * caffeine写入后失效时间(默认 5 * 60 秒)
     */
    private long caffeineDuration = 5 * 60;

    /**
     * caffeine最大容量大小(默认500)
     */
    private long caffeineMaximumSize = 5000;
}

具体使用示例:
在这里插入图片描述
经测试,请求详情后的10秒内(设置的有效时间是10秒)不论请求几次都仅和数据库连接一次,过期后重复第一次的结果,过期时间可以自定义。
如有不合理的地方还请指教!

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

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

相关文章

代码随想录——跳跃游戏Ⅱ(Leetcode 45)

题目链接 贪心 class Solution {public int jump(int[] nums) {if(nums.length 1){return 0;}int count 0;// 当前覆盖最远距离下标int curDistance 0;// 下一步覆盖距离最远下标int nextDistance 0;for(int i 0; i < nums.length; i){nextDistance Math.max(nums[…

搭建 MySQL MHA

搭建 MySQL MHA 搭建 MySQL MHA实验拓扑图实验环境实验思路MHA架构故障模拟 实验部署数据库安装主从复制部署时间同步主服务器配置从服务器配置创建链接 MHA搭建安装依赖的环境安装 node 组件安装 manager 组件配置无密码认证在 manager 节点上配置 MHA管理 mysql 节点服务器创…

【Git】远程仓库

一、常用的托管服务[远程仓库] 二、注册码云 三、创建远程仓库 四、配置SSH公钥 五、操作远程仓库 5.1、添加远程仓库 5.2、查看远程仓库 5.3、推送到远程仓库 5.4、 本地分支与远程分支的关联关系 5.5、从远程仓库克隆 5.6、从远程仓库中抓取和拉取 5.7、解决合并冲突 一、常…

Python 算法交易实验73 QTV200第二步: 数据清洗并写入ClickHouse

说明 先检查一下昨天启动的worker是否正常工作&#xff0c;然后做一些简单的清洗&#xff0c;存入clickhouse。 内容 1 检查数据 from Basefuncs import * # 将一般字符串转为UCS 名称 def dt_str2ucs_blockname(some_dt_str):some_dt_str1 some_dt_str.replace(-,.).re…

简单粗暴-安装detectron2

文章目录 一、下载detectron2二、修改文件三、安装detectron2参考文献&#xff1a; 其他配置版本&#xff1a; torch1.12.1CUDA&#xff1a;11.6python&#xff1a;3.9 建议以上配置&#xff0c;其他配置未测试&#xff0c;如果CUDA版本不匹配的话&#xff0c;可以多配置不同版…

达梦数据库的系统视图v$locked_object

达梦数据库的系统视图v$locked_object 在达梦数据库&#xff08;Dameng Database&#xff09;中&#xff0c;V$LOCKED_OBJECT 视图提供了与数据库中被锁定对象相关的信息。这通常用于监控和诊断数据库中的锁定问题&#xff0c;帮助管理员了解哪些对象被锁定了&#xff0c;以及…

PyCharm2024 for mac Python编辑开发

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff08;适合自己的M芯片版或Intel芯片版&#xff09;&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功3、打开访达&#xff0c;点击【文…

自动建立用户练习

一丶编辑文本存放用户名 vim userlist 二丶编辑文本存放需要创建用户的密码 vim passlist 三丶编辑脚本 vim create_user.sh #!bin/bash [ "$#" -lt "2" ] && { #echo error please input userlist anpassli…

postgre事务id用完后,如何解决这个问题

在PG中事务年龄不能超过2^31 &#xff08;2的31次方2,147,483,648&#xff09;&#xff0c;如果超过了&#xff0c;这条数据就会丢失。 PG中不允许这种情况出现&#xff0c;当事务的年龄离2^31还有1千万的时候&#xff0c;数据库的日志中就会 有如下告警&#xff1a; warning:…

【第一周】认识小程序

目录 认识小程序发展历史发展前景发展优势个人企业/创业 账号申请开发工具下载流程使用说明 协作项目交流收益渠道 认识小程序 发展历史 微信小程序自2016年首次提出以来&#xff0c;经历了快速的发展和完善过程&#xff0c;以下是其主要发展历史节点&#xff1a; 2016年1月…

监控员工电脑的软件有哪些?6款企业必备的电脑监控软件

监控员工电脑的软件在企业管理和网络安全领域扮演着重要角色&#xff0c;它们可以帮助企业提高工作效率&#xff0c;确保数据安全&#xff0c;以及合规性。以下是六款知名的员工电脑监控软件&#xff1a; 1.安企神 - 一个全面的企业级电脑监控和管理解决方案。 2.Work Examine…

从单点到全景:视频汇聚/安防监控EasyCVR全景视频监控技术的演进之路

在当今日新月异的科技浪潮中&#xff0c;安防监控领域的技术发展日新月异&#xff0c;全景摄像机便是这一领域的杰出代表。它以其独特的360度无死角监控能力&#xff0c;为各行各业提供了前所未有的安全保障&#xff0c;成为现代安防体系中的重要组成部分。 一、全景摄像机的技…

个人支付系统实现

基础首页&#xff1a; 订单&#xff1a; 智能售卡系统 基于webmanworkerman开发 禁用函数检查 使用这个脚本检查是否有禁用函数。命令行运行curl -Ss https://www.workerman.net/check | php 如果有提示Function 函数名 may be disabled. Please check disable_functions in …

仓库管理系统13--物资设置

1、添加窗体 2、设计UI界面 注意这个下拉框的绑定&#xff0c;你看到的选项是由displaymember决定&#xff0c;当你选择了哪个选项时&#xff0c;后台绑定这个选项的ID <UserControl x:Class"West.StoreMgr.View.GoodsView"xmlns"http://schemas.microsoft…

群晖系统百度网盘套件卸载之后无法再次安装 ContainerManager项目无法删除

前言 最近重新组了个NAS&#xff0c;在套件迁移的时候遇到个头疼的问题。在用矿神的百度网盘在迁移的时候出错了&#xff0c;于是我自己删掉baiduapp得容器和镜像然后卸载套件。不知道中间出了啥问题&#xff0c;套件是已经卸载了&#xff0c;但是群晖ContainerManager套件中的…

前端实现 海浪(波浪)进度条效果(支持自定义长度;调节速度,2s缓冲结束)

实现海浪进度条 文章目录 实现海浪进度条效果图如下(投入使用的版本)背景和过程一、调试和探索过程(下面都会给出来对应代码)二、类似Element-plus的进度条样式1. CSS的样式如下2. HTML结构如下 二、电涌效果的进度条如下1. CSS的样式如下2. HTML的结构如下:3. JavaScript代码如…

C# 异步编程详解(Task,async/await)

文章目录 1.什么是异步2.Task 产生背景3.Thread(线程) 和 Task(异步)的区别3.1 几个名词3.2 Thread 与 Task 的区别 4.Task API4.1 创建和启动任务4.2 Task 等待、延续和组合4.3 task.Result4.4 Task.Delay() 和 Thread.Sleep() 区别 5.CancellationToken 和 CancellationToken…

基于模型蒸馏的模型加速方案总结

1.简介 1.1目的 在过去的一段时间里&#xff0c;对基于模型蒸馏技术的模型加速方案的方法在多个数据集上进行了一系列的实验。所谓的模型蒸馏技术&#xff0c;简单的来说就是利用一个设计简单的小网络去学习一个设计比较复杂的大网络。特别的有&#xff0c;本次实验针对每一个…

计算机图形学入门18:阴影映射

1.前言 前面几篇关于光栅化的文章中介绍了如何计算物体表面的光照&#xff0c;但是着色并不会进行阴影的计算&#xff0c;阴影需要单独进行处理&#xff0c;目前最常用的阴影计算技术之一就是Shadow Mapping技术&#xff0c;也就是俗称的阴影映射技术。 2.阴影映射 Shadow Map…

压缩pdf文件大小的方法,如何压缩pdf格式的大小

pdf太大怎么压缩&#xff1f;当你需要通过电子邮件发送一个PDF文件&#xff0c;却发现文件太大无法成功发出时&#xff0c;这些情况下&#xff0c;我们都需要找到一种方法来压缩PDF文件&#xff0c;以便更便捷地进行分享和传输。PDF文件的大小通常与其中包含的图片、图形和文本…