隔壁董小花同学


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

Spring MVC 工作原理和内部流程

发表于 2019-08-23 | 分类于 spring

Spring MVC 工作原理图

SpringMVC流程

1、用户发送请求至前端控制器DispatcherServlet。
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter处理器适配器。
5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView。
7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。

组件说明:

以下组件通常使用框架提供实现:
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等.
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。

组件:

  1. 前端控制器DispatcherServlet(不需要工程师开发),由框架提供
  • 作用:接收请求,响应结果,相当于转发器,中央处理器。
    有了dispatcherServlet减少了其它组件之间的耦合度。用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
  1. 处理器映射器HandlerMapping(不需要工程师开发),由框架提供
  • 作用:根据请求的url查找Handler
    HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
  1. 处理器适配器HandlerAdapter
  • 作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
    通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
  1. 处理器Handler(需要工程师开发)
  • 注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
    Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
    由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
  1. 视图解析器View resolver(不需要工程师开发),由框架提供
  • 作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
    View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
    一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
  1. 视图View(需要工程师开发jsp…)
    View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)

    核心架构的具体流程步骤如下:

  2. 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
  3. DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
  4. DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  5. HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
  6. ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
  7. View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
  8. 返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

    下边两个组件通常情况下需要开发:

  • Handler:处理器,即后端控制器用controller表示。
  • View:视图,即展示给用户的界面,视图中通常需要标签语言展示模型数据。

在将SpringMVC之前我们先来看一下什么是MVC模式
MVC:MVC是一种设计模式
MVC的原理图:

分析:

  • M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
  • V-View 视图(做界面的展示 jsp,html……)
  • C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)

springMVC是什么

  springMVC是一个MVC的开源框架,springMVC=struts2+spring,springMVC就相当于是Struts2加上sring的整合,但是这里有一个疑惑就是,springMVC和spring是什么样的关系呢?这个在百度百科上有一个很好的解释:意思是说,springMVC是spring的一个后续产品,其实就是spring在原有基础上,又提供了web应用的MVC模块,可以简单的把springMVC理解为是spring的一个模块(类似AOP,IOC这样的模块),网络上经常会说springMVC和spring无缝集成,其实springMVC就是spring的一个子模块,所以根本不需要同spring进行整合。

SpringMVC的原理图:

看到这个图大家可能会有很多的疑惑,现在我们来看一下这个图的步骤:(可以对比MVC的原理图进行理解)

  • 第一步:用户发起请求到前端控制器(DispatcherServlet)
  • 第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找
  • 第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)
  • 第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)
  • 第五步:处理器适配器去执行Handler
  • 第六步:Handler执行完给处理器适配器返回ModelAndView
  • 第七步:处理器适配器向前端控制器返回ModelAndView
  • 第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析
  • 第九步:视图解析器像前端控制器返回View
  • 第十步:前端控制器对视图进行渲染
  • 第十一步:前端控制器向用户响应结果

看到这些步骤我相信大家很感觉非常的乱,这是正常的,但是这里主要是要大家理解springMVC中的几个组件:

  • 前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。
  • 处理器映射器(HandlerMapping):根据URL去查找处理器
  • 处理器(Handler):(需要程序员去写代码处理逻辑的)
  • 处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)
  • 视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面

摘自:https://www.cnblogs.com/xiaoxi/p/6164383.html

TIME_WAIT状态分析

发表于 2019-07-31 | 分类于 http

TCP三次握手四次挥手

三次握手 (建立连接协议)

  • 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

    握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP连接都将被一直保持下去。

四次挥手(连接终止协议)

  • 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接受数据。主动方处于FIN_WAIT_1状态
  • 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号, SYN 和 FIN 都有seq序号)。主动方处于FIN_WAIT_2状态
  • 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。被动房处于LAST_ACK状态
  • 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,主动方此时处于TIME_WAIT状态,被动方收到ACK报文后关闭连接,至此,完成四次挥手。

    由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

状态解释

1
2
3
4
5
6
7
8
9
10
11
CLOSED:无连接是活动的或正在进行
LISTEN:服务器在等待进入呼叫
SYN_RECV:一个连接请求已经到达,等待确认
SYN_SENT:应用已经开始,打开一个连接
ESTABLISHED:正常数据传输状态
FIN_WAIT1:应用说它已经完成
FIN_WAIT2:另一边已同意释放
ITMED_WAIT:等待所有分组死掉
CLOSING:两边同时尝试关闭
TIME_WAIT:另一边已初始化一个释放
LAST_ACK:等待所有分组死掉

TIME_WAIT过多问题原因及解决(Cannot assign requested address)

  • 什么时候会出现TIME_WAIT
    TCP在关闭的时候有个四次挥手的过程,主动关闭方在四次挥手的最后一个ACK发送之后会变成TIME_WAIT状态。

  • TIME_WAIT状态维持多久
    主动关闭方响应完最后一次ACK之后,会在TIME_WAIT这个状态维持2MSL

  • MSL
    MSL全称是maximum segment lifetime,最长分节生命期。MSL是任何IP数据报能够在因特网存活的最长时间。我们知道,这个时间是有限的,因为每个数据报都含有一个限跳(hop limit)的8位字段,它的最大值是255(简单的讲就是不同经过超过255个路由器)。尽管这个跳数限制而不是真正的时间限制,我们仍然假设最大限跳的分组在网络中存在的时间不可能超过MSL秒。

  • TIME_WAIT的作用
    1)可靠地实现TCP全双工连接的终止
    在进行关闭连接四次挥手协议时,最后的ACK是由主动关闭端发出的,如果这个最终的ACK丢失,服务器将重发最终的FIN,
    因此客户端必须维护状态信息允许它重发最终的ACK。如果不维持这个状态信息,那么客户端将响应RST分节,服务器将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。
    因而,要实现TCP全双工连接的正常终止,必须处理终止序列四个分节中任何一个分节的丢失情况,主动关闭的客户端必须维持状态信息进入TIME_WAIT状态。
    2)允许老的重复分节在网络中消逝
    TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个原来的迷途分节就称为lost duplicate。
    在关闭一个TCP连接后,马上又重新建立起一个相同的IP地址和端口之间的TCP连接,后一个连接被称为前一个连接的化身(incarnation),那么有可能出现这种情况,前一个连接的迷途重复分组在前一个连接终止后出现,从而被误解成从属于新的化身。
    为了避免这个情况,TCP不允许处于TIME_WAIT状态的连接启动一个新的化身,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个TCP连接的时候,来自连接先前化身的重复分组已经在网络中消逝。

  • 大量TIME_WAIT造成的影响
    在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。
    主动正常关闭TCP连接,都会出现TIMEWAIT。
    为什么我们要关注这个高并发短连接呢?有两个方面需要注意:

  1. 高并发可以让主动方(大部分是客户端)在短时间范围内同时占用大量端口,而端口有个0~65535的范围,并不是很多,刨除系统和其他服务要用的,剩下的就更少了。(服务端端口是复用的)
  2. 在这个场景中,短连接表示“业务处理+传输数据的时间 远远小于 TIMEWAIT超时的时间”的连接。
    例如: 取一个web页面,1秒钟的http短连接处理完业务,在关闭连接之后,这个业务用过的端口会停留在TIMEWAIT状态几分钟,而这几分钟,其他HTTP请求来临的时候是无法占用此端口的(占着茅坑不拉翔)。单用这个业务计算服务器的利用率会发现,服务器干正经事的时间和端口(资源)被挂着无法被使用的时间的比例是 1:几百,服务器资源严重浪费。(说个题外话,从这个意义出发来考虑服务器性能调优的话,长连接业务的服务就不需要考虑TIMEWAIT状态。同时,假如你对服务器业务场景非常熟悉,你会发现,在实际业务场景中,一般长连接对应的业务的并发量并不会很高。

shell连接远程服务器脚本

发表于 2019-07-29 | 分类于 shell

脚本示例

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/expect
set timeout 10
set password ***
spawn ssh root@{IP} -p 22
expect {
"password:" {
send "${password}\r";
exp_continue;
}
}
interact
  • 第1行:#!/usr/bin/expect 是告诉系统在执行这个脚本的时候用哪个命令来执行,这边是用expect,是一个类似bash和sh的shell。
  • 第2行 set timeout 10 设置超时时间为10s。
  • 第3行 set password 123456 定义了一个变量 password,这个变量记录了登陆远程服务器的密码。
  • 第4行 spawn ssh ubuntu@xxx.xxx.xxx.xxx 调用 spawn 命令来执行一个系统命令。spawn是expect shell的内建命令,只有在expect环境下才可以使用。这行代码的功能就是通过ssh连接远程ECS。
  • 5~10行 行用了一个 expect 命令,可以将这个命令理解为期待shell中输出结果中包含什么关键字。
    如果包含的是 password: 则表示不是第一次连接这个远程服务器,直接用send输出密码即可。

传送文件

  • 将本地文件复制到远程: scp {local txt} {userName}@{ip}:/{txt}
  • 将远程文件复制到本地: scp {usreName}@{ip}:/root/{txt}

maven版本区分

发表于 2019-07-22 | 分类于 basics

maven的快照版本和发布版本

在使用maven过程中,我们在开发阶段经常性的会有很多公共库处于不稳定状态,随时需要修改并发布,可能一天就要发布一次,遇到bug时,甚至一天要发布N次。我们知道,maven的依赖管理是基于版本管理的,对于发布状态的artifact,如果版本号相同,即使我们内部的镜像服务器上的组件比本地新,maven也不会主动下载的。如果我们在开发阶段都是基于正式发布版本来做依赖管理,那么遇到这个问题,就需要升级组件的版本号,可这样就明显不符合要求和实际情况了。但是,如果是基于快照版本,那么问题就自热而然的解决了,而maven已经为我们准备好了这一切。
maven中的仓库分为两种,snapshot快照仓库和release发布仓库。snapshot快照仓库用于保存开发过程中的不稳定版本,release正式仓库则是用来保存稳定的发行版本。定义一个组件/模块为快照版本,只需要在pom文件中在该模块的版本号后加上-SNAPSHOT即可(注意这里必须是大写),如下:

1
2
3
4
<groupId>cc.mzone</groupId>
<artifactId>m1</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>

maven2会根据模块的版本号(pom文件中的version)中是否带有-SNAPSHOT来判断是快照版本还是正式版本。如果是快照版本,那么在mvn deploy时会自动发布到快照版本库中,会覆盖老的快照版本,而在使用快照版本的模块,在不更改版本号的情况下,直接编译打包时,maven会自动从镜像服务器上下载最新的快照版本。如果是正式发布版本,那么在mvn deploy时会自动发布到正式版本库中,而使用正式版本的模块,在不更改版本号的情况下,编译打包时如果本地已经存在该版本的模块则不会主动去镜像服务器上下载。
所以,我们在开发阶段,可以将公用库的版本设置为快照版本,而被依赖组件则引用快照版本进行开发,在公用库的快照版本更新后,我们也不需要修改pom文件提示版本号来下载新的版本,直接mvn执行相关编译、打包命令即可重新下载最新的快照库了,从而也方便了我们进行开发。
目前在JAVA的世界中,maven已经成为事实上的构建标准,很多开源库的管理构建也是基于maven的,maven本身的学习曲线比较陡峭,遵循“约定优于配置”的理念,maven存在很多约定。本次我先描述下,关于版本的定义的选择,SNAPSHOT or RELEASE?

  • 版本之争
    在maven的约定中,依赖的版本分为两类——SNAPSHOT和RELEASE。SNAPSHOT依赖泛指以-SNAPSHOT为结尾的版本号,例如1.0.1-SNAPSHOT。除此之外,所有非-SNAPSHOT结尾的版本号则都被认定为RELEASE版本,即正式版,虽然会有beta、rc之类说法,但是这些只是软件工程角度的测试版,对于maven而言,这些都是RELEASE版本。既然Maven提供了这两类版本号,那么他们之前的优劣势是什么?分别在什么场景下使用?
  • 解读SNAPSHOT
    同一个SNAPSHOT版本的依赖可以多次发布(deploy)到仓库中,也就是说同一个SNAPSHOT版本的依赖可以在仓库中存在多份,每一份都是代码在某一个特定时间的快照,这也是SNAPSHOT的含义。
  • snapshot
    如上图,很好地表达了SNAPSHOT的细节,也阐述了一个SNAPSHOT很重要观点——SNAPSHOT不是一个特定的版本,而是一系列的版本的集合,其中HEAD总是指向最新的快照,对外界可见的一般也是最新版,这种给人的假象是新的覆盖了老的,从而使得使用SNAPSHOT依赖的客户端总是通过重新构建(有时候需要-U强制更新)就可以拿到最新的代码。例如:A–>B-1.3.8-SNAPSHOT(理解为A依赖了B的1.3.8-SNAPSHOT版本),那么B-1.3.8-SNAPSHOT更新之后重新deploy到仓库之后,A只需要重新构建就可以拿到最新的代码,并不需要改变依赖B的版本。由此可见,这样达到了变更传达的透明性,这对于开发过程中的团队协作的帮助不言而喻。
  • SNAPSHOT之殇
    SNAPSHOT版本的依赖因为存在变更传达的透明性的优势而被赏识,甚至被“溺爱”,有很多团队索性直接使用SNAPSHOT到生产环境中,这样对于变更直接生效,很方便。但是作为技术人员的我们其实应该很严谨地看待变更传达的透明性,变更就意味着风险,透明性更是把风险彻底隐藏了起来,生产环境中存在这样的现象更是心惊胆战。例如:A–>B.1.0.3-SNAPSHOT,B对一个A使用的功能实现进行了调整,直接发布到仓库,A重新构建或许就会失败,更糟糕的是构建成功,运行时异常。这个时候A甚至完全没有代码变更就突然失败了,会带来更多的困惑。
    这也是maven经常遭人诟病的一个因素,对于同一份代码,构建结果却不具备确定性,让很多人沮丧。当然这个不完全是因为依赖的问题,也有maven插件的问题,maven之前的版本寻找插件策略的方式也存在不确定性,maven在版本2的时候,会去寻找最新的插件版本(如果没配置的话)来执行构建,经常会找到SNAPSHOT版本的插件,所以依赖了一个不稳定的插件来执行构建,不确定性就大大增加。不过maven在3版本就改变了这个策略,会寻找最新稳定版的插件来执行构建,使得构建具备了确定性,稳定性也好多了。说明maven本身也在SNAPSHOT的问题上狠狠摔了一跤。
    归根到底,这些问题的根源就是SNAPSHOT是变化的,是不稳定的,而应用(软件)依赖于变化并且不稳定的SNAPSHOT的依赖会导致自身也在变化和不稳定中,这是稳定性的一个大忌,依赖不稳定的服务或者依赖,上述的maven2的问题就是一个典型反例。
  • RELEASE简介
    RELEASE版本和SNAPSHOT是相对的,非SANPSHOT版本即RELEASE版本,RELEASE版本是一个稳定的版本号,看清楚咯,是一个,不是一系列,可以认为RELEASE版本是不可变化的,一旦发布,即永远不会变化。
    虽然RELEASE版本是稳定不变的,但是仓库还是有策略让这个原则变得可配置,有的仓库会配置成redeploy覆盖,这样RELEASE版本就变成SNAPSHOT了,伪装成RELEASE的SNAPSHOT,会让问题更费解和棘手。
    记住,RELEASE一旦发布,就不可改变。
  • 如何选择
    那么什么时候使用SNAPSHOT?什么时候使用RELEASE?这个可以从他们各自的特性上来看,SNAPSHOT版本的库是一直在变化的,或者说随时都会变化的,这样虽然可以获取到最新的特性,但是也存在不稳定因素,依赖一个不稳定的模块或者库会让模块自身也变得不稳定,尤其是自身对被依赖模块的变化超出掌控的情况。即使可以掌控被依赖模块的变化,也会带来不稳定的因素,因为每次变更都有引入bug的可能性。如果这么说,那么我们是不是要摒弃SANPSHOT了呢?答案肯定是否定的。
    想象下,什么情况下,模块会一直变化或者变化比较剧烈?开发新特性的时候,所以对于团队之间协同开发的时候,模块之间出现依赖,变化会非常剧烈,如模块A依赖模块B,模块A必然需要最方便地获取模块B的特性,在开发期间,方便性比稳定性更重要。可以反证下,假设模块B使用RELEASE版本1.0.0,模块A依赖1.0.0,现在模块A出现了bug,需要修复下,那么A就要提供一个版本号1.0.1,这样所有依赖A模块都需要更新版本号,因为开发期间这种事情是如此多,所以会带来巨变。反观SNAPSHOT方案,如果模块B的版本是1.0.0-SNAPSHOT,模块A完全不需要修改版本号即可获取模块B的新特性。当开发进入预发布阶段,为了生产环境的稳定性,依赖应该是RELEASE版本,因为此时SNAPSHOT版本的模块自动获取新特性的特点恰恰会造成生产环境的不稳定性,生产环境上,稳定性重于一切。
  • 魔幻之手
    现在已经很明确了,在开发期间,活跃模块的版本号使用SNAPSHOT,在生产期间,依赖RELEASE版本模块。貌似,我们找到了银弹,不过这个只是理想状态,即所有的模块的版本都在自己的掌控或者间接掌控下,只有这样你才能影响对应模块的版本号。往往是理想很丰满,现实却很骨感,如果你依赖的一个模块只有SNAPSHOT版本,并且该模块也很活跃,最无助的是模块的维护人不理会你的请求,那么是否就没辙了,只能把应用构建在不稳定模块上呢?介绍一款maven插件——versions,这是一个非常强大的版本管理插件,其中有个对依赖版本加锁的特性——lock-snapshots,并且提供了参数可以控制锁定的依赖,就可以实现对特定的SNAPSHOT模块锁定版本,执行的命令如下:mvn versions:lock-snapshots -DincludesList=”groupId:artifactId:type:classifier:version”,执行这个命令之后,对应的版本号会变化,比如1.0.0-SNAPSHOT会变成1.0.0.20090327.172306-4,即完成了锁定,此时这个SNAPSHOT就变成了固定小版本的稳定版本,不会在变化了,也相当于正式版的功能了。当然以后也可以解锁,详细请看对应文档。

摘自: https://www.cnblogs.com/wuchanming/p/5484091.html

Redis

发表于 2019-07-19 | 分类于 数据库

Redis命令

  • 连接
    1
    redis-cli -h host -p post -a pwd

    基本命令

  • info – 查看信息
  • select [num] – 选择index
  • keys * – 查看所有key
  • SET key value – 设置指定 key 的值
  • GET key – 获取指定 key 的值
  • GETSET key value – 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
  • MGET key1 [key2..] – 获取所有(一个或多个)给定 key 的值。
  • SETEX key seconds value – 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
  • PSETEX key milliseconds value – 以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
  • SETNX key value – 只有在 key 不存在时设置 key 的值。
  • MSET key value [key value …] – 同时设置一个或多个 key-value 对。
  • APPEND key value – 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
  • DEL key – 该命令用于在 key 存在时删除 key。
  • EXISTS key – 检查给定 key 是否存在。
  • DUMP key – 序列化给定 key ,并返回被序列化的值
  • PERSIST key – 移除 key 的过期时间,key 将持久保持。
  • PTTL key – 以毫秒为单位返回 key 的剩余的过期时间
  • RANDOMKEY – 从当前数据库中随机返回一个 key
  • RENAME key newkey – 修改 key 的名称
  • RENAMENX key newkey – 仅当 newkey 不存在时,将 key 改名为 newkey
  • TYPE key – 返回 key 所储存的值的类型

pub(发布) - sub(订阅)

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道

列表(List)

  • LLEN key 获取列表长度
  • LPOP key 移除并获取列表第一个元素
  • RPOP key 移除并获取列表最后一个元素
  • LREM key count value 移除列表元素
  • RPUSHX key value 对已存在的列表添加元素
  • LPUSHX key value 将元素插入已存在列表头部

jstack (查看线程状态) jmap(查看内存)jstat(性能分析)

发表于 2019-06-14 | 分类于 Jvm

基本命令的使用

1
2
3
4
5
6
7
获取垃圾回收器的类型和系统参数 // jmap -heap pid  
查看应用启动的参数// jinfo -flags pid
查看当前各个代区的容量和使用量情况 // jstat
FGC、YGC的总次数和总耗时 // jstat
立即生成Dump文件 //jmap -dump:live,file=dump_001.bin PID
强制FullGC // jmap -dump:live
查看线程的运行信息(包括死锁的线程) // jstack -l pid

jstack

jstack是jdk自带的线程堆栈分析工具,使用该命令可以查看或导出 Java 应用程序中线程堆栈信息。

  • 功能
    jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
  • 线程的状态
    NEW,未启动的。不会出现在Dump中。
    RUNNABLE,在虚拟机内执行的。
    BLOCKED,受阻塞并等待监视器锁。
    WATING,无限期等待另一个线程执行特定操作。
    TIMED_WATING,有时限的等待另一个线程的特定操作。
    TERMINATED,已退出的。
  • 命令
    jstack -l pid
    获取pid:ps -ef | grep “name” | grep -v grep | awk ‘{print $2}’
    pid转16进制:echo ‘ibase=10;ibase=16;pid’|bc

jmap

jmap heap pid:查看 JDK的概况的最好的一个参数
Jmap是一个可以输出所有内存中对象的工具,甚至可以将VM 中的heap,以二进制输出成文本.
jmap -dump:format=b,file=outfile PID 可以将指定进程的内存heap输出出来到outfile文件里.

  • 命令格式
    jmap [ option ] pid
    jmap [ option ] executable core
    jmap [ option ] [server-id@]remote-hostname-or-IP
    参数说明
    options:
    executable :产生core dump的java可执行程序;
    core 将被打印信息的core dump文件;
    remote-hostname-or-IP 远程debug服务的主机名或ip;
    server-id 唯一id,假如一台主机上多个远程debug服务;

  • dump 文件分析
    导出整个JVM 中内存信息: jmap -dump:format=b,file=文件名.dump [pid]

    • 将文件远程复制
    1. 将本地文件复制到远程: scp {local txt} {userName}@{ip}:/{txt}
    2. 将远程文件复制到本地: scp {usreName}@{ip}:/root/{txt}

dubbo服务的telnet

发表于 2019-05-23 | 分类于 dubbo

Dubbo2.0.5以上版本服务提供端口支持telnet命令,我们可以进行调试、管理

1. 连接dubbo服务

1
telnet ip prot

会车进入dubbo命令模式

1
dubbo>

2. 查询服务列表

  • 查看服务

    1
    dubbo> ls
  • 查看服务中的接口

    1
    dubbo> ls 服务地址
  • ls

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    (list services and methods)

    ls
    显示服务列表。

    ls -l
    显示服务详细信息列表。

    ls XxxService
    显示服务的方法列表。

    ls -l XxxService
    显示服务的方法详细信息列表。

3. 调用服务接口

  • 调用dubbo接口以json格式传参

    1
    dubbo> invoke ${interface}()
  • invoke

    1
    2
    3
    4
    5
    invoke XxxService.xxxMethod({"prop": "value"})
    调用服务的方法。

    invoke xxxMethod({"prop": "value"})
    调用服务的方法(自动查找包含此方法的服务)。

    4. 查看服务状态

  • count

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    count XxxService
    统计1次服务任意方法的调用情况。

    count XxxService 10
    统计10次服务任意方法的调用情况。

    count XxxService xxxMethod
    统计1次服务方法的调用情况。

    count XxxService xxxMethod 10
    统计10次服务方法的调用情况。
  • status

    1
    2
    3
    4
    5
    6
    status

    显示汇总状态,该状态将汇总所有资源的状态,当全部OK时则显示OK,只要有一个ERROR则显示ERROR,只要有一个WARN则显示WARN。

    status -l
    显示状态列表。

telnet命令手册: http://alibaba.github.io/dubbo-doc-static/Telnet+Command+Reference-zh-showComments=true&showCommentArea=true.htm

Spring事务处理

发表于 2019-05-17 | 分类于 spring

什么是Spring事务

事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。
数据库向用户提供保存当前程序状态的方法,叫事务提交(commit);当事务执行过程中,使数据库忽略当前的状态并回到前面保存的状态的方法叫事务回滚(rollback)

事务特性(ACID)

原子性(atomicity): 将事务中所做的操作捆绑成一个原子单元,即对于事务所进行的数据修改等操作,要么全部执行,要么全部不执行。
一致性(Consistency): 事务在完成时,必须使所有的数据都保持一致状态,而且在相关数据中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构都应该是正确的。
隔离性(Isolation): 由并发事务所做的修改必须与任何其他事务所做的修改相隔离。事务查看数据时数据所处的状态,要么是被另一并发事务修改之前的状态,要么是被另一并发事务修改之后的状态,即事务不会查看由另一个并发事务正在修改的数据。这种隔离方式也叫可串行性。
持久性(Durability): 事务完成之后,它对系统的影响是永久的,即使出现系统故障也是如此。

事务隔离(Isolation Level)

事务隔离意味着对于某个运行着的事务来说,类似于系统中只有一个事物,其他并发事务都不存在一样
大部分情况下很少使用安全隔离的事务,但是不完全隔离的事务会带来如下问题:
更新丢失(Lost Update): 两个事务都企图去更新一行数据,导致事务抛出异常退出。
脏数据(Dirty Read): 如果第二个应用程序使用了第一个应用程序修改过的数据,而这个数据处于未提交状态,这时就会发生脏读。第一个应用程序随后可能会请求回滚被修改的数据,从而导致第二个事务使用的数据被损坏,即所谓的“变脏”。
不可重读(Unrepeatable Read): 一个事务两次读同一行数据,可是这两次读到的数据不一样,就叫不可重读。如果一个事务在提交数据之前,另一个事务可以修改和删除这些数据,就会发生不可重读。
幻读(Phantom Read): 一个事务执行了两次查询,发现第二次查询结果比第一次查询多出了一行,这可能是因为另一个事务在这两次查询之间插入了新行。

那么为了避免此类问题,提供出了以下隔离级别来防范:

读操作未提交(Read Uncommitted): 读取未提交的数据是允许的。说明一个事务在提交前,其变化对于其他事务来说是可见的。这样脏读、不可重读和幻读都是允许的。当一个事务已经写入一行数据但未提交,其他事务都不能再写入此行数据;但是,任何事务都可以读任何数据。这个隔离级别使用排写锁实现。
读操作已提交(Read Committed): 读取未提交的数据是不允许的,它使用临时的共读锁和排写锁实现。这种隔离级别不允许脏读,但不可重读和幻读是允许的。
可重读(Repeatable Read): 说明事务保证能够再次读取相同的数据而不会失败。此隔离级别不允许脏读和不可重读,但幻读会出现。
可串行化(Serializable): 提供最严格的事务隔离。这个隔离级别不允许事务并行执行,只允许串行执行。这样,脏读、不可重读或幻读都可发生。

事务隔离与隔离级别的关系:

隔离级别 脏读(Dirty Read) 不可重读(Unrepeatable read) 幻读(Phantom Read)
读操作未提交(Read Uncommitted) 可能 可能 可能
读操作已提交(Read Committed) 不可能 可能 可能
可重读(Repeatable Read) 不可能 不可能 可能
可串行化(Serializable) 不可能 不可能 不可能

事务的传播(Propagation)

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

readOnly 事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。这是一个最优化提示 。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具 (如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。
Timeout 在事务属性中还有定义“timeout”值的选项,指定事务超时为几秒。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。

事务的嵌套

  1. PROPAGATION_REQUIRED 加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务。即,如果存在主事务,则将此事务合并到主事务中,提交和回滚与主事务依赖,不存在主事务则会自起事务。
  2. PROPAGATION_SUPPORTS 如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行。
  3. PROPAGATION_MANDATORY 必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
  4. PROPAGATION_REQUIRES_NEW 在主事务中,创建一个新的事务(子事务)并挂起主事务,执行完子事务后,主事务继续执行,但是,如果子事务已经提交,当主事务失败回滚时,子事务是不回滚的
  5. PROPAGATION_NOT_SUPPORTED 当前不支持事务,当存在事务中时,会将主事务挂起,当以非事务状态执行完后在继续执行主事务
  6. PROPAGATION_NEVER 不能在事务中运行,否则抛异常。
  7. PROPAGATION_NESTED 它与PROPAGATION_REQUIRES_NEW的区别是,不另起事务,与主事务相依,而且需要等主事务提交时才会提交,主事务回滚它也回滚。

Spring事务处理

Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。
Spring处理事务主要有五种方法:

  1. 每个bin都有一个代理
  2. 所有bin共享一个代理基类
  3. 使用拦截器
  4. 使用tx标签配置的拦截器
  5. 全注解
    此处只简单介绍全注解:
    在配置好数据库配置后,需要 定义事务管理器
    1
    2
    3
    4
    <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource"/>
    </bean>
    然后在需要事务的方法上添加 @Transactional 注解。

@Transactional

@Transactional的含义

关键点之一是要考虑两个独立的概念,它们都有各自的范围和生命周期:

  • persistence context(持久化上下文)
  • database transaction(事务)
    @Transactional本身定义了单个事务的范围。这个事务在persistence context的范围内。
    JPA中的持久化上下文是EntityManager,内部实现使用了Hibernate Session(使用Hibernate作为持久化provider)
    持久化上下文仅仅是一个同步对象,它记录了有限集合的Java对象的状态,并且保证这些对象的变化最终持久化到数据库。
    这是与单个事务非常不同的概念。一个Entity Manager可以跨越多个事务使用,而且的确是这样使用的。

    EntityManager和Transaction之间的关系:

    JPA Entity Manager最常用的方式是“Entity Manager per application transaction”(每个事务都有自己的实体管理器)模式。entity manager注入的常用方法是:
    1
    2
    @PersistenceContext
    private EntityManager em;
    这里默认为“Entity Manager per transaction”模式。这种模式下如果在@Transactional方法内部使用该Entity Manager,那么该方法将在单一事务中运行。

@PersistenceContext的工作原理

随之而来的问题就是@PersistenceContext如何仅在容器启动时注入entity manager,假定entity manager生命周期很短暂,而且每次请求需要多个entity manager。
答案是它不能:EntityManager是一个接口,注入到spring bean中的不是entity manager本身,而是在运行时代理具体entity manager的context aware proxy(上下文感知代理)。
通常用于代理的具体类为SharedEntityManagerInvocationHandler,借助调试器可以确认这一点。

@Transactional如何工作的

实现了EntityManager接口的持久化上下文代理并不是声明式事务管理的唯一部分,事实上包含三个组成部分:

  • EntityManager Proxy本身
  • 事务的切面
  • 事务管理器
    事务的切面:
    事务的切面是一个“around(环绕)”切面,在注解的业务方法前后都可以被调用。实现切面的具体类是TransactionInterceptor。
    事务的切面有两个主要职责:
  • 在’before’时,切面提供一个调用点,来决定被调用业务方法应该在正在进行事务的范围内运行,还是开始一个新的独立事务。
  • 在’after’时,切面需要确定事务被提交,回滚或者继续运行。
  • 在’before’时,事务切面自身不包含任何决策逻辑,是否开始新事务的决策委派给事务管理器完成。
事务管理器:

事务管理器需要解决下面两个问题:

  • 新的Entity Manager是否应该被创建?
  • 是否应该开始新的事务?

这些需要事务切面’before’逻辑被调用时决定。事务管理器的决策基于以下两点:

  • 事务是否正在进行
  • 事务方法的propagation属性(比如REQUIRES_NEW总要开始新事务)

如果事务管理器确定要创建新事务,那么将:

  • 创建一个新的entity manager
  • entity manager绑定到当前线程
  • 从数据库连接池中获取连接
  • 将连接绑定到当前线程

摘自:https://www.cnblogs.com/mxmbk/p/5341258.html

Shell编程

发表于 2019-05-16 | 分类于 shell

####### 10进制转16进制: echo ‘ibase=10;ibase=16;{pid}’|bc

shell变量

定义变量

  • 定义变量时,变量名不加$
  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
  • 中间不能有空格,可以使用下划线(_)。
  • 不能使用标点符号。
  • 不能使用bash里的关键字(可用help命令查看保留关键字)

    使用变量

    使用一个定义过的变量,只要在变量名前面加 $ 即可
    例如:
    1
    2
    3
    4
    #!/bin/bash
    your_name="qinjx"
    echo $your_name
    echo ${your_name}
    已经定义的变量是可以被再次定义的:
    1
    2
    3
    4
    5
    #!/bin/bash
    your_name="tom"
    echo $your_name
    your_name="alibaba"
    echo $your_name
    注意:再次定义的时候不能使用$

    只读变量

    使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
    1
    2
    3
    #!/bin/bash
    myUrl="http://www.google.com"
    readonly myUrl

    删除变量

    1
    2
    3
    #!/bin/bash
    variable_name="自定义内容"
    unset variable_name
    变量被删除后不能再次使用。unset 命令不能删除只读变量。

变量类型

运行shell时,会同时存在三种变量:

1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

Shell 字符串

字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。

单引号

str=’this is a string’
单引号字符串的限制:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
  • 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

双引号

例如:

1
2
3
4
5
6
#!/bin/bash
your_name='runoob'
str="Hello, I know you are \"$your_name\"! \n"
echo -e $str
输出结果为:
Hello, I know you are "runoob"!

双引号的优点:

  • 双引号里可以有变量
  • 双引号里可以出现转义字符

获取字符串长度

1
2
3
#!/bin/bash
string="abcd"
echo ${#string} #输出 4

提取子字符串

以下实例从字符串第 2 个字符开始截取 4 个字符:

1
2
3
 #!/bin/bash
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo

查找子字符串

查找字符 i 或 o 的位置(哪个字母先出现就计算哪个):

1
2
3
#!/bin/bash
string="runoob is a great site"
echo `expr index "$string" io` # 输出 4

Shell 数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。
数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。

定义数组

Shell 中,用括号来表示数组,数组元素用”空格”符号分割开。定义数组的一般形式为:
数组名=(值1 值2 … 值n)

1
2
 #!/bin/bash
array_name=(value0 value1 value2 value3)

可以不使用连续的下标,而且下标的范围没有限制。

读取数组

读取数组元素值的一般格式是:

1
${数组名[下标]}

使用 @ 符号可以获取数组中的所有元素,例如:

1
echo ${array_name[@]}

获取数组的长度

1
2
3
4
5
6
7
获取数组长度的方法与获取字符串长度的方法相同,例如:  
取得数组元素的个数
length=${#array_name[@]}
或者
length=${#array_name[*]}
取得数组单个元素的长度
lengthn=${#array_name[n]}

Shell 注释

以 # 开头的行就是注释,会被解释器忽略。

多行注解

1
2
3
:<<!
...
!

Shell 传递参数

线上排查OOM

发表于 2019-05-15 | 分类于 basics

根据进程名称获取pid:ps -ef | grep “name” | grep -v grep | awk ‘{print $2}’

OOM 排查

最常见的原因:
  • 有可能是内存分配确实过小,而正常业务使用了大量内存
  • 某一个对象被频繁申请,却没有释放,内存不断泄漏,导致内存耗尽
  • 某一个资源被频繁申请,系统资源耗尽,例如:不断创建线程,不断发起网络连接
一、确认是不是内存本身就分配过小
1
jmap -heap PID
二、找到最耗内存的对象
1
jmap -histo PID | head -10
三、确认是否是资源耗尽

查看进程创建的线程数,以及网络连接数,如果资源耗尽,也可能出现OOM。

  • /proc/${PID}/fd | wc -l
  • /proc/${PID}/task | wc -l (效果等同pstree -p | wc -l)
    查看进程打开的句柄数和线程数。
线上服务CPU100%问题快速定位
一、找到最耗CPU的进程
  • 执行top -c ,显示进程运行信息列表
  • 键入P (大写p),进程按照CPU使用率排序
二、找到最耗CPU的线程
  • top -Hp PID ,显示一个进程的线程运行信息列表
  • 键入P (大写p),线程按照CPU使用率排序
三、将线程PID转化为16进制
  • printf “%x\n” TID
四、查看堆栈,找到线程在干嘛

jstack PID | grep ‘0xXXX’ -C5 –color

<123>
xiaohua Dong

xiaohua Dong

28 日志
12 分类
25 标签
© 2020 xiaohua Dong