开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Spring全家桶之Spring篇深度分析(一)

发表于 2021-11-14

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」。

👨‍🎓作者:Java学术趴

🏦仓库:Github、Gitee

✏️博客:CSDN、掘金、InfoQ、云+社区

💌公众号:Java学术趴

🚫特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系小编授权。

🙏版权声明:文章里的部分文字或者图片来自于互联网以及百度百科,如有侵权请尽快联系小编。微信搜索公众号Java学术趴联系小编。

☠️每日毒鸡汤:万物皆对象。

👋大家好!我是你们的老朋友Java学术趴。最近小编又在整了Spring全家桶笔记,笔记会每天定时的进行发放,喜欢的大佬们欢迎收藏点赞关注呦。小编会每天分享的呦。Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。

第一章 Spring概述

1.Spring框架介绍

  • Spring:出现在2002年左右,降低企业级开发难度。帮助进行模块之间、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
  • 2003年传入国内,被大量使用。2017出现新的流行框架SpringBoot,核心思想与Spring相同。
  • 核心技术:IoC、AOP,能使模块之间、类之间解耦合。
  • 依赖:class A使用class B的属性或方法,称之为class A依赖class B。
  • 官网:spring.io

2.Spring框架的特点

  • 轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。Spring框架运行占有资源少,运行效率高,不依赖其他jar。
  • 针对接口编程,解耦合:Spring提供了Ioc控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式(反射机制、new),现在由容器完成,对象之间的依赖解耦合。
  • AOP 编程的支持:通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易使用OOP实现的功能可以通过AOP轻松应对。
  • 方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,相反Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框的直接支持。

3. Spring体系结构

image-20211111160508537

Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理 (Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)。

  • myBatis存在于Data Access
  • SpringMVC中存在于Web
  • Java对象存在于Core Container中
  • 测试类似于junit,进行代码测试的

第二章 IoC控制反转

2.1 控制反转的概念

  • 控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代 码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对 象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。
  • 控制: 创建对象,对象的属性值赋值,对象之间的关系管理。
  • 反转: 把原来的开发人员管理,创建对象的权限转移交给代码之外的容器实现。由容器代替开发人员管理对象。创建对象,给属性赋值。
  • 正转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理对象,创建对象,给属性赋值。
  • 容器: 是一个服务软件,一个框架(Spring)
  • Ioc 的实现:
+ 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件。
+ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。**Spring 框架使用依赖注入(DI)实现 IoC。**

Java中创建对象的方式

  • 构造方法,new Student()
  • 反射 :Class
  • 序列化 :数据库
  • 克隆 : Clone
  • 动态代理 :AOP
  • IoC : 控制反转

IoC的体现

之前学习到的应用控制反转的实例:Servlet对象的创建管理,这一工作完全交给了Web容器。

Tomcat就是一个容器:里面存放有Servlet对象、Listener对象、Filter对象等。

IoC的技术实现

  • DI是IoC的技术实现,DI(Dependency Injection) : 依赖注入,只需要在程序中提供要使用的对象名就可以,至于对象如何在容器中创建、赋值、查找都由容器内部实现。
  • Spring是使用DI实现了IoC的功能,Spring底层创建对象,是使用的反射机制

2.2 Spring第一个程序

见项目。

今天就先分享到这里啦,明天小编继续给大家分享Spring全家桶笔记!!

**以上项目的源代码,点击星球进行免费获取 星球 (Github地址)如果没有Github的小伙伴儿。可以关注本人微信公众号:Java学术趴,发送Spring,免费给发给大家项目源码,代码是经过小编亲自测试的,绝对可靠。免费拿去使用。**

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis线程模型 事件 Redis是单线程模型为什么效率还

发表于 2021-11-14

「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战」。
image.png

事件

Redis服务器是一个事件驱动程序,服务器需要处理两类事件,一个是文件事件,一个是时间事件。这里重点说下文件事件。

文件事件

Redis服务器通过socket与客户端进行连接。而文件事件就是就是对socket操作的抽象,服务端和客户端的通信会产生响应的文件事件,而服务器则通过监听并处理这些事件来完成一系列的网络通信操作。

Redis基于Reactor模式开发了自己的网络事件处理器,这个处理器被称为文件事件处理器。

  • 文件事件处理器使用I/O多路复用程序来同时监听多个socket,并根据socket目前执行的任务来为socket关联不同的事件处理器。
  • 当被监听的socket准备好执行连接应答,读取,写入,关闭等操作,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

之所以说Redis是单线程的,是因为文件事件处理器是以单线程方式运行,但通过I/O多路复用程序来监听多个套接字,文件事件处理器实现了高性能网络通信模型。

文件事件处理器构成

文件事件处理器由四个部分构成,分别是socket,I/O多路复用程序、文件事件分派器、事件处理。

每当一个sokcet准备好执行连接应答、写入、读取、关闭等操作,就会产生一个文件事件。一个服务通常会连接多个socket,所以多个文件事件有可能并发出现。

I/O多路复用程序负责监听多个socket,并向文件事件分派器传送产生了事件的socket。

I/O多路复用程序会将所有产生了事件的socket放入队列,通过这个队列,以有序、同步、每次一个socket方式向文件事件分派器传送socket。当一个socket产生的事件被处理完之后,才会继续向文件事件分派器传送下一个socket。

image.png

文件事件分派器会根据socket产生的事件类型调用相应的事件处理器。

Redis的I/O多路复用程序的所有功能都是通过包装常见的select、epoll、evport等多路复用函数库来实现的。

事件类型

I/O多路复用程序可以监听多个socket的ae_readable事件和ae_writable事件,这两类事件和socket对应关系如下:

  • 客户端对socket执行write操作,此时socket就会变得可读,就会产生as_readable事件
  • 当客户端对socket执行read操作,socket会变的可写,产生ae_writable事件。

如果一个socket同时产生这两中事件,文件事件分派器会优先处理ae_readable事件。

文件事件的处理器

文件事件对应有多个处理器,来实现不同的网络通信需求

  1. 连接应答处理器

这个处理器用于对连接服务器监听socket的客户端进行应答。

当有客户端请求连接时,socket就会产生ae_readable事件,引发连接应答处理器执行。

  1. 命令请求处理器

这个处理器负责从socket读取客户端发送的命令请求内容。

当客户端通过连接应答处理器连接到服务器后,服务器会将客户端socket的ae_readable事件和命令请求处理器进行关联,当客户端向服务器发送命令请求,socket就会产生ae_readable事件,引发命令请求处理器执行。

  1. 命令回复处理器

负责将服务器执行命令后得到的命令回复通过socket返回给客户端。

当服务器有命令回复需要传送给客户端的时候,服务器会将客户端socket的ae_writable事件和命令回复处理器进行关联,当客户端准备好接收服务器传回的命令回复时,会产生ae_writable事件,引发回复处理器执行。

一次完整的客户端和服务器连接事件示例

假设一个Redis服务器在运作,这个服务器的监听socket的ae_readable事件会处于监听状态之下,该事件对应的处理器为连接应答处理器。

如果这时一个Redis客户端向服务器发起连接,那么监听socket将产生ae_readable事件,触发连接应答处理器执行,会创建客户端socket,然后将ae_readable与请求处理器关联。

之后假设客户端向主服务器发送一个命令请求,客户端socket会产生ae_readable事件,引发命令请求处理器执行。

执行命令产生相应的命令回复,为了将这些命令回复传给客户端,会将客户端socket的ae_writable事件与命令回复处理器进行管理。当客户端尝试读取命令回复时,会产生ae_writable事件,触发命令回复处理器执行,当命令回复处理器将命令回复全部写入到socket,服务器就会解除客户端socket的ae_writable事件与命令回复处理器之间的关联。

image.png

Redis是单线程模型为什么效率还这么高?

纯内存访问:数据存放在内存中,内存的响应时间大约是100纳秒,这是Redis每秒万亿级别访问的重要基础。

非阻塞I/O:Redis采用epoll做为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间。

单线程避免了线程切换和竞态产生的消耗。

Redis采用单线程模型,每条命令执行如果占用大量时间,会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java的JVM性能监控与故障处理工具详细介绍以及使用案例

发表于 2021-11-14

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」。

给一个系统定位问题的时候,知识、经验是关键基础,数据是依据。工具是运用知识处理数据的手段。这里说的数据包括:运行日志、异常堆栈、GC日志、线程快照文件(threaddump/javacore文件)、堆转储快照(heapdump/hprof文件)等。使用JVM命令和一查看这个JVM参数,帮助我们排查、解决问题。

1 JDK的命令行工具

JDK的命令行工具大多数是对JDK/lib/tools.jar类库的一层薄包装而已,它们的主要功能代码是在tools类库中实现的。Linux下的这些工具有的甚至是用shell脚本编写的。

常见工具如下:

名称 主要作用
jps JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程
jstat JVM Statistics Monitoring Tool,用于收集HotSpot虚拟机各方面的运行数据
jinfo Configuration Info for Java,显示虚拟机配置信息
jmap Memory Map for Java,生成虚拟机的内存转出快照(heapdump文件)
jhat JVM Heap Dump Browser,分析heapdump文件,可以在浏览器查看结果
jstack stack trace for java,显示虚拟机的线程快照

1.1 jps——虚拟机进程状况工具

列出正在运行的虚拟机进程,并显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一ID。

jps命令格式:

jps [ options ] [ hostid ]

jps可以通过RMI协议开启了RMI服务的远程虚拟机进程状态,hostid为RMI注册表中注册的主机名。

jps常用的option选项:

选项 作用
-q 只输出LVMID
-m 输出虚拟机进程启动时传递给主类main()函数的参数
-l 输出主类全名,如果进程执行的事jar包,输出jar路径
-v 输出虚拟机进程启动时JVM参数

1.1.1 案例

案例类如下

1
2
3
4
5
6
7
8
9
10
java复制代码public class Jstat {
/**
* vm参数为 -Xms30m -Xmx30m -Xmn10m
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000000);
}
}

运行之后,使用jps命令,将会展示虚拟机进程id和名字:

在这里插入图片描述

1.2 jstat——虚拟机统计工具监视工具

jstat是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾回收、JIT编译等运行数据,在没有GUI图形界面,只是提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。

jstat的命令格式:

jstat [option vmid [interval [s|ms] [count]] ]

  1. option: 参数选项
  2. -t: 可以在打印的列加上Timestamp列,用于显示系统运行的时间
  3. -h: 可以在周期性数据数据的时候,可以在指定输出多少行以后输出一次表头
  4. vmid: Virtual Machine ID( 进程的 pid)
  5. interval: 执行每次的间隔时间,单位为毫秒
  6. count: 用于指定输出多少次记录,缺省则会一直打印
  7. 对于命令格式中的VMID和LVMID,如过是本地虚拟机进程,VMID和LVMID是一致的,如果是远程虚拟机,那VMID的格式应当是:[protocol:]
    [//] lvmid[@hostname[:port]/servername]
  8. 参数interval 和count分别表示查询的间隔和次数,如果省略这两个参数,说明只查询一次。

Jstat常用option选项:

选项 作用
-class 类装载数量、卸载数量、总空间以及类状态所消耗时间
-GC 监视Java堆容量状况,包括Eden、Survivor、老年代、永久代等
-GCcapacity 监视Java堆最大、最小空间
-GCutil 关注已使用空间占总空间的百分比
-GCcause 类似GCutil,额外输出上次GC的原因
-GCnew 新生代GC状况
-GCnewcapacity 与-GCnew类似,输出主要关注使用到的最大、最小空间
-GCold 老年代GC状况
-GColdcapacity 与-GCold类似,输出主要关注使用到的最大、最小空间
-GCpermcapacity 输出永久代使用到的最大、最小空间
-compiler 输出JIT编译过的方法和耗时
-printcompilation 输出已经被JIT编译的方法
-GCmetacapacity 元数据空间统计

1.2.1 案例

加上-GC显示将会GC堆信息,使用上一个案例,设置VM参数为 -Xms30m -Xmx30m -Xmn10m ,即初始内存30m,最大内存30m,年轻代10m。

运行程序,使用 jstat -gc 6128 命令结果如下,可以看到GC堆信息:

在这里插入图片描述

S0C:年轻代中第一个Survivor(幸存区)的容量 (字节)
S1C:年轻代中第二个Survivor(幸存区)的容量 (字节)
S0U :年轻代中第一个Survivor(幸存区)目前已使用空间 (字节)
S1U :年轻代中第二个Survivor(幸存区)目前已使用空间 (字节)
EC :年轻代中Eden(伊甸园)的容量 (字节)
EU :年轻代中Eden(伊甸园)目前已使用空间 (字节)
OC :Old代的容量 (字节)
OU :Old代目前已使用空间 (字节)
MC:metaspace(元空间)的容量 (字节)
MU:metaspace(元空间)目前已使用空间 (字节)
YGC :从应用程序启动到采样时年轻代中GC次数
YGCT :从应用程序启动到采样时年轻代中GC所用时间(s)
FGC :从应用程序启动到采样时old代(全GC)GC次数
FGCT :从应用程序启动到采样时old代(全GC)GC所用时间(s)
GCT:从应用程序启动到采样时GC用的总时间(s)

从图中可以看出,各项结果符合我们的VM参数设置的信息。

1.3 jinfo——Java配置信息工具

实时查看和调整虚拟机各项参数。

jinfo常用option选项:

选项 作用 案例
-flag 调整虚拟机参数 jinfo -flag +PrintGCDetails 1479
-flags 查看指定进程所有参数 jinfo -flags 1479
-sysprops 打印虚拟机进程系统内容 jinfo -sysprops 1479
无参 打印所有 jinfo 1479

1.3.1 案例

使用上面的例子,加上vm参数信息 -Xms30m -Xmx30m -Xmn10m。使用jinfo -flags 6128 ,从结果中可以找到我们设置的vm信息。

在这里插入图片描述

1.4 jmap——Java内存映射工具

jmap命令用于生成堆转储快照。jmap的作用并不仅仅为了获取dump文件,它还可以查询finalize执行队列、java堆和永久代的详细信息。如空间使用率、当前用的是哪种收集器等。

jmap格式:

jmap [option] vmid

jmap常用option选项:

选项 作用
-dump 生成堆转储快照,格式为-dump:[live,]format=b,file=,不建议使用
-finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize方法的对象
-heap 显示java堆详细信息,回收器种类、参数配置、分代状况等
-histo 显示堆中对象统计信息,包括类、实例数量、合计容量,会先触发GC,再统计信息,不建议使用
-permstat 查看永久代内存状态,比较耗时,会暂停应用,不建议使用
-F 强制生成dump快照,当-dump失效时,再使用此命令

1.4.1 案例

还是上面的例子。

使用jmap -heap 6128,可以看到我们的VM参数设置的信息:

在这里插入图片描述

jmap -dump:live,format=b,file=‪C:\Users\lx\Desktop\test1.bin 9472

将生成堆转储快照,这里我生成到桌面。后面可以使用jhat分析dump文件。

在这里插入图片描述

1.5 jhat——虚拟机堆快照分析工具

Sun JDK提供jhat与jmap搭配使用,来分析dump生成的堆快照。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在浏览器中查看。

1.5.1 案例

jhat test1.bin

test1.bin为上面生成的dump文件。屏幕显示“Server is ready.”的提示后,用户在浏览器中键入http://localhost:7000就可以看到分析的结果了。

在这里插入图片描述

分析结果默认是以包为单位进行分组显示,分析内存泄漏问题主要会使用到其中的“Heap Histogram”与OQL标签的功能。前者可以找到内存中总容量最大的对象。后者是标准的对象查询语言,使用类似SQL的语法对内存中的对象进行查询统计。

在这里插入图片描述

1.6 jstack——Java堆栈跟踪工具

jstack命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程死锁、死循环、请求外部资源导致长时间等待等。

jstack 格式:

jstack [option] vmid

jstack常见option选项:

选项 作用 案例
-m 如果调用本地方法,则显示C/C++的堆栈 jstack -m 1479
-l 除堆栈外,显示关于锁的附加信息 jstack -l 1479
-F 当正常输出的请求不被响应时,强制输出线程堆栈 jstack -F 1479

1.6.1 案例

jstack -l 9472

会输出很多信息,我们可以找到如下信息:

在这里插入图片描述

可以看到,main线程正在限时等待——因为sleep的原因。

jstack 可以帮助我们用来分析线程信息,比如死锁,状态等。

2 JDK的可视化工具

JDK中除了提供大量的命令行工具外,还有两个功能强大的可视化工具:JConsole和VisualVM,这两个工具是JDK的正式成员,没有被贴上“unsupported and experimental”的标签。

2.1 JConsole——Java监视与管理控制台

从Java 5开始 引入了 JConsole,JConsole 是一个内置 Java 性能分析器。您可以轻松地使用 JConsole(或者,它更高端的 “近亲” jvisualvm )来监控 Java 应用程序性能和跟踪 Java 中的代码。(推荐使用升级版 JConsole 即 jvisualvm )

2.2 jvisualvm——多合一故障处理工具

JVisualVM是一个集成多个JDK命令行工具的可视化工具。JVisualVM基于NetBeans平台开发,它具备了插件扩展功能的特性,通过插件的扩展,可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等。JVisualVM在JDK/bin目录下。

以上两个工具的使用可以看这篇文章:Java中的死锁详解以及解决办法,其中的死锁检测部分有指导使用这两个工具。

3 其他外部工具

除了JDK自带的工具之外,使用外部工具,可以为我们提供更加强大功能。

3.1 MAT——内存分析工具

MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。

官网地址:www.eclipse.org/mat/

在这里插入图片描述

3.3.1 下载安装

下载地址:www.eclipse.org/mat/downloa…](download.csdn.net/download/we…%E3%80%82)

在这里插入图片描述

将下载得到的MemoryAnalyzer-1.8.0.20180604-win32.win32.x86_64.zip进行解压:

在这里插入图片描述

双击启动!

3.3.2 使用

dump文件就堆快照文件,可以使用jmap、Jconsole、和Visual VM等工具导出dump文件,MAT也可以直接导出dump文件,当然也可以直接打开现有的dump文件。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

查看对象以及它的依赖:

在这里插入图片描述

查看可能存在内存问题的分析:

在这里插入图片描述

3.3.3 案例

3.3.3.1 内存溢出代码

编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执行,最后打印ok。

vm参数设置为 -Xms4m -Xmx4m -XX:+HeapDumpOnOutOfMemoryError
HeapDumpOnOutOfMemoryError参数表示当JVM发生OOM时,自动生成DUMP文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public class TestJvmOutOfMemory {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
String str = "";
for (int j = 0; j < 1000; j++) {
str += UUID.randomUUID().toString();
}
list.add(str);
}
System.out.println("ok");
}
}

3.3.3.2 运行测试

结果如下:

在这里插入图片描述

可以看到,发生了内存溢出,当发生内存溢出时,会dump文件java_pid7264.hprof在工程目录下:

在这里插入图片描述

3.3.3.3 导入到MAT工具中进行分析

在这里插入图片描述

可以看到,有70.27%的内存由Object[]数组占有,所以比较可疑,这是非常有可能出现内存溢出的。

点击Details查看详情:

在这里插入图片描述

可以看到集合中存储了大量的uuid字符串。实际上这里已经告诉我们是不是因此导致的内存溢出了,我们在details详情的最下面可以看到:

在这里插入图片描述

就是这个大对象数组(list集合使用数组存放的元素),因为需要分配连续的内存,而内存不足导致的OOM。

3.2 GCEasy——GC日志可视化分析工具

直接将GC日志log文件,在官网gceasy.io/上打开,,就能快速进行…

在这里插入图片描述

上传后,点击“Analyze”按钮,即可查看报告。

3.2.1 案例

我们是用上面MAT的代码,设置vm参数为:

-Xms4m -Xmx4m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:./gc.log

表示将会输出GC日志,将gc.log上传,打开,有如下结果:

JVM memory size:分别使用了表格和图形界面来展示了JVM堆内存大小。

在这里插入图片描述

Key Performance Indicators:这一部分是关键的性能指标

  1. Throughput表示的是吞吐量
  2. Latency表示响应时间
  3. Avg Pause GC Time 平均GC时间
  4. Max Pause GC TIme 最大GC时间

在这里插入图片描述

后面还有些参数,这里不做作赘述。

相关文章:

  1. 《Java虚拟机规范》
  2. 《深入理解Java虚拟机》

如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言,Protobuf 入门详解! 基本语法 Proto

发表于 2021-11-14

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

Protobuf 是 Protocol Buffers 的简称,是一种与语言、平台无关,可扩展的序列化结构化数据的数据描述语言,Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言PRC接口的基础工具。

基本语法

hello.proto 文件

1
2
3
4
5
6
7
go复制代码syntax = "proto3";

package main;

message String {
string value = 1;
}
  1. 第一行声明使用 proto3 语法。否则,默认使用 proto2 语法,目前主流推荐使用 v3 版本。此声明必须是文件的非空、非注释的第一行。
  2. package 指令指明当前是 main 包,用户也可以针对不同的语言定制对应的包路径和名称。
  3. message 关键字定义一个 String 类型消息体,在最终生成的Go语言代码中对应一个 String 结构体。每一个消息体的字段包含三个属性:类型、字段名称、字段编号。在消息体的定义上,除类型以外均不可重复。此处 String 类型中只有一个字符串类型的 value 成员,该成员编码时用1编号代替名字。
  4. Protobuf 中最基本的数据单元是 message,类似 Go 语言中的结构体。在 message 中可以嵌套 message 或其它的基础数据类型的成员。

关于标识号

消息体中字段定义了唯一的数字值。这些数字是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。

最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]的标识号, Protobuf 协议实现中对这些进行了预留。如果非要在 .proto 文件中使用这些预留标识号,编译时就会报警。类似地,你不能使用之前保留的任何标识符。

添加注释

.proto 文件添加注释,可以使用C/C++风格的 // 和 /* … */ 语法格式

保留字段

如果从前面定义的消息中删除了 和 字段,应保留其字段编号,使用关键字 reserved:

1
2
3
4
5
6
go复制代码syntax "proto3";

message Stock {
reserved 3, 4;
// ...
}

还可以将 reserved 关键字用作将来可能添加的字段的占位符。 可以使用 to 关键字将连续字段号占位。

1
2
3
4
5
6
go复制代码syntax "proto3";

message Info {
reserved 2, 9 to 11, 15;
// ...
}

生成相应的Go代码

Protobuf 核心的工具集是 C++ 语言开发的,官方的 protoc 编译器中并不支持Go语言。要想基于上面
的 hello.proto 文件生成相应的Go代码,需要安装相应的插件。

  • 安装官方的 protoc 工具,可以从 github.com/google/prot… 下载。
  • 安装针对Go语言的代码生成插件,通过 go get github.com/golang/protobuf/protoc-gen-go 命令安装。

通过以下命令生成相应的Go代码:

$ protoc --go_out=. hello.proto

  • go_out 参数告知 protoc 编译器去加载对应的 protoc-gen-go 工具,生成的代码放到当前目录。最后是一系列要处理的protobuf文件的列表。
  • plugins=plugin1+plugin2:指定要加载的子插件列表,我们定义的 proto 文件是涉及了 RPC 服务的,而默认是不会生成 RPC 代码的,因此需要在 go_out 中给出 plugins 参数传递给 protoc-gen-go,告诉编译器,请支持 RPC(这里指定了内置的 grpc 插件)。

基本数据类型

protobuf 所生成出来的数据类型并非与原始的类型完全一致,下面是一些常见的类型映射:

生成的 hello.pb.go 文件

pb.go 文件是对 proto 文件所生成的对应的 Go 代码,在实际应用中将会引用到此文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
go复制代码// Code generated by protoc-gen-go. DO NOT EDIT.
// source: hello.proto

package main

import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

type String struct {
Value *String `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}

func (m *String) Reset() { *m = String{} }
func (m *String) String() string { return proto.CompactTextString(m) }
func (*String) ProtoMessage() {}
func (*String) Descriptor() ([]byte, []int) {
return fileDescriptor_61ef911816e0a8ce, []int{0}
}

func (m *String) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_String.Unmarshal(m, b)
}
func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_String.Marshal(b, m, deterministic)
}
func (m *String) XXX_Merge(src proto.Message) {
xxx_messageInfo_String.Merge(m, src)
}
func (m *String) XXX_Size() int {
return xxx_messageInfo_String.Size(m)
}
func (m *String) XXX_DiscardUnknown() {
xxx_messageInfo_String.DiscardUnknown(m)
}

var xxx_messageInfo_String proto.InternalMessageInfo

func (m *String) GetValue() *String {
if m != nil {
return m.Value
}
return nil
}

func init() {
proto.RegisterType((*String)(nil), "main.String")
}

func init() { proto.RegisterFile("hello.proto", fileDescriptor_61ef911816e0a8ce) }

var fileDescriptor_61ef911816e0a8ce = []byte{
// 84 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9,
0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x4d, 0xcc, 0xcc, 0x53, 0xd2, 0xe1,
0x62, 0x0b, 0x2e, 0x29, 0xca, 0xcc, 0x4b, 0x17, 0x52, 0xe2, 0x62, 0x2d, 0x4b, 0xcc, 0x29, 0x4d,
0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x36, 0xe2, 0xd1, 0x03, 0xc9, 0xeb, 0x41, 0x24, 0x83, 0x20,
0x52, 0x49, 0x6c, 0x60, 0xad, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x76, 0x0c, 0x0e, 0x54,
0x49, 0x00, 0x00, 0x00,
}
  • String 类型自动生成了一组方法,其中 ProtoMessage 方法表示这是一个实现了 proto.Message 接口的方法。此外 Protobuf 还为每个成员生成了一个Get方法,能够提供便捷的取值方式,并且处理了一些空指针取值的情况,还能够通过 Reset 方法来重置该参数。
  • .pb.go 文件的初始化方法,注意 fileDescriptor 的相关语句。fileDescriptor_61ef911816e0a8ce 表示是一个经过编译后的 proto 文件,是对 proto 文件的整体描述,其包含了 proto 文件名、引用(import)内容、包(package)名、选项设置、所有定义的消息体(message)、所有定义的枚举(enum)、所有定义的服务( service)、所有定义的方法(rpc method)等等内容。
  • 每一个 Message Type 中都包含了 Descriptor 方法,Descriptor 代指对一个消息体(message)定义的描述,而这一个方法则会在 fileDescriptor 中寻找属于自己 Message Field 所在的位置再进行返回。

Protobuf 和 RPC组合

基于 String 类型,重新实现 HelloService 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
go复制代码package main

import (
"log"
"net"
"net/rpc"
"rpc/protoc"
)

// HelloService is rpc server obj
type HelloService struct{}

//Hello方法的输入参数和输出的参数均改用 Protobuf 定义的 String 类型表示。
//因为新的输入参数为结构体类型,因此改用指针类型作为输入参数,函数的内部代码同时也做了相应的调整。
func (p *HelloService) Hello(request *protoc.String, reply *protoc.String) error {
reply.Value = "hello:" + request.GetValue()
return nil
}

func main() {
rpc.RegisterName("HelloService", new(HelloService))

listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal("ListenTCP error:", err)
}

conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error", err)
}

rpc.ServeConn(conn)
}

下面是客户端请求HelloService服务的代码 client.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
go复制代码package main

import (
"fmt"
"log"
"net/rpc"
"rpc/protoc"
)

func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing err:", err)
}

var reply = &protoc.String{}
var param = &protoc.String{
Value: "hello wekenw",
}

err = client.Call("HelloService.Hello", &param, &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}

开启服务器端,开启客户端。客户端的执行结果如下:

1
2
go复制代码$ go run client.go
value:"hello:hello wekenw"

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分布式事务(2) 两阶段提交

发表于 2021-11-14

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

两阶段提交 2PC

前面已经学习了分布式事务的基础理论,以理论为基础,针对不同的分布式场景业界的解决方案有 2pc 、TCC、可靠消息最终一致性、最大努力通知这几种。这片文章主要介绍两阶段提交的具体使用方式。

一、什么是 2pc

即两阶段提交协议,将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2指的是两个阶段,P指的是准备阶段,C指的是提交阶段

1.准备阶段:事务管理器给每个参与者发布Prepare消息,每个服务参与者在本地执行事务,并写在Undo/Redo日志,此时事务没有提交。(Undo日志记录修改前数据用于回滚,Redo记录修改后数据用于提交事务后写入数据文件)

2.提交阶段:如果事务管理器收到了参与者的执行失败或者超时消息时,直接给每个参与者进行回滚,否则发送提交消息。注意:必须在最后阶段释放锁资源

img

使用2pc 完成分布式事务有这两种情况:

  • 理想情况下:若所有的参与者都返回同意的消息,那么事务管理器就知道所有的参与者都已经准备好了,协调者就会向所有的参与者发回一个提交的消息,参与者在收到提交消息之后,会将本地事务进行提交,并且释放占用的资源,在处理完成之后,会返回一个完成的消息,分布式事务也就完成了。
  • 异常情况下: 假如其中的一个参与者出现了异常,这时候会向事务管理器发送终止消息,然后事务管理器收到回滚请求后,会将所有的操作进行撤销,恢复到事务启动前的状态,分布式事务执行失败。

看起来是没有问题,但是它肯定存在很多缺点:

  • 1、事务管理器挂掉了怎么办?整个分布式就没办法继续执行
  • 2、假设某个参与者的提交消息因为网络中断了,请求没有到达,这个也没有发送异常消息回来,那么事务就执行失败了。

针对 2pc 的缺点,提出了三阶段提交,它在协调者和参与者中都引入了超时机制,并且把两阶段提交协议的第一个阶段分为两步:询问,然后再锁资源,最后真正提交。

  • 1、预备阶段:协调者向参与者发送 commit 请求,参与则如果可用就返回 yes、或no 。
  • 2、预提交阶段:发送请求后执行事务操作,将信息记录到事务日志中。如果某个协调者没有执行,就中断事务
  • 3、真正提交:该过程来进行最终的事务执行或中断事务操作。

但是 2pc 和 3pc 都无法彻底解决分布式的一致性问题。这个理论在这里就不过多描述了。作为了解即可。

二、解决方案

2.1 XA 方案

两阶段提交是在数据库层面实现的,例如 Oracle、Mysql 都支持 2PC 协议

它是多段式事务的变种,MySQL的事务模型就是基于这张图

1. app发起事务,进入事务管理器

2. 事务管理器TM通知资源管理器RM准备执行具体业务,执行完毕后通知TM,业务已经OK

3. TM收到全部RM的OK后,就通知它们提交事务

image-20211117183317961

总结:整个 2PC 的事务流程涉及到三个角色 AP、RM、TM。AP 是指使用 2PC 事务的应用程序;RM 是指资源管理器;TM 是指事务管理器,它控制着整个全局事务。

  • 在准备阶段RM 执行实际的业务操作,但不提交事务,资源锁定
  • 在提交阶段 TM 会接受 RM 在准备阶段的执行回复,只要有任一个 RM 执行失败, TM 会通知所有的 RM 执行回滚操作,否则,TM 将会通知所有 RM 提交该事务。提交阶段结束资源锁释放。

XA 方案存在的问题:

  • 需要本地数据库支持 XA 协议
  • 资源锁需要等到两个阶段结束才释放,性能较差。

2.2 Seata 方案

Seata 是一款阿里开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。传统的 2PC 的问题在 Seata 中得到了解决,它通过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是性能较好,且不会长时间占用连接资源,它以高效并且对业务 0 侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供 AT 和 TCC 模式的分布式事务解决方案。

Seata 中有三大模块,分别是 TM、RM 和 TC。其中 TM 和 RM 是作为 Seata 的客户端与业务系统集成在一起,TC 作为 Seata 的服务端独立部署。

img

角色划分:

TM:事务管理器,开启、 提交、回滚分布式事务

RM: 资源管理器,注册、 汇报、执⾏资源

TC : 事务管理器服务功能,存储事务日志、补偿异常事务等、集中管理事务全局锁(全局行锁),seata服务端

事务执行整体流程:

  • TM 开启分布式事务(TM 向 TC 注册全局事务记录);
  • 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
  • TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
  • TC 汇总事务信息,决定分布式事务是提交还是回滚;
  • TC 通知所有 RM 提交/回滚 资源,事务二阶段结束;

2.3 方案对比

  • 架构层次:传统的 2PC 方案的 RM 实际上是在 数据库层,RM 本质上就是数据库自身,通过 XA 协议实现。而 Seata 的RM 是以 Jar 包的形式作为中间件层部署在应用程序这一侧的。
  • 两阶段提交:传统的2pc 无论第二阶段的决议是 commit 还是 rollback,事务资源的锁都要保持到第二阶段完成才释放,而Seata 的做法是在 第一阶段就将本地事务提交,这样就省去了锁占有的时间,整体提示效率。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring 自动装配

发表于 2021-11-14

Spring 自动装配

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

set注入和构造注入有时在做配置时比较麻烦。所以框架为了提高开发效率,提供自动装配功能,简化配置。spring框架式默认不支持自动装配的,要想使用自动装配需要修改spring配置文件中标签的autowire属性

先来讲讲Bean的作用域

一、Bean的作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .

创建一个bean定义,其实质是用该bean定义对应的类来创建真正实例的“配方”。把bean定义看成一个配方很有意义,它与class很类似,只根据一张“处方”就可以创建多个实例。不仅可以控制注入到对象中的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在Java Class级定义作用域。Spring Framework支持五种作用域,分别阐述如下表。

image.png
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

Singleton(单例模式)

当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:

1
xml复制代码 <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">xml

Prototype(原型模式)

当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

1
2
3
xml复制代码<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

Request

当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

1
xml复制代码 <bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

Session

当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

1
xml复制代码 <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

二、Bean的自动装配

1.byName

byName

autowire byName (按名称自动装配)

表示根据Property的Name自动装配,如果一个bean的name,和另一个bean中的Property的name相同,则自动装配这个bean到Property中。当一个bean节点带有 autowire byName的属性时,将查找其类中所有的set方法名,获得将set去掉并且首字母小写的字符串,然后去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

测试:

1、修改bean配置,增加一个属性 autowire=“byName”

1
2
3
xml复制代码<bean id="user" class="com.mq.pojo.User" autowire="byName">
<property name="str" value="hahha"/>
</bean>

小结:

当一个bean节点带有 autowire byName的属性时。

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常。

2.byType

byType

autowire byType (按类型自动装配)

使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。

1
java复制代码NoUniqueBeanDefinitionException

测试:

1、将user的bean配置修改一下 : autowire=“byType”

1
2
3
4
5
6
xml复制代码<bean id="dog" class="com.mq.pojo.Dog"/>
<bean id="cat" class="com.mq.pojo.Cat"/>

<bean id="user" class="com.mq.pojo.User" autowire="byType">
<property name="str" value="123456"/>
</bean>

假如我们再注册一个bean

1
xml复制代码<bean id="cat2" class="com.mq.pojo.Cat"/>

使用byType首先需要保证同一类型的对象,在spring容器中唯一,若不唯一会报不唯一的异常。
这样装配可以不需要id,和id无光。

这就是按照类型自动装配!

三、使用注解实现自动装配

要使用注解步骤

  1. 导入约束:
1
xml复制代码xmlns:context="http://www.springframework.org/schema/context"
  1. 配置注解的支持

<context:annotation-config/>

1
2
3
4
5
6
7
8
9
10
11
12
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

</beans>

下面介绍常用注解:

  1. @Autowired

默认byname方式。

直接在类的属性上使用,也可以在Set方式上使用

使用Autowired我们可以不用编写Set方法了,前提是这个自动装配的属性在Ioc(spring)容器中存在,且符合名字byname!

1
java复制代码@Nullable //字段标记了这个注解,说明这个字段可以为null
1
2
3
4
java复制代码//如果显示定义了Autowired的required属性值为false,说明这个对象可以为null,否则不能为空
public @interface Autowired {
boolean required() default true;
}

如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value=’xxx’)去配置

@Autowired的使用,指定一个唯一的bean的注入

1
2
3
java复制代码    @Autowired
@Qualifier(value = "add")
private Address address;
  1. @Resource
1
2
ini复制代码    @Resource(name ="'name1'")
private String name;

小结:

@Autowired和@Resource的区别:

  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowired通过byType的方式,而且必须要求这个对象存在【常用】
  • @Resource默认通过byName的方式实现,如果找不到名字,则通过bytype实现,如果都找不到就报错【常用】
  • 执行顺序不同

四、使用注解开发

在spring4之后,要使用注解开发,需要要保证aop包导入了

image.png

使用注解需要导入context约束,增加注解的支持

1
2
3
4
5
6
7
8
9
10
11
12
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

</beans>
  1. bean

@Component 组件 等价于<bean id="user" class="com.mq.pojo.User"/>

1
2
3
4
5
java复制代码//@Component 组件 等价于 <bean id="user" class="com.mq.pojo.User"/>
@Component
public class User {
private String name="哎呀";
}
  1. 属性如何注入

@Value(“哎呀”) 相当于<property name="name" value="哎呀"/>

1
2
3
4
5
6
7
8
java复制代码//@Component 组件 等价于 <bean id="user" class="com.mq.pojo.User"/>
@Component
public class User {

// 相当于<property name="name" value="哎呀"/>
@Value("哎呀")
private String name;
}
  1. 衍生的注解

@Component有几个衍生的注解,我们在web的开发中,会按照mvc三层架构分层

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller】

这四个注解功能是一样的,都是代表将某个类注册到Spring,装配bean

  1. 自动装配
1
2
3
java复制代码- @Autowired通过byType的方式,而且必须要求这个对象存在【常用】
- @Nullable 字段标记了这个注解,说明这个字段可以为null
- @Resource默认通过byName的方式实现,如果找不到名字,则通过bytype实现,如果都找不到就报错【常用】
  1. 作用域

@Scope(“prototype”)

1
2
3
4
5
6
7
8
9
java复制代码//@Component 组件 等价于 <bean id="user" class="com.mq.pojo.User"/>
@Component
@Scope("prototype")
public class User {

// 相当于<property name="name" value="哎呀"/>
@Value("哎呀")
private String name;
}
  1. 小结

xml与注解

  • xml更加强大,适用与任何场合,维护简单方便
  • 注解,不是自己的类使用不了,维护相对复杂

xml与注解最佳实践

  • xml用来管理bean
  • 注解负责完成属性的注入
  • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
1
2
3
xml复制代码<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.mq.pojo"/>
<context:annotation-config/>

五、使用Java类的方式配置Spring

我们现在要完全不使用Spring的xml配置,全权交给Java来做

JavaConfig是Spring的一个子项目

项目结构

image.png

所有重点和细节都在代码的注解里面,仔细看。

编写实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码package com.mq.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//这个注解就是说明这个类被Spring容器注册了
@Component
public class User {

private String name;

public String getName() {
return name;
}
@Value("xiaoxiao")
public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}

java Config配置文件 等同于beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码//@Configuration这个也会Spring容器托管,注册到容器中,因为他本来就是一个@Component
//@Configuration代表这是一个配置类,就和我们之前的beans.xml一样
@Configuration
@Import(Config2.class) //导入另外一个配置的java类 和之前导入另外的xml一样
public class Config {

@Bean //注册一个bean,就相当于之前的bean标签,方法的名字就相当于bean标签中的id,方法的返回值相当于bean标签的class属性
public User getuser(){
return new User();//就是返回要注入到bean的对象
}

}

测试类

1
2
3
4
5
6
7
8
9
java复制代码public class Mytext {

public static void main(String[] args) {
//如果完全使用了配置类的方式去做,我们就只能通过AnnotationConfig 上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
User getuser = (User) context.getBean("getuser");
System.out.println(getuser.getName());
}
}

结果:
我在实体类属性上加了这个注解@Value(“xiaoxiao”)所以输出这个
在这里插入图片描述

今日的分享就到这了吧!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

rust debug with vscode

发表于 2021-11-14

「这是我参与11月更文挑战的第 8 天,活动详情查看:2021最后一次更文挑战」


最近在学习一些 rust lib 时,需要观察内部的数据转换以及 API 使用,而最好的办法就是在看源码。在学习源码过程中,可以运行过程中 debug 看看流程。

目前 rust-analyzer 在vscode支持比较好,所以本文配置是在vscode完成的。

本文也是为了省未来思考的时间,记录一下配置的步骤

前置准备

需要现在 vscode 安装好以下plugin:

  1. 安装 rust-analyzer plugin
  2. 安装 C/C++ plugin(Windows) → DEBUGER

debug+lang上的插件都准备好了,下面需要配置一下 debug 的启动属性

debug config

单击 “调试(Debug)” → “添加配置(Add Configuration)”

在 Windows 上,请选择 C++ (Windows) → 使用的是 GDB 进行调试

会创建一个新的 launch.json 。需要开发者自行修改 program(可执行文件)

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
json复制代码{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: <https://go.microsoft.com/fwlink/?linkid=830387>
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) 启动",
"type": "cppvsdbg",
"request": "launch",
// 需要配置你执行文件的路径
"program": "${workspaceRoot}/examples-play/target/debug/examples/exam_gjson.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceRoot}",
"environment": [],
"console": "integratedTerminal",
}
]
}

当然如果你需要debug多个文件,那需要在 launch.json 配置多项。

然后需要验证一下是否开始 break:

image.png

剩下就可以开始添加断点 → 按 F5 开始启动执行。

限制条件

调试Rust的效果很好。它并不完美。基本类型工作良好。当然,前提是它们没有被编译器优化掉。

image.png

我发现Rust编译器在优化 “未使用的” 变量时比C++更积极一些。有时我在变量中存储中间值,只是为了调试器。它们的缺失会让人感到迷惑,这点不是很好。

vector的调试就很好。我希望未展开的 “预览”能提供更多信息:

image.png

不幸的是,其他的容器根本就不能工作。HashMap是难以辨认的垃圾,很难看清楚。 :(

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

常用MQ介绍与区别 RabbitMQ Kafka Rocke

发表于 2021-11-14

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」

RabbitMQ

RabbitMQ是实现AMQP协议(0.9.1) 的消息中间件的一种,由RabbitMQ Technologies Ltd开发并且提供商业支持的,最初起源于金融系统,服务器端用Erlang语言编写,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

image.png

RabbitMQ基本概念

  • Broker: 简单来说就是消息队列服务器实体
  • Exchange: 消息交换机,它指定消息按什么规则,路由到哪个队列
  • Queue: 消息队列载体,每个消息都会被投入到一个或多个队列
  • Binding: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来
  • Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
  • VHost: vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。
  • Producer: 消息生产者,就是投递消息的程序
  • Consumer: 消息消费者,就是接受消息的程序
  • Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务

由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的唯一的线路。

RabbitMQ处理流程

1.Producer会先建立Channel,建立到Broker上Virtual Host的连接,接下来就可以向这个Virtual Host中的Exchange发送消息。

2.Producer把消息发布到Exchange上,消息最终到达队列并被消费者接收,而Binding决定交换器的消息应该发送到那个队列。Exchange能够处理消息的前提是,它至少已经和某个Queue或者另外的Exchange形成了绑定关系,并设置好了到这些Queue和Excahnge的路由规则。在Exchange收到消息后,会根据设置的路由规则,将消息发送到符合要求的Queue或者Exchange中(路由规则与Message中的Routing Key属性配合使用)。

3.当Queue收到消息后,会进行如下处理:

  • 如果当前没有Consumer的Channel连接到这个Queue,那么Queue将会把这条消息进行存储,直到有Channel被创建。
  • 如果已经有Channel连接到这个Queue,那么消息将会按顺序发送给这个Channel。

4.当Consumer收到消息后,就可以进行消息的处理:

  • Consumer在完成某一条消息的处理后,将需要手动的发送一条ACK消息到对应的Queue,也可以设置为自动发送或者无需发送。
  • Queue在接收到这条ACK信息后,才认为这条消息处理成功,并将这条消息从Queue中移除。
  • 如果在对应的Channel断开后,Queue都没有这条消息的ACK信息,这条消息将会重新被发送给另外的Channel。也可以直接发送NACK信息,这样这条消息将会立即归队,并发送给另外的Channel。

RabbitMQ的工作模式

simple模式(即最简单的收发模式)

image.png

1.消息产生消息,将消息放入队列

2.消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。

work工作模式(资源的竞争)

image.png

1.消息产生者将消息放入队列。

2.消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。

publish/subscribe发布订阅(共享资源)

image.png

1、每个消费者监听自己的队列;

2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。

routing路由模式

image.png

1.消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;

2.根据业务功能定义路由字符串

3.从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中。

4.业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误;

topic 主题模式(路由模式的一种)

image.png

1.星号(*)井号(#)代表通配符

2.星号(*)代表多个单词,井号(#)代表一个单词

3.路由功能添加模糊匹配

4.消息产生者产生消息,把消息交给交换机

5.交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费

RabbitMQ优点

  • 由于erlang语言的特性,mq 性能较好,高并发;
  • 健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
  • 有消息确认机制和持久化机制,可靠性高;
  • 高度可定制的路由;
  • 管理界面较丰富,在互联网公司也有较大规模的应用;
  • 社区活跃度高;

RabbitMQ缺点

  • 尽管结合erlang语言本身的并发优势,性能较好,但是不利于做二次开发和维护;
  • 实现了代理架构,意味着消息在发送到客户端之前可以在中央节点上排队。此特性使得RabbitMQ易于使用和部署,但是使得其运行速度较慢,因为中央节点增加了延迟,消息封装后也比较大;
  • 需要学习比较复杂的接口和协议,学习和维护成本较高;

Kafka

Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消息。

image.png

Kafka基本概念

  • Broker

消息中间件处理节点,一个Kafka节点就是一个Broker,一个或者多个Broker可以组成一个Kafka集群

  • Topic

每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)

  • Partition

用于存放消息的队列,存放的消息都是有序的,同一主题可以分多个Partition,如分多个Partiton时,同样会以如partition1存放1、3、5消息,partition2存放2、4、6消息

  • Producer

消息生产者,向Broker发送消息的客户端

  • Consumer

消息消费者,从Broker读取消息的客户端,Consumer是通过offset进行标识消息被消费的位置

  • Consumer Group

每个Consumer属于一个特定的Consumer Group,一条消息可以发送到多个不同的Consumer Group,但是同一个Consumer Group中只能有一个Consumer能够消费该消息

Kafka数据处理步骤

  • Producer产生消息,发送到Broker中
  • Leader状态的Broker接收消息,写入到相应topic中
  • Leader状态的Broker接收完毕以后,传给Follow状态的Broker作为副本备份
  • Consumer消费Broker中的消息

Kafka优点

  • 客户端语言丰富,支持java、.net、php、ruby、python、go等多种语言;
  • 性能卓越,单机写入TPS约在百万条/秒,消息大小10个字节;
  • 提供完全分布式架构, 并有replica机制, 拥有较高的可用性和可靠性, 理论上支持消息无限堆积;
  • 支持批量操作;
  • 消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次;
  • 有优秀的第三方Kafka Web管理界面Kafka-Manager;
  • 在日志领域比较成熟,被多家公司和多个开源项目使用;

Kafka缺点

  • Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
  • 使用短轮询方式,实时性取决于轮询间隔时间;
  • 消费失败不支持重试;
  • 支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
  • 社区更新较慢;

RocketMQ

RocketMQ 是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于2017年9月25日成为 Apache 的顶级项目。作为经历过多次阿里巴巴双十一这种“超级工程”的洗礼并有稳定出色表现的国产中间件,以其高性能、低延时和高可靠等特性近年来已经也被越来越多的国内企业使用。

image.png

RocketMQ基本概念

生产者组(Producer)

负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。 RocketMQ 提供了三种方式发送消息:同步、异步和单向。

名称服务器(NameServer)

主要负责对于源数据的管理,包括了对于Topic和路由信息的管理。

NameServer 被设计成几乎无状态的,可以横向扩展,节点之间相互之间无通信,通过部署多台机器来标记自己是一个伪集群。每个 Broker 在启动的时候会到 NameServer 注册,Producer 在发送消息前会根据 Topic 到 NameServer 获取到 Broker 的路由信息,Consumer 也会定时获取 Topic 的路由信息。所以从功能上看应该是和 ZooKeeper 差不多,RocketMQ 的早期版本确实是使用的 ZooKeeper ,后来改为了自己实现的 NameServer 。

消息服务器(Broker)

是消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息。它还存储与消息相关的元数据,包括用户组、消费进度偏移量、队列信息等。从部署结构图中可以看出 Broker 有 Master 和 Slave 两种类型,Master 既可以写又可以读,Slave不可以写只可以读。从物理结构上看 Broker 的集群部署方式有四种:单 Master 、多 Master 、多 Master 多 Slave(同步刷盘)、多 Master多 Slave(异步刷盘)。

消费者组(Consumer)

负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序,支持PUSH和PULL两种消费模式,支持集群消费和广播消息,提供实时的消息订阅机制。

  • Pull:拉取型消费者(Pull Consumer)主动从消息服务器拉取信息,只要批量拉取到消息,用户应用就会启动消费过程,所以 Pull 称为主动消费型。
  • Push:推送型消费者(Push Consumer)封装了消息的拉取、消费进度和其他的内部维护工作,将消息到达时执行的回调接口留给用户应用程序来实现。所以 Push 称为被动消费类型,但从实现上看还是从消息服务器中拉取消息,不同于 Pull 的是 Push 首先要注册消费监听器,当监听器处触发后才开始消费消息。

RocketMQ特性

1.灵活可扩展性: 天然支持集群,其核心四组件(Name Server、Broker、Producer、Consumer)每一个都可以在没有单点故障的情况下进行水平扩展。

2.海量消息堆积: 采用零拷贝原理实现超大的消息的堆积能力,据说单机已可以支持亿级消息堆积,而且在堆积了这么多消息后依然保持写入低延迟。

3.顺序消息: 可以保证消息消费者按照消息发送的顺序对消息进行消费。顺序消息分为全局有序和局部有序,一般推荐使用局部有序,即生产者通过将某一类消息按顺序发送至同一个队列来实现。

4.消息过滤: 分为在服务器端过滤和在消费端过滤。服务器端过滤时可以按照消息消费者的要求做过滤,优点是减少不必要消息传输,缺点是增加了消息服务器的负担,实现相对复杂。消费端过滤则完全由具体应用自定义实现,这种方式更加灵活,缺点是很多无用的消息会传输给消息消费者。

5.事务消息: 除了支持普通消息,顺序消息之外还支持事务消息,这个特性对于分布式事务来说提供了又一种解决思路。

6.消息回溯: 是指消费者已经消费成功的消息,由于业务上需求需要重新消费,RocketMQ 支持按照时间回溯消费,时间维度精确到毫秒,可以向前回溯,也可以向后回溯。

RocketMQ优点

  • 单机支持 1 万以上持久化队列
  • RocketMQ 的所有消息都是持久化的,先写入系统 PAGECACHE,然后刷盘,可以保证内存与磁盘都有一份数据,访问时,直接从内存读取。
  • 模型简单,接口易用(JMS 的接口很多场合并不太实用);
  • 性能非常好,可以大量堆积消息在broker中;
  • 支持多种消费,包括集群消费、广播消费等。
  • 各个环节分布式扩展设计,主从HA;
  • 开发度较活跃,版本更新很快。

RocketMQ缺点

  • 支持的客户端语言不多,目前是java及c++,其中c++不成熟;
  • RocketMQ社区关注度及成熟度不高;
  • 没有web管理界面,提供了一个CLI(命令行界面)管理工具带来查询、管理和诊断各种问题;
  • 没有在 mq 核心中去实现JMS等接口;

ActiveMQ

ActiveMQ是由Apache出品,旨在为应用程序提供高效、可扩展、稳定、安全的企业级消息通信,它是一个完全支持JMS1.1和J2EE 1.4规范的JMS Provider实现,比如 JMX 管理、主从管理、消息组通信、消息优先级、延迟接收消息、虚拟接收者、消息持久化、消息队列监控等等。它非常快速,支持多种语言的客户端和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能。
和上面的mq类似,主要的基本组件有Broker、Producer、Consumer、Topic、Queue、Message

ActiveMQ优点

  • 跨平台(JAVA编写与平台无关有,ActiveMQ几乎可以运行在任何的JVM上)
  • 可以用JDBC:可以将数据持久化到数据库。虽然使用JDBC会降低ActiveMQ的性能,但是数据库一直都是开发人员最熟悉的存储介质。将消息存到数据库,看得见摸得着。而且公司有专门的DBA去对数据库进行调优,主从分离;
  • 支持JMS :支持JMS的统一接口;
  • 支持自动重连;
  • 有安全机制:支持基于shiro,jaas等多种安全配置机制,可以对Queue/Topic进行认证和授权。
  • 监控完善:拥有完善的监控,包括Web Console,JMX,Shell命令行,Jolokia的REST API;
  • 界面友善:提供的Web Console可以满足大部分情况,还有很多第三方的组件可以使用,如hawtio;

ActiveMQ缺点

  • 社区活跃度不及RabbitMQ高;
  • 根据其他用户反馈,会出莫名其妙的问题,会丢失消息;
  • 对5.x的维护较少;
  • 不适合用于上千个队列的应用场景;

RabbitMQ、Kafka、RocketMQ、ActiveMQ对比

特性 RabbitMQ Kafka RocketMQ ActiveMQ
开发语言 Erlang Scala&Java Java Java
客户端支持 官方支持Erlang、Java、Ruby等,社区产出多语言API,几乎支持所有常用语言 官方支持Java,社区有多语言版本,如PHP、Python、Go、C/C++、Ruby、NodeJs等 Java、C++ Java、C/C++、Python、PHP、Perl、.net等
协议支持 AMQP、XMPP、SMTP、SMTOP 自定义协议,社区提供了HTTP协议支持 自定义协议,社区提供JMS OpenWire、SMTOP、REST、XMPP、AMQP
可用性 高,基于主从架构实现高可用 很高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 很高,分布式架构 高,基于主从架构实现高可用
集群 支持 支持 支持 支持
负载均衡 支持 支持 支持 支持
单机吞吐量 万级 十万级 十万级 万级
topic数量对吞吐量的影响 - topic从几十到几百个时候,吞吐量会大幅度下降,因为Kafka的每个Topic、每个分区都会对应一个物理文件,若需要支撑大规模的topic,则需要增加更多的机器资源 topic达到几百/几千的级别后,吞吐量会有较小幅度的下降,在同等机器下,可以支撑大量的 topic -
消息批量操作 不支持 支持 支持 支持
消息推拉模式 pull/push均支持 pull pull/push均支持 pull/push均支持
消息可靠性 支持最少投递一次 支持最少投递一次 支持最少投递一次 有较低的概率丢失数据
消息延迟 微秒级 (最快) 毫秒级 毫秒级 毫秒级
持久化能力 内存、文件,支持数据堆积,但影响生产速率 磁盘文件,只要容量够,可以做到无限堆积 磁盘文件 内存、文件、数据库
事务消息 不支持 不支持 支持 支持
死信 支持 不支持 支持 支持
消息回放 不支持 支持offset维度回放 支持offset维度回放 不支持
延迟队列 支持 不支持 支持 支持

其他平台看到的

image.png

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Mysql存储过程必知必会

发表于 2021-11-14

「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战」。

存储过程

为什么使用存储过程

  • 通过把 “某个处理” 封装在一个易用的单元中,可以简化复杂的操作。
  • 由于不要求反复建立一系列处理步骤,因而保证了数据的一致性。
  • 简化对变动的管理。如果表名、列名或业务逻辑(或别的内容)有变化,那么只需要更改存储过程的代码。使用它的人员甚至不需要知道 这些变化。
  • 因为存储过程通常以编译过的形式存储,所以 DBMS 处理命令所需的工作量少,提高了性能。

简单来说,使用存储过程有三个主要的好处:简单、安全、高性能。

创建存储过程

我们来看一个简单的存储过程例子,对 草帽海贼团 人数进行计数。

1
2
3
4
5
6
7
sql复制代码mysql> DELIMITER $$
mysql> CREATE PROCEDURE personCount()
-> BEGIN
-> SELECT COUNT(*) AS num_person
-> FROM one_piece
-> WHERE pirates = '草帽海贼团';
-> END$$

在定义过程时,使用 DELIMITER $$ 命令将语句的结束符号从分号 ; 临时改为 $$,使得过程体中使用的分号被直接传递到服务器,而不会被 Mysql 解释。

使用 CALL 存储过程名(参数) 来调用存储过程。

1
2
sql复制代码mysql> DELIMITER ;
mysql> CALL personCount();

存储过程体

存储过程体包含了在过程调用时必须执行的语句,例如:dml、ddl 语句,if-then-else 和 while-do 语句、声明变量的 declare 语句等。

过程体格式:以 begin 开始,以 end 结束(可嵌套)

1
2
3
4
5
6
7
sql复制代码BEGIN
  BEGIN
    BEGIN
      statements;
    END
  END
END

注意:每个嵌套块及其中的每条语句,必须以分号结束,表示过程体结束的 begin-end 块(又叫做复合语句 compound statement ),则不需要分号。

存储过程参数

  存储过程可以有0个或多个参数,用于存储过程的定义。

3种参数类型:

  • IN (输入参数):表示调用者向过程传入值(传入值可以是字面量或变量)
  • OUT (输出参数):表示过程向调用者传出值(可以返回多个值)(传出值只能是变量)
  • INOUT (输入输出参数):既表示调用者向过程传入值,又表示过程向调用者传出值(值只能是变量)

IN输入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sql复制代码mysql> DELIMITER $$
mysql> CREATE PROCEDURE in_test(IN p_in INT)
-> BEGIN
-> SELECT p_in; -- 第一次查询
-> SET p_in = 2; -- 修改p_in的值
-> SELECT p_in; -- 第二次查询
-> END$$

mysql> DELIMITER ;

mysql> SET @p_in = 1;

mysql> CALL in_test(@p_in);
+------+
| p_in |
+------+
| 1 |
+------+

+------+
| p_in |
+------+
| 2 |
+------+

mysql> SELECT @p_in;
+-------+
| @p_in |
+-------+
| 1 |
+-------+

虽然 p_in 在存储过程中被修改,但并不影响 @p_in 的值,因为 in_test 只接受输入参数,并不输出参数,所以相当于在函数内改变,但并未将这个值输出给 @p_in 。

OUT输入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sql复制代码mysql> DELIMITER $$
mysql> CREATE PROCEDURE out_test(OUT p_out INT)
-> BEGIN
-> SELECT p_out; -- 第一次查询
-> SET p_out = 2; -- 修改p_out的值
-> SELECT p_out; -- 第二次查询
-> END$$

mysql> DELIMITER ;

mysql> SET @p_out = 1;

mysql> CALL out_test(@p_out);
+-------+
| p_out |
+-------+
| NULL |
+-------+

+-------+
| p_out |
+-------+
| 2 |
+-------+

mysql> SELECT @p_out;
+--------+
| @p_out |
+--------+
| 2 |
+--------+

第一个返回结果为 NULL 是因为 OUT 是向调用者输出参数,不接收输入的参数,所以第一次查询时 p_out 还未赋值,所以是 NULL 。最后 @p_out 变量的值变为2是因为调用了 out_test 存储过程,输出参数,改变了 p_out 变量的值。

INOUT输入输出参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
sql复制代码mysql> DELIMITER $$
mysql> CREATE PROCEDURE inout_test(INOUT p_inout INT)
-> BEGIN
-> SELECT p_inout; -- 第一次查询
-> SET p_inout = 2; -- 修改p_inout的值
-> SELECT p_inout; -- 第一次查询
-> END$$

mysql> DELIMITER ;

mysql> SET @p_inout = 1;

mysql> CALL inout_test(@p_inout);
+---------+
| p_inout |
+---------+
| 1 |
+---------+

+---------+
| p_inout |
+---------+
| 2 |
+---------+

mysql> SELECT @p_inout;
+----------+
| @p_inout |
+----------+
| 2 |
+----------+

调用 inout_test 存储过程,既接受了输入的参数,也输出参数, @p_inout 的值被改变。

删除存储过程

使用 DROP PROCEDURE 来删除存储过程。

1
sql复制代码mysql> DROP PROCEDURE in_test;

这就是今天要分享的内容,微信搜 Python新视野,每天带你了解更多有用的知识。更有整理的近千套简历模板,几百册电子书等你来领取哦!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL索引优化系列:(一)索引的类型 一、MySQL简介

发表于 2021-11-14

「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战」

一、MySQL简介

MySQL 是最流行的关系型数据库管理系统,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。

MySQL是一种开放源代码的关系型数据库管理系统,使用最常用的数据库管理语言–结构化查询语言(SQL)也就是我们常说的crud(增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写)来进行数据库管理。

简单来说,就是一个存储数据的仓库,对于网站运行MySQL就是它的命脉,同时MySQL 可以在几乎所有平台上(UNIX, Linux, Windows)运营。

这里有两个很重要的概念需要知道:

数据库

我们可以从其名字来说,数据库的意思是数据的集合。我们电脑上的文件夹可以看作一个数据库;使用音乐软件,里面的歌曲资源是存储在数据库里的;在社交媒体上发的动态同样也是存储在数据库里的。

用比较规范的语言描述一下就是:电子化的文件柜,是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。

SQL(结构化查询语言)

SQL 是用于访问和处理数据库的标准的计算机语言。

结构化查询语言,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。

SQL 的数据定义语言 (DDL) 部分使我们有能力创建或删除表格。例如:CREATE DATABASE - 创建新数据库

二、索引分类

mysql的索引我们分为三大类来讲单列索引(普通索引,唯一索引,主键索引)、组合索引(联合索引/多列索引)、全文索引。

索引

简单来说,索引就是一个指针,指向表里的数据。

  • 索引通常与相应的表时分开保存的,目的是提高检索的性能。
  • 索引的创建与删除不会影响数据本身,但会影响数据检索的速度。
  • 索引也会占据物理存储空间,可能比表本身还大,因此创建索引也要考虑存储空间。

单列索引

普通索引

没有任何限制,允许在定义索引的列中插入重复值和空值,创建一个单列索引的2种方式:

1
2
java复制代码1]create index index_name on tbl_name(index_col_name)
2]alter table 表名 add index index_name on(index_col_name)

这里,create index表示创建一个索引,index_name表示索引的名称,on tbl_name表示要创建索引的表, index_col_name表示表中要创建索引的列

唯一索引

索引列中的值必须是唯一的,但是允许为空值,也就是不允许具有索引值相同的行。

1
java复制代码CREATE UNIQUE CLUSTERED INDEX myclumn_cindex ON mytable(mycolumn)

这里,CLUSTERED INDEX是用来建立聚簇索引的关键字,此语句的意思是在表mytable上的mycolumn字段上创建一个名为myclumn_cindex的聚簇索引,且为唯一索引。

主键索引

是一种特殊的唯一索引,不允许有空值。简称主键,原文是PRIMARY KEY,由一个或多个列组成,用于唯一性标识数据表中的某一条记录。一个表可以没有主键,但最多只能有一个主键,并且主键值不能包含NULL。

可以这样来记忆:主键=主键索引=聚集索引

实例:一般id作为表的主键

1
java复制代码 PRIMARY KEY (`id`),

组合索引(联合索引/多列索引)

联合索引是指对表上的多个列进行索引,联合索引也是一棵B+树.
命名规则:表名_字段名

  • 1、需要加索引的字段,要在where条件中
  • 2、数据量少的字段不需要加索引
  • 3、如果where条件中是OR关系,加索引不起作用
  • 4、符合最左原则(where条件必须有联合索引的第一个字段)

例如:

1
2
3
4
5
6
7
8
9
10
java复制代码CREATE TABLE `test` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`name` varchar(10) NOT NULL,
`age` varchar(2) NOT NULL,
`sex` varchar(2) NOT NULL,
`phone` varchar(12) NOT NULL,
`email` varchar(25) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_name_age_sex` (`name`,`age`,`sex`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

这就是联合索引:

1
java复制代码 `idx_name_age_sex` (`name`,`age`,`sex`)

使用组合索引时遵循最左前缀集合,这里由name、age和sex个字段构成的索引,索引行中就按name/age/sex的顺序存放,索引可以索引下面字段组合(name,age,sex)、(name,age)或者(name)或者(name,sex)。

举例具体的sql,以下都会走联合索引的:

1
2
3
4
5
java复制代码 select  * from test where name = '张三' and age = '18' and sex = '男';
select * from test where name = '张三' and age = '18';
select * from test where name = '张三' ;
select * from test where name = '张三' and sex = '男';
select * from test where and sex = '男' and age = '18' name = '张三';

注意,第5条sql语句是可以执行的,联合索引与where条件的顺序无关!

然后看几个不会走联合索引的:

1
2
3
4
java复制代码 select  * from test where  and age = '18' and sex = '男';
select * from test where and age = '18' or sex = '男';
select * from test where and age = '18' ;
select * from test where and sex = '男';

覆盖索引

覆盖索引其实是一种特殊的联合索引,即是你查询的字段的所有数据都在索引上,不需要再进行一次回表查询,这样的索引即为覆盖索引。

1
java复制代码 SELECT name,age,sex from test where name = '%三%';

全文索引(fulltext/倒排文档技术)

只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引,全文索引在平时的业务中用到的不多。不过全文索引技术是搜索引擎的关键技术。也被叫做倒排文档技术。

创建全文索引实例:

1
2
3
4
java复制代码CREATE TABLE test (
title VARCHAR(40),
FULLTEXT(title)
);

全文索引的三种类型

  1. 自然语言搜索将搜索字符串解释为自然语言中短语。
  2. 布尔全文搜索
  3. 查询扩展搜索

这里就只作简单介绍,可以简单了解下,就不深入讲解了。

致谢

  1. 百度百科
  2. 2. MySQL是什么?
  3. w3school
  4. 索引的类型分类、区别、优缺点
  5. MySQL全文索引功能
  6. Mysql全文索引

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…343344345…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%