Google Guava 库

Guava 有很多学习材料

Optional

Optional 存在的意义就是为了替换 null,null 存在的大部分情况用户是无法知道方法想要返回的什么。null 多数情况下并不意味着返回值没有值,一些情况下可能表示 error,甚至也有人用 null 来表示成功,或者表示成功了但是没有返回值。因此 Optional 存在的意义并不是可读性,而是强迫使用他的人思考返回值应该是什么。

举个例子,当调用别人的 other.method(a, b) 方法时,可能经常忘记 method 返回值可能是 null,而当 method 返回值为 Optional 时,调用者几乎不可能忘记 null 的存在,因为调用者需要自己 unwrap 来获取结果。基于这个原因,Guava 通常建议将 Optional 作为返回值。1

Immutable Collections

不可变对象有很多好处:

  • 不受信任的库可以安全使用
  • 线程安全
  • Doesn’t need to support mutation, and can make time and space savings with that assumption. All immutable collection implementations are more memory-efficient than their mutable siblings. (analysis)
  • 可以当做常量使用

Guava 提供了简单,易于使用的不可变 Collection 类型的版本, JDK 提供了 Collections.unmodifiableXXX 方法,但有些缺陷

  • unwieldy and verbose; unpleasant to use everywhere you want to make defensive copies
  • unsafe: the returned collections are only truly immutable if nobody holds a reference to the original collection
  • inefficient: the data structures still have all the overhead of mutable collections, including concurrent modification checks, extra space in hash tables, etc.

创建 ImmutableXXX 可以有以下方式:

  • 使用 copyOf 方法,比如 ImmutableSet.copyOf(set)
  • 使用 of 方法,ImmutableSet.of("a", "b", "c")
  • 使用 Builder 方式

      public static final ImmutableSet<Color> GOOGLE_COLORS =
         ImmutableSet.<Color>builder()
         .addAll(WEBSAFE_COLORS)
         .add(new Color(0, 191, 255))
         .build();
    

Multiset

Multiset 和 set 的区别就在于可以存放相同的元素超过一次,multisets {a, a, b} 和 {a, b, a} 是相同的。官方文档上说可以用两种眼光看 Multiset

  • ArrayList<E> 没有排序的约束,顺序是不关心的
  • Map<E, Integer> 包含元素和计数

Multiset API 在设计的时候也充分的包含了着两种想法:

  • 当看成通常的 Collection 时, Multiset 表现和无序的 ArrayList 相似

    • 调用 add(E) 添加元素
    • 调用 iterator() 遍历
    • size() 是集合中所有元素的数量
  • 而其他查询操作,性能表现,和 Map<E, Integer> 类似

    • count(Object) 返回元素的计数,HashMultiset 的时间复杂度 O(1),而 TreeMultiset 是 O(log n)
    • entrySet() 返回 Set<Multiset.Entry<E>> 表现和 keySet() 类似
    • Multiset 的内存消耗在不同元素上是线性的

Multiset 接口定义的接口主要有:

Method 解释
add(E element) 向其中添加单个元素
add(E element,int occurrences) 向其中添加指定个数的元素
count(Object element) 返回给定参数元素的个数
remove(E element) 移除一个元素,其 count 值 会响应减少
remove(E element,int occurrences) 移除相应个数的元素
elementSet() 将不同的元素放入一个 Set 中
entrySet() 类似与 Map.entrySet 返回 Set<Multiset.Entry>。包含的 Entry 支持使用 getElement() 和 getCount()
setCount(E element ,int count) 设定某一个元素的重复次数
setCount(E element,int oldCount,int newCount) 将符合原有重复个数的元素修改为新的重复次数
retainAll(Collection c) 保留出现在给定集合参数的所有的元素
removeAll(Collectionc) 去除出现给给定集合参数的所有的元素

Multiset 的常用实现

Guava 提供了 Multiset 的多种实现,这些实现基本对应了 JDK 中 Map 的实现: Map | Corresponding Multiset | Supports null elements ——————|————————-|————————— HashMap | HashMultiset | Yes TreeMap | TreeMultiset | Yes (if the comparator does) LinkedHashMap | LinkedHashMultiset | Yes ConcurrentHashMap | ConcurrentHashMultiset | No ImmutableMap | ImmutableMultiset | No

Multimap

Java 开发中可能经常会需要实现 Map<K, List<V>> or Map<K, Set<V>> 类似的数据结构,Multimap 就是为了解决此类时间而生。

有两种看待 Multimap 的方式,单独的 key 对应 value

a -> 1 a -> 2 a -> 4 b -> 3 c -> 5

或者是,唯一的 key 对应一个集合

a -> [1, 2, 4] b -> [3] c -> [5]

通常来说,以第一种方式思考即可,但是 Multimap 也提供了 asMap() 方式来返回一个 Map<K, Collection<V>>。重要的是,没有 key map 到一个空集合这样的方式。

通常来说不会直接使用 Multimap 接口,而是使用 ListMultimap 或者 SetMultimap ,对应将 key 映射到 List 或者 Set 。

最直接构建 Multimap 的方式是使用 MultimapBuilder,这种方式允许用户自定义 key 和 value 应该有的样式。

```java // creates a ListMultimap with tree keys and array list values ListMultimap<String, Integer> treeListMultimap = MultimapBuilder.treeKeys().arrayListValues().build();

// creates a SetMultimap with hash keys and enum set values SetMultimap<Integer, MyEnum> hashEnumMultimap = MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build(); ```

当然也可以直接使用 create() 方法。

BiMap

BiMap 提供了一种新的集合类型,它提供了 key 和 value 的双向关联的数据结构。在使用 BiMap 时,会要求 Value 的唯一性。如果 value 重复了则会抛出错误:java.lang.IllegalArgumentException. 如果想要强制赋值,可以使用 BiMap.forcePut(key, value) 这样之前的可能存在的值会被覆盖。

```java BiMap<String, Integer> userId = HashBiMap.create(); …

String userForId = userId.inverse().get(id); ```

Cache

通过例子一目了然

java LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) throws AnyException { return createExpensiveGraph(key); } });

reference


2017-07-13 google , guava , java , java-lib , lib , utils

Spring MVC 处理文件上传

Spring MVC 处理文件上传

添加Maven依赖

<dependency>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
   <version>2.4</version>
</dependency>
<dependency>
   <groupId>commons-fileupload</groupId>
   <artifactId>commons-fileupload</artifactId>
   <version>1.3.1</version>
</dependency>

添加界面显示

<form method="POST" action="uploadFile" enctype="multipart/form-data">
	File to upload: <input type="file" name="file"> 
	Name: <input type="text" name="name"> 
	<input type="submit" value="Upload"> Press here to upload the file! 
</form>

form 的enctype 应该是 multipart/form-data

在 WEB-INF 目录下 servlet-context.xml 中添加配置

<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="104857600"/>
</bean>

注意这里的 size 单位是 B,所以上面的大小限制是 100MB = 1024 * 1024 * 100

添加上传逻辑

Controller 中代码

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@RequestMapping("/upload")
public class FileController {
	private static Logger logger = LoggerFactory.getLogger(FileController.class);

	@RequestMapping(value = "/zip", method = RequestMethod.POST)
	@ResponseBody
	public CommonResponse<ZipResponse> upload(@RequestParam("file") MultipartFile file,
													HttpServletRequest req,
													HttpServletResponse resp) throws BaseApiException {
		// deal with CSRF
		CommonResponse<ZipResponse> commonResponse = new CommonResponse<>();
		ZipResponse response = new ZipResponse();
		try {
			File cFile = new File("/tmp/" + file.getOriginalFilename());
			file.transferTo(cFile);

			FdsFileService fdsFileService = new FdsFileService();
			// store some where
			String url = "";
			response.setUrl(url);
			commonResponse.setData(response);
			cFile.delete();
		} catch (Exception e) {
			commonResponse.fail("fail");
			logger.error("上传ZIP失败", e);
		}
		return commonResponse;
	}
}

遇到问题

Spring 在处理文件上传的时候报错:

the request was rejected because no multipart boundary was found

解决方法:不要手动设置 Content-Type ,让 Chrome 或者其他浏览器处理, Postman 同理不需要设置 Content-Type。 Refer

reference


2017-07-11 SpringMVC , Java , Web , File

每天学习一个命令:使用 nethogs 查看每个进程流量

在 Linux 上查看系统流量有很多命令,平时一直使用 iftop 来查看单块网卡或者系统整体的流量,iftop 可以查看 TCP 链接的流量情况,分析出流量连往的 IP 地址。但是 iftop 无法做到查看系统中单个进程的网络流量情况。所以 Google 一下之后发现了 nethogs 。

nethogs 自己的介绍

NetHogs is a small ‘net top’ tool. Instead of breaking the traffic down per protocol or per subnet, like most such tools do, it groups bandwidth by process - and does not rely on a special kernel module to be loaded. So if there’s suddenly a lot of network traffic, you can fire up NetHogs and immediately see which PID is causing this, and if it’s some kind of spinning process, kill it.

看介绍就能很快速的知道 nethogs 的设计用途,他就是为了查看单独进程流量情况而被创造出来的。nethogs 是一个小型的 net top 工具,不和大多数工具那样按照每个协议或者子网的速度,而是按照进程进行带宽分组。 nethogs 不需要依赖某个特殊的内核模块,如果发生了网络阻塞,你可以启动 nethogs 立即看到哪个 PID 造成的,这样就可以轻松的找到占用带宽的程序,然后进行相应的内容控制。

Install

在 Debian/Ubuntu 下,sudo apt-get install nethogs 官方源的中 nethogs 因为版本过老,可能会有一些问题,不过可以先尝试安装一下。如果启动 sudo nethogs 之后发现有

creating socket failed while establishing local IP - are you root?

这样的错误。(PS: 这个错误已经在 0.8.1 中被解决)请使用编译安装。

wget -c https://github.com/raboof/nethogs/archive/v0.8.5.tar.gz
tar xf v0.8.5.tar.gz 
cd ./nethogs-0.8.5/

安装必要的依赖,编译安装

sudo apt-get install libncurses5-dev libpcap-dev
make && sudo make install 

检查版本并启动

nethogs -V
sudo nethogs

Usage

使用就非常简单了,直接运行就能查看结果.

~# nethogs
NetHogs version 0.8.5
 
  PID USER     PROGRAM                      DEV        SENT      RECEIVED
2214  root     /usr/lib/apt/methods/http    eth0       4.693     238.631 KB/sec
2051  ubuntu   sshd: ubuntu@pts/1           eth0       3.442       0.310 KB/sec
1120  ubuntu   sshd: ubuntu@pts/0           eth0       0.416       0.035 KB/sec
2213  root     /usr/lib/apt/methods/http    eth0       0.021       0.023 KB/sec
?     root     unknown TCP                             0.000       0.000 KB/sec
 
  TOTAL                                                8.572     239.000 KB/sec

指定某块网卡

nethogs eth0

自定义刷新频率

在启动 nethogs 时使用 -d seconds 参数定义刷新频率

nethogs -d 1 # 每秒钟刷新

交互模式

在进入 nethogs 之后,可以使用如下的交互命令:

m: 修改网速单位
r: 按照流量排序
s: 按照发送流量排序
q: 退出

reference


2017-07-09 linux , nethogs , network , command

使用 supervisor 管理进程

Supervisor (http://supervisord.org) 是一个用 Python 开发的进程管理工具(client/server),可以很方便的用来启动、重启、关闭进程(不仅仅是 Python 进程)。除了对单个进程的控制,还可以同时启动、关闭多个进程,比如很不幸的服务器出问题导致所有应用程序都被杀死,此时可以用 supervisor 同时启动所有应用程序而不是一个一个地敲命令启动。

安装

Supervisor 可以运行在 Linux、Mac OS X 上。如前所述,supervisor 是 Python 编写的,所以安装起来也很方便,可以直接用 pip :

sudo pip install supervisor

如果是 Ubuntu 系统,还可以使用 apt-get 安装。命令如下

sudo apt-get install supervisor

推荐使用 apt 方式安装,避免后期再配置开机启动脚本。

Supervisor 有两个主要的组成部分:

  • supervisord,运行 Supervisor 时会启动一个进程 supervisord,它负责

    1. 启动所管理的进程,并将所管理的进程作为自己的子进程来启动,而且可以在所管理的进程出现崩溃时自动重启。
    2. 响应客户端命令
    3. 日志输出管理
  • supervisorctl,是命令行管理工具,可以用来执行 stop、start、restart 等命令,对这些子进程进行管理。

手动 supervisord 配置

Supervisor 相当强大,提供了很丰富的功能,不过我们可能只需要用到其中一小部分。安装完成之后,可以编写配置文件,来满足自己的需求。为了方便,我们把配置分成两部分:supervisord(supervisor 是一个 C/S 模型的程序,这是 server 端,对应的有 client 端:supervisorctl)和应用程序(即我们要管理的程序)。

首先来看 supervisord 的配置文件。安装完 supervisor 之后,可以运行 echo_supervisord_conf 命令输出默认的配置项,也可以重定向到一个配置文件里(如果是 apt 安装,则默认配置在 /etc/supervisor/supervisord.conf 下):

echo_supervisord_conf > /etc/supervisord.conf

去除里面大部分注释和“不相关”的部分,我们可以先看这些配置:

[unix_http_server]
file=/tmp/supervisor.sock   ; UNIX socket 文件路径,supervisorctl 会使用
;chmod=0700                 ; socket 文件的 mode,默认是 0700
;chown=nobody:nogroup       ; socket 文件的 owner,格式: uid:gid

;[inet_http_server]         ; HTTP 服务器,提供 web 管理界面
;port=127.0.0.1:9001        ; Web 管理后台运行的 IP 和端口,如果开放到公网,需要注意安全性
;username=user              ; 登录管理后台的用户名
;password=123               ; 登录管理后台的密码

[supervisord]
logfile=/tmp/supervisord.log ; 日志文件,默认是 $CWD/supervisord.log
logfile_maxbytes=50MB        ; 日志文件大小,超出会 rotate (分割),默认 50MB
logfile_backups=10           ; 日志文件保留备份数量默认 10
loglevel=info                ; 日志级别,默认 info,其它:debug,warn,trace
pidfile=/tmp/supervisord.pid ; pid 文件
nodaemon=false               ; 是否在前台启动,默认是 false,即以 daemon 的方式启动
minfds=1024                  ; 可以打开的文件描述符的最小值,默认 1024
minprocs=200                 ; 可以打开的进程数的最小值,默认 200

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; 通过 UNIX socket 连接 supervisord,路径与 unix_http_server 部分的 file 一致
;serverurl=http://127.0.0.1:9001 ; 通过 HTTP 的方式连接 supervisord

; 包含其他的配置文件
[include]
files = relative/directory/*.ini    ; 可以是 *.conf 或 *.ini

我们把上面这部分配置保存到 /etc/supervisord.conf(或其他任意有权限访问的文件),然后启动 supervisord(通过 -c 选项指定配置文件路径,如果不指定会按照这个顺序查找配置文件:$CWD/supervisord.conf, $CWD/etc/supervisord.conf, /etc/supervisord.conf

supervisord -c /etc/supervisord.conf

查看 supervisord 是否在运行:

ps aux | grep supervisord

supervisord 是 supervisor 的守护进程,但是他自身并没有 reload 选项,因此需要使用

sudo service supervisor restart  # 来重启 supervisord
sudo /etc/init.d/supervisor restart

如果需要使用其他 conf 文件,在 stop supervisord 之后在使用 -c 参数后接配置文件。

APT 安装

可以使用 sudo service supervisor status 来查看 supervisord 的服务状态

program 配置

上面我们已经把 supervisrod 运行起来了,现在可以添加我们要管理的进程的配置文件。可以把所有配置项都写到 supervisord.conf 文件里,但并不推荐这样做,而是通过 include 的方式把不同的程序(组)写到不同的配置文件里。

为了举例,我们新建一个目录 /etc/supervisor/ 用于存放这些配置文件,相应的,把 /etc/supervisord.conf 里 include 部分的的配置修改一下:

[include]
files = /etc/supervisor/*.conf

假设有个用 Python 和 Flask 框架编写的用户中心系统,取名 program_name,用 gunicorn (http://gunicorn.org/) 做 web 服务器。项目代码位于 /home/einverne/projects/program_name,gunicorn 配置文件为 gunicorn.py,WSGI callable 是 wsgi.py 里的 app 属性。所以直接在命令行启动的方式可能是这样的:

cd /home/einverne/projects/program_name
gunicorn -c gunicorn.py wsgi:app

现在编写一份配置文件来管理这个进程(需要注意:用 supervisord 管理时,gunicorn 的 daemon 选项需要设置为 False):

; 设置进程的名称, 使用 supervisorctl 来管理进程需要使用该进程名
[program:your_program_name]
directory = /home/einverne/projects/name; 程序的启动目录
command = gunicorn -c gunicorn.py wsgi:app  ; 启动命令,与手动在命令行启动的命令是一样的
autostart = true     ; 在 supervisord 启动的时候也自动启动
startsecs = 5        ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true   ; 程序异常退出后自动重启
startretries = 3     ; 启动失败自动重试次数,默认是 3
user = root          ; 用哪个用户启动
redirect_stderr = true  ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 20MB  ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20     ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /data/logs/program_name_stdout.log
loglevel = info      ; loglevel 指定了日志级别, python 的 print 语句输出的日志是不会被记录到日志文件的,需要搭配 Python 的 logging 模块来输出指定级别的日志

; 可以通过 environment 来添加需要的环境变量,一种常见的用法是修改 PYTHONPATH
; environment=PYTHONPATH=$PYTHONPATH:/path/to/somewhere

一份配置文件至少需要一个 [program:x] 部分的配置,来告诉 supervisord 需要管理那个进程。[program:x] 语法中的 x 表示 program name,会在客户端(supervisorctl 或 web 界面)显示,在 supervisorctl 中通过这个值来对程序进行 start、restart、stop 等操作。更加详细的配置可以参考官网

配置一组程序

使用 group 开启或者关闭一组程序,在配置目录下加上额外的配置文件

[group:group1]
programs=group-member-1,group-member-2   ; each refers to 'x' in [program:x] definitions
priority=999                  ; the relative start priority (default 999)

[program:group-member-1]
command=xxx
autostart=true
autorestart=true
user=redis
stdout_logfile=xxx
stderr_logfile=xxx

[program:group-member-2]
command=xxx
autostart=true
autorestart=true
user=redis
stdout_logfile=xxx
stderr_logfile=xxx

添加了 group 配置之后, 进程管理名就变成了 group1:group-member-1 这样的形式,可以使用如下方法启动一组程序

supervisor> start group1:*

使用 supervisorctl

Supervisorctl 是 supervisord 的一个命令行客户端工具,启动时需要指定与 supervisord 使用同一份配置文件,否则与 supervisord 一样按照顺序查找配置文件。

supervisorctl -c /etc/supervisord.conf

上面这个命令会进入 supervisorctl 的 shell 界面,然后可以执行不同的命令了:

> status    # 查看程序状态
> stop program_name   # 关闭 program_name 程序
> start program_name  # 启动 program_name 程序
> restart program_name    # 重启 program_name 程序
> reread    # 读取有更新(增加)的配置文件,不会启动新添加的程序,也不会重启任何程序
> reload    #  载入最新的配置文件,停止原有的进程并按照新的配置启动
> update    # 重启配置文件修改过的程序,配置没有改动的进程不会收到影响而重启

上面这些命令都有相应的输出,除了进入 supervisorctl 的 shell 界面,也可以直接在 bash 终端运行:

$ supervisorctl status
$ supervisorctl stop program_name
$ supervisorctl start program_name
$ supervisorctl restart program_name
$ supervisorctl reread
$ supervisorctl reload
$ supervisorctl update

日志管理

当 supervisor 的日志文件大小超过 stdout_logfile_maxbytes 时,之前的日志文件会被放到 logfile.log.1 文件中备份。可以在相应 program 配置中配置如下两项改变日志的行为:

  • 配置 stdout_logfile_maxbytes 为 0 时,所有的日志文件都会被放到一个文件中
  • 配置 stdout_logfile_backups 为 0 时,当日志文件太大时,旧文件就会被删除而不是移动到单独的文件中。

配置 stderr_logfile_maxbytesstderr_logfile_backups 类似。

这样的日志方式叫做 log file rotation

其它

除了 supervisorctl 之外,还可以配置 supervisrod 启动 web 管理界面,这个 web 后台使用 Basic Auth 的方式进行身份认证。

除了单个进程的控制,还可以配置 group,进行分组管理。

经常查看日志文件,包括 supervisord 的日志和各个 pragram 的日志文件,程序 crash 或抛出异常的信息一半会输出到 stderr,可以查看相应的日志文件来查找问题。

Supervisor 有很丰富的功能,还有其他很多项配置,可以在官方文档获取更多信息:http://supervisord.org/index.html

一些问题

开机启动 supervisor

在使用 pip 安装的时候默认并没有安装成服务,因此如果想要使用开机启动可以使用 APT 安装。 而如果已经安装了 supervisor 想要自己配置开机启动脚本,可以使用这个 link 中的方法来添加。

supervisor 官方提供 的开机脚本似乎对于 Ubuntu 有些问题,可以使用上方 Serverfault 中提到的。

sudo curl https://gist.github.com/howthebodyworks/176149/raw/88d0d68c4af22a7474ad1d011659ea2d27e35b8d/supervisord.sh > /etc/init.d/supervisord
sudo chmod +x /etc/init.d/supervisord
sudo update-rc.d supervisord defaults

确保在 /etc/supervisord.conf 中配置了正确的 pid, 并且和 /etc/init.d/supervisord 相对应:

pidfile=/var/run/supervisord.pid

测试:

service supervisord stop
service supervisord start

某些情况下通过 supervisor 启动 program 会报错

错误关键字:supervisor can’t find command

这时候可以手动 开启一个 bash ,或者 sh

commmand=sh -c 'your command'

启动 supervisord 权限问题

问题关键字:error: <class ‘socket.error’>, [Errno 13] Permission denied: file: /usr/lib/python2.7/socket.py line

权限问题,绝大部分情况下使用 sudo supervisorctl 即可解决。当然如果你愿意配置一个 supervisor 用户组,然后在 配置文件中配置相应的权限也可以解决。

具体参考:https://github.com/Supervisor/supervisor/issues/173

reference


2017-07-08 supervisor , python , linux , client-server , process

MySQL 数据类型

了解并熟悉 MySQL 中的数据类型,对建表和数据库优化都非常重要。 MySQL 实现了 SQL 定义的类型,也响应的增加乐意 tiny, small, big 的类型。 MySQL 的数据类型主要分成三个部分:

  • Numeric Type 数值型
  • Date and Time Type 日期和时间
  • String Type 字符型

更多的内容可以在官网 查到。

整型

MySQL 数据类型 大小 范围(有符号)
TINYINT(m) 1 字节 范围 (-128~127)
SMALLINT(m) 2 个字节  范围 (-32768~32767)
MEDIUMINT(m) 3 个字节 范围 (-8388608~8388607)
INT(m) 4 个字节 范围 2^31-1(-2147483648~2147483647)
BIGINT(m) 8 个字节  范围 2^63-1(+-9.22*10 的 18 次方)

取值范围如果加了 unsigned (无符号),则最大值翻倍,如 TINYINT unsigned 的取值范围为 (0~256)。 INT(m) 里的 m 是表示 SELECT 查询结果集中的显示宽度,并不影响实际的取值范围,没有影响到显示的宽度,不知道这个 m 有什么用。

浮点型 (float 和 double)

MySQL 数据类型 大小 含义
float(m,d) 4 字节 单精度浮点型    8 位精度 (4 字节)        m 总个数,d 小数位
double(m,d) 8 字节 双精度浮点型    16 位精度 (8 字节)       m 总个数,d 小数位

设一个字段定义为 float(5,3),如果插入一个数 123.45678, 实际数据库里存的是 123.457,但总个数还以实际为准,即 6 位。

定点数

浮点型在数据库中存放的是近似值,而定点类型在数据库中存放的是精确值。 decimal(m,d) 参数 m<65 是总个数,d<30d<m 是小数位。

字符串 (char,varchar,text)

MySQL 数据类型 大小 含义
char(n) 0-255 字节 固定长度,最多 255 个字符
varchar(n) 0-65535 字节 固定长度,最多 65535 个字符
tinytext 0-255 字节 可变长度,最多 255 个字符
text 0-65535 字节 可变长度,最多 65535 个字符
mediumtext 0-16777 215 字节 可变长度,最多 2 的 24 次方 -1 个字符
longtext 0-4294967295 字节 可变长度,最多 2 的 32 次方 -1 个字符

char 和 varchar:

  1. char(n) 若存入字符数小于 n,则以空格补于其后,查询之时再将空格去掉。所以 char 类型存储的字符串末尾不能有空格,varchar 不限于此。
  2. char(n) 固定长度,char(4) 不管是存入几个字符,都将占用 4 个字节,varchar 是存入的实际字符数 +1 个字节(n<=255)或 2 个字节 (n>255),所以 varchar(4), 存入 3 个字符将占用 4 个字节。
  3. char 类型的字符串检索速度要比 varchar 类型的快。

varchar 和 text:

  1. varchar 可指定 n,text 不能指定,内部存储 varchar 是存入的实际字符数 +1 个字节(n<=255)或 2 个字节 (n>255),text 是实际字符数 +2 个字节。
  2. text 类型不能有默认值。
  3. varchar 可直接创建索引,text 创建索引要指定前多少个字符。varchar 查询速度快于 text, 在都创建索引的情况下,text 的索引似乎不起作用。

char(n)和 varchar(n)中括号中 n 代表字符的个数,并不代表字节个数,所以当使用了中文的时候 (UTF8) 意味着可以插入 m 个中文,但是实际会占用 m*3 个字节。同时 char 和 varchar 最大的区别就在于 char 不管实际 value 都会占用 n 个字符的空间,而 varchar 只会占用实际字符应该占用的空间 +1,并且实际空间 +1<=n

  • 超过 char 和 varchar 的 n 设置后,字符串会被截断
  • char 的上限为 255 字节,varchar 的上限 65535 字节,text 的上限为 65535
  • char 在存储的时候会截断尾部的空格,varchar 和 text 不会
  • varchar 会使用 1-3 个字节来存储长度,text 不会
Value CHAR(4) Storage Required VARCHAR(4) Storage Required
’’ ’ ‘ 4 bytes ’’ 1 byte
‘ab’ ‘ab ‘ 4 bytes ‘ab’ 3 bytes
‘abcd’ ‘abcd’ 4 bytes ‘abcd’ 5 bytes
‘abcdefg’ ‘abcd’ 4 bytes ‘abcd’ 5 bytes

在使用 MySQL 存储字符串时经常会疑惑选择哪一种数据类型。

首先从空间方面,当 varchar 大于某些值时,会自动转换我 text,大概为

  • 大于 varchar(255)变为 tinytext
  • 大于 varchar(500)变为 text
  • 大于 varchar(20000)变为 mediumtext

所以对于大内容 varchar 和 text 并没有太多区别。

其次从性能方面,索引是影响性能最关键的因素,对于 text 来说,只能添加前缀索引,并且索引最大只能 1000 字节。而 varchar 即使超过 1000 字节长,也会被截断。

二进制数据 (Blob)

  1. BLOB 和 TEXT 存储方式不同,TEXT 以文本方式存储,英文存储区分大小写,而 Blob 是以二进制方式存储,不分大小写。
  2. BLOB 存储的数据只能整体读出。
  3. TEXT 可以指定字符集,BLOB 不用指定字符集。
数据类型 大小 用途
TINYBLOB 0~255 字节 不超过 255 个字符二进制字符串
BLOB 0~65535 字节 二进制
MEDIUMBLOB 0-16 777 215 字节 二进制形式的中等长度文本数据
LONGBLOB 0-4 294 967 295 字节 二进制形式的极大文本数据

日期和时间类型

MySQL 数据类型 大小 范围 含义
date 3 字节 1000-01-01/9999-12-31 日期 ‘2008-12-2’
time 3 字节 ‘-838:59:59’/’838:59:59’ 时间 ‘12:25:36’
datetime 8 字节 1000-01-01 00:00:00/9999-12-31 23:59:59 日期时间 ‘2008-12-2 22:06:44’
timestamp 4 字节 1970-01-01 00:00:00/2037 年某时 自动存储记录修改时间

每个时间类型有一个有效值范围和一个”零”值,当指定不合法的 MySQL 不能表示的值时使用”零”值。若定义一个字段为 timestamp,这个字段里的时间数据会随其他字段修改的时候自动刷新,所以这个数据类型的字段可以存放这条记录最后被修改的时间。

Datetime 和 timestamp 的区别

区别 datetime timestamp
空间 8 字节 4 字节
是否允许空值 允许 允许
是否可以自定义值 可以 不可以
支持时间范围 1000-01-01 00:00:00/9999-12-31 23:59:59 不能早于 1970 或者晚于 2037 年
是否与时区相关 无关 值以 UTC 格式保存,存储 milliseconds,需要存储或者取出时手动转换时区
默认值 可以在指定 datetime 字段的值的时候使用 now() 变量来自动插入系统的当前时间 默认值为 CURRENT_TIMESTAMP() ,当前系统时间
结论 dateim 类型适合用来记录数据的原始创建时间,无论如何更改记录中其他字段, datetime 都不会改变,除非手动改变 数据库会自动修改其值,任何修改记录都会被更新,如果需要不设置自动更新,通过设置 DEFAULT CURRENT_TIMESTAMP 可实现。timestamp 类型适合用来记录数据最后修改时间。

数据类型的属性

MySQL 关键字 含义
NULL 数据列可包含 NULL 值
NOT NULL 数据列不允许包含 NULL 值
DEFAULT 默认值
PRIMARY KEY 主键
AUTO_INCREMENT 自动递增,适用于整数类型
UNSIGNED 无符号
CHARACTER SET name 指定一个字符集

2017-07-07 linux , mysql , sql

Java 查漏补缺之枚举

当创建 enum 时,编译器会自动创建一个继承自 java.lang.Enum 的类。

ordinal 方法

ordinal() 方法会返回一个 int 值,是每个 enum 实例声明时的次序,从 0 开始。枚举可以使用 ==比较,编译器会自动提供 equals()hashCode() 方法。 Enum 类实现了 Comparable 接口,具有 compareTo() 方法,同时也实现了 Serializable 接口。

values 方法

values() 方法是由编译器添加的 static 方法。

继承

enum 都继承自 java.lang.Enum ,并且 Java 不支持多继承,所以 enum 不能再继承其他类。然而我们在创建 enum 时可以实现一个或者多个 interface。

随机选择枚举

构造如下的工具类

public class EnumUtils {
    public static <T extends Enum<T>> T random(Class<T> c) {
        return random(c.getEnumConstants());
    }

    private static <T extends Enum<T>> T random(T[] values) {
        return values[new Random().nextInt(values.length)];
    }
}

然后使用

    Fruit random = EnumUtils.random(Fruit.class);
    print(random);

EnumSet

EnumSet 是一种特殊的 Set,这个集合中只能存储 enum 的值。EnumSet 的设计充分考了速度,内部实现就是将一个 long 值作为 bit 向量,所以 EnumSet 非常快。 EnumSet 的基础是 long,一个 long 值有 64 位,一个 enum 实例只需要一位 bit 表示是否存在,也就是说,在不超过 long 的表达能力的情况下, EnumSet 可以应用于最多不超过 64 个元素的 enum。如果超过 64 个元素呢?

当枚举数量小于 64 的时候,创建一个 RegularEnumSet 实例对象,大于 64 时则创建一个 JumboEnumSet 实例对象。枚举项的排序值 ordinal 是从 0,1,2,…… 依次递增的,没有重号,没有跳号,RegularEnumSet 就是利用这一点把每个枚举项的 ordinal 映射到一个 long 类型的每个位上的,

class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
    private long elements = 0L;// 记录所有枚举排序号,注意是 long 型
    RegularEnumSet(Class<E>elementType, Enum[] universe) {// 构造函数
        super(elementType, universe);
    }
    void addAll() {// 加入所有元素
        if (universe.length != 0)
            elements = -1L >>> -universe.length;
    }
}

long 类型是 64 位的,所以 RegularEnumSet 类型也就只能负责枚举项数量,不大于 64 的枚举,大于 64 则由 JumboEnumSet 处理

class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
    private long elements[];// 映射所有的枚举项
    JumboEnumSet(Class<E>elementType, Enum[] universe) {// 构造函数
        super(elementType, universe);
        elements = new long[(universe.length + 63) >>> 6];// 默认长度是枚举项数量除以 64 再加 1
    }
    void addAll() {//elements 中每个元素表示 64 个枚举项
        for (int i = 0; i < elements.length; i++)
            elements[i] = -1;
        elements[elements.length - 1] >>>= -universe.length;
        size = universe.length;
    }
}

JumboEnumSet 类把枚举项按照 64 个元素一组拆分了多组,每组都映射到一个 long 类型的数字上,然后该数组再放置到 elements 数组中,简单来说 JumboEnumSet 类的原理与 RegularEnumset 相似,只是 JumboEnumSet 使用了 long 数组能容纳更多的枚举项。

EnumMap

EnumMap 是一种特殊的 Map,要求其 key 必须来自一个 enum 。因为 enum 本身的限制,EnumMap 在内部使用数组实现,非常快。和 EnumSet 一样,enum 实例定义的次序决定了在 EnumMap 中的顺序。

enum 每个实例作为键总是存在的,如果没有为这个键调用 put 方法来存入相应的值,对应的值就是 null。

reference

  • Java 编程思想

2017-07-02 java , enum , linux

Java 查漏补缺之并发编程

Java 实现多线程,继承 Thread 类,另一种就是实现 Runnable 接口。实际 Thread 类源码也是实现了 Runnable,使用继承 Thread 方式创建多线程,最大的局限就是不能多继承,随意推荐实现 Runnable 。

Executor

Java 5 开始, Java 并发 API 提供了一套执行器框架 Executor Framework,围绕 Executor 接口和它的子接口 ExecutorService ,以及实现这两个接口的 ThreadPoolExecutor 类展开。这套机制将任务创建和执行分离。执行器通过创建所需的线程来负责 Runnable 对象的创建、实例化和运行。执行器使用线程池来提高应用性能。

执行器另一个优势是 Callable 接口,类似于 Runnable 接口,但是 Callable 接口的 call() 方法能够返回接口;当发送 Callable 对象给 Executor 时,将获得一个实现了 Future 接口的对象,通过这个对象,可以用来控制 Callable 的状态和结果。

Executor 是一个接口,用来表示一个对象能够接受 task 来执行。

执行器需要显示的结束它,否则程序不会结束。执行器没有任何任务可以执行,那么会一直等待。

ExecutorService

ExecutorService 接口继承 Executor 接口,提供做了更多管理生命周期的方法,他提供了内存队列,并且可以通过当前线程的可用性来安排任务执行。

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit);
    <T> Future<T> submit(Callable<T> task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks);
    <T> T invokeAny(Collection<? extends Callable<T>> tasks);
}

几个重要的方法:

  • submit 方法调用 Executor.execute() 然后返回 Future
  • invokeAnyinvokeAll 方法来提交一个集合任务,然后等待
  • shutdown 方法平稳关闭,不再接受新任务,同时等待已经提交的任务执行完毕,包括还未开始的任务
  • shutdownNow 方法将直接关闭过程,将尝试取消所有运行中的任务,不再启动队列中尚未开始的任务

ExecutorService 的生命周期有三种:运行,关闭和已终止。

  • ExecutorService 创建初期处于运行状态
  • 当所有任务都完成就进入终止状态

Executors

Executors 是一个工具类。 Executors 类中有很多创建线程池的方法,这些方法都是调用

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) { ... }

参数说明

  • corePoolSize 基本大小,在没有任务执行时,线程池的大小
  • maximumPoolSize 最大大小,可同时活动的线程数量上限
  • keepAliveTime 当某个线程空闲时间超过存活时间,会被标记为回收,当线程池大小超过基本大小时,该线程会被终止

在 Executors 中提供了很多静态方法:

  • newFixedThreadPool(int) 固定长度线程池
  • newCachedThreadPool() 可缓存线程池,线程池规模不存在任何限制
  • newSingleThreadExecutor() 单线程 Executor,如果这个线程出现异常,将创建新的线程补充。能够确保任务在队列中顺序执行,FIFO
  • newScheduledThreadPool(int) 固定长度线程池,延迟或定时执行任务,类似 Timer

ScheduledExecutorService

ScheduledExecutorService 和 ExecutorService 接口类似,但是提供了定时任务的方法。

Future

Future 用来表示异步操作的结果。他有方法可以用来检测任务有没有完成,也有方法来获取异步任务的结果。

volatile

关键字 volatile 主要作用是让变量在多个线程间可见。

volatile vs synchronized

  • volatile 关键字是程序同步轻量级实现,性能稍好,volatile 只能修饰变量,而 synchronized 可以修饰方法,代码块
  • 多线程访问 volatile 不会阻塞, synchronized 会阻塞
  • volatile 能保证数据可见性,不能保证原子性;synchronized 可以保证原子性,也能间接保证可见性,synchronized 会将私有内存和公共内存的数据同步。

thread setDaemon(boolean)

关于 Thread 类中 setDaemon(boolean) 中的 daemon 方法,一个守护线程是程序运行结束仍然运行的线程,垃圾回收线程就是典型的例子。在 Java 中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。只要当前 JVM 实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着 JVM 一同结束工作。

User 和 Daemon 两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread 已经全部退出运行了,只剩下 Daemon Thread 存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon 也就没有工作可做了,也就没有继续运行程序的必要了。


2017-06-27 java , thread , runnable , callable , executor

Java 查漏补缺之 defensive copy

什么是保护性拷贝(defensive copy,或者防御性拷贝)呢? 说到这个问题首先要知道 Java 中的不可变对象(Immutable Object),既然有不可变对象就一定有可变对象(Mutable Object),顾名思义可变对象就是在对象构造完成之后内部状态会改变的对象,比如 StringBuilder 和 Date 都是可变对象,而 String 和 Integer 是不可变对象。

一个类可能有两种方式包含可变的 field

  • 类状态只能被自身改变,也就是说类包含一个可变对象,而该字段只能由类自己改变,比如说类 Person 有一个字段 birthDate,而该字段由 Person 类在创建时创建
  • 另外一种类的状态可以被自身和调用者改变,也就是说类对象中包含在其他地方创建的可变对象,比如下面的例子中,假如构造函数中的 Date 没有进行保护性拷贝,可能导致错误

如果在构建类时只想要类状态被自身改变,那么在可变对象被传入或者被外部获取时,保护性拷贝是必要的。如果不那么做,调用者就可以轻易的破坏封装。

举例

比如下面 Planet 类,在构造函数和 get 函数中都做了保护性拷贝,这样外部调用者无法改变类内部状态,才能认为 Planet 是不可变对象。

```java import java.util.Date;

/** * Planet is an immutable class, since there is no way to change * its state after construction. */ public final class Planet {

public Planet (double aMass, String aName, Date aDateOfDiscovery) { fMass = aMass; fName = aName; //make a private copy of aDateOfDiscovery //this is the only way to keep the fDateOfDiscovery //field private, and shields this class from any changes that //the caller may make to the original aDateOfDiscovery object fDateOfDiscovery = new Date(aDateOfDiscovery.getTime()); }

/** * Returns a primitive value. * * The caller can do whatever they want with the return value, without * affecting the internals of this class. Why? Because this is a primitive * value. The caller sees its “own” double that simply has the * same value as fMass. */ public double getMass() { return fMass; }

/** * Returns an immutable object. * * The caller gets a direct reference to the internal field. But this is not * dangerous, since String is immutable and cannot be changed. */ public String getName() { return fName; }

// /** // * Returns a mutable object - likely bad style. // * // * The caller gets a direct reference to the internal field. This is usually dangerous, // * since the Date object state can be changed both by this class and its caller. // * That is, this class is no longer in complete control of fDate. // */ // public Date getDateOfDiscovery() { // return fDateOfDiscovery; // }

/** * Returns a mutable object - good style. * * Returns a defensive copy of the field. * The caller of this method can do anything they want with the * returned Date object, without affecting the internals of this * class in any way. Why? Because they do not have a reference to * fDate. Rather, they are playing with a second Date that initially has the * same data as fDate. */ public Date getDateOfDiscovery() { return new Date(fDateOfDiscovery.getTime()); }

// PRIVATE

/** * Final primitive data is always immutable. */ private final double fMass;

/** * An immutable object field. (String objects never change state.) */ private final String fName;

/** * A mutable object field. In this case, the state of this mutable field * is to be changed only by this class. (In other cases, it makes perfect * sense to allow the state of a field to be changed outside the native * class; this is the case when a field acts as a “pointer” to an object * created elsewhere.) * * In new code, you should use java.time classes, not java.util.Date. */ private final Date fDateOfDiscovery; } ```


2017-06-15 java , defensive-copy , linux , notes , effective-java

知乎上被删除的良心回答之油猴脚本

今天偶然间看到一个知乎问题:“最良心的软件可以良心到什么程度?”,当时在 Google+ 上点进去粗略看了一样,看到油猴脚本也就坦然了,而添加到稍后阅读,在晚上回来之后准备细看时却惊讶于知乎屏蔽的速度,在尝试使用 Google,bing 和 web archive ,baidu 的历史记录之后终于找回了一些渣滓。

我在很早就已经推荐过 Tampermonkey, 也借此推荐过 我用过的 Userscript 。 而 Tampermonkey 我也用了很多年也曾总结过 Tampermonkey 同步的功能。不过多少年过去了,很多脚本失效的失效,我自己之前写得找电影脚本也因为跨域问题,一直懒没有修复。

幸好看到这样一篇总结帖,看看这两年又更新出来多少新玩法。以下为从历史记录中搜出:

直接来看 油猴脚本能干啥:

  • 直接观看各大视频网站的VIP 视频
  • 去除各大视频广告,百度搜索广告
  • 免费收听网易云320k 高音质音乐
  • 高速下载百度云
  • 去除各种验证码

在国内互联网广告满天飞的现在,这些功能无疑会吸引很多的用户。

下面列举一些非常好用的脚本,我在 http://einverne.github.io/post/2015/08/userscripts.html 中列出的脚本不在更新。

网易云高音质下载

在网页上听网易高品质音乐,在网页歌曲页面播放按钮上面也能看到下载歌曲按钮,或者下载歌词,封面,MV 等等。

地址:https://greasyfork.org/zh-CN/scripts/23222

百度网盘直接下载助手

这个插件,你可以使用它可以直接获取文件原始链接,这样你就可以使用第三方(IDM,Folx)下载了,再也不用使用百度云客户端的龟速了。 还可以多个文件选择,批量下载。

地址:https://greasyfork.org/zh-CN/scripts/23635

解决百度云大文件下载限制 https://greasyfork.org/zh-CN/scripts/17800

百度网盘的下载助手,一直在有效,失效,修复,有效,再失效的过程中,因为在 Linux 下很久不用百度云的客户端,而百度最近频频限制下载速度,并且在分享页面增加很多限制,只能使用客户端下载,这么多动作的背后,一方面是因为带宽和存储费用逐渐增高,却无奈找不到任何赢利点,另一方面也因为监管力度的加强。在网盘大战落下帷幕的时候市场上还能够坚持到最后的也就剩下百度,金山,乐视云,360,大大小小的众网盘纷纷宣布停止运营。百度在一家独大之后也是频频限制免费用户的行为,所以用这么多的脚本来提高百度的体验,还不如彻底的原理百度云盘。用自己的一点钱买一个 Dropbox 或者 Google Drive 反而要轻松很多呢。

一键离线下载

将网页上的磁力链接离线到网盘,在寻找电影的时候,这也是经常的动作,这个脚本将找电影,然后到离线的过程自动化了。

https://greasyfork.org/zh-CN/scripts/22590

免费看VIP 视频

直接在 greasyfork.org 搜索 VIP 即可。

授人以鱼不如授人以渔,每次看到此类 Collection 的时候总是会想起之前的那篇文章 文章中有总结如今非常活跃的几个脚本聚集地。根据关键词应该能够搜索到很多关心的内容。


2017-06-09 Chrome , userscript , tampermonkey

每天学习一个命令:traceroute 查看路由信息

traceroute(跟踪路由)是路由跟踪实用程序,用于确定 IP 数据报访问目标所采取的路径。Traceroute命令用 IP 生存时间 (TTL) 字段和 ICMP 错误消息来确定从一个主机到网络上其他主机的路由。

通过traceroute命令可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。

linux系统中是 traceroute,在MS Windows中为tracert。traceroute通过发送小数据包到目的主机直到其返回,来测量其耗时。一条路径上的每个设备traceroute要测3次。输出结果中包括每次测试的时间(ms)和设备的名称及其IP地址。

在大多数情况下,我们会在linux主机系统下,直接执行命令行:

traceroute hostname

使用

命令格式:

traceroute [参数] [主机]

命令功能:

traceroute 预设数据包大小是40Bytes,可另行设置。

具体参数格式:

traceroute [-dFlnrvx][-f<存活数值>][-g<网关>...][-i<网络界面>][-m<存活数值>][-p<通信端口>][-s<来源地址>][-t<服务类型>][-w<超时秒数>][主机名称或IP地址][数据包大小]

命令参数:

-d 使用Socket层级的排错功能。 -f 设置第一个检测数据包的存活数值TTL的大小。 -F 设置勿离断位。 -g 设置来源路由网关,最多可设置8个。 -i 使用指定的网络界面送出数据包。 -I 使用ICMP回应取代UDP资料信息。 -m 设置检测数据包的最大存活数值TTL的大小。 -n 直接使用IP地址而非主机名称。 -p 设置UDP传输协议的通信端口。 -r 忽略普通的Routing Table,直接将数据包送到远端主机上。 -s 设置本地主机送出数据包的IP地址。 -t 设置检测数据包的TOS数值。 -v 详细显示指令的执行过程。 -w 设置等待远端主机回报的时间。 -x 开启或关闭数据包的正确性检验。

实例

最常用法

直接追踪路由

traceroute google.com

说明:

序列号从1开始,每个纪录就是一跳 ,每跳表示一个网关,每行有三个时间,单位是 ms,其实就是 -q 的默认参数。探测数据包向每个网关发送三个数据包后,网关响应后返回的时间;如果您用 traceroute -q 10 google.com ,表示向每个网关发送10个数据包。

traceroute 一台主机有时会看到一些行以星号表示,出现这样的情况,可能是防火墙封掉了ICMP的返回信息,所以得不到什么相关的数据包返回数据。

有时在某一网关处延时比较长,可能是某台网关比较阻塞,也可能是物理设备本身的原因。当然如果某台DNS出现问题时,不能解析主机名、域名时,也会有延时长的现象;您可以加 -n 参数来避免DNS解析,以IP格式输出数据。

如果在局域网中的不同网段之间,可以通过traceroute 来排查问题所在,是主机的问题还是网关的问题。如果通过远程来访问某台服务器遇到问题时,用到traceroute 追踪数据包所经过的网关,提交IDC服务商,也有助于解决问题;但目前看来在国内解决这样的问题是比较困难的,即使发现问题,IDC服务商也不可能帮助解决。

跳数设置

traceroute -m 10 google.com

不解析主机名

traceroute -n google.com

设置探测包数量

traceroute -q 4 google.com

绕过正常的路由表直接发送到网络相连的主机

traceroute -r douban.com

工作原理

traceroute 命令利用 ICMP 及IP header 的TTL(Time To Live)字段(field)。

  • traceroute 送出一个TTL是1的IP datagram到目的地(其实,每次送出的为3个40字节的包,包括源地址,目的地址和包发出的时间标签),当路径上的第一个路由器(router)收到这个 datagram 时,它将TTL减1。此时,TTL变为0了,所以该路由器会将此datagram丢掉,并送回一个「ICMP time exceeded」消息(包括发IP包的源地址,IP包的所有内容及路由器的IP地址),traceroute 收到这个消息后,便知道这个路由器存在于这个路径上
  • 接着traceroute 再送出另一个TTL是2 的datagram,发现第2个路由器
  • ……
  • traceroute 每次将送出的datagram的TTL 加1来发现另一个路由器,这个重复的动作一直持续到某个datagram 抵达目的地。当datagram到达目的地后,该主机并不会送回ICMP time exceeded消息,因为它已是目的地了。
  • 那么traceroute如何得知目的地到达了呢?traceroute 在送出 UDP datagrams 到目的地时,它所选择送达的 port number 是一个一般应用程序都不会用的端口(30000 以上),所以当此UDP datagram 到达目的地后该主机会回送一个(ICMP port unreachable)的消息,而当traceroute 收到这个消息时,便知道目的地已经到达了。所以traceroute 在 Server 端也是没有所谓的Daemon 程式。

traceroute 提取发 ICMP TTL到期消息设备的IP地址并作域名解析。每次traceroute都打印出一系列数据,包括所经过的路由设备的域名及IP地址,三个包每次来回所花时间。


2017-06-09 traceroute , network , linux , command

电子书

Google+

最近文章

  • 使用 alembic 迁移数据库结构 Alembic 是一个处理数据库更改的工具,它利用 SQLAlchemy 来实现形成迁移。 因为 SQLAlchemy 只会在我们使用时根据 metadata create_all 方法来创建缺少的表 ,它不会根据我们对代码的修改而更新数据库表中的列。它也不会自动帮助我们删除表。 Alembic 提供了一种更新 / 删除表,更改列名和添加新约束的方法。因为 Alembic 使用 SQLAlchemy 执行迁移,它们可用于各种后端数据库。
  • 每天学习一个命令:iotop 查看 Linux 下每个进程 IO 占用 iotop 是一个用来监控磁盘 I/O 的类似 top 的工具,Linux 下 IO 统计工具,比如 iostat, nmon 等只能统计到每个设备的读写情况,如果想要知道哪一个进程占用比较高的 IO 就要使用 iotop。 iotop 使用 Python 语言编写,要求 Python >= 2.5,Linux Kernel >= 2.6.20.
  • 修正关于 HTTP Header 的错误认识 HTTP 请求的 Header 是不区分大小写的!,一直以为 HTTP 请求的请求头是有区分大小的,知道今天调试发现 Spring 将 header 全部处理成小写,然后有人提了 Bug 58464 然后看到 Stackoverflow 上面有人回答。
  • 解决 failed to create bus connection no such file or directory 错误 今天在修改 hostname 使用 sudo hostnamectl set-hostname ds 命令时遇到问题:
  • Vim 的颜色主题 Retro groove color scheme for Vim