picgo插件代码示例

import picgo from 'picgo' // @ts-ignore import { PluginConfig } from 'picgo/dist/utils/interfaces' const config = (ctx: picgo): PluginConfig[] => { let userConfig = ctx.getConfig('picBed.imgYun') if (!userConfig) { userConfig = {} } return [ { name: 'serverHost', type: 'input', default: userConfig.serverHost || '', message: 'serverHost 不能为空', required: true }, { name: 'apiToken', type: 'password', default: userConfig.apiToken || '', message: 'apiToken 不能为空', required: true } ] } const postOptions = (fileName:string, options: any, image: Buffer): any => { return { method: 'POST', url: options.serverHost+'/api/img/upload', headers: { contentType: 'multipart/form-data', 'User-Agent': 'PicGo', 'token': options.apiToken }, formData: { file: { value: image, options: { filename: fileName } } } } } const handle = async (ctx: picgo): Promise<picgo> => { const imgYunOptions = ctx.getConfig('picBed.imgYun') if (!imgYunOptions) { throw new Error('imgYun 图床配置文件') } try { const imgList = ctx.output for (const img of imgList) { let image = img.buffer if (!image && img.base64Image) { image = Buffer.from(img.base64Image, 'base64') } const options = postOptions(img.fileName,imgYunOptions,image); let body = await ctx.Request.request(options) body = JSON.parse(body) if (body.success) { delete img.base64Image delete img.buffer img.imgUrl = body.obj } else { ctx.log.error(body) throw new Error('Server error, please try again') } } return ctx } catch (err) { throw err } } export = (ctx: picgo) => { const register = () => { ctx.helper.uploader.register('imgYun', { handle, name: 'imgYun', config: config }) } return { uploader: 'imgYun', register } }

September 27, 2024

Scala基础--关键字,数据类型,函数

基础语法 注意点 Scala 区分大小写 类名类似于Java 方法名命名类似于Java 老规矩,先上Hello World object Demo { def main(args: Array[String]): Unit = { println("hello world") } } 关键字 abstract case catch class def do else extends false final finally for forSome if implicit import lazy match new null object override package private protected return sealed super this throw trait try true type val var while with yield - : = => <- <: <% >: # @ 注释 注释也和Java类似 ...

September 27, 2024

shell学习笔记

变量 使用和创建变量 your_name="qinjx" echo $your_name echo ${your_name} 删除变量 #!/bin/sh myStr="deleted string" unset myStr echo $myStr 字符串 单引号和双引号的区别 单引号内的字符串会原样输出 双引号中的变量会进行替换 计算字符串长度 string="abcd" echo ${#string} #输出 4 截断字符串 string="runoob is a great site" echo ${string:1:4} # 输出 unoo 数组 数组名=(值1 值2 ... 值n) #!/bin/bash array=(1 2 3 4) echo ${array[1]} 获取数组长度 echo ${array_name[@]} # 或者 length=${#array_name[*]} 获取数组单个元素的长度 # 取得数组单个元素的长度 lengthn=${#array_name[n]} 注释 单行 # 注释内容 多行 :<<EOF 注释内容... 注释内容... 注释内容... EOF :<<' 注释内容... 注释内容... 注释内容... ' :<<! 注释内容... 注释内容... 注释内容... ! 参数接收 #!/bin/bash echo "Shell 传递参数实例!"; echo "执行的文件名:$0"; echo "第一个参数为:$1"; echo "第二个参数为:$2"; echo "第三个参数为:$3"; ./demo.sh 1 2 3 参数处理 说明 $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数。 如"$*“用「"」括起来的情况、以”$1 $2 … $n"的形式输出所有参数。 $$ 脚本运行的当前进程ID号 $! 后台运行的最后一个进程的ID号 $@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。 如"$@“用「"」括起来的情况、以”$1" “$2” … “$n” 的形式输出所有参数。 $- 显示Shell使用的当前选项,与set命令功能相同。 $? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 运算符 算数运算符 运算符 说明 举例 + 加法 expr $a + $b 结果为 30。 - 减法 expr $a - $b 结果为 -10。 * 乘法 expr $a \* $b 结果为 200。 / 除法 expr $b / $a 结果为 2。 % 取余 expr $b % $a 结果为 0。 = 赋值 a=$b 将把变量 b 的值赋给 a。 == 相等。用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false。 != 不相等。用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true。 关系运算符 运算符 说明 举例 -eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。 -ne 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。 -gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。 -lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。 -ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。 -le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。 … ...

September 27, 2024

Spark在Standalone运行架构中的运行流程

Standalone Standalone模式是Spark实现的资源调度框架,其主要的节点有Client节点、Master节点和Worker节点。其中Driver既可以运行在Master节点上中, 也可以运行在本地Client端。当用spark-shell交互式工具提交Spark的Job时,Driver在Master节点上运行; 当使用spark-submit工具提交Job或者在Eclips、IDEA等开发平台上使用”new SparkConf().setMaster(“spark://master:7077”)”方式运行Spark任务时, Driver是运行在本地Client端上的。 具体流程 我们提交一个任务,任务就叫Application 初始化程序的入口SparkContext 初始化DAG Scheduler 初始化Task Scheduler Task Scheduler向master去进行注册并申请资源(CPU Core和Memory) Master根据SparkContext的资源申请要求和Worker心跳周期内报告的信息决定在哪个Worker上分配资源,然后在该Worker上获取资源,然后启动StandaloneExecutorBackend;顺便初始化好了一个线程池 StandaloneExecutorBackend向Driver(SparkContext)注册,这样Driver就知道哪些Executor为他进行服务了。 到这个时候其实我们的初始化过程基本完成了,我们开始执行transform、ation的代码,但是代码并不会真正的运行,直到我们遇到一个action操作。先生产一个job任务,进行stage的划分。 SparkContext将Applicaiton代码发送给StandaloneExecutorBackend;并且SparkContext解析Applicaiton代码,构建DAG图,并提交给DAG Scheduler分解成Stage(当碰到Action操作时,就会催生Job;每个Job中含有1个或多个Stage,Stage一般在获取外部数据和shuffle之前产生)。 将Stage(或者称为TaskSet)提交给Task Scheduler。Task Scheduler负责将Task分配到相应的Worker,最后提交给StandaloneExecutorBackend执行; 对task进行序列化,并根据task的分配算法,分配task 对接收过来的task进行反序列化,把task封装成一个线程 开始执行Task,并向SparkContext报告,直至Task完成。 资源注销

September 27, 2024

Spark学习之Spark简述

Spark生态 Spark Core:包含Spark的基本功能;尤其是定义RDD的API、操作以及这两者上的动作。其他Spark的库都是构建在RDD和Spark Core之上的 Spark SQL:提供通过Apache Hive的SQL变体Hive查询语言(HiveQL)与Spark进行交互的API。每个数据库表被当做一个RDD,Spark SQL查询被转换为Spark操作。 Spark Streaming:对实时数据流进行处理和控制。Spark Streaming允许程序能够像普通RDD一样处理实时数据 MLlib:一个常用机器学习算法库,算法被实现为对RDD的Spark操作。这个库包含可扩展的学习算法,比如分类、回归等需要对大量数据集进行迭代的操作。 GraphX:控制图、并行图操作和计算的一组算法和工具的集合。GraphX扩展了RDD API,包含控制图、创建子图、访问路径上所有顶点的操作。 Spark的运行流程 构建Spark Application的运行环境,启动SparkContext SparkContext向资源管理器(可以是Standalone,Mesos,Yarn)申请运行Executor资源,并启动StandaloneExecutorbackend, Executor向SparkContext申请Task SparkContext将应用程序分发给Executor SparkContext构建成DAG图,将DAG图分解成Stage、将Taskset发送给Task Scheduler,最后由Task Scheduler将Task发送给Executor运行 Task在Executor上运行,运行完释放所有资源 Spark与Hadoop MapReduce采用硬盘保存临时数据,而Spark采用内存保存临时数据

September 27, 2024

Spark的运行原理

Spark基本概念 RDD:是弹性分布式数据集(Resilient Distributed Dataset)的简称,是分布式内存的一个抽象概念,提供了一种高度受限的共享内存模型。 DAGScheduler:有向无环图调度器基于DAG划分Stage并以TaskSet的形式提交Stage给TaskScheduler;负责将作业拆分成不同阶段的具有宽窄依赖关系的多批任务;最重要的任务之一就是:计算作业和任务的依赖关系,指定调度逻辑。在SparkContext初始化的过程中被实例化,一个SparkContext对应一个DAGScheduler。 DAG:是Directed Acyclic Graph(有向无环图)的简称,反映RDD之间的依赖关系。 窄依赖:父RDD每一个分区最多被一个子RDD的分区所用;表现为一个父RDD的分区对应于一个子RDD的分区,或两个父RDD的分区对应于一个子RDD的分区。 宽依赖:父RDD的每个分区都可能被多个子RDD分区所使用,子RDD分区通常对应所有的父RDD分区 常见的宽窄依赖有:map、filter、union、mapPartitions、mapValues、join(父RDD是hash-partitioned:如果joinAPI之前被调用的RDD API是宽依赖【存在shuffle】,而且两个join的RDD的分区数量一致,join结果的RDD分区数量也一样,这个时候join API是窄依赖)。 常见的宽依赖有:groupByKey、partitionBy、reduceByKey、join(父RDD不是hash-partitioned:除此之外的,RDD的join API都是宽依赖)。 Driver Program:控制程序,负责为Application构建DAG图。 Cluster Manager:集群资源管理中心,负责分配计算资源。 Worker Node:工作节点,负责完成具体计算。 Executor:是运行在工作节点(Worker Node)上的一个进程,负责运行Task,并为应用程序存储数据。 Application:用户编写的Spark应用程序,一个Application包含多个Job。 Job:作业,一个Job包含多个RDD及作用于相应RDD上的各种操作。 Stage:阶段,是作业的基本调度单位,一个作业会分为多组任务,每组任务被称为“阶段”。 TaskScheduler:任务调度器 将TaskSet提交给Worker(集群)运行并汇报结果;负责每个具体任务的实际物理调度。 Task:任务,运行在Executor上的工作单元,是Executor中的一个线程。 TaskSet:任务集 由一组关联的,但相互之间没有Shuffle依赖关系的任务所组成的任务集。 1) 一个Stage创建一个TaskSet; 2) 为Stage的每个RDD分区创建一个Task,多个Task分装成TaskSet。 总结:Application由多个Job组成,Job由多个Stage组成,Stage由多个Task组成。Stage是作业调度的基本单位。 整体结构 Spark作业执行过程 我们使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。根据你使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动,也可能在集群中某个工作节点上启动。而Driver进程要做的第一件事情,就是向集群管理器(可以是Spark Standalone集群,也可以是其他的资源管理集群,美团•大众点评使用的是YARN作为资源管理集群)申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU core。 在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了。Driver进程会将我们编写的Spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批Task,然后将这些Task分配到各个Executor进程中执行。Task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个Task处理的数据不同而已。一个stage的所有Task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个stage。下一个stage的Task的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据,得到我们想要的结果为止。 Spark是根据shuffle类算子来进行stage的划分。如果我们的代码中执行了某个shuffle类算子(比如reduceByKey、join等),那么就会在该算子处,划分出一个stage界限来。可以大致理解为,shuffle算子执行之前的代码会被划分为一个stage,shuffle算子执行以及之后的代码会被划分为下一个stage。因此一个stage刚开始执行的时候,它的每个Task可能都会从上一个stage的Task所在的节点,去通过网络传输拉取需要自己处理的所有key,然后对拉取到的所有相同的key使用我们自己编写的算子函数执行聚合操作(比如reduceByKey()算子接收的函数)。这个过程就是shuffle。 当我们在代码中执行了cache/persist等持久化操作时,根据我们选择的持久化级别的不同,每个Task计算出来的数据也会保存到Executor进程的内存或者所在节点的磁盘文件中。 因此Executor的内存主要分为三块:第一块是让Task执行我们自己编写的代码时使用,默认是占Executor总内存的20%;第二块是让Task通过shuffle过程拉取了上一个stage的Task的输出后,进行聚合等操作时使用,默认也是占Executor总内存的20%;第三块是让RDD持久化时使用,默认占Executor总内存的60%。 Task的执行速度是跟每个Executor进程的CPU core数量有直接关系的。一个CPU core同一时间只能执行一个线程。而每个Executor进程上分配到的多个Task,都是以每个Task一条线程的方式,多线程并发运行的。如果CPU core数量比较充足,而且分配到的Task数量比较合理,那么通常来说,可以比较快速和高效地执行完这些Task线程。

September 27, 2024

Spark的运行模式

Standalone模式 架构 Standalone运行过程 SparkContext连接到Master,向Master注册并申请资源(CPU Core and Memory)。 Master根据SparkContext的资源申请要求和Worker心跳周期内报告的信息决定哪个Worker上分配资源,然后在该Worker上获取资源,然后启动Executor,Executor向SparkContext注册。 SparkContext将Application代码发送到Executor。同时,SparkContext解析Application代码,构建DAG图,并提交给DAGScheduler分解成Stage,然后以Stage(或者称为TaskSet)提交给TaskScheduler,TaskScheduler负责将Task分配到相应的Worker,最后提交给Executor执行。 Executor会建立Executor线程池,开始执行Task,并向SparkContext报告,直至Task完成。 所有Task完成后,SparkContext向Master注销,释放资源。 Spark on Yarn模式 Yarn-Cluster模式 Client向Yarn中提交应用程序,包括ApplicationMaster程序、启动ApplicationMaster的命令、需要在Executor中运行的程序等。 ResourceManager收到请求后,在集群中选择一个NodeManager,为该应用程序分配第一个Container,要求它在这个Container中启动应用程序的ApplicationMaster,其中ApplicationMaster进行SparkContext等的初始化。 ApplicationMaster向ResourceManager注册,这样用户可以直接通过ResourceManage查看应用程序的运行状态,然后它将采用轮询的方式通过RPC协议为各个任务申请资源,并监控它们的运行状态直到运行结束。 一旦ApplicationMaster申请到资源后,便与对应的NodeManager通信,要求它在获得的Container中启动Executor,启动后会向ApplicationMaster中的SparkContext注册并申请Task。 ApplicationMaster中的SparkContext分配Task给Executor执行,Executor运行Task并向ApplicationMaster汇报运行的状态和进度,以让ApplicationMaster随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务。 应用程序运行完成后,ApplicationMaster向ResourceManager申请注销并关闭自己。 Spark on Yarn-Client 模式 Client向Yarn的ResourceManager申请启动ApplicationMaster。同时,在SparkContext初始化中将创建DAGScheduler和TaskScheduler。 ResourceManager收到请求后,在集群中选择一个NodeManager,为该能够用程序分配第一个Container,要求它在这个Container中启动应用程序的ApplicationMaster。 Client中的SparkContext初始化完毕后,与ApplicationMaster建立通讯,向ResourceManager注册,根据任务信息向ResourceManager申请资源。 一旦ApplicationMaster申请到资源后,便与对应的NodeManager通信,要求它在获得的Container中启动Executor,启动后会向Client中的SparkContext注册并申请Task。 Client中的SparkContext分配Task给Executor执行,Executor运行Task并向Driver汇报运行的状态和进度,以让Client随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务。 应用程序运行完成后,Client的SparkContext向ResourceManager申请注销并关闭自己。 Yarn-Cluster与Yarn-Client的区别 它们的区别就是ApplicationMaster的区别:Yarn-Cluster中ApplicationMaster不仅负责申请资源,并负责监控Task的运行状况,因此可以关掉Client;而Yarn-Client中ApplicationMaster仅负责申请资源,由Client中的Driver来监控调度Task的运行,因此不能关掉Client。 Spark on Yarn与MapReduce on Yarn的区别 Spark的高性能一定程度上取决于它采用的异步并发模型(这里指server/driver端采用 的模型),这与Hadoop 2.0(包括Yarn和MapReduce)是一致的。Hadoop 2.0自己实现了类似Actor的异步并发模型,实现方式是epoll+状态机,而Spark则直接采用了开源软件Akka,该软件实现了Actor模型,性能非常高。尽管二者在server端采用了一致的并发模型,但在任务级别(特指Spark任务和MapReduce任务)上却采用了不同的并发机制:Hadoop MapReduce采用了多进程模型,而Spark采用了多线程模型。 注意,这里的多进程和多线程,指的是同一个节点上多个任务的运行模式。无论是MapReduce和Spark,整体上看,都是多进程:MapReduce应用程序是由多个独立的Task进程组成的;Spark应用成的运行环境是由多个独立的Executor进程构建的临时资源池构成的。 多进程模型便于细粒度控制每个任务占用的资源,但会消耗较多的启动时间,不适合运行低延迟类型的作业,这是MapReduce广为诟病的原因之一。而多线程模型则相反,该模型使得Spark很适合运行低延迟类型的作业。总之,Spark同节点上的任务以多线程的方式运行在一个JVM进程中,可带来以下好处: 任务启动速度快,与之相反的是MapReduce Task进程的慢启动速度,通常需要1s左右; 同节点上所有任务运行在一个进程中,有利于共享内存。这非常适合内存密集型任务,尤其对于那些需要加载大量词典的应用程序,可大大节省内存; 同节点上所有任务可运行在一个JVM进程(Executor)中,且Executor所占资源可连续被多批任务使用,不会在运行部分任务后释放掉,这避免了每个任务重复申请资源带来的时间开销,对于任务数目非常多的应用,可大大降低运行时间。与之对比的是MapReduce中的Task:每个Task单独申请资源,用完后马上释放,不能被其他任务重用,尽管1.0支持JVM重用在一定程度上弥补了该问题,但2.0尚未支持该功能。 尽管Spark的多线程模型带来了很多好处,但同样存在不足,主要有: 由于节点上所有任务运行在一个进程中,因此,会出现严重的资源争用,难以细粒度控制每个任务占用资源。与之相反的是MapReduce,它允许用户单独为Map Task和Reduce Task设置不同的资源,进而细粒度控制任务占用资源量,有利于大作业的正常平稳运行。 MapReduce和Spark运行模型 MapReduce多进程模型 每个Task运行在一个独立的JVM进程中; 可单独为不同类型的Task设置不同的资源量,目前支持内存和CPU两种资源; 每个Task运行完后,释放所占用的资源,这些资源不能被其他Task复用,即使是同一个作业相同类型的Task。也就是说,每个Task都要经历“申请资源->运行Task->释放资源”的过程。 Spark多线程模型 每个节点上可以运行一个或多个Executor服务(一个节点上可以有多个Container); 每个Executor配有一定数量的slot,表示该Executor中可以同时运行多少个ShuffleMap Task或者Reduce Task; 每个Executor单独运行在一个JVM进程中,每个Task则是运行在Executor中的一个线程(每个线程可以通过轮询的方式运行每个Task); 同一个Executor内部的Task可共享内存,比如通过函数SparkContext.broadcast广播的文件或者数据结构只会在每个Executor中加载一次,而不会像MapReduce那样,每个Task加载一次; Executor一旦启动后,将一直运行,且它的资源可以一直被Task复用,直到Spark程序运行完成后才释放退出。 总结 总体上看,Spark采用的是经典的scheduler/workers模式,每个Spark应用程序运行的第一步是构建一个可重用的资源池,然后在这个资源池里运行所有的ShuffleMap Task和Reduce Task(注意,尽管Spark引擎内部只用两类Task便可表示出一个复杂的应用程序,即ShuffleMap Task和Reduce Task),而MapReduce应用程序则不同,它不会构建一个可重用的资源池,而是让每个Task动态申请资源,且运行完后马上释放资源。

September 27, 2024

SpringBoot配置文件——加载顺序

SpringBoot配置文件——加载顺序 配置文件所在目录 file:./config/ 根目录中的config目录中 file:./ 根目录中 classpath:/config/ resource目录中的config目录下 classpath:/ resource目录下(类路径下 classpath) 读取顺序(优先级顺序) 如果在不同的目录中存在多个配置文件,它的读取顺序是: 1、config/application.properties(项目根目录中config目录下) 2、config/application.yml 3、application.properties(项目根目录下) 4、application.yml 5、resources/config/application.properties(项目resources目录中config目录下) 6、resources/config/application.yml 7、resources/application.properties(项目的resources目录下) 8、resources/application.yml 结论: 配置的优先级如上所示,如果同一个配置配置在多个配置文件中,则优先使用先读取到的,也就是说,1-8的优先级是由高到低的 PS: 创建SpringBoot项目时,一般的配置文件放置在项目的resources目录下,因为配置文件的修改,通过热部署不用重新启动项目,而热部署的作用范围是classpath下 参考: springboot官方文档:https://docs.spring.io/spring-boot/docs/2.3.3.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config

September 27, 2024

Spring的启动流程--注解方式启动

Bean生命周期 Spring的启动流程–注解方式启动 1. 创建AnnotationConfigApplicationContext 1.1 创建beanFactory this.beanFactory = new DefaultListableBeanFactory(); 1.2 创建Reader this.reader = new AnnotatedBeanDefinitionReader(this); 关注 /* * 将一些RootBeanDefinition注册进DefaultListableBeanFactory */ AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source) { DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory != null) { if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8); if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor. if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor. if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, AnnotationConfigUtils.class.getClassLoader())); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex); } def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME)); } if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME)); } return beanDefs; } 1.3 创建Scancer this.scanner = new ClassPathBeanDefinitionScanner(this); 关注 ...

September 27, 2024

分布式锁的几种常见实现方式

数据库乐观锁 实现方式 用表中一行记录来表示锁,其中表示锁的字段采用唯一约束 当多个线程同时执行插入语句时,只有一个能插入成功,可以认为获得到了锁,其他则会报错:ERROR 1062 (23000): Duplicate entry ‘1’ for key ‘uiq_idx_resource’),这种情况可以认为没有获得到锁 当需要释放锁时,可以删除这一条记录就可以了 这种锁的特点: 锁没有失效时间,一旦锁释放失败,便会一直留在数据库中,影响其他线程获取锁。可以采用定时任务对其进行清理 锁依赖数据库,需要考虑单点故障问题。同时也要考虑给数据库性能带来的影响 锁是非阻塞的,一旦插入失败,直接便会报错,需要采用轮询的方式来实现 锁也是非可重入的,一旦一个线程获取到了锁,由于数据库中已经存在了这一条记录,其本身也不能再次获取到这个锁。这一点可以将主机和线程信息存入数据库,如果存在锁,并且锁也归属于当前线程,则可以将此锁再次分配给它 Redis实现 基本上基于setnx命令实现 SETNX 是SET if Not eXists的简写 解释: 当且仅当 key 不存在,将 key 的值设为 value,返回1; 若给定的 key 已经存在,则 SETNX 不做任何动作,返回0。 @Component public class RedisService { @Autowired private RedisTemplate<String,String> redisTemplate; private static final String COMPARE_AND_DELETE = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; public Boolean getLock(String key,String value){ return redisTemplate.opsForValue().setIfAbsent(key, value, 2, TimeUnit.MINUTES); } public void releaseLock(String key,String value){ List<String> keys = Collections.singletonList(key); redisTemplate.execute(new DefaultRedisScript<>(COMPARE_AND_DELETE,Long.class), keys, value); } } 这里并没有直接使用delete命令,是为了防止删除其他线程的key。、 ...

September 27, 2024
苏ICP备2021016520号-1