YDDMAX

代码奔腾


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 搜索

Pro Java 7 NIO 2读书笔记

发表于 2017-06-04 | 分类于 technology

转自
Pro Java 7 NIO.2
花了几天把这本书初略的看了一下, 之所以初略的看了一下,是因为这书里确实没啥内容。如果去了NIO 1的内容和大段大段的代码, 基本上压缩到50页应该没问题。

下面简单的罗列一下看到的内容:

Path

这个类在java.nio.file,在NIO里对文件系统进行了进一步的抽象。 是用来替换原来的java.io.File。其中 FileSystems, Files, Path, PathMatcher 成为一个体系。

在java7里File和Path可以相互转换: File.toPath(), Path.toFile()
原来的File里包含了文件引用和文件,在Path系统里, Path里是文件的引用,而文件操作都放到了Files的静态方法里。这种方式究竟好不好用,我没啥感觉。不过我个人偏向于把操作放到另外一个类里面。

获得Path实例的方式:
File.toPath(), Paths.get(), FileSystem.getPath()

注意: Path和File一样,能创建出实例不代表着这个文件一定在文件系统里真是存在。

Path和File相比使用上方便了的地方:

不用特意指定路径的separator:
Paths.get("C:","folder1","subfolder","aa.txt")

在windows系统了是C:\folder1\subfolder\aa.txt
在Mac下是C:/folder/subfolder/aa.txt
Java会根据当前操作系统来决定separator

重复利用基本路径:

1
2
3
4
5
6
7
8
9
10
//define the fixed path
Path base = Paths.get("C:/rafaelnadal/tournaments/2009");
//resolve BNP.txt file
Path path_1 = base.resolve("BNP.txt");
//output: C:\rafaelnadal\tournaments\2009\BNP.txt
System.out.println(path_1.toString());
//resolve AEGON.txt file
Path path_2 = base.resolve("AEGON.txt");
//output: C:\rafaelnadal\tournaments\2009\AEGON.txt
System.out.println(path_2.toString());

而resolveSibling方法更加好用:可以直接获取相同目录下的其他文件:

1
2
3
4
5
6
//define the fixed path
Path base = Paths.get("C:/rafaelnadal/tournaments/2009/BNP.txt");
//resolve sibling AEGON.txt file
Path path = base.resolveSibling("AEGON.txt");
//output: C:\rafaelnadal\tournaments\2009\AEGON.txt
System.out.println(path.toString());

取得相对路径:
假设BNP.txt和AEGON.txt在同一目录下

1
2
3
4
5
6
7
8
9
Path path01 = Paths.get("BNP.txt");
Path path02 = Paths.get("AEGON.txt");
//output: ..\AEGON.txt
Path path01_to_path02 = path01.relativize(path02);
System.out.println(path01_to_path02);
//output: ..\BNP.txt
Path path02_to_path01 = path02.relativize(path01);
System.out.println(path02_to_path01);

假设:

1
2
Path path01 = Paths.get("/tournaments/2009/BNP.txt");
Path path02 = Paths.get("/tournaments/2011");

那么:

1
2
3
4
5
6
//output: ..\..\2011
Path path01_to_path02 = path01.relativize(path02);
System.out.println(path01_to_path02);
//output: ..\2009\BNP.txt
Path path02_to_path01 = path02.relativize(path01);
System.out.println(path02_to_path01);

遍历:
Path实现了Iterable接口,也就是说我们可以这样:

1
2
3
4
Path path = Paths.get("C:", "rafaelnadal/tournaments/2009", "BNP.txt");
for (Path name : path) {
System.out.println(name);
}

猜猜结果是什么?
我一开始以为是吧目录下的文件遍历出来呢。结果是这样的:
rafaelnadal
tournaments
2009
BNP.txt
说实话,我觉得这个比较坑爹!

文件属性

java.nio.file.attribute包下的类提供了获取文件属性的类,针对不同操作系统使用的类不太一样,当然也有所有操作系统通用的属性。

属性分类有一些几种:
BasicFileAttributeView
DosFileAttributeView
PosixFileAttributeView
FileOwnerAttributeView
AclFileAttributeView
UserDefinedFileAttributeView

获取属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BasicFileAttributes attr = null;
Path path = Paths.get("C:/rafaelnadal/tournaments/2009", "BNP.txt");
try {
attr = Files.readAttributes(path, BasicFileAttributes.class);
} catch (IOException e) {
System.err.println(e);
}
System.out.println("File size: " + attr.size());
System.out.println("File creation time: " + attr.creationTime());
System.out.println("File was last accessed at: " + attr.lastAccessTime());
System.out.println("File was last modified at: " + attr.lastModifiedTime());
System.out.println("Is directory? " + attr.isDirectory());
System.out.println("Is regular file? " + attr.isRegularFile());
System.out.println("Is symbolic link? " + attr.isSymbolicLink());
System.out.println("Is other? " + attr.isOther());

或

long size = (Long)Files.getAttribute(path, "basic:size", NOFOLLOW_LINKS);

设置属性:

1
2
3
4
5
6
7
8
9
Path path = Paths.get("C:/rafaelnadal/tournaments/2009", "BNP.txt");
long time = System.currentTimeMillis();
FileTime fileTime = FileTime.fromMillis(time);
try {
Files.getFileAttributeView(path,
BasicFileAttributeView.class).setTimes(fileTime, fileTime, fileTime);
} catch (IOException e) {
System.err.println(e);
}

或

1
2
3
4
5
6
7
long time = System.currentTimeMillis();
FileTime fileTime = FileTime.fromMillis(time);
try {
Files.setLastModifiedTime(path, fileTime);
} catch (IOException e) {
System.err.println(e);
}

Symbolic和Hard Links
相同于用Java程序实现linux下的 ln命令。

1
2
3
Path link = FileSystems.getDefault().getPath("rafael.nadal.1");
Path target= FileSystems.getDefault().getPath("C:/rafaelnadal/photos", "rafa_winner.jpg");
Files.createSymbolicLink(link, target);

DirectoryStream

这也是一个比较有用的类:用来遍历路径下的子路径或文件,而且支持通配符过滤。

1
2
3
4
5
6
7
8
9
10
11
Path path = Paths.get("C:/rafaelnadal/tournaments/2009");
…
//glob pattern applied
System.out.println("\nGlob pattern applied:");
try (DirectoryStream<Path> ds = Files.newDirectoryStream(path, "*.{png,jpg,bmp}")) {
for (Path file : ds) {
System.out.println(file.getFileName());
}
} catch (IOException e) {
System.err.println(e);
}

FileVisitor和Files.walkFileTree

FileVisitor是一个接口,Files.walkFileTree是一个方法。 通过两者的配合可以遍历整个某个路径下的所有子路径和文件。没有这个之前我们用递归方法也能实现,有了这个不能说是现实更加容易, 只能说是现实更加规范, 如果大家都用这个,代码的可维护性会更好。我觉得仅此而已。

FileVisitor有四个方法

  • FileVisitResult postVisitDirectory(T dir, IOException exc)
    Invoked for a directory after entries in the directory, and all of their descendants, have been visited.
  • FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
    Invoked for a directory before entries in the directory are visited.
    +FileVisitResult visitFile(T file, BasicFileAttributes attrs)
    Invoked for a file in a directory.
  • FileVisitResult visitFileFailed(T file, IOException exc)
    Invoked for a file that could not be visited.

FileVisitResult枚举类型:
CONTINUE,SKIP_SIBLINGS,SKIP_SUBTREE,TERMINATE

Watch Service API

这是NIO2里比较重要的一个新增功能, 以前直接用java监视文件系统的变化是不可能的,只能通过jni的方式调用操作系统的api来对文件系统进行监视。在java7里这部分被加到了标准库里,这样我们就不能在去寻找jni的结果方案了。但是事实上为了保持java的扩平台特性,监控的功能范围被定为各个操作系统的交集,所以没有特殊的情况还是需要直接调用操作系统的api来实现。

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
public void watchRNDir(Path path) throws IOException, InterruptedException {
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
//start an infinite loop
while (true) {
//retrieve and remove the next watch key
final WatchKey key = watchService.take();
//get list of pending events for the watch key
for (WatchEvent<?> watchEvent : key.pollEvents()) {
//get the kind of event (create, modify, delete)
final Kind<?> kind = watchEvent.kind();
//handle OVERFLOW event
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
//get the filename for the event
final WatchEvent<Path> watchEventPath = (WatchEvent<Path>) watchEvent;
final Path filename = watchEventPath.context();
//print it out
System.out.println(kind + " -> " + filename);
}
//reset the key
boolean valid = key.reset();
//exit loop if the key is not valid (if the directory was deleted, for example)
if (!valid) {
break;
}
}
}
}

Random Access Files

主要是提供了一个SeekableByteChannel接口,配合ByteBuffer使随机访问文件更加方便。

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
Path path = Paths.get("C:/rafaelnadal/tournaments/2009", "MovistarOpen.txt");
ByteBuffer buffer = ByteBuffer.allocate(1);
String encoding = System.getProperty("file.encoding");
try (SeekableByteChannel seekableByteChannel = (Files.newByteChannel(path, EnumSet.of(StandardOpenOption.READ)))) {
//the initial position should be 0 anyway
seekableByteChannel.position(0);
System.out.println("Reading one character from position: " + seekableByteChannel.position());
seekableByteChannel.read(buffer);
buffer.flip();
System.out.print(Charset.forName(encoding).decode(buffer));
buffer.rewind();
//get into the middle
seekableByteChannel.position(seekableByteChannel.size()/2);
System.out.println("\nReading one character from position: " + seekableByteChannel.position());
seekableByteChannel.read(buffer);
buffer.flip();
System.out.print(Charset.forName(encoding).decode(buffer));
buffer.rewind();
//get to the end
seekableByteChannel.position(seekableByteChannel.size()-1);
System.out.println("\nReading one character from position: " + seekableByteChannel.position());
seekableByteChannel.read(buffer);
buffer.flip();
System.out.print(Charset.forName(encoding).decode(buffer));
buffer.clear();
} catch (IOException ex) {
System.err.println(ex);
}

Asynchronous Channel API

这个是NIO2的较大的变化,有原来的Selecor方法变成方法回调模式。使用上更加方便。并且文件的读写也可以异步的方式实现了。

异步读取文件 :

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
ByteBuffer buffer = ByteBuffer.allocate(100);
Path path = Paths.get("C:/rafaelnadal/grandslam/RolandGarros", "story.txt");
try (AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
current = Thread.currentThread();
asynchronousFileChannel.read(buffer, 0, "Read operation status ...", new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println(attachment);
System.out.print("Read bytes: " + result);
current.interrupt();
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println(attachment);
System.out.println("Error:" + exc);
current.interrupt();
}
});
System.out.println("\nWaiting for reading operation to end ...\n");
try {
current.join();
} catch (InterruptedException e) {
}
//now the buffer contains the read bytes
System.out.println("\n\nClose everything and leave! Bye, bye ...");
} catch (Exception ex) {
System.err.println(ex);
}

异步socket服务器

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
78
79
80
81
public class AsyncEchoServer {
private AsynchronousServerSocketChannel serverChannel;
public void start() throws IOException {
System.out.println(String.format("start: name: %s", Thread.currentThread().getName()));
serverChannel = AsynchronousServerSocketChannel.open();
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
serverChannel.bind(new InetSocketAddress(8000));
serverChannel.accept(serverChannel, new Acceptor());
}
class Acceptor implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel> {
private final ByteBuffer buffer = ByteBuffer.allocate(1024);
public Acceptor(){
System.out.println("an acceptor has created.");
}
public void completed(final AsynchronousSocketChannel channel, AsynchronousServerSocketChannel serverChannel) {
System.out.println(String.format("write: name: %s", Thread.currentThread().getName()));
channel.read(buffer, channel, new Reader(buffer));
serverChannel.accept(serverChannel, new Acceptor());
}
public void failed(Throwable exception, AsynchronousServerSocketChannel serverChannel) {
throw new RuntimeException(exception);
}
}
class Reader implements CompletionHandler<Integer, AsynchronousSocketChannel> {
private ByteBuffer buffer;
public Reader(ByteBuffer buffer){
this.buffer = buffer;
}
public void completed(Integer result, AsynchronousSocketChannel channel){
System.out.println(String.format("read: name: %s", Thread.currentThread().getName()));
if(result != null && result < 0){
try{
channel.close();
return;
}catch(IOException ignore){}
}
buffer.flip();
channel.write(buffer, channel, new Writer(buffer));
}
public void failed(Throwable exception, AsynchronousSocketChannel channel){
throw new RuntimeException(exception);
}
}
class Writer implements CompletionHandler<Integer, AsynchronousSocketChannel> {
private ByteBuffer buffer;
public Writer(ByteBuffer buffer){
this.buffer = buffer;
}
public void completed(Integer result, AsynchronousSocketChannel channel) {
System.out.println(String.format("write: name: %s", Thread.currentThread().getName()));
buffer.clear();
channel.read(buffer, channel, new Reader(buffer));
}
public void failed(Throwable exception, AsynchronousSocketChannel channel) {
throw new RuntimeException(exception);
}
}
public static void main(String[] args) throws IOException, InterruptedException{
new AsyncEchoServer().start();
while(true){
Thread.sleep(1000L);
}
}
}

负载均衡

发表于 2017-06-03 | 分类于 technology

DNS

DNS负责提供域名解析服务,当访问某个站点时,实际上首先需要通过该站点域名的DNS服务器来获取域名指向的IP地址,在这一过程中,DNS服务器完成了域名到IP地址的映射,同样,这样映射也可以是一对多的,这时候,DNS服务器便充当了负载均衡调度器,它就像http重定向转换策略一样,将用户的请求分散到多台服务器上,但是它的实现机制完全不同。

下图展示百度有三个IP地址:
百度DNS

  • 优点:
  1. 可以根据用户IP来进行智能解析。DNS服务器可以在所有可用的A记录中寻找离用记最近的一台服务器。
  2. 动态DNS:在每次IP地址变更时,及时更新DNS服务器。当然,因为缓存,一定的延迟不可避免。
  • 缺点:
  1. 没有用户能直接看到DNS解析到了哪一台实际服务器,加服务器运维人员的调试带来了不便。
  2. 策略的局限性。例如你无法将HTTP请求的上下文引入到调度策略中,而在前面介绍的基于HTTP重定向的负载均衡系统中,调度器工作在HTTP层面,它可以充分理解HTTP请求后根据站点的应用逻辑来设计调度策略,比如根据请求不同的URL来进行合理的过滤和转移。
  3. 如果要根据实际服务器的实时负载差异来调整调度策略,这需要DNS服务器在每次解析操作时分析各服务器的健康状态,对于DNS服务器来说,这种自定义开发存在较高的门槛,更何况大多数站点只是使用第三方DNS服务。
  4. DNS记录缓存,各级节点的DNS服务器不同程序的缓存会让你晕头转向。
  5. 基于以上几点,DNS服务器并不能很好地完成工作量均衡分配,最后,是否选择基于DNS的负载均衡方式完全取决于你的需要。

HTTP重定向

当http代理(比如浏览器)向web服务器请求某个URL后,web服务器可以通过http响应头信息中的Location标记来返回一个新的URL。这意味着HTTP代理需要继续请求这个新的URL,完成自动跳转。

  1. 吞吐率限制
    主站点服务器的吞吐率平均分配到了被转移的服务器。现假设使用RR(Round Robin)调度策略,子服务器的最大吞吐率为1000reqs/s,那么主服务器的吞吐率要达到3000reqs/s才能完全发挥三台子服务器的作用,那么如果有100台子服务器,那么主服务器的吞吐率可想而知得有大?相反,如果主服务的最大吞吐率为6000reqs/s,那么平均分配到子服务器的吞吐率为2000reqs/s,而现子服务器的最大吞吐率为1000reqs/s,因此就得增加子服务器的数量,增加到6个才能满足。
  2. 重定向访问深度不同
    有的重定向一个静态页面,有的重定向相比复杂的动态页面,那么实际服务器的负载差异是不可预料的,而主站服务器却一无所知。因此整站使用重定向方法做负载均衡不太好。

我们需要权衡转移请求的开销和处理实际请求的开销,前者相对于后者越小,那么重定向的意义就越大,例如下载。你可以去很多镜像下载网站试下,会发现基本下载都使用了Location做了重定向。

LVS(四层)

以下内容参考自LVS

NAT

通过将请求报文的目标地址和目标端口修改为挑选出的某RS的RIP和PORT实现转发;
特点:

  1. RIP和DIP必须在同一IP网络,且使用私网地址RS的网关应该指向DIP(保证响应报文必须经由VS)
  2. 请求和响应报文都要经由director转发;极高负载的场景中,Director可能会成为系统性能瓶颈
  3. 支持端口映射
  4. VS必须为Linux,RS可以是任意的OS
  5. 调度器上需要两块网卡,一个配置vip 一个配置dip

NAT

FULLNAT

非标准模型, ipvs默认不支持,ipvsadm也不支持。
NAT模式的扩展(阿里云的四层SLB使用的是此方式,因为此种方式下负载均衡器和后端服务器的部署不需要部署在同一网络内)
通过同时修改请求报文的源IP地址(cip–>dip)和目标IP地址(vip –> rip)实现转发
特点:

  1. 调度器和后端服务器可以不在同一IP网络中
  2. RS收到的请求报文的源IP为DIP,因此其响应报文将发送给DIP;
  3. 请求报文和响应报文都必须经由director;
  4. 支持端口映射;
  5. RS可使用任意OS;

fullNAT

DR

直接路由
通过为请求报文重新封装一个MAC首部进行转发,源MAC是DIP所在接口的MAC,目标MAC是挑选出的某RS的RIP所在接口的MAC地址;IP首部不会发生变化(源IP为CIP,目标IP始终为VIP)

  1. RS跟Director必须在同一物理网络中;RS的网关必须不能指向DIP
  2. 请求报文必须由Director调度,但响应报文必须不能经由Director
  3. 不支持端口映射
  4. 各RS可以使用大多数的OS;一般是linux

情形1: RIP DIP VIP 都在一个网络, 都是公网IP 地址

dr-same

情形2: VIP 是公网ip地址, RIP,DIP是私有地址, 情况要复制些, RS要通过另一个路由出去

dr-nosame

注意:
一个路由其可以有多个网络接口
一个交换机可以承载多个ip网络
所以路由器1和路由器2可以使用同一个
私网交换机和公网交换机也可以用同一个

TUN

ip tunnel,ip隧道
转发方式:不修改请求报文的IP首部(源IP为CIP,目标IP为VIP),而是在原有的IP首部之外再次封装一个IP首部(源IP为DIP,目标IP为RIP)

  1. RIP,DIP,VIP全得是公网地址
  2. RS的网关不能也不可能指向DIP
  3. 请求报文经由Director调度,但响应报文将直接发给CIP
  4. 不支持端口映射
  5. RS的OS必须支持IP隧道功能;
  6. 容易超出MTU, 弊端比较大

tunnel

反向代理(七层)

这个肯定大家都有所接触,因为几乎所有主流的Web服务器都热衷于支持基于反向代理的负载均衡。
相比前面的HTTP重定向和DNS解析,反向代理的调度器扮演的是用户和实际服务器中间人的角色:

  1. 任何对于实际服务器的HTTP请求都必须经过调度器
  2. 调度器必须等待实际服务器的HTTP响应,并将它反馈给用户(前两种方式不需要经过调度反馈,是实际服务器直接发送给用户)
  • 优点:
  1. 调度策略丰富。例如可以为不同的实际服务器设置不同的权重,以达到能者多劳的效果。
  2. 对反向代理服务器的并发处理能力要求高,因为它工作在HTTP层面。
  3. 反向代理服务器可以监控后端服务器,比如系统负载、响应时间、是否可用、TCP连接数、流量等,从而根据这些数据调整负载均衡的策略。
  4. 反射代理服务器可以让用户在一次会话周期内的所有请求始终转发到一台特定的后端服务器上(粘滞会话),这样的好处一是保持session的本地访问,二是防止后端服务器的动态内存缓存的资源浪费。
  • 缺点:
    反向代理服务器进行转发操作本身是需要一定开销的,比如创建线程、与后端服务器建立TCP连接、接收后端服务器返回的处理结果、分析HTTP头部信息、用户空间和内核空间的频繁切换等,虽然这部分时间并不长,但是当后端服务器处理请求的时间非常短时,转发的开销就显得尤为突出。例如请求静态文件,更适合使用前面介绍的基于DNS的负载均衡方式。

    硬件

    一般工作在四层。
    性能较高,但是价格昂贵。

架构的八荣八耻和20个能力

发表于 2017-06-03 | 分类于 technology

架构的八荣八耻


此处输入图片的描述

聚石塔

发表于 2017-06-03 | 分类于 ali

遵循互联网架构“八荣八耻”,解析EWS高质量架构6个维度的20个能力,本文简单介绍了聚石塔和EWS。


聚石塔是阿里为卖家系统提供的云计算环境,该云环境在全球提供了相应的负载均衡、安全、计算、存储等解决方案。卖家利用聚石塔的基础设施和服务可以快速搭建一套性能优越、扩容简单、资源弹性伸缩、容灾强、支持多活的系统,聚石塔的功能是赋能商家。
EWS(Enterprise Workstation),是聚石塔中的企业工作站,下图是EWS的系统总架构图。

聚石塔系统架构图

12
yunzhu

yunzhu

code rush

14 日志
5 分类
12 标签
RSS
WeChat GitHub Weibo QQ
© 2017 yunzhu
由 Hexo 强力驱动
主题 - NexT.Pisces