Hello Coder


  • 首页

  • 归档

  • 标签

  • 搜索
close

I/O模型

发表于 2016-10-27

同步与异步

同步与异步主要是从消息通知机制角度来说的.

  • 所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列
  • 所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。

消息通知

异步的概念和同步相对。当一个同步调用发出后,调用者要一直等待返回消息(结果)通知后,才能进行后续的执行;当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果),实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

执行部件和调用者通过三种途径返回结果:状态、通知和回调。使用哪一种通知机制,依赖于执行部件的实现,除非执行部件提供多种选择,否则不受调用者控制。

  • 如果执行部件用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(Java Future)
  • 如果是使用通知的方式,效率则很高,因为执行部件几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别(Guava Future)

阻塞与非阻塞

阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务

这里的阻塞调用与同步调用容易混淆:

对于同步调用来说,很多时候当前线程可能还是激活的,只是从逻辑上当前函数没有返回而已,此时,这个线程可能也会处理其他的消息

  • 非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。虽然表面上看非阻塞的方式可以明显的提高CPU的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的CPU执行时间能不能补偿系统的切换成本需要好好评估。

用户空间与内核空间

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

文件描述符

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

缓存 I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

缓存 I/O 的缺点:

数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

系统调用

进程想要获取磁盘中的数据,而能和硬件打交道的只能是内核,进程通知内核说我要磁盘中的数据,此过程就是系统调用。

一次I/O的完成的步骤:

当进程发起系统调用时,这个系统调用就进入内核模式,然后开始I/O操作

I/O操作分为两个步骤:

  • 1、磁盘把数据装载到内核的内存空间,
  • 2、内核的内存空间的数据copy到用户的内存空间中(此过程是I/O发生的地方)

对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核总的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。

进程获取数据的详细图解过程:

0.png

整个过程:此进程需要对磁盘中的数据进行操作,则会向内核发起一个系统调用,然后此进程,将会被切换出去,此进程会被挂起或者进入睡眠状态,也叫不可中断的睡眠,因为数据还没有得到,只有等到系统调用的结果完成后,则进程会被唤醒,继续接下来的操作,从系统调用的开始到系统调用结束经过的步骤:

①进程向内核发起一个系统调用,

②内核接收到系统调用,知道是对文件的请求,于是告诉磁盘,把文件读取出来

③磁盘接收到来着内核的命令后,把文件载入到内核的内存空间里面

④内核的内存空间接收到数据之后,把数据copy到用户进程的内存空间(此过程是I/O发生的地方)

⑤进程内存空间得到数据后,给内核发送通知

⑥内核把接收到的通知回复给进程,此过程为唤醒进程,然后进程得到数据,进行下一步操作

阅读全文 »

JAVA NIO

发表于 2016-10-27

NIO优点

传统流I/O是基于字节的,所有I/O都被视为单个字节的移动;而NIO是基于块的,NIO的性能肯定优于流I/O。其性能的提高要得益于其使用的结构更接近操作系统执行I/O的方式:通道和缓冲器。我们可以把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送 到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互;我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。

I/O的终极目标是效率,而效率离不开底层操作系统和文件系统的特性支持。这些特性包括:文件锁定、非阻塞I/O、就绪性选择、和内存映射。当今操作系统大都支持这些特性,而Java传统I/O机制并没有模拟这些通用的I/O服务。

NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。

缓冲区操作

缓冲区操作是所有I/O的基础,进程执行I/O操作,归结起来就是向操作系统发出请求,让它要么把缓冲区里的数据排干(写),要么把缓冲区填满(读)。如下图
15184210_Xonl

进程使用read()系统调用,要求其缓冲区被填满。内核随即向磁盘控制硬件发出命令,要求其从磁盘读取数据。磁盘控制器把数据直接写入内核内存缓冲区,这一步通过 DMA 完成,无需主CPU协助。一旦磁盘控制器把缓冲区装满,内核即把数据从内核空间的临时缓冲区拷贝到进程执行read()调用时指定的缓冲区。

JAVA NIO

JAVA的NIO是基于IO多路复用模型,在不同平台上有不同的实现方式。Linux下面用的是poll和epoll,在BSD上用kqueue,在Windows上是重叠I/O。

1. 缓冲区

通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。

Buffer是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream 对象中; 在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,都是将它放到缓冲区中。

缓冲区实质上就是一个数组,但它不仅仅是一个数组,缓冲区还提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

缓冲区类型:

1
2
3
4
5
6
7
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
1.1 缓冲区基础

所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息。

  • 容量(Capacity)

缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能
被改变。

limit 决不能大于 capacity。

  • 上界(Limit)

缓冲区的第一个不能被读或写的元素limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。

position 总是小于或者等于 limit。或者说,缓冲区中现存元素的计数。

  • 位置(Position)

下一个要被读或写的元素的索引。位置会自动由相应的get()和put( )方法更新。

您可以回想一下,缓冲区实际上就是美化了的数组。在从通道读取时,您将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。因此,如果您从通道中读三个字节到缓冲区中,那么缓冲区的 position 将会设置为3,指向数组中第四个元素。

同样,在写入通道时,您是从缓冲区中获取数据。 position 值跟踪从缓冲区中获取了多少数据。更准确地说,它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。

  • 标记(Mark)

一个备忘位置。调用mark( )来设定mark= postion。调用reset( )设定position=
mark。标记在设定前是未定义的(undefined)。

这四个属性之间总是遵循以下关系:0 <= mark <= position <= limit <= capacity

阅读全文 »

maven 通过配置打不同环境的包

发表于 2016-10-19

背景:一

项目由不同的分支(如:dev, test, product),每一分支对应的环境变量有差异。需要根据maven打包命令传入不同的参数(如 -Ptest)来实现具体使用哪个分支的配置。

解决方案:

  • 在pom.xml配置的<profiles> </profiles>中添加<profile> </profile>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault> <! - -这里默认是dev- ->
</activation>
<properties>
<envcfg.dir>dev</envcfg.dir>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<envcfg.dir>test</envcfg.dir>
</properties>
</profile>
<profile>
<id>pro</id>
<properties>
<envcfg.dir>product</envcfg.dir>
</properties>
</profile>

在<properties> </properties>中配置不同的环境参数(这里是配置文件目录名)。

  • 在pom.xml文件中配置build
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<outputDirectory>target/classes</outputDirectory>
<testOutputDirectory>target/test-classes</testOutputDirectory>
<testResources>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<!--注意这里引用的${envcfg.dir}-->
<directory>src/main/conf/${envcfg.dir}</directory>
</resource>
</resources>
<directory>target</directory>
</build>

在<build></build> 配置中添加上一步的文件目录名,这样使用maven -Ptest 命令打包的时候,会将src/main/conf/test目录下的文件都添加到项目路径下面。

背景: 二

如果只配置了<profiles> </profiles>,则当前仓库中同一版本只能有一个jar包,这样选择-Ptest或者-Ppro命令就会覆盖jar包,导致客户端调用产生问题(比如:location-local-0.3.3.jar,这个版本可能是test或者pro,客户端如果生产版本调用了test jar包则会产生异常)。

解决方案: 在每一个分支jar包后面带上分支名称,如:location-local-0.3.5-test.jar;这样客户端就可以选择不同分支的jar包。

  • 在服务端pom.xml中如下配置:(注意加**的地方与背景一不同)
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
77
在<build></build>标签中配置:
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<outputDirectory>target/classes</outputDirectory>
<testOutputDirectory>target/test-classes</testOutputDirectory>
<testResources>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/conf/${envcfg.dir}</directory>
</resource>
</resources>
<directory>target</directory>
<!--新增部分配置-->
**
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>default-jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>${envcfg.classifier}</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
</plugins>
</build>
**
在<profile></profile>标签中配置:
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
** <envcfg.classifier>dev</envcfg.classifier> **
<envcfg.dir>dev</envcfg.dir>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
** <envcfg.classifier>test</envcfg.classifier> **
<envcfg.dir>test</envcfg.dir>
</properties>
</profile>
<profile>
<id>pro</id>
<properties>
** <envcfg.classifier>product</envcfg.classifier> **
<envcfg.dir>product</envcfg.dir>
</properties>
</profile>

这里的classifier指向jar包后缀的名称。

  • 客户端pom.xml引用配置如下:
1
2
3
4
5
6
<dependency>
<groupId>com.ximalaya.location</groupId>
<artifactId>location-local</artifactId>
<version>0.3.5</version>
<classifier>test</classifier>
</dependency>

通过classifier可以选择需要引用的jar包。

Google S2

发表于 2016-10-17

Google S2作用:

根据多边形的边界区域映射成由许多一维数值构成的集合,这样就可以由这个集合代替多边形区域,便于后续计算。
典型的可以将不同国家(或者地区)的边界区域(经纬度)计算成一维数值的集合,可以用于判断该区域的任何一个坐标是否包涵在内,简单的说就是地理定位。

s2库提供了一个Cell的概念,每个Cell相当于一个单元格,在一个单元格内的所有Point,都可以算出同一个CellId。同时可以认为,一个Polygon可以用一个Cell的集合来表示。
那么这时的步骤就是通过区域的坐标,来初始化其内包含的所有的CellId,将这些CellId的id放入redis中做key,对应的value是区域名称(譬如,上海浦东新区),构建完全国范围的CellId后,查询用户的区域就可以通过用户的坐标获得一个CellId,然后去redis中查询,在使用LocalCache后,基本就没有什么计算成本,完全可以满足性能需求。

操作步骤:

1)首先获取地域图形的坐标点集合,可以从Natural Earth网站获取全球的坐标元数据(.shp文件)。
2)解析shp文件,生成区域坐标集合,这里选择pyshp(解析shp文件的python插件),这里需要注意:同一个国家可能包涵多个区域(muti Polygon),需要分开处理。下载地址:https://pypi.python.org/pypi/pyshp/
3)使用Google S2将多边形的边界坐标集合转化为一维数值集合,代表这个多边形的覆盖区域。

S2原理:

1
2
3
4
5
6
7
8
9
10
11
S2RegionCoverer coverer = new S2RegionCoverer();
ArrayList<S2CellId> covering = new ArrayList<S2CellId>();
coverer.setMinLevel(minLevel);
coverer.setMaxLevel(maxLevel);
coverer.setMaxCells(1000000);
S2Polygon s2Polygon = S2Polygon.makePolygon(polygonValue);
//getCovering()表示可以最小程度覆盖整个区域,外接多边形
coverer.getCovering(s2Polygon, covering);
//getInteriorCovering()表示最大程度填充整个区域,内接多边形
// coverer.getInteriorCovering(s2Polygon, covering);
System.out.println("covering of size:" + covering.size());

这里采用getCovering()外接多边形模式,避免多边形区域有盲点。MinLevel=9,MaxLevel=14

说明:MaxLevel最多可以设置30级,每升一级精度提升接近4倍,14级单个Cell可以表示范围:200平米~400平米,层级越高,构建越慢。

Cell areas

S2需要将地理位置表示为计算机可以理解的方式,即二进制码的表示,所以我们的目标就是用二进制码来表示三维空间。如何来做呢?需要进行降维。

阅读全文 »

mvc:interceptors

发表于 2016-10-14

一. spring MVC Interceptor 作用:

Spring MVC框架中的Interceptor,与Servlet API中的Filter十分类似,用于对Web请求进行预处理/后处理。通常情况下这些预处理/后处理逻辑是通用的,可以被应用于所有或多个Web请求,例如:

  • 记录Web请求相关日志,可以用于做一些信息监控、统计、分析
  • 检查Web请求访问权限,例如发现用户没有登录后,重定向到登录页面
  • 打开/关闭数据库连接——预处理时打开,后处理关闭,可以避免在所有业务方法中都编写类似代码,也不会忘记关闭数据库连接

二. 拦截器和过滤器filter的区别:

  1. Filter是Servlet规范规定的,只能用于web程序中。而拦截器既可以用于web程序,也可以用于Application、Swing程序中。
  1. Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器中的,是Spring框架支持的。
  2. 同其他代码块一样,拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过IoC注入到拦截器即可;而Filter则不能
  3. Filter只在Servlet前后起作用。而拦截器能够深入得到方法前后、异常抛出前后等,因为拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器。

三. Spring MVC请求处理流程

alter-text

上图是Spring MVC框架处理Web请求的基本流程,请求会经过DispatcherServlet的分发后,会按顺序经过一系列的Interceptor并执行其中的预处理方法(preHandle),在请求返回时同样会执行其中的后处理方法(postHandle)。

四. 拦截器接口-HandlerInterceptor

Spring MVC中拦截器是实现了HandlerInterceptor接口的Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;
void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception;
void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception;
}

HandlerInterceptor 接口中定义了三个方法,就是通过这三个方法来对用户的请求进行拦截处理的。

  • (1). preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法。该方法将在请求处理之前进行调用。SpringMVC 中的Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
  • (2). postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法,由preHandle 方法的解释我们知道这个方法包括后面要说到的afterCompletion 方法都只能是在当前所属的Interceptor 的preHandle 方法的返回值为true 时才能被调用。postHandle 方法,顾名思义就是在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行,这和Struts2 里面的Interceptor 的执行过程有点类型。Struts2 里面的Interceptor 的执行过程也是链式的,只是在Struts2 里面需要手动调用ActionInvocation 的invoke 方法来触发对下一个Interceptor 或者是Action 的调用,然后每一个Interceptor 中在invoke 方法调用之前的内容都是按照声明顺序执行的,而invoke 方法之后的内容就是反向的。
  • (3). afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法,该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。 我们的系统日志的拦截在这个方法中,可以记录日志的相关的参数,检测方法的执行。

五. 拦截器配置

  • DefaultAnnotationHandlerMapping

    自定义的拦截器可以通过DefaultAnnotationHandlerMapping注入到spring框架中。可以打开org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping,可以看到具体的注入细节。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- 通过DefaultAnnotationHandlerMapping将拦截器注入 -->
    <bean           class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="interceptors">
    <list>
    <!-- 这边可以实现多个拦截器 -->
    <ref bean="DefaultInterceptor"  />
    </list>
    </property>
    </bean>
    <!-- 自定义的default的拦截器 -->
    <bean id="DefaultInterceptor" class="com.xxx.test.DefaultInterceptor">
    </bean>
  • 通过 <mvc:interceptors> 的方式配置:

    Spring MVC interceptor一般配置这种模式。

    1
    2
    3
    4
    5
    6
    <mvc:interceptors>
    <mvc:interceptor>
    <mvc:mapping path="/api/time/get_time/" />
    <bean class="com.xxx.test.DefaultInterceptor"></bean>
    </mvc:interceptor>
    </mvc:interceptors>

    ​

1…181920…31
David

David

Develop Notes

155 日志
37 标签
GitHub Weibo
© 2016 - 2020 David
由 Hexo 强力驱动
主题 - NexT.Pisces