易天行

积累点滴,汇聚江河


  • 首页

  • 关于

  • 标签

【ES】match_phrase、match、prefix、wildcard比较

发表于 2019-07-11

match

GET /my_index/address/_search
{
query: {match:"hello world"}
}

句子中包含hello或world的都会被搜索出,比如下面的句子都会被搜索到:

1.hello tom, do you know me
2.see the world

match_phrase

GET /my_index/address/_search
{
query: {match_phrase:"hello world"}
}

也就是说hello world 必须相邻才能被搜索出来,比如下面的句子:

1.Hello World tom, do you know me // 能搜到
2.see the world // 搜不到
3.Hello tom // 搜不到

match_phrase slop

GET /my_index/address/_search
{
query: {match_phrase:{content:"hello world", slop: 2}}
}

可以通过指定slot来控制移动词数。这里中间间隔的词数<2才能搜到。对于下面的例子:

1.hello world // 能搜到
2.hello es world // 能搜索到
3.hello tom es world // 不能搜到
4.hello lity do my world // 搜不到 3>2

match_phrase原理

match_phrase执行过程:
1.如match搜索一样进行分词,
2.对分词后的单词到field中去进行搜索(多个term匹配)。这一步返回每个单词对应的doc,并返回这些单词在对应的doc中的位置,
3.对返回的doc进行第一步的筛选,找到每个单词都在同一个field的doc。
4.对第3步进行筛选后的doc进行再一次的筛选,选回位置符合要求的doc。比如,对于match_phrase,就是找到后一个单词的位置比前一个单词的位置大1。或者移动次数<slot的文档。
5.proximity match(使用slot)原理一样,只是第四位对位置进行筛选时的方法不同。

比如要搜索“hello world”

  1. 分词为 hello 和 world
  2. 分别对term hello和world去搜索。返回两者匹配到的文档。
  3. 第一次筛选,取两个的交集。
  4. 继续筛选,对于match_phrase,就是找到后一个单词world的位置比前一个单词hello的位置大1的文档

prefix

前缀搜索
它会对分词后的term进行前缀搜索。

  • 它不会分析要搜索字符串,传入的前缀就是想要查找的前缀
  • 默认状态下,前缀查询不做相关度分数计算,它只是将所有匹配的文档返回,然后赋予所有相关分数值为1。它的行为更像是一个过滤器而不是查询。两者实际的区别就是过滤器是可以被缓存的,而前缀查询不行。
  • 只能找到反向索引中存在的术语

prefix的原理:
需要遍历所有倒排索引,并比较每个term是否已所指定的前缀开头。
比如:

Term:          Doc IDs:
-------------------------
"SW50BE" | 5
"W1F7HW" | 3
"W1V3DG" | 1
"W2F8HW" | 2
"WC1N1LZ" | 4
-------------------------

GET /my_index/address/_search
{
"query": {
"prefix": {
"postcode": "W1"
}
}
}

prefix原理

prefix搜索过程:
为了支持前缀匹配,查询会做以下事情:

  1. 扫描术语列表并查找到第一个以 W1 开始的术语。
  2. 搜集关联的ID
  3. 移动到下一个术语
  4. 如果这个术语也是以 W1 开头,查询跳回到第二步再重复执行,直到下一个术语不以 W1 为止。

如果以w1开头的term很多,那么会有严重的性能问题。但是如果term比较小集合,可以放心使用。

wildcard

模糊查询

  • 工作原理和prefix相同,只不过它在1不是只比较开头,它能支持更为复杂的匹配模式。
  • 它使用标准的 shell 模糊查询:? 匹配任意字符,* 匹配0个或多个字符。
    GET /my_index/address/_search
    {
    "query": {
    "regexp": {
    "postcode": "W[0-9].+" #1
    }
    }
    }

这也意味着我们需要注意与前缀查询中相同的性能问题,执行这些查询可能会消耗非常多的资源,所以我们需要避免使用左模糊这样的模式匹配(如,foo 或 .foo 这样的正则式)

注意:
prefix、wildcard 和 regrep 查询是基于术语操作的,如果我们用它们来查询分析过的字段(analyzed field),他们会检查字段里面的每个术语,而不是将字段作为整体进行处理。

match_phrase_prefix

实时模糊搜索
这种查询的行为与 match_phrase 查询一致,但是它将查询字符串的最后一个词作为前缀(prefix)使用。

比如:

{
"match_phrase_prefix" : {
"brand": "johnnie walker bl"
}
}

下面的句子可能被搜索到:

  • johnnie walker blll
  • johnnie walker bl33
  • johnnie walker blyu

查询步骤:

  • johnnie walker
  • 跟着 一个以 bl 开始的词(prefix)

与 match_phrase 一样,它也可以接受 slop 参数让相对词序位置不那么严格:

{
"match_phrase_prefix" : {
"brand" : {
"query": "walker johnnie bl", #1
"slop": 10
}
}
}

下面的句子可能被搜索到:

  • johnnie ee walker blll
  • johnnie aa walker bl33
  • johnnie cc gg walker blyu

我们可以通过设置 max_expansions 参数来限制前缀扩展的影响,一个合理的值是可能是50:

{
"match_phrase_prefix" : {
"brand" : {
"query":"johnnie walker bl",
"max_expansions": 50
}
}
}

参数max_expansions控制着可以与前缀匹配的术语的数量

另一个即时搜索的方法是,使用 Ngram部分匹配, 这种方法会增加索引的开销,但是会加快查询速度。具体可以自行查阅。

roketmq 使用示例

发表于 2019-07-11

如何使用mvn下载jar包

  • 安装mvn http://maven.apache.org/install.html
  • 编辑pom.xml,填写要引入的jar包
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>temp.download</groupId>
<artifactId>temp-download</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 需要下载什么jar包 添加相应依赖 其余部分无需在意-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
</dependencies>
</project>
  • 使用命令下载
mvn -f /Users/userName/maven2Jars/pom.xml dependency:copy-dependencies
  • over! target/denpendency/* 目录下会有所有依赖的jar包文件

mq生产者示例

package com.dishui.service;

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.commons.lang.StringUtils;


public class Producer {
public static void main(String[] args) throws Exception {
//Instantiate with a producer group name.
DefaultMQProducer producer = new
DefaultMQProducer("STest");
// Specify name server addresses.
producer.setNamesrvAddr("172.21.0.48:7876");
//Launch the instance.
producer.start();
for (int i = 0; i < 100; i++) {
//Create a message instance, specifying topic, tag and message body.
Message msg = new Message("TopicNotifyTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
//Call send message to deliver message to one of brokers.
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
}
//Shut down once the producer instance is not longer in use.
producer.shutdown();
}
}

mq消费者示例

package com.dishui.service;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {

public static void main(String[] args) throws InterruptedException, MQClientException {

// Instantiate with specified consumer group name.
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group_tom");

// Specify name server addresses.
consumer.setNamesrvAddr("localhost:9876");

// Subscribe one more more topics to consume.
consumer.subscribe("TopicTest", "*");
// Register callback to execute on arrival of messages fetched from brokers.
consumer.registerMessageListener(new MessageListenerConcurrently() {

@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});

//Launch the consumer instance.
consumer.start();

System.out.printf("Consumer Started.%n");
}
}

未命名

发表于 2019-06-19

elasticsearch 命令

es 2.3的相关的索引命令记录。
官方文档参考

索引相关

参考

创建索引

$ curl -XPUT 'http://localhost:9200/twitter/' -d '{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}
}'
  • number_of_shards 主分片数 默认 5
  • number_of_replicas 每个主分片的副本数 1

简写:

$ curl -XPUT 'http://localhost:9200/twitter/' -d '{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 2
}
}'

可以创建索引时,同时指定mapping结构:


curl -XPOST localhost:9200/test -d '{
"settings" : {
"number_of_shards" : 1
},
"mappings" : {
"type1" : {
"properties" : {
"field1" : { "type" : "string", "index" : "not_analyzed" }
}
}
}
}'

properties.index说明:

  • analyzed (默认项)分析,将单词转为小写,并切分单词,用于搜索
  • not_analyzed 不分析,字符串作为一个整体分析,用于精确匹配
  • no 不用于索引,无法用于搜索。对不需要搜索只用于存储的字段设置为此,可以节省空间,缩短索引和搜索时间。

示例创建客户:

curl -X POST \
http://127.0.0.1:9200/customer \
-H 'cache-control: no-cache' \
-d '{
"settings":{
"number_of_shards": 7,
"number_of_replicas":1
},
"mappings" : {
"customer" : {
"properties" : {
"account" : {
"type" : "string",
"index" : "not_analyzed"
},
"name" : {
"type" : "string",
},
"phone" : {
"type" : "string",
"index" : "not_analyzed"
},
"createTime" : {
"type" : "string",
"index" : "no"
},
"notifyTime": {
"type": "string",
"index" : "no"
}
}
}
}
}'
curl -X POST \
http://127.0.0.1:9200/kmdoc \
-H 'Content-Type: application/json' \
-H 'cache-control: no-cache' \
-d '{
"mappings" : {
"kmdoc" : {
"properties" : {
"accountId" : {
"type" : "string",
"index" : "not_analyzed"
},
"title" : {
"type" : "string"
},
"content" : {
"type" : "string"
},
"cid" : {
"type" : "string",
"index" : "not_analyzed"
},
"kmType" : {
"type" : "string",
"index" : "not_analyzed"
},
"lastTime" : {
"type" : "string",
"format": "no"
}
}
}
}
}'

查看索引

$ curl -XGET 'http://localhost:9200/twitter/'
# 过滤字段,只返回setting和mapping
$ curl -XGET 'http://localhost:9200/twitter/_settings,_mappings'

删除索引

$ curl -XDELETE 'http://localhost:9200/twitter/'

PUT mapping

可以用这个语法来添加索引,给指定索引类型添加字段,添加类型,有下面三种形式

PUT twitter 
{
"mappings": {
"tweet": {
"properties": {
"message": {
"type": "string"
}
}
}
}
}

PUT twitter/_mapping/user
{
"properties": {
"name": {
"type": "string"
}
}
}

PUT twitter/_mapping/tweet
{
"properties": {
"user_name": {
"type": "string"
}
}
}

查看mapping

curl -XGET 'http://localhost:9200/twitter/_mapping/tweet'
host:port/{index}/_mapping/{type}
curl -XGET 'http://localhost:9200/_all/_mapping/tweet,book'

更新索引设置

更新路径是/_settings or {index}/_settings,

下面的例子设置了某个index的副本分片数:

curl -XPUT 'localhost:9200/my_index/_settings' -d '
{
"index" : {
"number_of_replicas" : 4
}
}'

# 修改刷新间隔
curl -XPUT localhost:9200/test/_settings -d '{
"index" : {
"refresh_interval" : "1s"
} }'

迁移时优化

  • 更新设置api可以实时的修改索引间隔“refresh_interval”:“ - 1”,设置为-1,关闭自动刷新;当迁移完成之后,恢复"refresh_interval" : "1s"。
  • 在迁移前,可以设置分片数为0,加快迁移速度。

迁移前:

curl -XPUT localhost:9200/test/_settings -d '{
"index" : {
"refresh_interval" : "-1",
"number_of_replicas" : 0
} }'

curl -XPUT localhost:9200/test/_settings -d '{
"index" : {
"refresh_interval" : "1s",
"number_of_replicas" : 1
} }'

刷新

$ curl -XPOST 'http://localhost:9200/twitter/_refresh'
$ curl -XPOST 'http://localhost:9200/kimchy,elasticsearch/_refresh'

可以使用手动强制刷新,使索引马上可用于搜索。默认情况下,索引是根据refresh_interval控制的。

强制合并

force merge API允许通过API强制合并一个或多个索引。合并与Lucene索引在每个分片中保存的段数相关。强制合并操作允许通过合并它们来减少段的数量。

此调用将阻止,直到合并完成。如果http连接丢失,请求将在后台继续,并且任何新请求将阻塞,直到上一次强制合并完成。

$ curl -XPOST 'http://localhost:9200/twitter/_forcemerge'
  • max_num_segments 合并后的最大段文件数,设置为1完全合并。
  • only_expunge_deletes ,Should the merge process only expunge segments with deletes in it. In Lucene, a document is not deleted from a segment, just marked as deleted. During a merge process of segments, a new segment is created that does not have those deletes. This flag allows to only merge segments that have deletes. Defaults to false. Note that this won’t override the index.merge.policy.expunge_deletes_allowed threshold.
  • flush true 合并后是否执行刷新

优化

$ curl -XPOST ‘http://localhost:9200/twitter/_optimize‘

参考

linux日常命令记录2(一直更新)

发表于 2019-05-20

linux命令繁杂,确必不可少,这里用来总结日常用的命令记录,随时更新…

free

free -s 3 # 持续刷新,每3s
free -m 以M展示
free -g 以G展示
free -h 更友好的展示,自动选择单位

free.png

  • total 本机的物理内存+swap
  • used 已使用的内存和swap
  • free 尚未分配的内存和swap
  • shared 被共享的内存的大小,一般可以忽略
  • buff/cache buffer 和 cache 使用的物理内存大小。
  • available 列显示还可以被应用程序使用的物理内存大小。

公式:

  • available = free + buffer/cache
  • total = used + avaliable

free available : 在 free 命令的输出中,有一个 free 列,同时还有一个 available 列。这二者到底有何区别?
free 是真正尚未被使用的物理内存数量。available 是从应用程序的角度看到的可用内存数量。Linux 内核为了提升磁盘操作的性能,会消耗一部分内存去缓存磁盘数据,就是我们介绍的 buffer 和 cache。所以对于内核来说,buffer 和 cache 都属于已经被使用的内存。当应用程序需要内存时,如果没有足够的 free 内存可以用,内核就会从 buffer 和 cache 中回收内存来满足应用程序的请求。所以从应用程序的角度来说,available = free + buffer + cache。请注意,这只是一个很理想的计算方式,实际中的数据往往有较大的误差。

另一种展示方式:
free2.png

  • total 物理内总量
  • used 所有已使用的内存,包括实际使用的+bufers + cached
  • free 未被分配的内存
  • shared 共享内存,一般不会用到
  • buffers 系统分配但未被使用的buffers数量
  • cached 系统分配但未被使用的cache数量
  • 第二行 buffers/cache
    • used2 实际使用的buffers 与cache总量,也是实际使用的内存总量
    • free2 未被使用的buffers 与cache 和未被分配的内存之和,这就是系统当前实际可用内存。

故:
total1 = used1 + free1
total1 = used2 + free2
used1 = buffers1 + cached1 + used2
free2 = free1 + buffers1 + cached1

那么为什么 free 命令不直接称为 cache 而非要写成 buff/cache?

这是因为缓冲区和页高速缓存的实现并非天生就是统一的。在 linux 内核 2.4 中才将它们统一。更早的内核中有两个独立的磁盘缓存:页高速缓存和缓冲区高速缓存。前者缓存页面,后者缓存缓冲区。当你知道了这些故事之后,输出中列的名称可能已经不再重要了。

ss

ss命令用于显示socket状态,建议使用ss命令替代netstat部分命令,例如netsat -ant/lnt等.

time netstat -lnp | wc -l
time ss -lnp | wc -l

ss -tlnp | grep 3100

  • -n 地址使用数字展示(端口默认展示为别名)
  • -p 展示端口所在的进程信息
  • -l 展示listening的端口
  • -t TCP
  • -u UDP

或者

ss -tlnp sport = :3100
sport = :3100
用于过滤

vmstat

vmstat 1 1000 每秒刷新一次,1000次

字段含义说明:

  • Procs(进程)
    • r 等待执行的任务数。展示了正在执行和等待cpu资源的任务个数。当这个值超过了cpu个数,就会出现cpu瓶颈。
    • B 等待IO的进程数量
  • Memory(内存)
    • swpd 正在使用虚拟的内存大小,单位k
    • free 空闲内存大小
    • buff 已用的buff大小,对块设备的读写进行缓冲
    • cache 已用的cache大小,文件系统的cache
    • inact 非活跃内存大小,即被标明可回收的内存,区别于free和active
    • active 活跃的内存大小
  • Swap
    • si 每秒从交换区写入内存的大小(单位:kb/s)(注意是写入)
    • so 每秒从内存写到交换区的大小
  • IO
    • bi 每秒读取的块数(读磁盘)(注意是读!就是读!)。现在的Linux版本块的大小为1024bytes。
    • bo 每秒写入的块数(写磁盘)
  • system
    • in 每秒中断数,包括时钟中断,这两个值越大,会看到由内核消耗的cpu时间会越多
    • cs 每秒上下文切换数
  • CPU(以百分比表示)
    • Us 用户进程执行消耗cpu时间(user time)。us的值比较高时,说明用户进程消耗的cpu时间多,但是如果长期超过50%的使用,那么我们就该考虑优化程序算法或其他措施了
    • Sy 系统进程消耗cpu时间(system time)。sys的值过高时,说明系统内核消耗的cpu资源多,这个不是良性的表现,我们应该检查原因。
    • Id 空闲时间(包括IO等待时间)
    • wa 等待IO时间。Wa过高时,说明io等待比较严重,这可能是由于磁盘大量随机访问造成的,也有可能是磁盘的带宽出现瓶颈。

关于bi,bo,wa: vmstat下表io下面的bi表示读取和bo表示写入,单位是block(硬盘读写的最小单位是扇区,一个扇区是512 bytes。一次硬盘读写的数据量不会超过512 bytes,这一次读写的数据量就称为1个block。在大文件的读写操作中,基本可以按乘512来根据block计算出读写的实际数据量,误差很小。)cpu下面的wa,这个wa就是wait的缩写,代表的意思是CPU在等待硬盘读写操作的时间,用百分比表示。wait越大则机器io性能就越差。

iostat

iostat 1 10 每1s刷新一次,刷新十次

ps

$ ps -ef | grep nginx
$ sudo lsof -nP -i:9300 # 端口 -> 进程
$ sudo netstat -tlnp | grep port # 端口 -> 进程
$ sudo lsof -p 12764 | grep TCP # 进程 -> 端口
$ sudo netstat -tlnp | grep pid # 进程 -> 端口

lsof

查看进程正在打开文件、网络、socket的状态

ps -ef | grep nginx  #进程名 -> pid
sudo lsof -i :9300 # 端口号 -> 进程
sudo lsof -i @140.143.60.140 # ip地址 -> 进程
lsof -p 12764 | grep TCP # 进程 -> 端口
或者
lsof -i | grep 12764 # 进程 -> 端口
lsof -p 12764 # 进程 -> 打开资源状态,包括文件、网络、socket等

-n no host names
-P no port names
-i select by IPv[46] address: [46][proto][@host|addr][:svc_list|port_list]
-p exclude(^)|select PIDs

mac:

sudo lsof -nP -i :20002
sudo lsof -p 1234 -P # -P显示端口号

netstat

查看当前的网络连接状态,主要用来查看本机和外部的的网络连接,包括端口,连接情况

netstat -anp # 所有端口
netstat -tlnp # 所有listening的tcp端口

netstat -tlnp | grep tcp | grep port # 端口 -> 进程
netstat -tlnp | grep pid # 进程 -> 端口

$ netstat -at # 所有 tcp
$ netstat -au # 所有 udp

# -p (root)Show the PID and name of the program to which each socket belongs
# -n 不显示主机\端口\用户名,加速输出
# -t 显示tcp连接
# -u 显示udp

# -a Show both listening and non-listening (for TCP this means established connections) sockets
# -l Show only listening sockets. (These are omitted by default.)

top

命令参数:

top -p 11232 # 仅展示某个进程
top -H -p pid # 显示某个进程所有活跃的线程消耗情况。
top -c # 展示进程详细信息

执行命令:

  • -p 监控特定的PID
  • -c 命令行列显示程序名以及参数
  • -H 设置为线程模式

快捷键总结:

  • V 大写V,以进程树的形式展示命令行列
  • c 命令行列显示程序名以及参数
  • E 更改总体内存展示单位
  • e 更改每个进程内存展示的单位
  • 1 展示每个单核的负荷
  • P 大写P 按cpu排序
  • M 大写M 按内存排序
  • t 可以更动态的展示cpu
  • m 更动态的展示内存
  • f 更改展示字段
  • s 改变更新周期
  • %cpu可能会超过100%,因为是多核心负荷的累加。如果是12核机器,最多可达1200%!。使用cat /proc/cpuinfo| grep "processor"| wc -l查看核心数

实用的内部命令:

top 运行中可以通过 top 的内部命令对进程的显示方式进行控制。内部命令如下:
s – 改变画面更新频率
l – 关闭或开启第一部分第一行 top 信息的表示
t – 关闭或开启第一部分第二行 Tasks 和第三行 Cpus 信息的表示
m – 关闭或开启第一部分第四行 Mem 和 第五行 Swap 信息的表示
N – 以 PID 的大小的顺序排列表示进程列表
P – 以 CPU 占用率大小的顺序排列进程列表
M – 以内存占用率大小的顺序排列进程列表
h – 显示帮助
n – 设置在进程列表所显示进程的数量
q – 退出 top
s – 改变画面更新周期

字段含义:

序号 列名 含义
a PID 进程id
b PPID 父进程id
c RUSER Real user name
d UID 进程所有者的用户id
e USER 进程所有者的用户名
f GROUP 进程所有者的组名
g TTY 启动进程的终端名。不是从终端启动的进程则显示为 ?
h PR 优先级
i NI nice值。负值表示高优先级,正值表示低优先级
j P 最后使用的CPU,仅在多CPU环境下有意义
k %CPU 上次更新到现在的CPU时间占用百分比
l TIME 进程使用的CPU时间总计,单位秒
m TIME+ 进程使用的CPU时间总计,单位1/100秒
n %MEM 进程使用的物理内存百分比
o VIRT 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
p SWAP 进程使用的虚拟内存中,被换出的大小,单位kb。
q RES 驻留内存大小。驻留内存是任务使用的非交换物理内存大小,RES=CODE+DATA
r CODE 可执行代码占用的物理内存大小,单位kb
s DATA 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb
t SHR 共享内存大小,单位kb
u nFLT 页面错误次数
v nDRT 最后一次写入到现在,被修改过的页面数。
w S 进程状态(D=不可中断的睡眠状态,R=运行,S=睡眠,T=跟踪/停止,Z=僵尸进程)
x COMMAND 命令名/命令行
y WCHAN 若该进程在睡眠,则显示睡眠中的系统函数名
z Flags 任务标志,参考 sched.h

默认情况下仅显示比较重要的 PID、USER、PR、NI、VIRT、RES、SHR、S、%CPU、%MEM、TIME+、COMMAND 列。可以通过下面的快捷键来更改显示内容。

通过 f 键可以选择显示的内容。按 f 键之后会显示列的列表,按 a-z 即可显示或隐藏对应的列,最后按回车键确定。
按 o 键可以改变列的显示顺序。按小写的 a-z 可以将相应的列向右移动,而大写的 A-Z 可以将相应的列向左移动。最后按回车键确定。
按大写的 F 或 O 键,然后按 a-z 可以将进程按照相应的列进行排序。而大写的 R 键可以将当前的排序倒转。

ip

ip addr  # 网络状态
ifconfig # 网络状态

# 外网地址
$ curl ifconfig.me
$ curl icanhazip.com
$ curl ident.me

ls

mac:
ls -lT
-T 参数展示详细时间

linux:
ls --full-time
ls -la --time-style=full-iso
--full-time
--time-style=full-iso

ps

ps -ef、ps aux命令可以展示进程的大概信息,启动时间展示不完整,如果想得知进程的详细启动时间,怎么办呢?

linux:
$ ps -eo pid,lstart,cmd

outPut:
PID STARTED CMD
1 Mon Jun 19 21:31:08 2017 /sbin/init
2 Mon Jun 19 21:31:08 2017 [kthreadd]
3 Mon Jun 19 21:31:08 2017 [ksoftirqd/0]

系统信息

# linux内核信息
cat /proc/version
uname -a
# 查看linux发行版信息
lsb_release -a
# cpu核心数
cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c
# 内存情况
cat /proc/meminfo
free
# 磁盘情况
sudo fdisk -l

Linux查看物理CPU个数、核数、逻辑CPU个数
# 总核数 = 物理CPU个数 X 每颗物理CPU的核数
# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数
# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq
# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l
# 查看CPU信息(型号)
cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c

pidstat

使用示例

pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ]

# pid4989的cpu详细详细,每个1s打印一次,打印8次
pidstat -u -p 4989 1 8
# 内存
pidstat -r -p 4989 1 8
# 磁盘
pidstat -d -p 4989 1 8
# 展示三者
pidstat -urd -p 4989 1 8
# -h 在一行展示所有信息
pidstat -urdh -p 4989 1 8
# 更多
man pidstat

参数说明

-u:默认的参数,显示各个进程的cpu使用统计
-r:显示各个进程的内存使用统计
-d:显示各个进程的IO使用情况
-p:指定进程号
-w:显示每个进程的上下文切换情况
-t:显示选择任务的线程的统计信息外的额外信息
-T { TASK | CHILD | ALL }
这个选项指定了pidstat监控的。TASK表示报告独立的task,CHILD关键字表示报告进程下所有线程统计信息。ALL表示报告独立的task和task下面的所有线程。
注意:task和子线程的全局的统计信息和pidstat选项无关。这些统计信息不会对应到当前的统计间隔,这些统计信息只有在子线程kill或者完成的时候才会被收集。
-V:版本号
-h:在一行上显示了所有活动,这样其他程序可以容易解析。
-I:在SMP环境,表示任务的CPU使用率/内核数量
-l:显示命令名和所有参数

node进程监控PM2

发表于 2019-04-21

pm2是什么?它是一个node的进程管理和监控工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。

官方文档

callcheat

# Fork mode
pm2 start app.js --name my-api # Name process

# Cluster mode
pm2 start app.js -i 0 # Will start maximum processes with LB depending on available CPUs
pm2 start app.js -i max # Same as above, but deprecated.
pm2 scale app +3 # Scales `app` up by 3 workers
pm2 scale app 2 # Scales `app` up or down to 2 workers total

# Listing

pm2 list # Display all processes status
pm2 jlist # Print process list in raw JSON
pm2 prettylist # Print process list in beautified JSON

pm2 describe 0 # Display all informations about a specific process

pm2 monit # Monitor all processes

# Logs

pm2 logs [--raw] # Display all processes logs in streaming
pm2 flush # Empty all log files
pm2 reloadLogs # Reload all logs

# Actions

pm2 stop all # Stop all processes
pm2 restart all # Restart all processes

pm2 reload all # Will 0s downtime reload (for NETWORKED apps)

pm2 stop 0 # Stop specific process id
pm2 restart 0 # Restart specific process id

pm2 delete 0 # Will remove process from pm2 list
pm2 delete all # Will remove all processes from pm2 list

# Misc

pm2 reset <process> # Reset meta data (restarted time...)
pm2 updatePM2 # Update in memory pm2
pm2 ping # Ensure pm2 daemon has been launched
pm2 sendSignal SIGUSR2 my-app # Send system signal to script
pm2 start app.js --no-daemon
pm2 start app.js --no-vizion
pm2 start app.js --no-autorestart

命令

日志官方文档

# Display option for pm2 logs command
pm2 logs -h

# Display all apps logs
pm2 logs

# Display only logs about process containing "api" in their name
pm2 logs /api/

# It's a regex so you can filter with the normal syntax to filter with OR condition
pm2 logs /server_[12]/

# Display only api app logs
pm2 logs api

# Display X lines of api log file
pm2 logs big-api --lines 1000

cluster模块

node.js试运行在v8引擎上的,以单线程的方式运行,在多核心处理器的系统中并不能发挥其最大性能。可以使用cluster模块,下面
是一个例子:

var cluster = require('cluster');  
var http = require('http');
var os = require('os');

var numCPUs = os.cpus().length;

if (cluster.isMaster) {
// Master:
// Let's fork as many workers as you have CPU cores

for (var i = 0; i < numCPUs; ++i) {
cluster.fork();
}
} else {
// Worker:
// Let's spawn a HTTP server
// (Workers can share any TCP connection.
// In this case its a HTTP server)

http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world");
}).listen(8080);
}

pm2替代cluster模块

// 原始代码
var http = require('http');

http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world");
}).listen(8080);

执行pm2 start app.js -i 4,-i <number of workers>参数用来告诉PM2以cluster_mode的形式运行你的app(对应的叫fork_mode),后面的数字表示要启动的工作线程的数量。如果给定的数字为0,PM2则会根据你CPU核心的数量来生成对应的工作线程。

优点:

  • 不论什么情况下,你的应用将一直运行,如果任意一个工作线程挂了,PM2会立即重启一个。
  • 实时扩展集群,如果你需要增加工作线程的数量,可以通过pm2 scale <app name> <n>来对集群进行扩展。参数指定工作线程的数量,被用来增加或减少集群数。你也可以通过pm2 scale app +3的方式来指定要增加多少工作线程。
  • 生产环境零停机更新。PM2的reload <app name>功能将依次重启所有的工作线程。每一个线程会等待在新的线程创建之后才会被终止掉。
  • 使用gracefulReload功能可以达到相同的目的,不同的是它不会立即终止工作线程,而是通过IPC发送一个shutdown信号来关闭所有当前的连接并处理一些自定义的任务,然后再优雅地退出。
process.on('message', function(msg) {  
if (msg === 'shutdown') {
close_all_connections();
delete_cache();
server.close();
process.exit(0);
}
});

PM2使用

  • 安装 npm install -g pm2
  • 更多命令
# 安装
$ npm install -g pm2
# 启动 --name tank是给这个进程取个名字
$ pm2 start index.js --name tankName

# 常用命令
$pm2 stop <app_name|id|all> 停止

$pm2 delete <app_name|id|all> 删除

$pm2 restart <app_name|id|all> 重启

$pm2 reload <app_name|id|all> 重载

跟多命令


$ pm2 start app.js -i 4 #后台运行pm2,启动4个app.js
# 也可以把'max' 参数传递给 start
# 正确的进程数目依赖于Cpu的核心数目
$ pm2 start app.js --name my-api # 命名进程
$ pm2 list # 显示所有进程状态
$ pm2 monit # 监视所有进程
$ pm2 logs # 显示所有进程日志
$ pm2 stop all # 停止所有进程
$ pm2 restart all # 重启所有进程
$ pm2 reload all # 0秒停机重载进程 (用于 NETWORKED 进程)
$ pm2 stop 0 # 停止指定的进程
$ pm2 restart 0 # 重启指定的进程
$ pm2 startup # 产生 init 脚本 保持进程活着
$ pm2 web # 运行健壮的 computer API endpoint (http://localhost:9615)
$ pm2 delete 0 # 杀死指定的进程
$ pm2 delete all # 杀死全部进程

运行进程的不同方式:
$ pm2 start app.js -i max # 根据有效CPU数目启动最大进程数目
$ pm2 start app.js -i 3 # 启动3个进程
$ pm2 start app.js -x #用fork模式启动 app.js 而不是使用 cluster
$ pm2 start app.js -x -- -a 23 # 用fork模式启动 app.js 并且传递参数 (-a 23)
$ pm2 start app.js --name serverone # 启动一个进程并把它命名为 serverone
$ pm2 stop serverone # 停止 serverone 进程
$ pm2 start app.json # 启动进程, 在 app.json里设置选项
$ pm2 start app.js -i max -- -a 23 #在--之后给 app.js 传递参数
$ pm2 start app.js -i max -e err.log -o out.log # 启动 并 生成一个配置文件
你也可以执行用其他语言编写的app ( fork 模式):
$ pm2 start my-bash-script.sh -x --interpreter bash
$ pm2 start my-python-script.py -x --interpreter python

0秒停机重载:
这项功能允许你重新载入代码而不用失去请求连接。
注意:
仅能用于web应用
运行于Node 0.11.x版本
运行于 cluster 模式(默认模式)
$ pm2 reload all

CoffeeScript:
$ pm2 start my_app.coffee #这就是全部

PM2准备好为产品级服务了吗?
只需在你的服务器上测试
$ git clone https://github.com/Unitech/pm2.git
$ cd pm2
$ npm install # 或者 npm install --dev ,如果devDependencies 没有安装
$ npm test

错误日志位置:

error log path /root/.pm2/pids/anaweb-13.pid

错误日志,出现启动 error找她就没错

out log path /root/.pm2/logs/anaweb-out-13.log

参考1: https://cnodejs.org/topic/5021c2cff767cc9a51e684e3
参考2: http://tcrct.iteye.com/blog/2043644
forever github: https://github.com/foreverjs/forever

git实用总结

发表于 2019-04-15

git小记

  • https://githowto.com/aliases
  • git rebase -i HEAD~4用来合并最近4次的提交
  • git log origin/master查看的是本地版本库的origin/master分支,如果要看远程,需要先git fetch从远程取下来再git ls orign/master
  • 工作区 暂存区 本地版本库 远程版本库
  • git rebase branchname是变基合并,它不会产生新的merge记录,会把之前的提交记录依次放在本分支;
  • 尽量不要在master分支git rebase branch操作
  • git commit --amend用于修改上一次commit信息
  • git reflog用于查看git操作日志,可以方便的用git reset --hard hashid回滚,或git reset --hard HEAD^到上一个版本
  • git push origin dev:dev是推送分支到远程的命令;dev和dev存在追踪关系时,git push origin dev;当前分支且存在追踪关系,git push origin;只有一个远程分支,且当前分支存在远程追踪关系git push
  • git pull <远程主机名> <远程分支名>:<本地分支名>同push,git pull git pull origin shaoc 都是简写
  • git merge --no-ff shaoc不使用fast-forward,肯定会生成一个合并历史,方便查看历史。git merge如果不存在冲突的时候,默认会使用fast-forward就不会生成新的commit
  • git pull --rebase,fetch后,合并时使用rebase方式,等于git fetch + git merge;git pull = git fetch + git rebase
  • git stash功能是把所有没有提交的修改暂存到stash里面。可用git stash pop恢复,git stash drop用于删除。
  • stash save message推荐使用,等价于stash push -m "message";如果要暂存部分,stash push log/file -m "sth"
  • git checkout -b b1 origin/master表示从origin/master创建并切换分支b1

场景1:多人开发当前dev分支,拉取代码可以使用git pull --rebase,避免生成多余记录;但是如果冲突太多,一个个合并就太麻烦了,需要使用git pull

场景2:分支feature正在开发,最好及时拉取master的最新代码合并,可以使用git merge --no-ff origin/master,强制记一条记录;如果冲突不多,使用git rebase origin/master,把master的提交记录都平移过来

场景3:分支阶段开发完成,需要合会主分支,请使用git merge --no-ff branch,这样不管有没有冲突都会记录一条commit信息;如果预计没有冲突,且想把分支的提交记录平移过来,而不是只记录一条提交日志,请使用git rebase branch,不推荐这样,是因为会导致主分支的commit历史记录收到影响。

合并是很常见的操作,那么到底什么时间改用什么命令呢?两者的使用需要,具体看冲突commit次数的多少,是否想要平移commit记录,是否需要记录一次merge历史,根据需求自行选择;

stash

  • git stash 暂存
  • git stash save 'shaoc'推荐加一个message,等价于git stash push -m "message"
  • stash list 查看
  • stash drop [name],默认移除最近一个
  • stash clear, 清空stash list
  • stash show [name],查看某个stash
  • stash pop
  • stash apply

默认情况下,git stash会缓存下列文件:

  • 添加到暂存区的修改(staged changes)
  • Git跟踪的但并未添加到暂存区的修改(unstaged changes)

但不会缓存以下文件:

  • 在工作目录中新的文件(untracked files)
  • 被忽略的文件(ignored files)

git stash命令提供了参数用于缓存上面两种类型的文件。使用
-u或者--include-untracked可以stash untracked文件。使用-a或者–all命令可以stash当前目录下的所有修改。

git stash push 和 git stash save

git stash push 目前不被推荐使用了,推荐使用save

功能相同点:

  • 用于本地存储修改,并加一个message说明,git stash git stash save git stash push这个三个命令是等价的

不同:

  • 语法不同
// 这两者是等价的
git stash save "mytemp"
git stash push -m "mytemp"
  • push和单独暂存某个文件
// 只暂存subDir.txt一个文件
git stash push subDir/subDir.txt
git stash save subDir/subDir.txt(不会生效)

git一些命令

分支->主干

git checkout master  //切换分支
git pull (--rebase) //拉取代码,根据需要选择rebase或merge的冲突处理
git merge shaoc //合并,用merge
git rebase shaoc //合并,用rebase
git commit and push //提交

主干合并到分支

$ git checkout b1
$ git pull (--rebae) //更新到最新
$ git merge (--no-ff) origin/master // merge合并,--no-ff强制生成一个commit记录,不论是否有冲突
$ git rebase origin/master // rebase合并,线性历史,但一个个处理冲突
$ git rebase --continue
$ git rebase --abort

删除操作

git branch -d Chapater8 //删除本地分支
git push origin --delete shaoc //删除远程分支1
git branch -r -d origin/cat //删除远程分支2
git rm --cached readme1.txt //删除readme1.txt的跟踪,并保留在本地。
git rm --f readme1.txt //删除readme1.txt的跟踪,并且删除本地文件。

fork合并操作步骤

//fork 情况下合并到本地分支
//此时origin,为你自己的地址
git remote add upstream URL
git fetch upstream 拉去原作者的仓库更新
git checkout master 切换到自己的master
git merge upstream/master
git rebase upstream/master
merge或者rebase到你的master

git reflog

git reflog //查看历史
git reset --hard hashid //跳转到某个id
git reset --hard HEAD^ //跳转到前一个
git show HEAD@{2} //查看某个记录

强制拉取到最新

//强制拉取到最新
git fetch --all //拉取所有remote
git git reset --hard origin/master // 移动到origin/master
(参考)
git fetch //查看
git checkout . // 情况本地所有工作区
git stash //收藏本地所有内容

git stash

// 收藏文件或修改
git stash
git stash save 'message' //保存时打标记
git stash list
git stash show stash@{0}
git stash pop|apply
git stash pop stash@{0} //应用任意暂存到当前
git diff file //查看文件修改
git stash drop stash@{1}

git branch

git push origin dev  //本地dev分支,push到远程dev分支,不存在则创建
git push origin dev:dev2 // 本地devpush到远程dev2
git checkout -b branch1 origin/branch1 //从远程分支创建本地新分支并检出

git branch //列出本地分支
git branch -r //查看远程分支
git branch -a //所有分支:本地 and 远程
git branch dev //创建本地dev分支
git branch -d dev //删除本地dev分支
git branch -r -d origin/dev //删除远程分支
git branch -vv //查看本地分支对应的远程分支
git branch -m oname newname //分支重命名

git checkout

checkout的作用
// 操作文件
git checkout filename //放弃单个文件修改
git checkout . 放弃当前目录下的修改
// 操作分支
git checkout master 切换分支
git checkout -b branch1 //分支存在则只切换分支;不存在创建并切换到branch1

git log
git log -p -2 //-p 选项展开显示每次提交的内容差异

简单步骤

git stash
git checkout .
git pull
git stash pop
->处理冲突
npm run lint
->处理规范
npm run commit

alias

git config --global alias.ls "log --color --graph --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Cblue %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit"

git config --global alias.lss "log --color --stat --graph --date=format:'%Y-%m-%d %H:%M:%S' --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Cblue %s %Cgreen(%cd) %C(bold blue)<%an>%Creset' --abbrev-commit"

git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

--author='shaoc'
--no-merges

used:
git ls
git ls origin/master
git ls --no-merges origin/master

Jvm命令操作解释

发表于 2019-04-10

介绍了分析java进程或线程的步骤?jstack,jstat,jinfo等命令的用法

分析步骤命令

# 检测运行状态
$ top free

# 查看进程信息
$ ps -ef | grep cc-doc [pid]

# 查看进程堆栈
$ jstack [-l] [-F] pid > 1.txt
$ sz 1.txt

# 列出线程信息,找到占用cpu\时间高的线程tid
$ ps -mp pid -o THREAD,tid,time

# 用linux命令tid转为16进制
$ printf "%x" tid
# ee2f

# 查看线程的堆栈信息,-A30显示后30行
$ jstack pid | grep ee2f -A30

jstat

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

$ jstat [option vmid] [interval[s|ms] [count]]]
$ jstat -gcutil pid 250 20
$ jstat -gc pid 250 20 //250ms间隔,刷新20次

jstack 堆栈跟踪工具

“jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)”
“线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者等待着什么资源”

# jstack -l 3500 
# jstack -lm 3500 > a.txt

jinfo

“jinfo(Configuration Info for Java)的作用是实时地查看和调整虚拟机各项参数。使用jps命令的-v参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值,除了去找资料外,就只能使用jinfo的-flag选项进行查询了”

$jinfo -flags pid
$jinfo pid

jmap java内存映像工具

“jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)
如果不使用jmap命令,要想获取Java堆转储快照,还有一些比较“暴力”的手段:譬如在第2章中用过的-XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出现之后自动生成dump文件,通过-XX:+HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break]键让虚拟机生成dump文件,又或者在Linux系统下通过Kill-3命令发送进程退出信号“吓唬”一下虚拟机,也能拿到dump文件”

“jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。”

$ jmap -dump:format=b,file=dump.bin 21561 # 导出虚拟机dump文件
$ jmap -heap # 显示虚拟机堆信息

# jmap -heap 21561
输出:
Attaching to process ID 21561, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.77-b03

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 89128960 (85.0MB)
MaxNewSize = 357564416 (341.0MB)
OldSize = 179306496 (171.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 135790592 (129.5MB)
used = 47917952 (45.6981201171875MB)
free = 87872640 (83.8018798828125MB)
35.28812364261583% used
From Space:
capacity = 47710208 (45.5MB)
used = 229376 (0.21875MB)
free = 47480832 (45.28125MB)
0.4807692307692308% used
To Space:
capacity = 49283072 (47.0MB)
used = 0 (0.0MB)
free = 49283072 (47.0MB)
0.0% used
PS Old Generation
capacity = 463470592 (442.0MB)
used = 202372448 (192.99740600585938MB)
free = 261098144 (249.00259399414062MB)
43.66457149453832% used

22290 interned Strings occupying 2157896 bytes.

jhat 内存快照分析工具

“JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照”
“一般都不会去直接使用jhat命令来分析dump文件,主要原因有二:一是一般不会在部署应用程序的服务器上直接分析dump文件,即使可以这样做,也会尽量将dump文件复制到其他机器[1]上进行分析,因为分析工作是一个耗时而且消耗硬件资源的过程,既然都要在其他机器进行,就没有必要受到命令行工具的限制了;另一个原因是jhat的分析功能相对来说比较简陋,后文将会介绍到的VisualVM,以及专业用于分析dump文件的Eclipse Memory Analyzer、IBM HeapAnalyzer[2]等工具,都能实现比jhat更强大更专业的分析功能”

$ jhat dump.bin //一般用不到

可视化监测工具 jconsole,jvisualvm

  • “其中JConsole是在JDK 1.5时期就已经提供的虚拟机监控工具,而VisualVM在JDK 1.6 Update7中才首次发布,现在已经成为Sun(Oracle)主力推动的多合一故障处理工具[1],并且已经从JDK中分离出来成为可以独立发展的开源项目。JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。”

启动命令,mac、windows可以在机器上直接启动

$ jconsole
$ jvisualvm
  • “VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随JDK发布的功能最强大的运行监视和故障处理程序,并且可以预见在未来一段时间内都是官方主力发展的虚拟机故障处理工具”, “VisualVM在JDK 1.6 update 7中才首次出现,但并不意味着它只能监控运行于JDK 1.6上的程序,它具备很强的向下兼容能力,甚至能向下兼容至近10年前发布的JDK 1.4.2平台[1]”

补充知识

  1. Java运行时内存分为5个部分:
    • 程序计数器(Program Counter Register) 当前线程所执行的字节码的行号指示器,是线程私有的
    • java虚拟机栈(VM stack) 描述的是java方法执行时的内存模型,每个方法在执行的同时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等。每个方法调用直到执行完成,就对应着一个栈在虚拟机中从入栈到出栈的过程。经常有人把java内存分为栈内存和堆内存,栈就是现在讲的虚拟机栈,或者说是其中的局部变量表部分。线程私有的,生命周期与线程相同。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError,允许动态扩展但是内存不够时,会抛出OutOfMemoryError
    • 本地方法栈(Native Method Stack) 与Vm stack相似,不过他用于为本地方法服务
    • java堆(Heap) 被所有线程共享的区域,启动JVM时初始化,所有对象实例以及数组都要在堆上分配。它是垃圾回收器管理的主要区域,也称为GC堆。可以分为新生代和老年代,再细致一点有Eden空间、From Survivor空间、To Survivor空间。堆内存不够时,会抛出OutOfMemory异常
    • 方法区(Method Area) 各个线程共享的区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池属于方法区的一部分。

jvm内存区域详见: “深入理解Java虚拟机:JVM高级特性与最佳实践。” 第二章、2.2节、 Apple Books.

  1. HotSpot虚拟机常见的垃圾收集器:
  • “Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机新生代收集的唯一选择。大家看名字就会知道,这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。”“Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择”
  • “ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。”
  • “Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器”;“Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。;停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。”
  • “Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用”
  • “Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器外别无选择(还记得上面说过Parallel Scavenge收集器无法与CMS收集器配合工作吗?)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”
  • “CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。;从名字(包含”Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:初始标记(CMS initial mark)并发标记(CMS concurrent mark)重新标记(CMS remark)并发清除(CMS concurrent sweep)”
  • “G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK 1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点” :

    • “并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。

    • 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。

    • 空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行[…]”

垃圾收集器详见: “深入理解Java虚拟机:JVM高级特性与最佳实践。” 第三章、3.5节、 Apple Books.

  1. 对象已死的判断:
    引用计数法:无法解决循环引用的问题
    可达性分析算法:判断从gcRoots是否可达(Java、c#等主流商用语言都用这个算法)
    详见: “深入理解Java虚拟机:JVM高级特性与最佳实践。” 第三章、3.2节、

  2. 回收算法:
    标记清除;复制;标记整理;分代收集算法
    详见: “深入理解Java虚拟机:JVM高级特性与最佳实践。” 第三章、3.3节、

  3. 内存分配策略

  • 对象优先在新生代的Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC;
  • 大对象直接进入老年代;
  • 长期存活的对象直接进入老年代;
  1. Minor GC和Full GC有什么不一样吗?
  • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

  • 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。”

为什么要用redmine

发表于 2018-12-03

Redmine是个项目管理软件。如何降低沟通成本、如何规避开发风险、如何压缩项目人力?这些问题看似高屋建瓴,实际上都不是非高手不可触及的。

假设有一个研发团队,L是老大,他负责接入需求并安排任务。

##一、分配任务
L理解某波需求后,会拉上相关人讨论,讲一下为什么做,做成什么样,怎么做。
会后,经常有不清楚的、不理解的、有矛盾的问题会问到L,L的时间就这么被碎片化的浪费掉了。
而且,那些讨论出来的实现思路、设计方法,以及之后琐碎问题及答案都没有被记录了下来,
日后需要回顾的时候,只能靠脑子回忆,或者找人帮你回忆了。
不管对于L来说,还是对组员们来说,需要一个地方记录下这些事,无论是当时还是半个月后,总会有用到的时候。
用Redmine的话
L理解需求后,创建Task及其子Task,并指派给相应的人,在Task描述上写清楚任务内容。
相关人会收到任务通知,查看任务内容后,对不理解的、有疑问的地方进行回复询问,然后等待L回复解答。
无论时隔多久,大家都可以检索到什么时候做了什么事、遇到哪些问题、当初的设计思路是什么等内容。

二、任务跟踪

L答应产品经理说xx时间点完成项目,所以他要清楚每个人是否能在这个点之前完成任务,而且要时不时的问一下每个人完成进度如何。
组员对于L来说往往是个黑盒,往往只有在任务完成或者要延期时,盒子才会被打开,才能知道,哦~完成了呀,或者,哦?为什么延期?
L无法做到及时的延期风险控制,真的非常不专业。
用Redmine的话
组员每天下班前更新自己的任务进度,登记工时,并写上今天完成的内容。
L可以随时看到这些进度和任务内容,通过项目甘特图,就能及时发现风险并对其进行事先规避。

三、周报

L需要每周对上汇报团队工作进度,所以他也要求组员们每周都要发周报给他。
可是组员们对写周报真的很反感,每次都要努力的回忆这周做了什么,还要计算好工时,不能让L觉得自己偷懒,必要的时候还要编一点。
用Redmine的话
因为每天的工作有TASK记录,并有工时记录,所以周报只需要点几下鼠标就能导出了,上边有本周详尽的工作内容以及消耗的工时。

四、线上操作

线上系统的升级、维护、事故处理等都需要严格的执行步骤,特别是与钱有关的关键服务,都要谨慎处理。
但是经常会有人马虎大意,不是忘记这个就是忘记那个,一不小心,公司就损失了xx万,执行人可能会被批斗一顿,L也会被要求加强流程管理。
领导说的轻松,一句我们要有流程呀,L就要好好想想了,怎么保证不出错呢?
用Redmine的话
线上操作的执行人创建一个TASK,写清楚操作的目的,步骤,以及每一步的check方法。
写完后他需要找另外一个人进行review,检查步骤是否合理,是否有遗漏,有问题就回复抛出建议,没问题就回复review通过。
通过后执行人才可以执行,关键步骤最好也让审核人在旁边看着操作,完成后让审核人检查执行结果。
这样做到了至少双人的double check,而且线上操作的TASK记录了操作的整个过程,一旦出现问题,回顾以及追责都是非常方便的。

五、项目Wiki

Word、PPT、PDF、邮件,写文档、发文档、更新文档,要不要这么麻烦!
我们需要一个集中的、有层次的、方便分享的文档管理工具,那就是Wiki。
Redmine的Wiki本身的优势并不大,但是一个明显的好处是它和项目、版本、甚至具体TASK结合在一起。
比如在某个Task中需要出一个小文档,那就可以写一个Wiki页面,并附在Task中,反过来依然可以。
就像自从超市有了灯泡卖,就不用非要去五金店买了,你要的,都在Redmine上了,而且都是彼此关联的。

六、其他说明:

  1. 能否让工具发挥它应有的作用,不是工具本身好不好,关键看使用者是否好好发挥它。
    很多团队只是简单的用一下Redmine,Task更新不及时,Wiki组织不当,都反而会让Redmine成为一个累赘。
    所以,重点是培养团队的习惯,而不只是学会用Redmine而已。
  2. 上述优势并不是Redmine的全部,还有很多值得发觉的亮点在,这些只是我亲身带一个团队使用它之后,给我们团队带来的好处。
  3. 像L这种项目管理的角色,能做的都做好,尽量减少组员不必要的沟通和打断,不能只做个需求传达者,
    有些事先想好,然后告诉每个人他们要做的事即可,这样能大大提高团队整体的工作效率,尤其是对程序员这种需要静下心来不被打断的工种,一定要好好呵护。
    当然,L还要尽量让组员了解大局,了解为什么,不能让他们认为自己只是个螺丝钉而已,要让他们成为操着整个航母心的螺丝钉

<完>

https://www.cnblogs.com/zuohongming/p/4495164.html

数据结构中的几种树的区别

发表于 2018-11-27

常见的树结构

B-/+

平衡二叉树

红黑树

mongoDB索引

发表于 2018-11-23

mongo支持多种index类型,其最大的作用就是提高查询性能,可以避免对collection的逐文档扫描。index保存指定的或者多个field值,并且按照field值的顺序排序,索引条目的有序性使得“相等比较eq”、range查询等性能很高。
mongo内部的索引结构采用B-/+树来实现,单次对索引的查询最多访问h(层高)个节点,时间复杂度为O(logN)。

基本操作命令

//创建索引
db.collec.createIndex({age:1,name:1},{unique:true,sparse:true,background:true,expireAfterSeconds:10,name:"index_name"})

db.collection.getIndexes()
db.collection.dropIndex("indexName")
db.collection.dropIndexes() //删除所有
db.col.totalIndexSize() // 查看索引大小 (bytes)
db.col.totalSize() //集合大小 (bytes)

//unique sparse background expireAfterSeconds参数下面会有说明

注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。

  • mongo提供两种建所以的方式foreground和background。foreground(默认)即前台操作,它会阻塞用户对数据的读写操作直到index构建完毕,即任何需要获取read、write操作都会阻塞;background即后台模式,不阻塞数据读写操作,独立的后台线程异步构建索引,此时仍然允许对数据的读写操作;其中background比foreground更加耗时。

索引分类

下面我们要说的mongo最常用的几种索引类型,有_id索引 单字段索引 组合索引 复合索引 地理空间索引 文本索引 哈希索引等

  1. _id索引:每个document都必须有_id字段,如果不指定,mongodb会默认生成;_id字段为内置的“唯一索引”,整个collection中不会存在_id值相同的document,这是mongodb的内部机制,开发者无法改变,也不需要开发者显式的为_id建立索引,当然也无法remove它。通过_id字段来查询document是性能极高的,所以如果尽可能的通过_id查询或者update数据。
  2. 单字段索引 也叫普通索引,对单个字段建立的索引
  3. 组合索引Compound 对多个field建立的索引。比如{user:1,age:-1},索引条目按user正序排列,相同user索引条目再根据age值倒序排列。需要特别注意的是索引的field顺序很重要!。需要结合具体的查询和排序去分析,下面会提到建组合索引的注意事项
  4. 复合键索引(Multikey) 对document中的数组字段创建索引,就是复合键索引
  5. 地理空间索引(Geospatial) 为了支持高效的地址位置数据查询,mongodb提供了2种特殊的索引,2d索引(坐标平面)和2sphere索引(经纬球面)
  6. 文本索引(Text) 支持对collection中的string内容进行搜索操作,即text search;mongodb内置支持几种语言的分词,不过这种文本搜索的能力与专业的搜索引擎相比,还相当简陋。不支持中文~
  7. 哈希索引(Hash) 主要使用场景为“hash sharding”,即在“Sharding”架构模式中,对sharding key使用hash索引,以便数据的sharding和路由操作。hash索引只支持一个字段,它不能和其他字段组合,同时也不能是“unique”,使用db.<collection>.createIndex({a : "hashed"})来创建hash索引。

索引属性(property)

  1. 唯一索引(Unique):即索引字段的值是唯一的,全局无重复;如果尝试存入重复的值(write),操作将会被拒绝。可以通过db..createIndex({ “userid” : 1}, {unique : true})创建唯一索引。我们可以对一个组合索引使用unique属性,表示它们的组合值不能重复,默认情况下unique为false。如果插入的document不包含某个索引字段,那么此字段的值保存为null,mongodb也会用unique约束这样的文档,即不包含某个字段的文档只能保存一条。比如唯一索引{“x” : 1, “y” : 1},只能插入一条{“x” : “content”}(等同于{“x” : “content”,”y” : null})。对于hash索引而言,则不能使用unique属性。unique通常与sparse一起使用。

  2. 稀疏索引(Sparse) :只有当document中包含索引字段时,才会为此文档建立索引,但是这些文档仍然能够保存成功·;因为mongodb的document结构是模式自由的,所以document中不包含某个Field也是“正常的”;稀疏索引将会忽略那些document中不包含索引字段的记录,因此稀疏索引并没有包含collection中的所有文档;非sparse索引,将会为那些缺失的字段存储为null,因此索引包含了全部文档。 我们可以将“Sparse”和“Unique”选项同时约束一个index,以避免包含重复值的document,同时还忽略那些不包含索引字段的document(不建立索引,但是文档仍然会被保存)。当sparse与unique同时约束索引时,比如createIndex({“score” : 1}, {sparse : true, unique: true}),如果文档中不包含score字段,那么文档都可以保存成功,但是不会对这些文档建立索引,但是对于包含了score字段的文档,那么score值则不能重复。

  3. TTL索引 对“date”、“timestamp”类型的字段建立TTL索引,并在索引选项中指定索引保存的时长,那么mongodb将会在一定时间之后自动移除那些“过期”的索引和documents。通过TTL索引,可以实现一个清洗数据的“定时任务”。TTL索引只能支持一个字段(同hash索引,不能对组合索引使用此特性),不能和其他字段组合,即一个索引中只能有一个字段;比如document中created字段类型为date,表示文档插入的时间,我们希望文档在3600秒后过期删除,那么可以通过db..createIndex({ “created” : 1},{expireAfterSeconds : 3600})创建索引,其中“expireAfterSeconds”为TTL索引的必须选项。如果document中不包含索引字段,文档将不会过期。mongodb将会创建一个后台线程,读取index值并删除那些过期的document;在createIndex时,mongodb也会在构建索引的同时检测过期,删除那些过期的数据。TTL索引和众多的缓存服务一样,不能保证数据过期则立即被删除(对用户操作不可见),mongodb扫描数据(从头到尾)是有时间间隔的(60秒),因此过期数据的删除可能存在延迟问题。对于“replica Set”,这个后台线程只在primary上运行,primary上的删除操作同步给secondaries。mongodb的存储引擎的特性决定,这种大量的随机删除会导致严重的存储碎片,索引如果collection中存在TTL索引时,将会开启“usePowerOf2Sizes”选项而且不能修改,“usePowerOf2Sizes”可以降低存储碎片的可能。collection.createIndex(new Document("created",1),new IndexOptions().expireAfter(15L,TimeUnit.MILLISECONDS));

索引的基本策略

虽然索引可以提高query的查询效率,当然也会增加write时带来的开支,增加内存、磁盘的使用。建索引时要根据业务的查询和排序条件来合理选择。

索引的选择性。是判定一个索引筛选数据的能力,越高效的索引其选择性越高,即可以排除掉更多的不匹配数据,更少的数据遍历。比如文档中有一个字段status,这个status有2个合法值“new”、“processed”,如果在status字段上建立索引,那么此索引的选择性就比较低,因为它只能筛选掉一半数据,这也意味着即使索引生效,也需要遍历整个collection中一半的数据才能得到最终结果,事实上对status建立索引是没有必要的。

  • 索引支持匹配最左前缀,比如索引{x: 1, y : 1}能支持 {x : 1} 和 {x : 1, y : 1}两个key的查询。
  • 尽量避免大量数据内存排序 设计索引时尽量能直接获取到排好序的结果,性能最好;如果不能,mongo可以将数据进行内存中的排序,在没有使用索引情况下排序操作所占用内存不得超过32M,否则将中断排序。
  • 多字段索引顺序很重要 sort操作使用索引时必须要和index顺序一致,比如索引{a : 1, b : 1},将不支持sort( { b : 1, a : 1});且排序的方向必须和index声明的保持一致,比如上述索引支持{a : 1, b : 1} 和 { a : -1, b : -1},但无法支持{a : 1, b : -1} 和 { a : -1, b : 1}。
  • 查询和排序字段结合的分析

     如果参与排序的字段(和顺序)和索引字段一样或者是索引的前缀字段,且查询条件中也包含索引的前缀字段,那么mongodb则可以使用索引进行排序,这是一种比较高效的做法。比如索引{ a : 1, b : 1, c : 1},如下查询均可以利用索引排序:
    db.data.find().sort( { a: 1 } )
    db.data.find().sort( { a: -1 } )
    db.data.find().sort( { a: 1, b: 1 } )
    db.data.find().sort( { a: -1, b: -1 } )
    db.data.find().sort( { a: 1, b: 1, c: 1 } )
    db.data.find( { a: { $gt: 4 } } ).sort( { a: 1, b: 1 } )
    如果参与排序的字段不是索引的前缀字段,具体是否能够利用索引排序,就要求查询条件的字段需要为索引的最左前缀字段,同时查询条件的字段和sort字段的也应该构成组合索引的左前缀,比如索引 { a : 1, b : 1, c: 1},那么此索引将支持如下几种排序方式:
    db.data.find( { a: 5 } ).sort( { b: 1, c: 1 } )
    db.data.find( { b: 3, a: 4 } ).sort( { c: 1 } )
    db.data.find( { a: 5, b: { $lt: 3} } ).sort( { b: 1 } )
    如果查询字段和sort字段不能构成组合索引的左前缀,那么将不能高效的利用索引排序,或者有可能不会使用此索引,比如:
    db.data.find( { a: { $gt: 2 } } ).sort( { c: 1 } )
    db.data.find( { c: 5 } ).sort( { c: 1 } )

优化建议

1.索引覆盖查询
mongoDB可以做索引覆盖查询,这种查询不会访问指针指向的文档,而是直接用索引的数据返回结果。只有当要查询返回的字段,都在索引中的时候。

2.常见慢查询:
1).不等于和不包含查询一般不使用索引 $ne 或者$nin
2).通配符在前面的模糊查询, like ‘%xxx’
3).无索引的count 查询 和 排序(复合索引顺序不匹配)
4).多个范围查询(范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引)
5).skip跳过过多的行数
6).$where操作符

3.对现有的数据大表建立索引的时候,采用后台运行方式

db.test.ensureIndex({"username":1,"age":-1},{"background":true}) #默认情况下background是false。

查询计划explain

db.person.find( {age: 18} ).explain()可以查看mongo的查询计划,包括索引的使用、文档扫描的多少、查询步骤等信息

一个例子

mongo-9552:PRIMARY&gt; db.person.find({age: 18}).explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.person",
"indexFilterSet" : false,
"parsedQuery" : {
"age" : {
"$eq" : 18
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"age" : 1
},
"indexName" : "age_1",
"isMultiKey" : false,
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 1,
"direction" : "forward",
"indexBounds" : {
"age" : [
"[18.0, 18.0]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "localhost",
"port" : 9552,
"version" : "3.2.3",
"gitVersion" : "b326ba837cf6f49d65c2f85e1b70f6f31ece7937"
},
"ok" : 1
}

stage常见类型

如explain.queryPlanner.winningPlan.stage和explain.queryPlanner.winningPlan.inputStage等。
文档中仅有如下几类介绍

COLLSCAN 全表扫描

IXSCAN 索引扫描

FETCH 根据索引去检索指定document

SHARD_MERGE 将各个分片返回数据进行merge

但是根据源码中的信息,个人还总结了文档中没有的如下几类(常用如下,由于是通过源码查找,可能有所遗漏)

SORT 表明在内存中进行了排序(与老版本的scanAndOrder:true一致)

LIMIT 使用limit限制返回数

SKIP 使用skip进行跳过

IDHACK 针对_id进行查询

SHARDING_FILTER 通过mongos对分片数据进行查询

COUNT 利用db.coll.explain().count()之类进行count运算

COUNTSCAN count不使用用Index进行count时的stage返回

COUNT_SCAN count使用了Index进行count时的stage返回

SUBPLA 未使用到索引的$or查询的stage返回

TEXT 使用全文索引进行查询时候的stage返回

<完>

并发策略 cas算法

发表于 2018-11-19

并发策略-CAS算法

  对于并发控制而言,我们平时用的锁(synchronized,Lock)是一种悲观的策略。它总是假设每一次临界区操作会产生冲突,因此,必须对每次操作都小心翼翼。如果多个线程同时访问临界区资源,就宁可牺牲性能让线程进行等待,所以锁会阻塞线程执行。

  与之相对的有一种乐观的策略,它会假设对资源的访问是没有冲突的。既然没有冲突也就无需等待了,所有的线程都在不停顿的状态下持续执行。那如果遇到问题了无锁的策略使用一种叫做比较交换(CAS Compare And Swap)来鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突。CAS算法是非阻塞的,它对死锁问题天生免疫,而且它比基于锁的方式拥有更优越的性能。

  CAS算法的过程是这样:它包含三个参数 CAS(V,E,N)。V表示要更新的变量,E表示预期的值,N表示新值。仅当V值等于E值时,才会将V的值设置成N,否则什么都不做。最后CAS返回当前V的值。CAS算法需要你额外给出一个期望值,也就是你认为现在变量应该是什么样子,如果变量不是你想象的那样,那说明已经被别人修改过。你就重新读取,再次尝试修改即可。

  JDK并发包有一个atomic包,里面实现了一些直接使用CAS操作的线程安全的类型。其中最常用的一个类应该就是AtomicInteger。我们以此为例来研究一下没有锁的情况下如何做到线程安全。

private volatile int value;
//这是AtomicInteger类的核心字段,代表当前实际取值,借助volatile保证线程间数据的可见性。

//获取内部数据的方法:
public final int get() {
return value;
}

//我们关注一下incrementAndGet()的内部实现  
1 public final int incrementAndGet() {
2 for (;;) {
3 int current = get();
4 int next = current + 1;
5 if (compareAndSet(current, next))
6 return next;
7 }
8 }

  代码第二行使用了一个死循环,原因是:CAS的操作未必都是成功的,因此对于不成功的情况,我们就需要进行不断的尝试。第三行取得当前值,接着+1得到新值next。这里我们使用CAS必需的两个参数:期望值以及新值。使用compareAndSet()将新值next写入。成功的条件是在写入的时刻,当前的值应该要等于刚刚取到的current。如果不是这样则说明AtomicInteger的值在第3行到第5行之间被其他线程修改过了。当前看到的状态是一个过期的状态,因此返回失败,需要进行下一次重试,知道成功为止。


public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

//整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。大概的逻辑应该是这样:

if (this == expect) {
this = update
return true;
} else {
return false;
}

  CAS虽然能高效的解决原子问题,但是CAS也会带来1个经典问题即ABA问题:

  因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

  ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

  从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类在内部不仅维护了对象值,还维护了一个时间戳(可以是任意的一个整数来表示状态值)。当设置对象值时,对象值和状态值都必须满足期望值才会写入成功。因此即使对象被反复读写,写会原值,只要状态值发生变化,就能防止不恰当的写入。  

/**
* @param expectedReference 期望值
* @param newReference 写入新值
* @param expectedStamp 期望状态值
* @param newStamp 新状态值
* @return true if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}

— 不管多忙,每天给自己预留至少半小时的学习时间,拒绝做代码垃圾的搬运工!

mongo中使用$type查询字段的BSON类型

发表于 2018-10-09

$type操作符用来查询某个字段是否是的指定BSON类型。当文档的数据类型是高度的非结构化时,这非常的有用
{ field: { $type: <BSON type> } },你可以通过数组或别名来指定BSON的type
{ field: { $type: [ <BSON type1> , <BSON type2>, ... ] } },这将匹配数组中BSON类型的任意一个

BSON type总结(version 3.2)

3.2版本可以使用别名或数字来指定BSON类型,在3.2之前的版本只能使用数字来匹配。

Type Number Alias Notes
Double 1 “double”
String 2 “string”
Object 3 “object”
Array 4 “array”
Binary data 5 “binData”
Undefined 6 “undefined” 废弃
ObjectId 7 “objectId”
Boolean 8 “bool”
Date 9 “date”
Null 10 “null”
RegularExpression 11 “regex”
DBPointer 12 “dbPointer”
JavaScript 13 “javascript”
Sysmbol 14 “symbol” 废弃
JavaScript(with scope) 15 “javascriptWithScope”
32-bit integer 16 “int”
Timestamp 17 “timestamp”
64-bit integer 18 “long”
Decimal128 19 “decimal” 3.4版本新增
Min key -1 “minKey”
Max Key 127 “maxKey”

示例

源数据

[
{ "_id" : 1, address : "2030 Martian Way", zipCode : "90698345" },
{ "_id" : 2, address: "156 Lunar Place", zipCode : 43339374 },
{ "_id" : 3, address : "2324 Pluto Place", zipCode: NumberLong(3921412) },
{ "_id" : 4, address : "55 Saturn Ring" , zipCode : NumberInt(88602117) }
]

查询zipCode字段是string的:

db.addressBook.find( { "zipCode" : { $type : 2 } } );
//等价于
db.addressBook.find( { "zipCode" : { $type : "string" } } );

//返回结果
{ "_id" : 1, "address" : "2030 Martian Way", "zipCode" : "90698345" }

zipCode的BSON类型是double的:

db.addressBook.find( { "zipCode" : { $type : 1 } } )
//等价于
db.addressBook.find( { "zipCode" : { $type : "double" } } )
//返回结果
{ "_id" : 2, "address" : "156 Lunar Place", "zip" : 43339374 }

使用number类型别名匹配BSON类型是double``int或long的:

db.addressBook.find( { "zipCode" : { $type : "number" } } )
//返回结果
{ "_id" : 2, address : "156 Lunar Place", zipCode : 43339374 }
{ "_id" : 3, address : "2324 Pluto Place", zipCode: NumberLong(3921412) }
{ "_id" : 4, address : "55 Saturn Ring" , zipCode : 88602117 }

示例2

源数据

[
{ "_id" : 1, name : "Alice King" , classAverage : 87.333333333333333 },
{ "_id" : 2, name : "Bob Jenkins", classAverage : "83.52" },
{ "_id" : 3, name : "Cathy Hart", classAverage: "94.06" },
{ "_id" : 4, name : "Drew Williams" , classAverage : 93 }
]

查询classAverage字段是BSON类型string或double的:

db.grades.find( { "classAverage" : { $type : [ 2 , 1 ] } } );
//等价于
db.grades.find( { "classAverage" : { $type : [ "string" , "double" ] } } );
//返回结果
{ "_id" : 1, name : "Alice King" , classAverage : 87.333333333333333 }
{ "_id" : 2, name : "Bob Jenkins", classAverage : "83.52" }
{ "_id" : 3, name : "Cathy Hart", classAverage: "94.06" }

使用forever/pm2让node.js持久运行

发表于 2018-10-09

这里我们讨论几种使node应用在后台运行的几种方式,简单的是linux后台执行,另外两种是使用进程管理工具forever pm2。

linux nohup & 命令

使用&符号后台执行,并利用nohup命令实现进程禁止挂起

$ nohup node app.js &

forever工具

基本命令

npm install forever -g   #安装
forever start app.js #启动应用
forever stop app.js #关闭应用
forever restartall #重启所有应用

#输出日志和错误
forever start -l forever.log -o out.log -e err.log app.js

# 指定forever信息输出文件,当然,默认它会放到~/.forever/forever.log
forever start -l forever.log app.js

# 指定app.js中的日志信息和错误日志输出文件,
# -o 就是console.log输出的信息,-e 就是console.error输出的信息
forever start -o out.log -e err.log app.js

# 追加日志,forever默认是不能覆盖上次的启动日志,
# 所以如果第二次启动不加-a,则会不让运行
forever start -l forever.log -a app.js

# 监听当前文件夹下的所有文件改动(不太建议这样)
forever start -w app.js

# 显示所有运行的服务
forever list

######停止操作

# 停止所有运行的node App
forever stopall

# 停止其中一个node App
forever stop app.js

# 当然还可以这样
# forever list 找到对应的id,然后:
forever stop [id]

# 开发环境下
NODE_ENV=development forever start -l forever.log -e err.log -a app.js
# 线上环境下
NODE_ENV=production forever start -l ~/.forever/forever.log -e ~/.forever/err.log -w -a app.js
#上面加上NODE_ENV为了让app.js辨认当前是什么环境用的
The End

PM2

PM2是一个内置的负载平衡器Node.js应用生产过程管理。它允许您永久地保存应用程序,并在不停机的情况下重新加载它们

# 安装
$ npm install -g pm2
# 启动 --name tank是给这个进程取个名字
$ pm2 start index.js --name tankName

# 常用命令
$pm2 stop <app_name|id|all> 停止

$pm2 delete <app_name|id|all> 删除

$pm2 restart <app_name|id|all> 重启

$pm2 reload <app_name|id|all> 重载

跟多命令


$ pm2 start app.js -i 4 #后台运行pm2,启动4个app.js
# 也可以把'max' 参数传递给 start
# 正确的进程数目依赖于Cpu的核心数目
$ pm2 start app.js --name my-api # 命名进程
$ pm2 list # 显示所有进程状态
$ pm2 monit # 监视所有进程
$ pm2 logs # 显示所有进程日志
$ pm2 stop all # 停止所有进程
$ pm2 restart all # 重启所有进程
$ pm2 reload all # 0秒停机重载进程 (用于 NETWORKED 进程)
$ pm2 stop 0 # 停止指定的进程
$ pm2 restart 0 # 重启指定的进程
$ pm2 startup # 产生 init 脚本 保持进程活着
$ pm2 web # 运行健壮的 computer API endpoint (http://localhost:9615)
$ pm2 delete 0 # 杀死指定的进程
$ pm2 delete all # 杀死全部进程

运行进程的不同方式:
$ pm2 start app.js -i max # 根据有效CPU数目启动最大进程数目
$ pm2 start app.js -i 3 # 启动3个进程
$ pm2 start app.js -x #用fork模式启动 app.js 而不是使用 cluster
$ pm2 start app.js -x -- -a 23 # 用fork模式启动 app.js 并且传递参数 (-a 23)
$ pm2 start app.js --name serverone # 启动一个进程并把它命名为 serverone
$ pm2 stop serverone # 停止 serverone 进程
$ pm2 start app.json # 启动进程, 在 app.json里设置选项
$ pm2 start app.js -i max -- -a 23 #在--之后给 app.js 传递参数
$ pm2 start app.js -i max -e err.log -o out.log # 启动 并 生成一个配置文件
你也可以执行用其他语言编写的app ( fork 模式):
$ pm2 start my-bash-script.sh -x --interpreter bash
$ pm2 start my-python-script.py -x --interpreter python

0秒停机重载:
这项功能允许你重新载入代码而不用失去请求连接。
注意:
仅能用于web应用
运行于Node 0.11.x版本
运行于 cluster 模式(默认模式)
$ pm2 reload all

CoffeeScript:
$ pm2 start my_app.coffee #这就是全部

PM2准备好为产品级服务了吗?
只需在你的服务器上测试
$ git clone https://github.com/Unitech/pm2.git
$ cd pm2
$ npm install # 或者 npm install --dev ,如果devDependencies 没有安装
$ npm test

错误日志位置:

error log path /root/.pm2/pids/anaweb-13.pid

错误日志,出现启动 error找她就没错

out log path /root/.pm2/logs/anaweb-out-13.log

参考1: https://cnodejs.org/topic/5021c2cff767cc9a51e684e3
参考2: http://tcrct.iteye.com/blog/2043644
forever github: https://github.com/foreverjs/forever

ps -ef、 ps aux、top、pidstat

发表于 2018-08-30

linux中ps -ef 和 ps aux都支持用来查看进程快照,下面总结了他们的显示格式区别和各列的含义。(ps -aux已废弃,不要再用了)
推荐使用ps -ef

ps -ef 标准的格式显示进程(System V风格)

UID PID PPID C STIME TTY TIME CMD
用户 进程id 父进程id cpu百分比 启动时间 所在终端 占用cpu时间 命令参数
root 1 0 0 06:50 ? 00:00:10 /sbin/init
root 40 1 0 12:33 pts/0 00:00:03 /java/

其中各列的内容意思如下

  • UID //用户ID、但输出的是用户名
  • PID //进程的ID
  • PPID //父进程ID
  • C //进程占用CPU的百分比
  • STIME //进程启动时间
  • TTY //该进程在那个终端上运行,若与终端无关,则显示? 若为pts/0等,则表示由网络连接主机进程。
  • TIME //进程使用cpu的时间
  • CMD //命令的名称和参数

ps -ef展示启动时间

ps -ef、ps aux命令可以展示进程的大概信息,启动时间展示不完整,如果想得知进程的详细启动时间,怎么办呢?

linux:
$ ps -eo pid,lstart,cmd

outPut:
PID STARTED CMD
1 Mon Jun 19 21:31:08 2017 /sbin/init
2 Mon Jun 19 21:31:08 2017 [kthreadd]
3 Mon Jun 19 21:31:08 2017 [ksoftirqd/0]

ps aux BSD的格式来显示

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 1.2 19221 2989 ? S 12:20 00:09 /sbin/init

同ps -ef 不同的有列有

  • USER //用户名
  • %CPU //进程占用的CPU百分比
  • %MEM //占用内存的百分比
  • VSZ //该进程使用的虚拟內存量(KB)
  • RSS //该进程占用的固定內存量(KB)(驻留中页的数量)
  • STAT //进程的状态
  • START //该进程被触发启动时间
  • TIME //该进程实际使用CPU运行的时间

其中STAT状态位常见的状态字符有

  • D //无法中断的休眠状态(通常 IO 的进程);
  • R //正在运行可中在队列中可过行的;
  • S //处于休眠状态;
  • T //停止或被追踪;
  • W //进入内存交换 (从内核2.6开始无效);
  • X //死掉的进程 (基本很少见);
  • Z //僵尸进程;
  • < //优先级高的进程
  • N //优先级较低的进程
  • L //有些页被锁进内存;
  • s //进程的领导者(在它之下有子进程);
  • l //多线程,克隆线程(使用 CLONE_THREAD, 类似 NPTL pthreads);
    • //位于后台的进程组;

top

使用示例

$ top -p 4989
$ top

列说明

PID,USER进程号,用户名。

NI,nice,好看不好看。。动态修正CPU调度。范围(-20~19)。越大,cpu调度越一般,越小,cpu调度越偏向它。一般用于后台进程,调整也是往大了调,用来给前台进程让出CPU资源。

PR:优先级,会有两种格式,一种是数字(默认20),一种是RT字符串。

PR默认是20,越小,优先级越高。修改nice可以同时修改PR,测试过程:先开一个窗口,运行wc,另开一个窗口运行top,按N按照PID倒序排,按r输入要renice的PID,然后输入-19~20之间的值,可以看到NI变成输入的值,PR=PR+NI。修改NI得到PR的范围是0~39。优先级由高到低

RT是real-time。只能用chrt -p (1~99) pid来修改。chrt -p 1 1234会将1234的PR改成-2,chrt -p 98 1234变成-99。chrt -p 99 1234会变成RT......只要chrt过,修改nice后PR不会再更改。修改chrt得到的PR范围是RT~-2。优先级由高到低

VIRT:一个进程瞬时可以访问的所有内存总和大小,包括RES自己在使用的,共享的类库,和其他进程共享的内存,内存中的文件数据。共享的类库,一个大文件,只有一个程序片段被用到,这个文件会被 map到VIRT和SHR中,程序片段会在RES中。

S:状态S -- Process Status. The status of the task which can be one of:
D Uninterruptible sleep (usually IO)
R Running or runnable (on run queue)
S Interruptible sleep (waiting for an event to complete)
T Stopped, either by a job control signal or because it is being traced.
W paging (not valid since the 2.6.xx kernel)
X dead (should never be seen)
Z Defunct ("zombie") process, terminated but not reaped by its parent.

%CPU,总体CPU百分比,按H可以显示所有线程。8个核,从0~800%。
%mem,RES占总MEM的百分比
TIME+,自启动到现在占用的CPU时间。

pidstat 进程的信息

使用示例

pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ]

# pid4989的cpu详细详细,每个1s打印一次,打印8次
pidstat -u -p 4989 1 8
# 内存
pidstat -r -p 4989 1 8
# 磁盘
pidstat -d -p 4989 1 8
# 展示三者
pidstat -urd -p 4989 1 8
# -h 在一行展示所有信息
pidstat -urdh -p 4989 1 8
# 更多
man pidstat

参数说明

-u:默认的参数,显示各个进程的cpu使用统计
-r:显示各个进程的内存使用统计
-d:显示各个进程的IO使用情况
-p:指定进程号
-w:显示每个进程的上下文切换情况
-t:显示选择任务的线程的统计信息外的额外信息
-T { TASK | CHILD | ALL }
这个选项指定了pidstat监控的。TASK表示报告独立的task,CHILD关键字表示报告进程下所有线程统计信息。ALL表示报告独立的task和task下面的所有线程。
注意:task和子线程的全局的统计信息和pidstat选项无关。这些统计信息不会对应到当前的统计间隔,这些统计信息只有在子线程kill或者完成的时候才会被收集。
-V:版本号
-h:在一行上显示了所有活动,这样其他程序可以容易解析。
-I:在SMP环境,表示任务的CPU使用率/内核数量
-l:显示命令名和所有参数

linux内存命令-top,free细解

发表于 2018-08-23

linux查看内存

1.top

排序

  • 进程的CPU使用率排序:运行top命令后,键入大写P
  • 内存使用率排序:运行top命令后,键入大写M

各列的说明

  • PID:进程的ID
  • USER:进程所有者
  • PR:进程的优先级别,越小越优先被执行
  • NInice:值
  • VIRT:进程占用的虚拟内存
  • RES:进程占用的物理内存
  • SHR:进程使用的共享内存
  • S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数
  • %CPU:进程占用CPU的使用率
  • %MEM:进程使用的物理内存和总内存的百分比
  • TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。
  • COMMAND:进程启动命令名称

2.free

  • total:总计物理内存的大小。
  • used:已使用多大。
  • free:可用有多少。
  • Shared:多个进程共享的内存总额。
  • Buffers/cached:磁盘缓存的大小。

  • 区别:第二行(mem)的used/free与第三行(-/+ buffers/cache) used/free的区别。 这两个的区别在于使用的角度来看,第一行是从OS的角度来看,因为对于OS,buffers/cached 都是属于被使用,所以他的可用内存是434884KB,已用内存是3489800KB,

  • 第三行所指的是从应用程序角度来看,对于应用程序来说,buffers/cached 是等于可用的,因为buffer/cached是为了提高文件读取的性能,当应用程序需在用到内存的时候,buffer/cached会很快地被回收。
  • 所以从应用程序的角度来说,可用内存 = 系统Free Memory + buffers + cached
usage: free [-b|-k|-m|-g] [-l] [-o] [-t] [-s delay] [-c count] [-V]
-b,-k,-m,-g show output in bytes, KB, MB, or GB
-l show detailed low and high memory statistics
-o use old format (no -/+buffers/cache line)
-t display total for RAM + swap
-s update every [delay] seconds
-c update [count] times
-V display version information and exit

Java线程安全容器小记

发表于 2018-08-22

HashMap线程安全

ConcurrentHashMap

//使用ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap;

public static Map<String, LinkedHashSet<String>> peerAssignHistoryMap = new ConcurrentHashMap<String, LinkedHashSet<String>>();

支持获取的完全并发和更新的所期望可调整并发的哈希表。此类遵守与 Hashtable 相同的功能规范,并且包括对应于 Hashtable 的每个方法的方法版本。不过,尽管所有操作都是线程安全的,但获取操作不 必锁定,并且不 支持以某种防止所有访问的方式锁定整个表。此类可以通过程序完全与 Hashtable 进行互操作,这取决于其线程安全,而与其同步细节无关。

获取操作(包括 get)通常不会受阻塞,因此,可能与更新操作交迭(包括 put 和 remove)。获取会影响最近完成的 更新操作的结果。对于一些聚合操作,比如 putAll 和 clear,并发获取可能只影响某些条目的插入和移除。类似地,在创建迭代器/枚举时或自此之后,Iterators 和 Enumerations 返回在某一时间点上影响哈希表状态的元素。它们不会 抛出 ConcurrentModificationException。不过,迭代器被设计成每次仅由一个线程使用。

这允许通过可选的 concurrencyLevel 构造方法参数(默认值为 16)来引导更新操作之间的并发,该参数用作内部调整大小的一个提示。表是在内部进行分区的,试图允许指示无争用并发更新的数量。因为哈希表中的位置基本上是随意的,所以实际的并发将各不相同。理想情况下,应该选择一个尽可能多地容纳并发修改该表的线程的值。使用一个比所需要的值高很多的值可能会浪费空间和时间,而使用一个显然低很多的值可能导致线程争用。对数量级估计过高或估计过低通常都会带来非常显著的影响。当仅有一个线程将执行修改操作,而其他所有线程都只是执行读取操作时,才认为某个值是合适的。此外,重新调整此类或其他任何种类哈希表的大小都是一个相对较慢的操作,因此,在可能的时候,提供构造方法中期望表大小的估计值是一个好主意。

此类及其视图和迭代器实现了 Map 和 Iterator 接口的所有可选 方法。

此类与 Hashtable 相似,但与 HashMap 不同,它不 允许将 null 用作键或值。

get与containsKey两个方法几乎完全一致:他们都没有使用锁,而是通过Unsafe对象的getObjectVolatile()方法提供的原子读语义,来获得Segment以及对应的链表,然后对链表遍历判断是否存在key相同的节点以及获得该节点的value。但由于遍历过程中其他线程可能对链表结构做了调整,因此get和containsKey返回的可能是过时的数据,这一点是ConcurrentHashMap在弱一致性上的体现。如果要求强一致性,那么必须使用Collections.synchronizedMap()方法。

putIfAbsent

public V putIfAbsent(K key,
V value)
如果指定键已经不再与某个值相关联,则将它与给定值关联。这等价于:
if (!map.containsKey(key))
return map.put(key, value);
else
return map.get(key);
除了原子地执行此操作之外。
指定者:
接口 ConcurrentMap 中的 putIfAbsent
参数:
key - 与指定值相关联的键
value - 与指定键相关联的值
返回:
以前与指定键相关联的值,如果该键没有映射关系,则返回 null
抛出:
NullPointerException - 如果指定键或值为 null

Collections.synchronizedMap


//使用Collections.synchronizedMap
Map m = Collections.synchronizedMap(new HashMap());

返回由指定映射支持的同步(线程安全的)映射。为了保证按顺序访问,必须通过返回的映射完成 所有对底层实现映射的访问。
在返回映射的任意 collection 视图上进行迭代时,用户必须手工在返回的映射上进行同步:

Map m = Collections.synchronizedMap(new HashMap());
…
Set s = m.keySet(); // Needn’t be in synchronized block
…
synchronized(m) { // Synchronizing on m, not s!
Iterator i = s.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}

不遵从此建议将导致无法确定的行为。

HashTable

Set线程安全

Collections.synchronizedSet

返回指定 set 支持的同步(线程安全的)set。为了保证按顺序访问,必须通过返回的 set 完成对 所有底层实现 set 的访问。
在返回的 set 上进行迭代时,用户必须手工在返回的 set 上进行同步:

Set s = Collections.synchronizedSet(new HashSet());
...
synchronized(s) {
Iterator i = s.iterator(); // Must be in the synchronized block
while (i.hasNext())
foo(i.next());
}

不遵从此建议将导致无法确定的行为。

ConcurrentLinkedDeque

Java7中引入的非阻塞并发列表
参考:https://blog.csdn.net/sprita1/article/details/58609070

AtomicInteger

原子操作基本数据类型

以上两段代码,在使用Integer的时候,必须加上synchronized保证不会出现并发线程同时访问的情况,而在AtomicInteger中却不用加上synchronized,在这里AtomicInteger是提供原子操作的,下面就对这进行相应的介绍。

//正常
public class Sample1 {
private static Integer count = 0;
synchronized public static void increment() {
count++;
}
}

//AtomicInteger
public class Sample2 {
private static AtomicInteger count = new AtomicInteger(0);
public static void increment() {
count.getAndIncrement();
}
}

Node.js Event Loop,Timers定时器和process.nextTick()

发表于 2018-08-22

什么是事件循环?

事件循环是Node.js中实现非阻塞IO的机制,它通过及时的转发操作请求给系统内核来实现,尽管JavaScript是单线程的。
目前大部分的系统内核都是多线程的,可以后台同时处理多个操作请求。当某个请求处理完毕后,内核会通知node把对应的回调函数加入poll事件队列等待执行。我们稍后具体阐述这个流程。

事件循环解释

node退出进程时,关闭mongo连接

发表于 2018-07-26

Shouldn’t we be closing the mongo connection somehow? It seems to be increasing with use in the mongod window

var mongoose = require('mongoose');
var gracefulShutdown;
var dbURI = 'mongodb://localhost/mean-todo';
if (process.env.NODE_ENV === 'production') {
dbURI = process.env.MONGOLAB_URI;
}

mongoose.connect(dbURI);

// CONNECTION EVENTS
mongoose.connection.on('connected', function() {
console.log('Mongoose connected to ' + dbURI);
});
mongoose.connection.on('error', function(err) {
console.log('Mongoose connection error: ' + err);
});
mongoose.connection.on('disconnected', function() {
console.log('Mongoose disconnected');
});

// CAPTURE APP TERMINATION / RESTART EVENTS
// To be called when process is restarted or terminated
gracefulShutdown = function(msg, callback) {
mongoose.connection.close(function() {
console.log('Mongoose disconnected through ' + msg);
callback();
});
};
// For nodemon restarts
process.once('SIGUSR2', function() {
gracefulShutdown('nodemon restart', function() {
process.kill(process.pid, 'SIGUSR2');
});
});
// For app termination
process.on('SIGINT', function() {
gracefulShutdown('app termination', function() {
process.exit(0);
});
});
// For Heroku app termination
process.on('SIGTERM', function() {
gracefulShutdown('Heroku app termination', function() {
process.exit(0);
});
});

SVN介绍

发表于 2018-07-13

SVN简介

它是什么

SVN是Subversion的简称,是一个开放源代码的版本控制系统,和git(分布式版本控制)比它采用了集中式版本控制,相较于RCS、CVS,它采用了分支管理系统,它的设计目标就是取代CVS。它管理者很多数据,这些数据放置在一个中央资料档案库(repository) 中。 这个档案库很像一个普通的文件服务器, 不过它会记住每一次文件的变动。 这样你就可以把档案恢复到旧的版本, 或是浏览文件的变动历史。

几个概念

  • repository(源代码库):源代码统一存放的地方
  • Checkout(提取):当你手上没有源代码的时候,你需要从repository checkout一份
  • Commit(提交):当你已经修改了代码,你就需要Commit到repository
  • Update (更新):当你已经Checkout了一份源代码, Update一下你就可以和Repository上的源代码同步,你手上的代码就会有最新的变更
  • Merge (合并): 当你有多个分支时,一般需要从trunk合并到分支,或分支合并到主干
  • Trunk (主分支): 主分支
  • Branches (分支): 分支,开发一般需要新建一个branch,最后将代码合并到Trunk

与git比较

git也是一个开源的分布式版本控制系统,是目前世界上最先进的分布式版本控制系统。相比较git有以下优势:

  • git是分布式的,svn是集中式的。(最核心)
  • git是每个历史版本都存储完整的文件,便于恢复,svn是存储差异文件,历史版本不可恢复。(核心)
  • git可离线完成大部分操作,svn则不能
  • git分支和合并更强大
  • git有着更强的撤销修改和修改历史版本的能力
  • git速度更快,效率更高。

唯一弱点,Git 没有严格的权限管理控制,一般通过系统设置文件读写权限的方式来做权限控制。不过git的发展伴随着开源的发展,与github是亲兄弟。

常用命令

创建分支


svn cp -m "my new branch" http://svn_server/xxx_repository/trunk http://svn_server/xxx_repository/branches/br_feature001

删除分支或tags


svn rm http://svn_server/xxx_repository/branches/br_feature001

svn rm http://svn_server/xxx_repository/tags/release-1.0

checkout 到工作目录


svn co http://svn_server/xxx_repository/branches/br_feature001

从主干合并到分支(主干更新后,想要拉取到我的分支)


cd br_feature001

svn merge http://svn_server/xxx_repository/trunk

合并分支到主干(分支开发完毕后,需要合并到分支)


cd trunk

svn merge --reintegrate http://svn_server/xxx_repository/branches/br_feature001

mongo原子性和事务

发表于 2018-07-12

关于mongodb原子性和事务的说明,大部分翻译自官方文档(https://docs.mongodb.com/manual/tutorial)

原子性和事务

在MongoDb中,写操作在文档级别是原子性的,即使该文档包含多个嵌入的子文档。

当一次写操作修改多个文档时,对每个文档的更新时原子性的,但整个写入操作不是原子性的,而且中间可能会插入多个其他操作。

模拟事务

因为一个文档可以包含多个子文档,所以文档级别的原子性可以满足大部分的应用场景。如果你想把多个操作作为一个事务来处理,你可以实现一个two-phase commit。

然而,two-phase commit仅仅可以提供类事务语义。使用它可以保证数据的一致性,但是在two-phase commit或回滚进行时可能会返回中间数据。关于two-phase commit的更多信息,请看这里 Perform Two Phase Commits

并发控制 Concurrency Control

并发控制可以保证多个应用运行时的一致性,避免导致数据不一致或数据冲突问题。

方法一是在一个有唯一数据值的字段上创建一个唯一索引unique index。这样可以防止插入或更新产生重复数据。在多个字段上创建唯一索引,来保证这多个字段组合值的唯一性。示例操作请看,update() and Unique Index, findAndModify() and Unique Index。

方法二是,在查询条件上对指定字段的当前值进行判断。在写操作中,two-phase commit模式提供了一个变量,其中的查询条件包含了应用标识application identifier和写操作中数据的预期状态。

nginx入门使用

发表于 2018-07-12

本文综合介绍了Nginx的概念、特性、配置文件说明、常用命令等。

简介

Nginx是一个轻量级、高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。它是由c语言开发,由俄罗斯人伊戈尔·赛索耶夫开发的,第一个公开版本发布于2004年10月4日。因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。其特点是占有内存少,并发能力强,事实上nginx是并发能力最好的web服务器,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

优点

  • 作为 Web 服务器:相比 Apache,Nginx 使用更少的资源,支持更多的并发连接,这点使 Nginx 尤其受到虚拟主机提供商的欢迎,能够支持高达 50,000 个并发连接数的响应。

  • 作为反向代理和负载均衡服务器:Nginx 既可以在内部直接支持 Rails 和 PHP,也可以支持作为 HTTP代理服务器 对外进行服务。

  • 作为邮件代理服务器: Nginx 同时也是一个非常优秀的邮件代理服务器(最早开发这个产品的目的之一也是作为邮件代理服务器)。

  • Nginx 安装非常简单,配置文件非常简洁,Bugs非常少,资源占用低: Nginx 启动特别容易,并且几乎可以做到7*24不间断运行,即使运行数个月也不需要重新启动。

安装步骤(Mac)

brew update
brew search nginx # 检查是否存在
brew info nginx # 查看要安装的信息
brew install nginx # 默认port 8080,访问:localhost:8080
  • 默认的配置文件目录:/usr/local/etc/nginx/nginx.conf
  • 默认安装目录:/usr/local/Cellar/nginx/1.13.9,里面会有以nginx版本号命名的文件夹,这里就是安装的根目录。进入1.13.9/bin可以看到nginx的可执行文件。
  • 在1.13.9目录下,还有一个名字为html的快捷方式文件夹,它指向的是/usr/local/var/www目录,该目录下包含50x.html和index.html文件,是nginx服务器默认的显示页面。当它不存在时,访问出现403错误。
  • 使用nginx命令后台启动服务器

常用命令

nginx            #打开 nginx
nginx -s reload #重新加载配置
nginx -s reopen #重启nginx
nginx -s stop #停止nginx
nginx -s quit #退出nginx
nginx -t #测试配置是否有语法错误

配置文件示例

#运行用户
user nobody;
#启动进程,通常设置成和cpu的数量相等
worker_processes 1;

#全局错误日志及PID文件
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;

#工作模式及连接数上限
events {
#epoll是多路复用IO(I/O Multiplexing)中的一种方式,
#仅用于linux2.6以上内核,可以大大提高nginx的性能
use epoll;

#单个后台worker process进程的最大并发链接数
worker_connections 1024;

# 并发总数是 worker_processes 和 worker_connections 的乘积
# 即 max_clients = worker_processes * worker_connections
# 在设置了反向代理的情况下,max_clients = worker_processes * worker_connections / 4 为什么
# 为什么上面反向代理要除以4,应该说是一个经验值
# 根据以上条件,正常情况下的Nginx Server可以应付的最大连接数为:4 * 8000 = 32000
# worker_connections 值的设置跟物理内存大小有关
# 因为并发受IO约束,max_clients的值须小于系统可以打开的最大文件数
# 而系统可以打开的最大文件数和内存大小成正比,一般1GB内存的机器上可以打开的文件数大约是10万左右
# 我们来看看360M内存的VPS可以打开的文件句柄数是多少:
# $ cat /proc/sys/fs/file-max
# 输出 34336
# 32000 < 34336,即并发连接总数小于系统可以打开的文件句柄总数,这样就在操作系统可以承受的范围之内
# 所以,worker_connections 的值需根据 worker_processes 进程数目和系统可以打开的最大文件总数进行适当地进行设置
# 使得并发总数小于操作系统可以打开的最大文件数目
# 其实质也就是根据主机的物理CPU和内存进行配置
# 当然,理论上的并发总数可能会和实际有所偏差,因为主机还有其他的工作进程需要消耗系统资源。
# ulimit -SHn 65535

}


http {
#设定mime类型,类型由mime.type文件定义
include mime.types;
default_type application/octet-stream;
#设定日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log logs/access.log main;

#sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件,
#对于普通应用,必须设为 on,
#如果用来进行下载等应用磁盘IO重负载应用,可设置为 off,
#以平衡磁盘与网络I/O处理速度,降低系统的uptime.
sendfile on;
#tcp_nopush on;

#连接超时时间
#keepalive_timeout 0;
keepalive_timeout 65;
tcp_nodelay on;

#开启gzip压缩
gzip on;
gzip_disable "MSIE [1-6].";

#设定请求缓冲
client_header_buffer_size 128k;
large_client_header_buffers 4 128k;


#设定虚拟主机配置
server {
#侦听80端口
listen 80;
#定义使用 www.nginx.cn访问
server_name www.nginx.cn;

#定义服务器的默认网站根目录位置
root html;

#设定本虚拟主机的访问日志
access_log logs/nginx.access.log main;

#默认请求
location / {

#定义首页索引文件的名称
index index.php index.html index.htm;

}

# 定义错误提示页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}

#静态文件,nginx自己处理
location ~ ^/(images|javascript|js|css|flash|media|static)/ {

#过期30天,静态文件不怎么更新,过期可以设大一点,
#如果频繁更新,则可以设置得小一点。
expires 30d;
}

#PHP 脚本请求全部转发到 FastCGI处理. 使用FastCGI默认配置.
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

#禁止访问 .htxxx 文件
location ~ /.ht {
deny all;
}

}
}

node.js错误类型

发表于 2018-07-11

本文大致介绍了node.js中四种常见的错误类型,列举了常见的标准错误,比如SyntaxError``ReferenceError等等,然后列举了自定义error和assertError的实例及错误的捕获和处理的几种方式。

四种错误类型:

1.标准错误

这些错误在开发中都很常见,错误类型的有

  • SyntaxError js语法错误
  • ReferenceError 使用未定义变量时抛出
  • RangeError 当一个值不在指定范围内时抛出
  • TypeError 传递错误的参数类型时抛出
  • EvalError 调用eval()失败时抛出
  • URIError 当一个全局的URI函数被错误使用时抛出

2.自定义错误

使用Error构造函数抛出错误信息:

var a = 1;
if(a<2){
throw Error("less than 2");
}
console.log('last') //这里不会在执行

/Users/shaoc/svn/npmtest/src/tableheader/b.js:4
throw Error("less than 2");
^

Error: less than 2
at Error (native)
at Object.<anonymous> (/Users/shaoc/svn/npmtest/src/tableheader/b.js:4:9)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)

3.断言错误

当代码违反业务逻辑时,由assert模块触发

var assert = require("assert")
var a = 1;
assert(a > 10, "a不能小于10")
console.log('last')
assert.js:85

throw new assert.AssertionError({
^
AssertionError: a不能小于10
at Object.<anonymous> (/Users/shaoc/svn/npmtest/src/tableheader/b.js:5:1)
at Module._compile (module.js:570:32)
at Object.Module._extensions..js (module.js:579:10)
at Module.load (module.js:487:32)
at tryModuleLoad (module.js:446:12)
at Function.Module._load (module.js:438:3)
at Module.runMain (module.js:604:10)
at run (bootstrap_node.js:394:7)
at startup (bootstrap_node.js:149:9)
at bootstrap_node.js:509:3

4.系统错误

系统错误是对JavaScript错误Error对象的一个扩展,它们表示程序能够处理的操作错误,这些错误信息都是在系统级别生成的。系统错误实例中除Error实例中的属性外,还包括以下几个属性:

1、error.syscall - 一个表示失败的系统调用信息的字符串
2、error.errno - 一个整数的错误码
3、error.code - 表示错误字符串,通常是大写字母E开头

错误处理

  • node可以使用try catch 捕获异常,但是没有办法捕获到异步调用中发生的异常
  • 使用错误优先的回调函数,function(err, data) //优先处理错误信息
const fs = require('fs');
fs.readFile('一个不存在的文件', (err, data) => {
if (err) {
console.error('读取文件错误', err);
return;
}
// 其它处理
});
  • 使用事件机制捕获异常
const net = require('net');
const connection = net.connect('localhost');

// 为stream添加一个 'error' 事件处理器:
connection.on('error', (err) => {
// 如果连接被服务器重置或连接不到指定的服务器
// 或其它连接错误时,错误会被传递到这里
console.error(err);
});

connection.pipe(process.stdout);

//全局
process.on('uncaughtException', function (err) {
log.app.error("Caught exception:----->", err);
process.exit(8);
});
  • 使用domain

node.js中的module.exports和exports

发表于 2018-05-15

之前对exports和module.exports一直存在疑惑,知道exports使用不当可能会导致问题,所以一直使用module.exports, 理解没有深入。通过阅读官方文档,对它们的区别、联系和使用注意事项做一个总结。

翻译自官方文档

官方文档
在node每个模块中,都有一个module变量保存着对当前模块的引用。为了方便,module.exports也可以通过exports全局模块来访问。module不是全局的,对每一个模块来说,它都是一个本地变量。

module.exports

module.exports对象,是由模块系统创建的。当你想在其他类中引用一个模块时,可以用它来实现,通过将你要导出的对象赋值给module.exports。千万注意,本地的exports对象仅仅在初始的时候保存着对module.exports的引用,有些时候可能不会得到你想要的结果。
举个例子来说,创建一个a.js的模块:

const EventEmitter = require('events');
module.exports = new EventEmitter();
// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(() => {
module.exports.emit('ready');
}, 1000);

然后在另一个模块,我们可以引入它:

const a = require('./a');
a.on('ready', () => {
console.log('module "a" is ready');
});

请注意,对module.exports的复制必须是立即执行的,不可以在任何回调中进行导出。比如,下面这样无效:

setTimeout(() => {
module.exports = { a: 'hello' };
}, 0);

exports shortcut

exports变量仅在模块的文件作用域内有效,在初始时,它保存着对module.exports的应用。所以有些时候,module.exports.f = ...可以简写为exports.f = ....。但是,千万注意,在对它进行重新赋值之后,它将不再绑定到module.exports:

module.exports.hello = true; // Exported from require of module
exports = { hello: false }; // Not exported, only available in the module

当对module.exports重新赋值为新对象的时候,最好把exports也重新赋值:

module.exports = exports = function Constructor() {
// ... etc.
};

require实现

为了更好的解释这一点,下面是require()方法的简要实现,它和实际的实现基本原理相同:

function require(/* ... */) {
const module = { exports: {} };
((module, exports) => {
// Module code here. In this example, define a function.
function someFunc() {}
exports = someFunc;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = someFunc;
// At this point, the module will now export someFunc, instead of the
// default object.
})(module, module.exports);
return module.exports;
}

总结

  • 在一个模块内,exports仅仅是对module.exports的引用
  • 在未对exports重新赋值前,可以使用exports.myfun=function(){}的形式导出,但请不要使用exports = { } 的形式
  • 整对象导出时,尽量使用module.exports=exports= function Constructor(){ }的形式,避免出现漏洞

node.js异步框架thenjs

发表于 2018-05-14

项目中存在大量的es5回调的代码,而且生产环境对原生Promise、awati/async 支持不够好,所以使用then.js作为异步解决方案,避免回调地狱的问题,也能兼容老代码。本文简要介绍了then.js异步框架的特点、安装和基本用法。

它是什么

一个解决js异步回调问题的方案,它可以将“回调地狱”处理成并列的then链,方便代码的阅读和维护。
官网地址

特点

  • 可以像标准的 Promise 那样,把N多异步回调函数写成一个长长的 then 链,并且比 Promise 更简洁自然。因为如果使用标准 Promise 的 then 链,其中的异步函数都必须转换成 Promise,Thenjs 则无需转换,像使用 callback 一样执行异步函数即可。

  • 强大的 Error 机制,可以捕捉任何同步或异步的异常错误,甚至是位于异步函数中的语法错误。并且捕捉的错误任君处置。

  • 开启debug模式,可以把每一个then链运行结果输出到debug函数(未定义debug函数则用 console.log),方便调试。

安装&使用

$ npm install thenjs --save
var Thenjs = require("thenjs");

使用方式

demo

Thenjs(function (cont) {
cont(null, result);
})
.then(function (cont) {
cont(new Error('error!'), 123);
})
.fin(function (cont, error, result) {
console.log(error, result);
cont();
})
.each([0, 1, 2], function (cont, value) {
//do sth with value
cont(null); // 并行执行队列任务,把队列 list 中的每一个值输入到 task 中运行
})
.then(function (cont, result) {
console.log(result);
cont();
})
.series([ // 串行执行队列任务
function (cont) { task(88, cont); }, // 队列第一个是异步任务
function (cont) { cont(null, 99); } // 第二个是同步任务
])
.then(function (cont, result) {
console.log(result);
cont(new Error('error!!'));
})
.fail(function (cont, error) { // 通常应该在链的最后放置一个 `fail` 方法收集异常
console.log(error);
console.log('DEMO END!');
});

两种方式定义

Thenjs().then(function(cont) { 
cont(null);
});
Thenjs(function(cont, result) {
cont(result);
})
.then(function(cont, result) {
});

Thenjs.each(array, iterator)

将 array 中的值应用于 iterator 函数(同步或异步),并行执行。返回一个新的 Thenjs 对象。
返回结果result是一个数组,包含每个cont返回的值

Thenjs.each([0, 1, 2], function (cont, value) {
task(value * 2, cont);
})
.then(function (cont, result) {
console.log(result);
});

Thenjs.eachSeries(array, iterator)

同上,串行执行

Thenjs.parallel(tasksArray)

tasksArray 是一个函数(同步或异步)数组,并行执行。返回一个新的 Thenjs 对象。

Thenjs.parallel([
function (cont) { task(88, cont); },
function (cont) { cont(null, 99); }
])
.then(function (cont, result) { //result 是返回结果的数组
console.log(result);
});

Thenjs.series(tasksArray)

同上,接受函数数组参数,串行执行

Thenjs.parallelLimit(tasksArray, limit)

同parallel,并行执行,不过限制最大并发数为limit

Thenjs.eachLimit(array, iterator, limit)

同each,并行执行,限制最大并发数为limit

Thenjs.prototype.then(successHandler, [errorHandler])

如果上一链正确,则进入 successHandler 执行,否则进入 errorHandler 执行。返回一个新的 Thenjs 对象。

Thenjs(function (cont) {
task(10, cont);
})
.then(function (cont, arg) {
console.log(arg);
}, function (cont, error) {
console.error(error);
});

Thenjs.prototype.finally(finallyHandler)

别名fin
无论上一链是否存在 error,均进入 finallyHandler 执行,等效于 .then(successHandler, errorHandler)。返回一个新的 Thenjs 对象。

Thenjs.prototype.fail(errorHandler)

fail 用于捕捉 error,如果在它之前的任意一个链上产生了 error,并且未被 then, finally 等捕获,则会跳过中间链,直接进入 fail。返回一个新的 Thenjs 对象。

prototype也有each eachSeries parallel series

Thenjs.prototype.each(array, iterator)
参数类似 Thenjs.each,返回一个新的 Thenjs 对象。

不同在于,参数可省略,如果没有参数,则会查找上一个链的输出结果作为参数,即上一个链可以这样 cont(null, array, iterator) 传输参数到each。下面三个队列方法行为类似。

Thenjs.prototype.eachSeries(array, iterator)
参数类似 Thenjs.eachSeries,返回一个新的 Thenjs 对象。

Thenjs.prototype.parallel(tasksArray)
参数类似 Thenjs.parallel,返回一个新的 Thenjs 对象。

Thenjs.prototype.series(tasksArray)
参数类似 Thenjs.series,返回一个新的 Thenjs 对象。

Thenjs.prototype.parallelLimit(tasksArray, limit)
参数类似 Thenjs.parallelLimit,返回一个新的 Thenjs 对象。

Thenjs.prototype.eachLimit(array, iterator, limit)
参数类似 Thenjs.eachLimit,返回一个新的 Thenjs 对象。

设计模式6原则

发表于 2017-12-05

设计模式6原则

参考自csdn:设计模式之六大原则

1 单一职责原则 SRP

单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。

==单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。==

2 开闭原则 OCP

开闭原则是面向对象的可复用设计的第一块基石,它是最重要的面向对象设计原则。

开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

任何软件都需要面临一个很重要的问题,即它们的需求会随时间的推移而发生变化。当软件系统需要面对新的需求时,我们应该尽量保证系统的设计框架是稳定的。如果一个软件设计符合开闭原则,那么可以非常方便地对系统进行扩展,而且在扩展时无须修改现有代码,使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。随着软件规模越来越大,软件寿命越来越长,软件维护成本越来越高,设计满足开闭原则的软件系统也变得越来越重要。

为了满足开闭原则,==需要对系统进行抽象化设计,抽象化是开闭原则的关键==。在Java、C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了接口、抽象类等机制,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。==如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。==

3 里氏替换原则 LSP

里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。

==通俗的讲:“老鼠的儿子会打洞”==

里氏代换原则告诉我们,==在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象==。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。

例如有两个类,一个类为BaseClass,另一个是SubClass类,并且SubClass类是BaseClass类的子类,那么一个方法如果可以接受一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub,method1(sub)能够正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:method2(sub),那么一般而言不可以有method2(base),除非是重载方法。

==里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。==

在使用里氏代换原则时需要注意如下几个问题:

(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。

(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。

(3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。

4 依赖倒置原则 DIP

依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

==依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。==

在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。

在实现依赖倒转原则时,==我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。==

5 接口隔离原则 ISP

接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。==每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。==这里的“接口”往往有两种不同的含义:一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象;另外一种是指某种语言具体的“接口”定义,有严格的定义和结构,比如Java语言中的interface。对于这两种不同的含义,ISP的表达方式以及含义都有所不同:

(1) 当把“接口”理解成一个类型所提供的所有方法特征的集合的时候,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分。可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此时,这个原则可以叫做==“角色隔离原则”==。

(2) 如果把“接口”理解成狭义的特定语言的接口,那么ISP表达的意思是指==接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口==。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的所有方法,因此大的总接口使用起来不一定很方便,为了使接口的职责单一,需要将大接口中的方法根据其职责不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并都承担某一单一角色。接口应该尽量细化,同时接口中的方法应该尽量少,每个接口中只包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也称为“定制服务”,即为不同的客户端提供宽窄不同的接口。

6 迪米特法则 LOD

==迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。==

如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。==迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。==

迪米特法则还有几种定义形式,包括:不要和“陌生人”说话、只与你的直接朋友通信等。

迪米特法则要求我们在设计系统时,==应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。==

在将迪米特法则运用到系统设计中时,要注意下面的几点:==在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。==

正则表达式总结

发表于 2017-08-02

正则表达式总结

语法

创建

// 直接实例化
var reg = new RegExp(pattern [, flags]);
// 隐式创建(推荐)
var reg = /pattern/flags;
  • 参数 pattern 是一个字符串,指定了正则表达式的模式或其他正则表达式。

  • 参数 [, flags] 是一个可选的字符串,包含属性 “g”(global )、”i” (ignoreCase)和 “m”(multiline)。

子表达式

在正则表达式中,使用括号括起来的内容是一个子表达式,子表达式匹配到的内容会被系统捕获至缓冲区,使用\n(n:数字)来反向引用系统的第n号缓冲区的内容。

使用场景:后面的内容要求与前面的一致,可以使用子表达式

// 查找连续相同的四个数字
var str = "1212ab45677778cd";
var reg = /(\d)\1\1\1/gi;
console.log(str.match(reg));
// OUTPUT:7777

方括号(字符簇)

var str = "Is this all there is?";
var patt1 = /[a-h]/g;
document.write(str.match(patt1));
// OUTPUT:h,a,h,e,e

方括号 作用

[abc]	查找方括号之间的任何字符。
[^abc]查找任何不在方括号之间的字符。
[0-9] 查找任何从 0 至 9 的数字。同 \d
[a-z] 查找任何从小写 a 到小写 z 的字符。
[A-Z] 查找任何从大写 A 到大写 Z 的字符。
[A-z] 查找任何从大写 A 到小写 z 的字符。
[0-9a-zA-Z] 查找0-9,a-z,A-Z

元字符

元字符(Metacharacter)是拥有特殊含义的字符:
元字符 | 作用
—|—
\ | 转义符 (、)、/、\
| | 选择匹配符,可以匹配多个规则
. | 查找单个字符,除了换行和行结束符。
\w | 查找单词字符。字符 ( 字母 ,数字,下划线_ )
\W | 查找非单词字符。
\d | 查找数字。
\D | 查找非数字字符。
\s | 查找空白字符。空格
\S | 查找非空白字符。
\b | 匹配单词边界。
\B | 匹配非单词边界。
\0 | 查找 NUL 字符。
\n | 查找换行符。
\f | 查找换页符。
\r | 查找回车符。
\t | 查找制表符。
\v | 查找垂直制表符。
\xxx | 查找以八进制数 xxx 规定的字符。
\xdd | 查找以十六进制数 dd 规定的字符。
\uxxxx | 查找以十六进制数 xxxx 规定的 Unicode 字符。

量词

量词	作用
n+ 匹配任何包含至少一个 n 的字符串。同 {1,}
n* 匹配任何包含零个或多个 n 的字符串。同 {0,}
n? 匹配任何包含零个或一个 n 的字符串。同 {0,1}
n{X} 匹配包含 X 个 n 的序列的字符串。
n{X,Y} 匹配包含 X 至 Y 个 n 的序列的字符串。
n{X,} 匹配包含至少 X 个 n 的序列的字符串。
n$ 匹配任何结尾为 n 的字符串。
^n 匹配任何开头为 n 的字符串。注意 /[^a] / 和 /^ [a]/是不一样的,前者是排除的,后者是代表首位。
(?=n) 匹配任何其后紧接指定字符串 n 的字符串。正向预查
(?!n) 匹配任何其后没有紧接指定字符串 n 的字符串。反向预查

RegExp 对象方法

test()

test() 方法检索字符串中是否存在指定的值。返回值是 true 或 false。

var patt1 = new RegExp('e');
console.log(patt1.test('some text'));
// OUTPUT:true
var patt2 = new RegExp('ee');
console.log(patt2.test('some text'));
// OUTPUT:false

exec()

exec() 方法检索字符串中的指定值。返回值是被找到的值。如果没有发现匹配,则返回 null。

var patt1 = new RegExp('e');
console.log(patt1.exec('some text'));
// OUTPUT:e
var patt2 = new RegExp('ee');
console.log(patt2.exec('some text'));
// OUTPUT:null

compile()

compile() 既可以改变检索模式,也可以添加或删除第二个参数。

var patt1=new RegExp("e");
document.write(patt1.test("The best things in life are free")); // true
// 改变了检索模式
patt1.compile("eee");
document.write(patt1.test("The best things in life are free")); // false

支持正则表达式的 String 对象的方法

search 检索与正则表达式相匹配的值,输出所在位置。

var str = "Visit W3School!"
console.log(str.search(/W3School/))
// OUTPUT:6

match 找到一个或多个正则表达式的匹配。

var str="1 plus 2 equal 3"
console.log(str.match(/\d+/g))
// OUTPUT:1,2,3

replace 替换与正则表达式匹配的子串。

var str = "Visit Microsoft!"
console.log(str.replace(/Microsoft/, "W3School"));
// OUTPUT:Visit W3School!

split 把字符串分割为字符串数组。

var str = "How are you doing today?"
document.write(str.split(/\s+/));
// OUTPUT:How,are,you,doing,today?

常见的正则验证

// 常见的 正则表达式 校验
// QQ号、手机号、Email、是否是数字、去掉前后空格、是否存在中文、邮编、身份证、URL、日期格式、IP
var myRegExp = {
// 检查字符串是否为合法QQ号码
isQQ: function(str) {
// 1 首位不能是0 ^[1-9]
// 2 必须是 [5, 11] 位的数字 \d{4, 9}
var reg = /^[1-9][0-9]{4,9}$/gim;
if (reg.test(str)) {
console.log('QQ号码格式输入正确');
return true;
} else {
console.log('请输入正确格式的QQ号码');
return false;
}
},
// 检查字符串是否为合法手机号码
isPhone: function(str) {
var reg = /^(0|86|17951)?(13[0-9]|15[012356789]|18[0-9]|14[57]|17[678])[0-9]{8}$/;
if (reg.test(str)) {
console.log('手机号码格式输入正确');
return true;
} else {
console.log('请输入正确格式的手机号码');
return false;
}
},
// 检查字符串是否为合法Email地址
isEmail: function(str) {
var reg = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
// var reg = /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/;
if (reg.test(str)) {
console.log('Email格式输入正确');
return true;
} else {
console.log('请输入正确格式的Email');
return false;
}
},
// 检查字符串是否是数字
isNumber: function(str) {
var reg = /^\d+$/;
if (reg.test(str)) {
console.log(str + '是数字');
return true;
} else {
console.log(str + '不是数字');
return false;
}
},
// 去掉前后空格
trim: function(str) {
var reg = /^\s+|\s+$/g;
return str.replace(reg, '');
},
// 检查字符串是否存在中文
isChinese: function(str) {
var reg = /[\u4e00-\u9fa5]/gm;
if (reg.test(str)) {
console.log(str + ' 中存在中文');
return true;
} else {
console.log(str + ' 中不存在中文');
return false;
}
},
// 检查字符串是否为合法邮政编码
isPostcode: function(str) {
// 起始数字不能为0,然后是5个数字 [1-9]\d{5}
var reg = /^[1-9]\d{5}$/g;
// var reg = /^[1-9]\d{5}(?!\d)$/;
if (reg.test(str)) {
console.log(str + ' 是合法的邮编格式');
return true;
} else {
console.log(str + ' 是不合法的邮编格式');
return false;
}
},
// 检查字符串是否为合法身份证号码
isIDcard: function(str) {
var reg = /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/;
if (reg.test(str)) {
console.log(str + ' 是合法的身份证号码');
return true;
} else {
console.log(str + ' 是不合法的身份证号码');
return false;
}
},
// 检查字符串是否为合法URL
isURL: function(str) {
var reg = /^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i;
if (reg.test(str)) {
console.log(str + ' 是合法的URL');
return true;
} else {
console.log(str + ' 是不合法的URL');
return false;
}
},
// 检查字符串是否为合法日期格式 yyyy-mm-dd
isDate: function(str) {
var reg = /^[1-2][0-9][0-9][0-9]-[0-1]{0,1}[0-9]-[0-3]{0,1}[0-9]$/;
if (reg.test(str)) {
console.log(str + ' 是合法的日期格式');
return true;
} else {
console.log(str + ' 是不合法的日期格式,yyyy-mm-dd');
return false;
}
},
// 检查字符串是否为合法IP地址
isIP: function(str) {
// 1.1.1.1 四段 [0 , 255]
// 第一段不能为0
// 每个段不能以0开头
//
// 本机IP: 58.50.120.18 湖北省荆州市 电信
var reg = /^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$/gi;
if (reg.test(str)) {
console.log(str + ' 是合法的IP地址');
return true;
} else {
console.log(str + ' 是不合法的IP地址');
return false;
}
}
}
// 测试
// console.log(myRegExp.isQQ('80583600'));
// console.log(myRegExp.isPhone('17607160722'));
// console.log(myRegExp.isEmail('80583600@qq.com'));
// console.log(myRegExp.isNumber('100a'));
// console.log(myRegExp.trim(' 100 '));
// console.log(myRegExp.isChinese('baixiaoming'));
// console.log(myRegExp.isChinese('小明'));
// console.log(myRegExp.isPostcode('412345'));
// console.log(myRegExp.isIDcard('42091119940927001X'));
// console.log(myRegExp.isURL('https://www.baidu.com/'));
// console.log(myRegExp.isDate('2017-4-4'));
// console.log(myRegExp.isIP('1.0.0.0'));

校验数字的表达式

数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$
正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
非负整数:^\d+$ 或 ^[1-9]\d*|0$
非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$

校验字符的表达式

汉字:^[\u4e00-\u9fa5]{0,}$
英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符:^.{3,20}$
由26个英文字母组成的字符串:^[A-Za-z]+$
由26个大写英文字母组成的字符串:^[A-Z]+$
由26个小写英文字母组成的字符串:^[a-z]+$
由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
禁止输入含有~的字符:[^~\x22]+

特殊需求的表达式

Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
身份证号(15位、18位数字):^\d{15}|\d{18}$
短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
日期格式:^\d{4}-\d{1,2}-\d{1,2}
一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
中文字符的正则表达式:[\u4e00-\u9fa5]
双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
空白行的正则表达式:\n\s*\r (可以用来删除空白行)
HTML标记的正则表达式:<(\S*?)[^>]*>.*?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)
IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))

钱的输入格式

1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$
2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$
6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里

+a、NaN

发表于 2017-07-17

关于+a

+a是转为number类型,同parseInt(a)
var a = 1;
console.log(+a); //parseInt(a)=1
console.log(a); //1

NaN

NaN == NaN   //false
NaN === NaN //false

Mac环境变量配置

发表于 2017-06-28

Mac环境变量配置

mac一般使用bash作为默认shell,如果安装了oh my sh,则默认使用zshshell。

Mac系统环境变量的加载顺序:

/etc/profile
/etc/paths
~/.bash_profile
~/.bash_login
~/.profile
~/.bashrc

  • /etc/profile和/etc/paths是系统级别的,系统启动后就会加载。后面几个是当前用户级的环境变量。
  • 如果~/.bash_profile存在,后面几个文件就会忽略不读,不存在时,才会以此类推读取后面的文件。
  • ~/.bashrc没有上述规则,他始终加载,他是在bash shell打开的时候载入的。

设置Path的语法

# 中间使用冒号分隔
export PATH=$PATH:<PATH 1>:<PATH 2>:<PATH 3>:------:<PATH N>

全局设置

下面的几个文件设置是全局的,修改时需要root权限

  • /etc/paths (全局建议修改这个文件 )
    编辑 paths,将环境变量添加到 paths文件中 ,一行一个路径.

/etc/paths 文件:

/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin
  • /etc/profile (建议不修改这个文件 )
    全局(公有)配置,不管是哪个用户,登录时都会读取该文件。

  • /etc/bashrc (一般在这个文件中添加系统级环境变量)
    全局(公有)配置,bash shell执行时,不管是何种方式,都会读取此文件。

单个用户设置

  • ~/.bash_profile (添加用户级环境变量)
    (注:Linux 里面是 .bashrc 而 Mac 是 .bash_profile)
    若bash shell是以login方式执行时,才会读取此文件,该文件仅仅执行一次,默认情况下,他设置一些环境变量。
    设置命令别名
alias ll=’ls -la’

设置环境变量:

export PATH=/opt/local/bin:/opt/local/sbin:$PATH

比如设置ANDROID_HOME到PATH:

export ANDROID_HOME=/Users/shaoc/Library/Android/sdk

export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH
  • ~/.bashrc 同上

一般重启shell设置就会生效,如果想立刻生效,则可执行下面的语句:

$ source 相应的文件

zsh中配置环境变量

在安装了oh my zsh后, .bash_profile 文件中的环境变量就无法起到作用,因为终端默认启动的是zsh,而不是bash shell,所以无法加载。

解决方法1:

在~/.zshrc配置文件中,增加对.bash_profile的引用:

source ~/.bash_profile

.bash_profile文件示例:

export ANDROID_HOME=/Users/pengdan/software/sdk
export NDK=/Users/pengdan/software/android-ndk-r10d
export GRADLE_HOME=/Users/pengdan/software/gradle-2.4
export SUBLIME=/Users/pengdan/home/subin
export PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$GRADLE_HOME/bin:$PATH
export PATH=$PATH:$SUBLIME:/usr/local/mysql/bin
export PATH=$PATH:/Users/pengdan/software/apache-tomcat-7.0.70/bin

解决方法2:
可以使用zsh的方法进行配置:
(1)可以直接在~/.zshrc中添加path或者环境变量
(2)在目录~/oh-my-zsh/custom文件夹下的任何.zsh文件中的环境变量都将会加载。

.zshrc:

# Path to your oh-my-zsh installation.
export ZSH=/Users/shaoc/.oh-my-zsh
source $ZSH/oh-my-zsh.sh

alias zshconfig="vim ~/.zshrc"
source ~/.bash_profile

~/oh-my-zsh/custom/my.zsh:


alias subl=\''/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl'\'

关于Homebrew

发表于 2017-05-24

Homebrew介绍

官网https://brew.sh/index_zh-cn.html

它是什么

Homebrew是macOs下的一个软件包管理器,它是为macOs安装一些Unix上的工具最简单好用灵活的一种方式。

安装

粘贴下面命令到终端并执行:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

常用命令

使用 --verbose 或 -v,很多命令会打印出额外的调试和安装信息

  • $ brew --version 查看版本
  • $ brew install formula 安装软件包
  • $ brew uninstall formula 卸载某个软件包
  • $ brew update 更新homebrew到最新版
  • $ brew list 列出所有安装的软件包
  • $ brew search formula 搜索某个软件包

更多命令http://docs.brew.sh/Manpage.html

他能做什么

  • 可以使用 Homebrew 安装 Apple 没有预装但你需要的东西,比如

    $ brew install wget
  • Homebrew会将软件包安装到独立目录,并将其文件软链接至 /usr/local 。

  • Homebrew 不会将文件安装到它本身目录之外,所以您可将 Homebrew 安装到任意位置。

  • 轻松创建你自己的 Homebrew 包。

    brew create https://foo.com/bar-1.0.tgz
    Created /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/bar.rb
  • 完全基于 git 和 ruby,所以自由修改的同时你仍可以轻松撤销你的变更或与上游更新合并。

  • Homebrew 的配方都是简单的 Ruby 脚本:

    class Wget < Formula
    homepage "https://www.gnu.org/software/wget/"
    url "https://ftp.gnu.org/gnu/wget/wget-1.15.tar.gz"
    sha256 "52126be8cf1bddd7536886e74c053ad7d0ed2aa89b4b630f76785bac21695fcd"

    def install
    system "./configure", "--prefix=#{prefix}"
    system "make", "install"
    end
    end
  • Homebrew 使 macOS 更完整。使用 gem 来安装 gems、用 brew 来安装那些依赖包。

Cordova、Config.xml中几个重要配置解释

发表于 2017-04-26

概述

Config.xml存储着cordova应用的全局配置信息。

本文重点解释了config.xml中的几个常用配置,比如:access allow-navigation allow-intent 等。

更多信息请查看官方参考文档。

widget、name、description、author

  • widget:config.xml中的根元素
  • name:指定app名,它会显示在设备的主屏幕上
  • description:app描述
  • author:作者信息

content

指定app的根页面,默认是index.html
示例:

<widget ...>
<content src="startPage.html"></content>
</widget>

access

配置允许app发起的网络请求(images, XHRs, etc)地址

注意:没有配置access标签时,,只有file://urls被允许访问。但一般情况,app包含一个默认的<access origin="*">标签。

示例:

<!--允许访问 google.com:-->
<access origin="http://google.com" />

<!--允许访问安全地址 google.com (https://):-->
<access origin="https://google.com" />

<!--允许访问 google.com 的所有子域名, 比如 mail.google.com and docs.google.com:-->
<access origin="http://*.google.com" />

<!--不阻止任何网络请求,比如 google.com and developer.mozilla.org:-->
<access origin="*" />
<!--This is the default value for newly created CLI projects.-->

更多请查看network-request-whitelist

allow-navigation

控制内部的WebView可以加载的URL,只适用于顶级导航。

默认情况下,只允许file://类型的URLS访问。如果要加载其他的urls,需要配置<allow-navigation>标签

<!-- 允许访问 example.com -->
<allow-navigation href="http://example.com/*" />

<!-- 允许使用通配符,协议,主机,地址都可以使用 -->
<allow-navigation href="*://*.example.com/*" />

<!-- 使用通配符,允许所有网址访问,包括HTTP and HTTPS and file
*不推荐使用* -->
<allow-navigation href="*" />

<!-- 上面的和下面这三条等价 -->
<allow-navigation href="http://*/*" />
<allow-navigation href="https://*/*" />
<allow-navigation href="data:*" />

更多请查看cordova-plugin-whitelist

allow-intent

定义app可以要求操作系统打开的链接地址,默认,任何外部请求链接都被禁止。

示例:

<!-- 允许在浏览器中打开网址 -->
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />

<!-- 允许在浏览器中打开 example.com -->
<allow-intent href="http://example.com/*" />

<!-- 允许通配符的使用,在协议、主机、地址 -->
<allow-intent href="*://*.example.com/*" />

<!-- 允许在短信应用中打开sms:链接 -->
<allow-intent href="sms:*" />

<!-- 允许在电话应用中打开tel:链接 -->
<allow-intent href="tel:*" />

<!-- 允许在地图应用中打开geo:链接 -->
<allow-intent href="geo:*" />

<!-- 允许在已安装的app中打开未被识别的url地址
*NOT RECOMMENDED* -->
<allow-intent href="*" />

查看更多cordova-plugin-whitelist

engine

Specifies details about what platform to restore during a prepare.

plugin

Specifies details about what plugin to restore during a prepare. This element is automatically added to a project’s config.xml when a plugin is added using the –save flag.

variable

Persists the value of a CLI variable to be used when restoring a plugin during a prepare. This element is added to config.xml when a plugin that uses CLI variables is added using the –save flag.

示例:

<plugin name="cordova-plugin-device" spec="^1.1.0">
<variable name="MY_VARIABLE" value="my_variable_value" />
</plugin>

preference

Sets various options as pairs of name/value attributes. Each preference’s name is case-insensitive. Many preferences are unique to specific platforms, and will be indicated as such.

配置详情:https://cordova.apache.org/docs/en/6.x/config_ref/index.html#preference

feature

If you use the CLI to build applications, you use the plugin command to enable device APIs. This does not modify the top-level config.xml file, so the element does not apply to your workflow. If you work directly in an SDK and using the platform-specific config.xml file as source, you use the tag to enable device-level APIs and external plugins. They often appear with custom values in platform-specific config.xml files. See the API Reference for details on how to specify each feature

更多请参考:feature

platform

When using the CLI to build applications, it is sometimes necessary to specify preferences or other elements specific to a particular platform.

<platform name="android">
<preference name="Fullscreen" value="true" />
</platform>

hook

Represents your custom script which will be called by Cordova when certain action occurs (for example, after plugin is added or platform prepare logic is invoked). This is useful when you need to extend default Cordova functionality.

更多请参考:https://cordova.apache.org/docs/en/6.x/config_ref/index.html#hook

<hook type="after_plugin_install" src="scripts/afterPluginInstall.js" />

date:2017-04-26 10:37:40
版本有变化时,配置可能发生变化,详细信息请参考官方文档

12
Ethan Hunt

Ethan Hunt

Make more time

58 日志
4 分类
20 标签
© 2019 Ethan Hunt
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.3