Lock4j简单的支持不同方案的高性能分布式锁实现及源码解析

news2024/10/5 14:11:29

文章目录

  • 1.Lock4j是什么?
    • 1.1简介
    • 1.2项目地址
    • 1.3 我之前手写的分布式锁和限流的实现
  • 2.特性
  • 3.如何使用
    • 3.1引入相关依赖
    • 3.2 配置redis或zookeeper
    • 3.3 使用方式
      • 3.3.1 注解式自动式
      • 3.3.2 手动式
  • 4.源码解析
    • 4.1项目目录
    • 4.2实现思路
  • 5.总结

1.Lock4j是什么?

1.1简介

    lock4j是苞米豆提供的一个分布式锁组件,其提供了多种不同的支持以满足不同性能和环境的需求。

    立志打造一个简单但富有内涵的分布式锁组件。

1.2项目地址

https://gitee.com/baomidou/lock4j/tree/v2.2.6/

1.3 我之前手写的分布式锁和限流的实现

https://mp.weixin.qq.com/s/_MX4K_zXc2AbuvN-YrCzoA
https://blog.csdn.net/qq_34905631/article/details/139033796?spm=1001.2014.3001.5501

2.特性

  1. 简单易用,功能强大,扩展性强。
  2. 支持redission,redisTemplate,zookeeper。可混用,支持扩展。

3.如何使用

3.1引入相关依赖

<dependencies>
    <!--若使用redisTemplate作为分布式锁底层,则需要引入-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
        <version>${latest.version}</version>
    </dependency>
    <!--若使用redisson作为分布式锁底层,则需要引入-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
        <version>${latest.version}</version>
    </dependency>
    <!--若使用zookeeper作为分布式锁底层,则需要引入-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>lock4j-zookeeper-spring-boot-starter</artifactId>
        <version>${latest.version}</version>
    </dependency>
</dependencies>

3.2 配置redis或zookeeper

spring:
  redis:
    host: 127.0.0.1
    ...
  coordinate:
    zookeeper:
      zkServers: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183

配置全局默认的获取锁超时时间和锁过期时间。

lock4j:
  acquire-timeout: 3000 #默认值3s,可不设置
  expire: 30000 #默认值30s,可不设置
  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
  lock-key-prefix: lock4j #锁key前缀, 默认值lock4j,可不设置

    acquire-timeout 可以理解为排队时长,超过这个时才就退出排队,抛出获取锁超时异常。

    expire 锁过期时间 。 主要是防止死锁。 建议估计好你锁方法运行时常,正常没有复杂业务的增删改查最多几秒,留有一定冗余,10秒足够。 默认30秒是为了兼容绝大部分场景。

3.3 使用方式

3.3.1 注解式自动式

使用Lock4j注解

@Service
public class DemoService {

    //默认获取锁超时3秒,30秒锁过期
    @Lock4j
    public void simple() {
        //do something
    }

    //完全配置,支持spel
    @Lock4j(keys = {"#user.id", "#user.name"}, expire = 60000, acquireTimeout = 1000)
    public User customMethod(User user) {
        return user;
    }

}

3.3.2 手动式

@Service
public class ProgrammaticService {
    @Autowired
    private LockTemplate lockTemplate;

    public void programmaticLock(String userId) {
        // 各种查询操作 不上锁
        // ...
        // 获取锁
        final LockInfo lockInfo = lockTemplate.lock(userId, 30000L, 5000L, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }
        // 获取锁成功,处理业务
        try {
            System.out.println("执行简单方法1 , 当前线程:" + Thread.currentThread().getName() + " , counter:" + (counter++));
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
        //结束
    }
}

    关于其它高级功能可以参看项目地址的README.md有详细的说明

4.源码解析

4.1项目目录

image-20240706165424138

4.2实现思路

    利用了springBoot自动装配的能力解析配置文件和利用aop解析代用Lock4j注解的方法的类生成动态代理对象,根据不同的配置选择不同的分布式锁的实现,里面最关键的实现就是这个:

    AbstractLockExecutor抽象类,提供了三种实现:

image-20240706165737533

    三种实现如下:

image-20240706170039030

    最关键的一个实现是LockTemplate类,源码如下:

/*
 *  Copyright (c) 2018-2022, baomidou (63976799@qq.com).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.baomidou.lock;

import com.baomidou.lock.exception.LockException;
import com.baomidou.lock.executor.LockExecutor;
import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties;
import com.baomidou.lock.util.LockUtil;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * <p>
 * 锁模板方法
 * </p>
 *
 * @author zengzhihong TaoYu
 */
@SuppressWarnings("rawtypes")
@Slf4j
public class LockTemplate implements InitializingBean {

    private final Map<Class<? extends LockExecutor>, LockExecutor> executorMap = new LinkedHashMap<>();
    @Setter
    private Lock4jProperties properties;
    @Setter
    private List<LockExecutor> executors;

    private LockExecutor primaryExecutor;

    public LockTemplate() {
    }

    public LockInfo lock(String key) {
        return lock(key, 0, -1);
    }

    public LockInfo lock(String key, long expire, long acquireTimeout) {
        return lock(key, expire, acquireTimeout, null);
    }

    /**
     * 加锁方法
     *
     * @param key            锁key 同一个key只能被一个客户端持有
     * @param expire         过期时间(ms) 防止死锁
     * @param acquireTimeout 尝试获取锁超时时间(ms)
     * @param executor       执行器
     * @return 加锁成功返回锁信息 失败返回null
     */
    public LockInfo lock(String key, long expire, long acquireTimeout, Class<? extends LockExecutor> executor) {
        acquireTimeout = acquireTimeout < 0 ? properties.getAcquireTimeout() : acquireTimeout;
        long retryInterval = properties.getRetryInterval();
        LockExecutor lockExecutor = obtainExecutor(executor);
        log.debug(String.format("use lock class: %s", lockExecutor.getClass()));
        expire = !lockExecutor.renewal() && expire <= 0 ? properties.getExpire() : expire;
        int acquireCount = 0;
        String value = LockUtil.simpleUUID();
        long start = System.currentTimeMillis();
        try {
            do {
                acquireCount++;
                Object lockInstance = lockExecutor.acquire(key, value, expire, acquireTimeout);
                if (null != lockInstance) {
                    return new LockInfo(key, value, expire, acquireTimeout, acquireCount, lockInstance,
                            lockExecutor);
                }
                TimeUnit.MILLISECONDS.sleep(retryInterval);
            } while (System.currentTimeMillis() - start < acquireTimeout);
        } catch (InterruptedException e) {
            log.error("lock error", e);
            throw new LockException();
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public boolean releaseLock(LockInfo lockInfo) {
        if (null == lockInfo) {
            return false;
        }
        return lockInfo.getLockExecutor().releaseLock(lockInfo.getLockKey(), lockInfo.getLockValue(),
                lockInfo.getLockInstance());
    }

    protected LockExecutor obtainExecutor(Class<? extends LockExecutor> clazz) {
        if (null == clazz || clazz == LockExecutor.class) {
            return primaryExecutor;
        }
        final LockExecutor lockExecutor = executorMap.get(clazz);
        Assert.notNull(lockExecutor, String.format("can not get bean type of %s", clazz));
        return lockExecutor;
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        Assert.isTrue(properties.getAcquireTimeout() >= 0, "tryTimeout must least 0");
        Assert.isTrue(properties.getExpire() >= -1, "expireTime must lease -1");
        Assert.isTrue(properties.getRetryInterval() >= 0, "retryInterval must more than 0");
        Assert.hasText(properties.getLockKeyPrefix(), "lock key prefix must be not blank");
        Assert.notEmpty(executors, "executors must have at least one");

        for (LockExecutor executor : executors) {
            executorMap.put(executor.getClass(), executor);
        }

        final Class<? extends LockExecutor> primaryExecutor = properties.getPrimaryExecutor();
        if (null == primaryExecutor) {
            this.primaryExecutor = executors.get(0);
        } else {
            this.primaryExecutor = executorMap.get(primaryExecutor);
            Assert.notNull(this.primaryExecutor, "primaryExecutor must be not null");
        }
    }
}

    该LockTemplate模板类是通过lock4j-core的自动装配LockAutoConfiguration类在自动装配的时候解析了Lock4jProperties配置了,根据配置了去选择注入对应的锁实现,还有一个比较关键的类就是这个

    LockInterceptor类:

/*
 *  Copyright (c) 2018-2022, baomidou (63976799@qq.com).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.baomidou.lock.aop;

import com.baomidou.lock.LockFailureStrategy;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockKeyBuilder;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.StringUtils;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * 分布式锁aop处理器
 *
 * @author zengzhihong TaoYu
 */
@Slf4j
@RequiredArgsConstructor
public class LockInterceptor implements MethodInterceptor,InitializingBean {
    private final Map<Class<? extends LockKeyBuilder>, LockKeyBuilder> keyBuilderMap = new LinkedHashMap<>();
    private final Map<Class<? extends LockFailureStrategy>, LockFailureStrategy> failureStrategyMap = new LinkedHashMap<>();

    private final LockTemplate lockTemplate;

    private final List<LockKeyBuilder> keyBuilders;

    private final List<LockFailureStrategy> failureStrategies;

    private final Lock4jProperties lock4jProperties;

    private LockOperation  primaryLockOperation;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //fix 使用其他aop组件时,aop切了两次.
        Class<?> cls = AopProxyUtils.ultimateTargetClass(invocation.getThis());
        if (!cls.equals(invocation.getThis().getClass())) {
            return invocation.proceed();
        }
        Lock4j lock4j = AnnotatedElementUtils.findMergedAnnotation(invocation.getMethod(),Lock4j.class);
        LockInfo lockInfo = null;
        try {
            LockOperation lockOperation = buildLockOperation(lock4j);
            String prefix = lock4jProperties.getLockKeyPrefix() + ":";
            prefix += StringUtils.hasText(lock4j.name()) ? lock4j.name() :
                    invocation.getMethod().getDeclaringClass().getName() + invocation.getMethod().getName();
            String key = prefix + "#" + lockOperation.lockKeyBuilder.buildKey(invocation, lock4j.keys());
            lockInfo = lockTemplate.lock(key, lock4j.expire(), lock4j.acquireTimeout(), lock4j.executor());
            if (null != lockInfo) {
                return invocation.proceed();
            }
            // lock failure
            lockOperation.lockFailureStrategy.onLockFailure(key, invocation.getMethod(), invocation.getArguments());
            return null;
        } finally {
            if (null != lockInfo && lock4j.autoRelease()) {
                final boolean releaseLock = lockTemplate.releaseLock(lockInfo);
                if (!releaseLock) {
                    log.error("releaseLock fail,lockKey={},lockValue={}", lockInfo.getLockKey(),
                            lockInfo.getLockValue());
                }
            }
        }
    }

    @Override
    public void afterPropertiesSet(){
        keyBuilderMap.putAll(keyBuilders.stream().collect(Collectors.toMap(LockKeyBuilder::getClass, x -> x)));
        failureStrategyMap.putAll(failureStrategies.stream().collect(Collectors.toMap(LockFailureStrategy::getClass, x -> x)));
        LockKeyBuilder lockKeyBuilder;
        LockFailureStrategy lockFailureStrategy;
        List<LockKeyBuilder> priorityOrderedLockBuilders = keyBuilders.stream().filter(Ordered.class::isInstance).collect(Collectors.toList());
        if (lock4jProperties.getPrimaryKeyBuilder() != null) {
            lockKeyBuilder = keyBuilderMap.get(lock4jProperties.getPrimaryKeyBuilder());
        } else if (!priorityOrderedLockBuilders.isEmpty()) {
            sortOperation(priorityOrderedLockBuilders);
            lockKeyBuilder = priorityOrderedLockBuilders.get(0);
        } else {
            lockKeyBuilder = keyBuilders.get(0);
        }
        List<LockFailureStrategy> priorityOrderedFailures = failureStrategies.stream().filter(Ordered.class::isInstance).collect(Collectors.toList());
        if (lock4jProperties.getPrimaryFailureStrategy() != null) {
            lockFailureStrategy = failureStrategyMap.get(lock4jProperties.getPrimaryFailureStrategy());
        } else if (!priorityOrderedFailures.isEmpty()) {
            sortOperation(priorityOrderedFailures);
            lockFailureStrategy = priorityOrderedFailures.get(0);
        } else {
            lockFailureStrategy = failureStrategies.get(0);
        }
        primaryLockOperation = LockOperation.builder().lockKeyBuilder(lockKeyBuilder).lockFailureStrategy(lockFailureStrategy).build();
    }


    @Builder
    private static class LockOperation{
        /**
         * key生成器
         */
        private LockKeyBuilder lockKeyBuilder;
        /**
         * 锁失败策略
         */
        private LockFailureStrategy lockFailureStrategy;
    }

    private LockOperation buildLockOperation(Lock4j lock4j){
        LockKeyBuilder lockKeyBuilder;
        LockFailureStrategy lockFailureStrategy;
        Class<? extends LockFailureStrategy> failStrategy = lock4j.failStrategy();
        Class<? extends LockKeyBuilder> keyBuilderStrategy = lock4j.keyBuilderStrategy();
        if (keyBuilderStrategy == null || keyBuilderStrategy == LockKeyBuilder.class) {
            lockKeyBuilder = primaryLockOperation.lockKeyBuilder;
        } else {
            lockKeyBuilder = keyBuilderMap.get(keyBuilderStrategy);
        }
        if (failStrategy == null || failStrategy == LockFailureStrategy.class) {
            lockFailureStrategy = primaryLockOperation.lockFailureStrategy;
        } else {
            lockFailureStrategy = failureStrategyMap.get(failStrategy);
        }
        return LockOperation.builder().lockKeyBuilder(lockKeyBuilder).lockFailureStrategy(lockFailureStrategy).build();
    }

    private void sortOperation(List<?> operations){
        if (operations.size()<=1){
            return;
        }
        operations.sort(OrderComparator.INSTANCE);
    }

}

    aop解析到加了@Lock4j注解方法的类上根据注解的属性(过期时间,keys通过SPEL表达式解析到的,超时时间,失败策略,keys生成策略,锁是否自动释放,方法名称,失败策略等)生成动态代理,在目标对象的目标方法被调用前会被LockInterceptor拦截器invoke方法会执行,在执行目标方法前对其进行功能增强,LockInterceptor的invoke方法会调用lockTemplate.lock方法进行加锁,而lockTemplate.lock方法是对三种分布式锁实现的一个模板统一的抽象,具体实现会下沉到不同的实现starter里面,给个实现的starter也会通过spirngBoot的自动装配,LockAutoConfiguration会把所有的锁实现的LockExecutor

    @Bean
    @ConditionalOnMissingBean
    public LockTemplate lockTemplate(List<LockExecutor> executors) {
        LockTemplate lockTemplate = new LockTemplate();
        lockTemplate.setProperties(properties);
        lockTemplate.setExecutors(executors);
        return lockTemplate;
    }

    装配到LockTemplate的executors属性上

   @Setter
   private List<LockExecutor> executors;

    在LockTemplate的afterPropertiesSet的方法会在spring属性注入后置操作的一个接口,会根据Lock4jProperties配置的执行器设置主执行器,会把所有分布式锁的执行器的bean放到了executorMap中

@Override
    public void afterPropertiesSet() throws Exception {

        Assert.isTrue(properties.getAcquireTimeout() >= 0, "tryTimeout must least 0");
        Assert.isTrue(properties.getExpire() >= -1, "expireTime must lease -1");
        Assert.isTrue(properties.getRetryInterval() >= 0, "retryInterval must more than 0");
        Assert.hasText(properties.getLockKeyPrefix(), "lock key prefix must be not blank");
        Assert.notEmpty(executors, "executors must have at least one");

        for (LockExecutor executor : executors) {
            executorMap.put(executor.getClass(), executor);
        }

        final Class<? extends LockExecutor> primaryExecutor = properties.getPrimaryExecutor();
        if (null == primaryExecutor) {
            this.primaryExecutor = executors.get(0);
        } else {
            this.primaryExecutor = executorMap.get(primaryExecutor);
            Assert.notNull(this.primaryExecutor, "primaryExecutor must be not null");
        }
    }

    然后在LockTemplate的lock方法中去找到一个执行器实现,然后去加锁解锁等操作

LockExecutor lockExecutor = obtainExecutor(executor);

    大致上的一个实现源码思路解析就是上面哪些,RedisTemplateLockExecutor的实现其实是利用redisTemplate先redis提交执行了一下两个luna脚本:

    private static final RedisScript<String> SCRIPT_LOCK = new DefaultRedisScript<>("return redis.call('set',KEYS[1]," +
            "ARGV[1],'NX','PX',ARGV[2])", String.class);
    private static final RedisScript<String> SCRIPT_UNLOCK = new DefaultRedisScript<>("if redis.call('get',KEYS[1]) " +
            "== ARGV[1] then return tostring(redis.call('del', KEYS[1])==1) else return 'false' end", String.class);
    private static final RedisScript<Boolean> SCRIPT_RENEWAL = new DefaultRedisScript<>("if redis.call('get', KEYS[1]) ==ARGV[1] " +
            "then return redis.call('pexpire', KEYS[1], ARGV[2]) else  return 0  end", Boolean.class);

    他的这个实现就是redisson实现的简化版本,redisson的实现原理其实也是利用luna脚本语言执行的原子特性,redisson作为一个开源的分布式锁实现框架,还提供了其它的一些能力不至于分布式锁,它提供的锁的类型姿势功能更多更丰富和强大,ZooKeeper分布式锁的实现主要依赖于其的特性。

5.总结

    Lokck4j实现分布式锁的使用、原理和源码就都已经分享完了,zk看他的配置是支持多节点高可用的,但是对于redis实现的分布式锁,看着只可以配置一个单节点,所以redis方式就存在单节点风险,跟我之前那个实现有同样的问题,redis节点下可以正常使用,多节点发生主备切换,主节点宕机从节点顶上的一瞬间就可能会重复加锁,之前节点加的锁没有释放等问题,所以就需要重新拓展实现一下才行,Lokck4j提供的这个限流的功能有限,可以使用我上面写那个好用开源的轮子,可以解决业务上遇到的限流问题,你可以使用我写的开源的轮子或者是使用这个Lock4J让你写的业务代码中使用分布式锁的姿势更加优雅或者你可以写其它的实现方式,Lock4J的源码写的确实优雅,用到了建造者模式、模板方法模式、桥接模式、策略模式等设计模式,使用好的设计模式和设计思想,可以使写出的代码高内聚,低耦合,鲁棒性更强,可拓展性更强、可维护性更高,养成良好的编码习惯尤为重要,写出干净整洁优美优雅的代码,让代码可读性更强,我的分享到此结束,希望对你有所启发和帮助,请一键三连,么么么哒!

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

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

相关文章

平均102天 Accept的国产医学顶刊,影响因子4连涨,还免版面费!

《Asian Journal of Pharmaceutical Sciences》 (亚洲药物制剂科学) 是由沈阳药科大学主办、Elsevier合作出版的全英文药剂学学术期刊&#xff0c;是“中国科技期刊卓越行动计划”资助期刊&#xff0c;现已被SCIE、PubMed Central、Scopus和DOAJ等国际著名检索系统收录&#xf…

【已解决】“import ... =“ 只能在 TypeScript 文件中使用

现象 在使用 import 语法的时候&#xff0c;代码报红&#xff0c;提示&#xff1a;“import ... “ 只能在 TypeScript 文件中使用 原因 代码被 VSCode 解析成 TypeScript 语法 解决方案&#xff1a; 关闭 JavaScript 的验证启用即可。 mac 快捷方式&#xff1a;comman s…

25.【C语言】循环结构之for 上

1.基本使用 类比while 在while循环中&#xff0c;有三个不可或缺的部分&#xff1a;初始化&#xff0c;判断部分&#xff0c;调整部分 int i 0;//初始化 while (i < 10)//判断部分 {……i;//调整部分 }三个部分太分散&#xff0c;用for循环可集为一体&#xff0c;简洁 …

【海贼王的数据航海】ST表——RMQ问题

目录 1 -> RMQ问题 1.1 -> 定义 1.2 -> 解决策略 2 -> ST表 2.1 -> 定义 2.2 什么是可重复贡献问题 2.3 -> 预处理ST表 2.4 -> 处理查询 2.5 -> 实际问题 1 -> RMQ问题 1.1 -> 定义 RMQ (Range Minimum/Maximum Query)即区间最值查询…

印度第二大移动提供商 3.75 亿数据待售

一个名为“xenZen”的威胁行为者已在 BreachForums 上出售 Airtel 的数据库。 该列表包含来自 3.75 亿客户的数据。 数据详情&#xff1a; 手机号码 名 出生日期 父亲的名字 地址 电子邮件ID 类型 国籍 阿达尔 带照片的身份证明详细信息 地址详细信息证明等 鉴于…

【反悔堆 优先队列 临项交换 决策包容性】630. 课程表 III

本文涉及知识点 贪心 反悔堆 优先队列 临项交换 Leetcode630. 课程表 III 这里有 n 门不同的在线课程&#xff0c;按从 1 到 n 编号。给你一个数组 courses &#xff0c;其中 courses[i] [durationi, lastDayi] 表示第 i 门课将会 持续 上 durationi 天课&#xff0c;并且必…

应用信息查看器更新至1.5.0

https://download.csdn.net/download/zzmzzff/89518318

构建滑块组件_第 1 部分

前言 ● 本次将和大家一起学习实现滑块的功能 ● 由于这有些错乱&#xff0c;我们将用图片来代替&#xff0c;以实现功能 ● 这里我们简单的说一下原理&#xff0c;如下图所示&#xff0c;通过改变tanslateX的值来达到滑动的效果&#xff0c;所以最核心的就是我们需要通过…

ODOO17的邮件机制-系统自动推送修改密码的邮件

用户收到被要求重置密码的邮件&#xff1a; 我们来分析一下ODOO此邮件的工作机制&#xff1a; 1、邮件模板定义 2、渲染模板的函数&#xff1a; 3、调用此函数的机制&#xff1a; 当用户移除或增加了信任的设备&#xff08;如电脑、手机端等&#xff09;&#xff0c;系统会自…

银河麒麟V10 SP1 审计工具 auditd更新

前言 银河麒麟V10 SP1 审计工具 auditd 引发的内存占用过高&#xff0c; 内存使用率一直在 60% 以上&#xff0c; 内存一直不释放 排查 可以使用ps或者top查看系统进程使用情况 ps -aux|sort -k4nr|head -n 5 发现银河麒麟审计工具 auditd 一直占用内存不释放 解决 办法一…

GD32MCU如何实现掉电数据保存?

大家在GD32 MCU应用时&#xff0c;是否会碰到以下应用需求&#xff1a;希望在MCU掉电时保存一定的数据或标志&#xff0c;用以记录一些关键的数据。 以GD32E103为例&#xff0c;数据的存储介质可以选择内部Flash或者备份数据寄存器。 如下图所示&#xff0c;片内Flash具有10年…

如何使用uer做多分类任务

如何使用uer做多分类任务 语料集下载 找到这里点击即可 里面是这有json文件的 因此我们对此要做一些处理&#xff0c;将其转为tsv格式 # -*- coding: utf-8 -*- import json import csv import chardet# 检测文件编码 def detect_encoding(file_path):with open(file_path,…

OceanBase Meetup北京站|跨行业应用场景中的一体化分布式数据库:AI赋能下的探索与实践

随着业务规模的不断扩张和数据处理需求的日益复杂化&#xff0c;传统数据库架构逐渐暴露出业务稳定性波动、扩展性受限、处理效率降低以及运营成本高等一系列问题。众多行业及其业务场景纷纷踏上了数据库现代化升级之路。 为应对这些挑战&#xff0c;7月6日&#xff0c;OceanB…

在Linux环境下搭建Redis服务结合内网穿透实现通过GUI工具远程管理数据库

文章目录 前言1. 安装Docker步骤2. 使用docker拉取redis镜像3. 启动redis容器4. 本地连接测试4.1 安装redis图形化界面工具4.2 使用RDM连接测试 5. 公网远程访问本地redis5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定TCP地址远程访问 前言 本文主要介绍如何在Li…

1.Python学习笔记

一、环境配置 1.Python解释器 把程序员用编程语言编写的程序&#xff0c;翻译成计算机可以执行的机器语言 安装&#xff1a; 双击Python3.7.0-选择自定义安装【Customize installation】-勾选配置环境变量 如果没有勾选配置环境变量&#xff0c;输入python就会提示找不到命令…

第四届BPAA算法大赛成功举办!共研算法未来

大家好&#xff0c;我是herosunly。985院校硕士毕业&#xff0c;现担任算法研究员一职&#xff0c;热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名&#xff0c;CCF比赛第二名&#xff0c;科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的…

vue学习笔记(购物车小案例)

用一个简单的购物车demo来回顾一下其中需要注意的细节。 先看一下最终效果 功能&#xff1a; &#xff08;1&#xff09;全选按钮和下面的商品项的选中状态同步&#xff0c;当下面的商品全部选中时&#xff0c;全选勾选&#xff0c;反之&#xff0c;则不勾选。 &#xff08…

最短路:Dijkstra

原始模板&#xff1a; 时间复杂度O() 使用于图很满的情况 struct Node{int y,v;Node(int _y,int _v){y_y;v_v;} };vector<Node> edge[N1]; int n,m,dist[N1]; bool b[N1];int Dijistra(int s,int t){memset(b,false,sizeof(b));memset(dist,127,sizeof(dist));dist[s]…

Windows 11文件资源管理器选项卡的4个高级用法,肯定有你喜欢的

作为一个每天使用文件资源管理器来管理我的工作流程的人,选项卡帮助我为处于不同完成阶段的工作创建了不同的文件夹。以下是我使用选项卡提高工作效率的最佳技巧。 打开和关闭选项卡 假设你的计算机上安装了Windows 11的最新更新,请按Ctrl+E打开文件资源管理器。在我发现“…

软件工程学面向对象

一、面向对象方法学概述 传统的生命周期方法学在消除软件非结构化、促进软件开发工程化方面起了积极的作用&#xff0c;但仍有许多不足&#xff0c;存在的主要问题有&#xff1a;①生产率提高的幅度不能满足需要&#xff1b; ②软件重用程度很低&#xff1b; ③软件很难维护&a…