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

文章目录

  • 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秒锁过期@Lock4jpublic 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 {@Autowiredprivate 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<>();@Setterprivate Lock4jProperties properties;@Setterprivate 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;}@Overridepublic 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;@Overridepublic 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 failurelockOperation.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());}}}}@Overridepublic 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();}@Builderprivate 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@ConditionalOnMissingBeanpublic LockTemplate lockTemplate(List<LockExecutor> executors) {LockTemplate lockTemplate = new LockTemplate();lockTemplate.setProperties(properties);lockTemplate.setExecutors(executors);return lockTemplate;}

    装配到LockTemplate的executors属性上

   @Setterprivate List<LockExecutor> executors;

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

@Overridepublic 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.mzph.cn/news/867372.shtml

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

相关文章

昇思第6天

函数式自动微分 神经网络的训练主要使用反向传播算法&#xff0c;模型预测值&#xff08;logits&#xff09;与正确标签&#xff08;label&#xff09;送入损失函数&#xff08;loss function&#xff09;获得loss&#xff0c;然后进行反向传播计算&#xff0c;求得梯度&#…

【算法专题】双指针算法

1. 移动零 题目分析 对于这类数组分块的问题&#xff0c;我们应该首先想到用双指针的思路来进行处理&#xff0c;因为数组可以通过下标进行访问&#xff0c;所以说我们不用真的定义指针&#xff0c;用下标即可。比如本题就要求将数组划分为零区域和非零区域&#xff0c;我们不…

时序分析基本概念介绍——SI/crosstalk/delta delay/noise/timing Window

文章目录 前言一、Crosstalk1. Crosstalk Delay Effects2. Crosstalk Noise Effects 二、Crosstalk Analysis1. Crosstalk Delay Analysis2. Crosstalk Noise Analysis 三、如何 fix delta delay 和 noise violations1. 检查delta delay 和 noisedelta delay checknoise check …

【C语言小知识】缓冲区

缓冲区 当我们使用printf()将数据显示在屏幕上&#xff0c;或者使用scanf()函数将数据输入到电脑里&#xff0c;我们是否会产生些许疑问&#xff1f;为何输入的字符会直接显示到屏幕上等等。这里需要介绍一个C语言中的一个关键概念——缓冲区。 当我们使用老式系统进行运行代码…

suricata7 rule加载(一)加载 action

suricata7.0.5 一、前提条件 1.1 关键字注册 main | --> SuricataMain|--> PostConfLoadedSetup|--> SigTableSetupsigmatch_table是一个全局数组&#xff0c;每个元素就是一个关键字节点&#xff0c;是对关键字如何处理等相关回调函数。非常重要的一个结构&#x…

PyCharm如何安装requirements.txt中的依赖包

问题&#xff1a;下载别人的源码&#xff0c;如何安装代码中requirement.txt中的依赖包。 解决方案&#xff1a; &#xff08;1&#xff09;打开PyCharm下面的Terminal&#xff0c;先为代码创建单独的虚拟环境并进入到虚拟环境中&#xff08;每个项目单独的环境&#xff0c;这…

GlusterFS分布式存储系统

GlusterFS分布式存储系统 一&#xff0c;分布式文件系统理论基础 1.1 分布式文件系统出现 计算机通过文件系统管理&#xff0c;存储数据&#xff0c;而现在数据信息爆炸的时代中人们可以获取的数据成指数倍的增长&#xff0c;单纯通过增加硬盘个数来扩展计算机文件系统的存储…

Docker搭建MySQL双主复制详细教程

在此之前需要提前安装好Docker和 Docker Compose 。 一、创建目录 首先创建一个本地数据挂载目录。 mkdir -p master1-data master2-data二、编写docker-compose.yml version: 3.7services:mysql-master1:image: mysql:5.7.36container_name: mysql-master1environment:MYSQL_…

VBA初学:零件成本统计之四(汇总计算)

第四步&#xff0c;最后进行汇总计算 汇总统计的计算 Sub count() Dim rng As Range Dim i As Long, j As Long Dim arr_s, arr, brr, crr, drr Dim rowscount As Long Dim X As Variant Dim rg As Single, xb As Single, zj As SingleMsgBox "汇总计算时间较久&#xff…

【HTML入门】第二课 - head标签下的常见标签们

目录 1 本节概要 2 head下的常见标签 2.1 网页编码设置 2.2 网页的标题 2.3 样式标签 3 head标签的内容不会显示到网页上 4 查看网页源代码 1 本节概要 上一节&#xff0c;我们说了HTML网页最基本的框架标签&#xff0c;说到标签分为head头部和body身体部分。这一小节呢…

Windows Server 2016 搭建 网络负载平衡 服务

网络负载平衡功能的安装 添加角色 默认不动————功能 勾选上 < 网络负载平衡 > 在工具中————打开 < 网络负载平衡管理器 > 网络负载平衡群集创建 注意 : 提前 将两台 web 站点服务器 都安装好 < 网络负载平衡功能 > 右键 选择 ————新建群集 ——…

【学习笔记】爱立信SPO 1400 CRAFT软件基础知识6——配置的备份与恢复的详细方法

一、前期准备 提示&#xff1a;下面所有学习内容都是基于以下条件完成的 条件1.已经正确安装并正常运行SPO 1400 CRAFT软件&#xff08;以下简称LCT&#xff09; 条件2.确认已正确使用爱立信SPO 1400 CRAFT软件通过网络登录设备&#xff08;以下简称NE&#xff09; 具体登录…

【图解大数据技术】Flume、Kafka、Sqoop

【图解大数据技术】Flume、Kafka、Sqoop FlumeFlume简介Flume的应用场景 KafkaKafka简介Kafka架构Flume与Kafka集成 SqoopSqoop简介Sqoop原理sqoop搭配任务调度器实现定时数据同步 Flume Flume简介 Flume是一个数据采集工具&#xff0c;多用于大数据技术架构下的日志采集。 …

SQL-DCL(三)

一.DCL介绍 DCL英文全称是Data Control Language(数据库控制语言),用来管理数据库 用户,控制数据库的访问权限。 二.两个方面 1.数据库可以由那些用户访问 2.可以访问那些内容 三.DCL-管理用户 1.查询用户 USE mysql SELECT * FROM user 2.创建用户 CREATE USER…

基于Qwen2/Lllama3等大模型,部署团队私有化RAG知识库系统的详细教程(Docker+AnythingLLM)

自 ChatGPT 发布以来&#xff0c;大型语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff0c;大模型&#xff09;得到了飞速发展&#xff0c;它在处理复杂任务、增强自然语言理解和生成类人文本等方面的能力让人惊叹&#xff0c;几乎各行各业均可从中获益。 然…

利用级数公式计算圆周率(π)

π是是指圆的周长与直径的比值&#xff0c;是无限不循环小数&#xff0c;有很多种方法可以求得它的近似值。这里用比较容易实现的关于π的无穷级数来求它的前10000位的取值。 π / 2 π 具体的&#xff0c;用两个字符数组x,z分别存放当前计算得到的pi值&#xff0c;数组…

有趣的算法

目录&#xff1a; 1、百钱买百鸡 2、韩信点兵 1&#xff09;概述 2&#xff09;正常取余算法 3&#xff09;循环算法 1、百钱买百鸡 我国古代《算经》中的“百钱买百鸡”问题&#xff1a; 鸡翁一&#xff0c;值钱五&#xff1b;鸡母一&#xff0c;值钱三&#xff1b;鸡…

并口、串口和GPIO口区别

并口 并行接口,简称并口。并口采用的是25针D形接头。所谓“并行”,是指8位数据同时通过并行线进行传送,这样数据传送速度大大提高,但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,数据也就容易出错,目前,并行接口主要作为打印机端口等。 并口的工作模式 …

景色短视频:成都柏煜文化传媒有限公司

景色短视频&#xff1a;定格自然之美&#xff0c;邂逅心灵之旅 在这个被数字洪流包围的时代&#xff0c;短视频以其独特的魅力&#xff0c;为我们打开了一扇通往无限可能的大门。而在众多短视频类型中&#xff0c;景色短视频以其无与伦比的视觉冲击力&#xff0c;成为了许多人…

优化路由,优化请求url

1、使用父子关系调整下使其更加整洁 2、比如说我修改了下url,那所有的页面都要更改 优化&#xff1a;把这个url抽出来&#xff0c;新建一个Api文件夹用于存放所有接口的url&#xff0c;在业务里只需要关注业务就可以 使用时 导包 发请求 如果想要更改路径&#xff0c;在这里…