Wetts's blog

Stay Hungry, Stay Foolish.

0%

转自:https://segmentfault.com/a/1190000011440752

Redis提供了 5 种数据结构,但除此之外,Redis 还提供了注入慢查询分析、Redis Shell、Pipeline、事务、与Lua 脚本、Bitmaps、HyperLogLog、PubSub、GEO 等附加功能,这些功能可以在某些场景发挥很重要的作用。

Pipeline

Pipeline 概念

Redis 客户端执行一条命令分为以下四个步骤:

  1. 发送命令
  2. 命令排队
  3. 命令执行
  4. 返回结果

其中,第一步+第四步称为 Round Trip Time(RTT,往返时间).

没有Pipeline执行n次命令

Redis 提供了批量操作命令(例如 mget、mset 等),有效的节约 RTT。但大部分命令是不支持批量操作的,例如要执行 n 次 hgetall 命令,并没有 mhgetall 存在,需要消耗 n 次 RTT。Redis 的客户端和服务端可能不是在不同的机器上。例如客户端在北京,Redis 服务端在上海,两地直线距离为 1300 公里,那么 1 次 $RTT时间=1300×2/(300000×2/3)=13毫秒$(光在真空中传输速度为每秒 30 万公里,这里假设光纤的速度为光速的 2/3),那么客户端在 1 秒内大约只能执行 80 次左右的命令,这个和 Redis 的高并发高吞吐背道而驰。

Pipeline(流水线)机制能改善上面这类问题,它能将一组 Redis 命令进行组装,通过一次 RTT 传输给 Redis,再将这组 Redis 命令按照顺序执行并装填结果返回给客户端。上图未使用 Pipeline 执行了 n 次命令,整个过程需要 n 个 RTT。

Pipeline 并不是什么新的技术和机制,很多技术上都使用过。而且 RTT 在不同网络环境下会有不同,例如同机房和同机器会比较快,跨机房跨地区会比较慢。Redis 命令真正执行的时间通常在微秒级别,所以才会有 Redis 性能瓶颈是网络这样的说法。

原生批量命令与 Pipeline 对比

可以使用 Pipeline 模拟出批量操作的效果,但是在使用时需要质疑它与原生批量命令的区别,具体包含几点:

  • 原生批量命令是原子性,Pipeline 是非原子性的。
  • 原生批量命令是一个命令对应多个 key,Pipeline 支持多个命令。
  • 原生批量命令是 Redis 服务端支持实现的,而 Pipeline 需要服务端与客户端的共同实现。

Pipeline 总结

Pipeline 虽然好用,但是每次 Pipeline 组装的命令个数不能没有节制,否则一次组装 Pipeline 数据量过大,一方面会增加客户端的等待时机,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的 Pipeline 拆分成多次较小的 Pipeline 来完成。

Pipeline 只能操作一个 Redis 实例,但即使在分布式 Redis 场景中,也可以作为批量操作的重要优化方法.

事务

为了保证多条命令组合的原子性,Redis 提供了简单的事务以及集成 Lua 脚本来解决这个问题。

熟悉关系型数据库的开发者应该对事务比较了解,简单地说,事务表示一组动作,要么全部成功,要不全部不成功。例如在在电商网站中用户购买商品A那么需要将商品A的库存 -1,并创建一个订单。这两个操作要么远不执行成功,要么全部执行不成功,否则会出现数据不一致的情况。

Redis 提供了简单的功能,将一组需要一起执行的命令放到 multi 和 exec 两个命令之间。multi 命令代表事务的开始,exec 命令代表事务结束,他们之间的命令是原子顺序执行的。

例如上述的用户购买商品问题:

1
2
3
4
5
6
127.0.0.1:6379> multi
OK
127.0.0.1:6379> hincrby commodity:a:detail stock -1
QUEUE
127.0.0.1:6379> rpush user:1:orders {"commodity":'a',..}
QUEUE

可以看到数据操作命令返回的结果是 QUEUE,代表命令并没有真正执行,而是暂时保存在 Redis 中。如果此时另一个客户端执行 llen user:1:orders 返回结果为 0。

1
2
127.0.0.1:6379> llen user:1:orders
(integer) 0

只有当 exec 执行后,用户购买商品的行为才算完成,如下两个结果对应 hincrby 和 rpush 命令。

1
2
3
4
5
127.0.0.1:6379> exec
1) (integer) 4 # 商品原库存为5
2) (integer) 1
127.0.0.1:6379> llen user:1:orders
(integer) 1

如果要停止事务的执行,可以使用 discard 命令替代 exec 命令即可.

1
2
3
4
127.0.0.1:6379> discard
OK
127.0.0.1:6379> llen user:1:orders
(integer) 0

如果事务中的命令出现错误,Redis 的处理机制也不尽相同.

命令错误

例如下面操作错将 set 写成了 sett,属于语法错误,会造成整个事务无法执行,key 和 counter 的值未发生变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> mget key counter
1) "hello"
2) "100"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sett key world
(error) ERR unknown command 'sett'
127.0.0.1:6379> incr counter
QUEUE
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> mget key counter
1) "hello"
2) "100"

运行时错误

例如用户购买商品,误把 rpush 写成了 zadd

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> multi
OK
127.0.0.1:6379> hincrby commodity:a:detail stock -1
QUEUED
127.0.0.1:6379> zadd user:1:orders {"commodity":'a',..}
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value.
127.0.0.1:6379> hget commodity:a:detail stack
(integer) 3

可以看到 Redis 并不支持回滚功能,hincrby commodity:a:detail stock -1 命令已经执行成功,开发者需要自己修改这类问题。

转自:https://blog.csdn.net/Roy_70/article/details/78260826

前言

在一些对高并发请求有限制的系统或者功能里,比如说秒杀活动,或者一些网站返回的当前用户过多,请稍后尝试。这些都是通过对同一时刻请求数量进行了限制,一般用作对后台系统的保护,防止系统因为过大的流量冲击而崩溃。对于系统崩溃带来的后果,显然还是拒绝一部分请求更能被维护者所接受。

而在各种限流中,除了系统自身设计的带锁机制的计数器外,利用 Redis 实现显然是一种既高效安全又便捷方便的方式。

incr命令

Redis Incr 命令将 key 中储存的数字值增一。

如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。

如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。

本操作的值限制在 64 位(bit)有符号数字表示之内。
示例:

1
2
3
4
5
6
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> incr num
(integer) 11
127.0.0.1:6379> get num # 数字值在 Redis 中以字符串的形式保存
"11"

注意: 由于 redis 并没有一个明确的类型来表示整型数据,所以这个操作是一个字符串操作。

执行这个操作的时候,key 对应存储的字符串被解析为 10 进制的 64 位有符号整型数据。

事实上,Redis 内部采用整数形式(Integer representation)来存储对应的整数值,所以对该类字符串值实际上是用整数保存,也就不存在存储整数的字符串表示(String representation)所带来的额外消耗。

使用场景

计数器

使用思路是:每次有相关操作的时候,就向 Redis 服务器发送一个 incr 命令。

例如这样一个场景:我们有一个 web 应用,我们想记录每个用户每天访问这个网站的次数。

web 应用只需要通过拼接用户 id 和代表当前时间的字符串作为key,每次用户访问这个页面的时候对这个 key 执行一下 incr 命令。

这个场景可以有很多种扩展方法:

  • 通过结合使用 INCR 和 EXPIRE 命令,可以实现一个只记录用户在指定间隔时间内的访问次数的计数器
  • 客户端可以通过 GETSET 命令获取当前计数器的值并且重置为 0
  • 通过类似于 DECR 或者 INCRBY 等原子递增/递减的命令,可以根据用户的操作来增加或者减少某些值 比如在线游戏,需要对用户的游戏分数进行实时控制,分数可能增加也可能减少。

限速器

限速器是一种可以限制某些操作执行速率的特殊场景。

传统的例子就是限制某个公共 api 的请求数目。

假设我们要解决如下问题:限制某个 api 每秒每个 ip 的请求次数不超过 10 次。

我们可以通过 incr 命令来实现两种方法解决这个问题。

流量控制之 java 实现

这里我们将在 java 中使用 redis-incr 的特性来构建一个 1 分钟内只允许请求 100 次的控制代码,key 代表在 redis 内存放的被控制的键值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static boolean flowControl(String key){
//最大允许100
int max = 100;
long total = 1L;
try {
if (jedisInstance.get(key) == null) {
//jedisInstance是Jedis连接实例,可以使单链接也可以使用链接池获取,实现方式请参考之前的blog内容
//如果redis目前没有这个key,创建并赋予0,有效时间为60s
jedisInstance.setex(key, 60, "0");
} else {
//获取加1后的值
total = jedisInstance.incr(redisKey).longValue();
//Redis TTL命令以秒为单位返回key的剩余过期时间。当key不存在时,返回-2。当key存在但没有设置剩余生存时间时,返回-1。否则,以秒为单位,返回key的剩余生存时间。
if (jedisInstance.ttl(redisKey).longValue() == -1L)
{
//为给定key设置生存时间,当key过期时(生存时间为0),它会被自动删除。
jedisInstance.expire(redisKey, 60);
}
}
} catch (Exception e) {
logger.error("流量控制组件:执行计数操作失败,无法执行计数");
}
long keytotaltransations = max;
//判断是否已超过最大值,超过则返回false
if (total > keytotaltransations) {
return false;
}
return true;
}

文件描述符

Linux 系统预留可三个文件描述符:0、1 和 2,他们的意义如下所示:

  1. 0——标准输入(stdin)
  2. 1——标准输出(stdout)
  3. 2——标准错误(stderr)

标准输出——stdout

假设:在当前目录下,有且只有一个文件名称为 123.txt 的文件,这时我们运行这个命令 ls 123.txt,就会获得一个标准输出 stdout 的输出结果:123.txt

错误输出——stderr

按照上面的假设,我们运行另一条命令 ls abc.txt,这样我们就会获得一个标准错误 stderr 的输出结果 ls:无法访问abc.txt:没有那个文件或目录


重定向

重定向的符号有两个:> 或 >>,两者的区别是:前者会先清空文件,然后再写入内容,后者会将重定向的内容追加到现有文件的尾部。


Linux 特殊文件

/dev/null 是一个特殊的设备文件,这个文件接收到任何数据都会被丢弃。因此,null 这个设备通常也被称为位桶(bit bucket)或黑洞。


find 路径 -name 文件名 2>/dev/null: 过滤掉没有查看权限的文件

转自:https://www.cnblogs.com/huanongying/p/7021555.html

本文实验的测试环境:Windows 10+cmd+MySQL5.6.36+InnoDB

事务的基本要素(ACID)

  1. 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
  2. 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如 A 向 B 转账,不可能 A 扣了钱,B 却没收到。
  3. 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B 不能向这张卡转账。
  4. 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

事务的并发问题

  1. 脏读:事务 A 读取了事务B更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据
  2. 不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致。
  3. 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为 ABCDE 等级,但是系统管理员 B 就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

MySQL 事务隔离级别

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

mysql 默认的事务隔离级别为 repeatable-read
1

用例子说明各个隔离级别的情况

读未提交

  1. 打开一个客户端 A,并设置当前事务模式为 read uncommitted(未提交读),查询表 account 的初始值:
    2

  2. 在客户端 A 的事务提交之前,打开另一个客户端 B,更新表 account:
    3

  3. 这时,虽然客户端 B 的事务还没提交,但是客户端 A 就可以查询到 B 已经更新的数据:
    4

  4. 一旦客户端 B 的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端 A 查询到的数据其实就是脏数据:
    5

  5. 在客户端 A 执行更新语句 update account set balance = balance - 50 where id =1,lilei 的 balance 没有变成 350,居然是 400,是不是很奇怪,数据不一致啊,如果你这么想就太天真 了,在应用程序中,我们会用 400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别
    6

读已提交

  1. 打开一个客户端A,并设置当前事务模式为 read committed(未提交读),查询表 account 的所有记录:
    7

  2. 在客户端 A 的事务提交之前,打开另一个客户端 B,更新表 account:
    8

  3. 这时,客户端B的事务还没提交,客户端 A 不能查询到B已经更新的数据,解决了脏读问题:
    9

  4. 客户端 B 的事务提交
    10

  5. 客户端 A 执行与上一步相同的查询,结果与上一步不一致,即产生了不可重复读的问题
    11

可重复读

  1. 打开一个客户端 A,并设置当前事务模式为 repeatable read,查询表 account 的所有记录
    12

  2. 在客户端 A 的事务提交之前,打开另一个客户端 B,更新表 account 并提交
    13

  3. 在客户端 A 查询表 account 的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题
    14

  4. 在客户端 A,接着执行 update balance = balance - 50 where id = 1,balance 没有变成 400-50=350,lilei 的 balance 值用的是步骤(2)中的 350 来算的,所以是 300,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了 MVCC 机制,select 操作不会更新版本号,是快照读(历史版本);insert、update 和 delete 会更新版本号,是当前读(当前版本)。
    15

  5. 重新打开客户端 B,插入一条新数据后提交
    16

  6. 在客户端 A 查询表 account 的所有记录,没有查出新增数据,所以没有出现幻读
    17

串行化

  1. 打开一个客户端A,并设置当前事务模式为 serializable,查询表 account 的初始值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    mysql> set session transaction isolation level serializable;
    Query OK, 0 rows affected (0.00 sec)

    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select * from account;
    +------+--------+---------+
    | id | name | balance |
    +------+--------+---------+
    | 1 | lilei | 10000 |
    | 2 | hanmei | 10000 |
    | 3 | lucy | 10000 |
    | 4 | lily | 10000 |
    +------+--------+---------+
    4 rows in set (0.00 sec)
  2. 打开一个客户端 B,并设置当前事务模式为 serializable,插入一条记录报错,表被锁了插入失败,mysql 中事务隔离级别为 serializable 时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。

    1
    2
    3
    4
    5
    6
    7
    8
    mysql> set session transaction isolation level serializable;
    Query OK, 0 rows affected (0.00 sec)

    mysql> start transaction;
    Query OK, 0 rows affected (0.00 sec)

    mysql> insert into account values(5,'tom',0);
    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

补充:

  1. 事务隔离级别为读提交时,写数据只会锁住相应的行
  2. 事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是 next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
  3. 事务隔离级别为串行化时,读写数据都会锁住整张表
  4. 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
  5. MYSQL MVCC 实现机制参考链接:https://blog.csdn.net/whoamiyang/article/details/51901888
  6. 关于 next-key 锁可以参考链接:https://blog.csdn.net/bigtree_3721/article/details/73731377

转自:https://www.cnblogs.com/rjzheng/p/9955395.html

引言

大家在面试中一定碰到过

说说事务的隔离级别吧?

老实说,事务隔离级别这个问题,无论是校招还是社招,面试官都爱问!然而目前网上很多文章,说句实在话啊,我看了后我都怀疑作者弄懂没!因为他们对可重复读(Repeatable Read)和串行化(serializable)的解析实在是看的我一头雾水!

再加上很多书都说可重复读解决了幻读问题,比如《mysql技术内幕–innodb存储引擎》等,不一一列举了,因此网上关于事务隔离级别的文章大多是有问题的,所以再开一文说明!

本文所讲大部分内容,皆有官网作为佐证,因此对本文内容你可以看完后,你完全可以当概念记在脑海里,除非官网的开发手册是错的,否则应当无误!
另外,本文会重点说一下

可重复读(Repeatable Read)是否真的解决幻读的问题!

正文

开始我先提一下,根据事务的隔离级别不同,会有三种情况发生。即脏读、不可重复读、幻读。这里我先不提这三种情况的定义,后面在讲隔离级别的时候会补上。

这里,大家记住一点,根据脏读、不可重复读、幻读定义来看(自己总结,官网没有),有如下包含关系:
18

那么,这张图怎么理解呢?

即,如果发生了脏读,那么不可重复读和幻读是一定发生的。因为拿脏读的现象,用不可重复读,幻读的定义也能解释的通。但是反过来,拿不可重复读的现象,用脏读的定义就不一定解释的通了!

假设有表 tx_tb 如下,pId 为主键

pId name
1 zhangsan

读未提交(READ_UNCOMMITTED)

其实这个从隔离名字就可以看出来,一个事务可以读到另一个事务未提交的数据!为了便于说明,我简单的画图说明!
19

如图所示,一个事务检索的数据被另一个未提交的事务给修改了。

官网对脏读定义的地址为https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_dirty_read

其内容为

**dirty read

An operation that retrieves unreliable data, data that was updated by another transaction but not yet committed.

**

翻译过来就是

检索操作出来的数据是不可靠的,是可以被另一个未提交的事务修改的!

你会发现,我们的演示结果和官网对脏读的定义一致。根据我们最开始的推理,如果存在脏读,那么不可重复读和幻读一定是存在的。

读已提交(READ_COMMITTED)

这个也能看的出来,一个事务能读到另一个事务已提交的数据!为了便于说明,我简单的画图说明!
20

如图所示,一个事务检索的数据只能被另一个已提交的事务修改。

官网对不可重复读定义的地址为https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_non_repeatable_read

其内容为

**non-repeatable read

The situation when a query retrieves data, and a later query within the same transaction retrieves what should be the same data, but the queries return different results (changed by another transaction committing in the meantime).

**

翻译过来就是

一个查询语句检索数据,随后又有一个查询语句在同一个事务中检索数据,两个数据应该是一样的,但是实际情况返回了不同的结果。!

ps:作者注,这里的不同结果,指的是在行不变的情况下(专业点说,主键索引没变),主键索引指向的磁盘上的数据内容变了。如果主键索引变了,比如新增一条数据或者删除一条数据,就不是不可重复读。

显然,我们这个现象符合不可重复读的定义。下面,大家做一个思考:

  • 这个不可重复读的定义,放到脏读的现象里是不是也可以说的通。显然脏读的现象,也就是读未提交(READ_UNCOMMITTED)的那个例子,是不是也符合在同一个事务中返回了不同结果!
  • 但是反过来就不一定通了,一个事务 A 中查询两次的结果在被另一个事务 B 改变的情况下,如果事务 B 未提交就改变了事务 A 的结果,就属于脏读,也属于不可重复读。如果该事务B提交了才改变事务A的结果,就不属于脏读,但属于不可重复读。

可重复读(REPEATABLE_READ)

这里,我改变一下顺序,先上幻读的定义

官网对幻读定义的地址为https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_phantom

phantom

A row that appears in the result set of a query, but not in the result set of an earlier query. For example, if a query is run twice within a transaction, and in the meantime, another transaction commits after inserting a new row or updating a row so that it matches the WHERE clause of the query.

翻译过来就是

在一次查询的结果集里出现了某一行数据,但是该数据并未出现在更早的查询结果集里。例如,在一次事务里进行了两次查询,同时另一个事务插入某一行或更新某一行数据后(该数据符合查询语句里 where 后的条件),并提交了!

好了,接下来上图,大家自己评定该现象是否符合幻读的定义
21

显然,该现象是符合幻读的定义的。同一事务的两次相同查询出现不同行。下面,大家做一个思考:

  • 这个幻读的定义,放到不可重复读的现象里是不是也可以说的通。大家自行思考!
  • 反过来就不一定通了。事务第二次查询出了一个数据,但是该数据并未出现在第一次查询的结果集里。如果该数据是修改数据,那么该现象既属于不可重复读,也属于幻读。如果该数据是新增或删除的数据,那该现象就不属于不可重复读,但属于幻读。

接下来说一下,为什么很多文章都产生误传,说是可重复读可以解决幻读问题!原因出自官网的一句话(地址是:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-record-locks)

原文内容如下

By default, InnoDB operates in REPEATABLE READ transaction isolation level. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 14.7.4, “Phantom Rows”).

按照原本这句话的意思,应该是

InnoDB 默认用了 REPEATABLE READ。在这种情况下,使用 next-key locks 解决幻读问题!

结果估计,某个国内翻译人员翻着翻着变成了

InnoDB 默认用了 REPEATABLE READ。在这种情况下,可以解决幻读问题!

然后大家继续你抄我,我抄你,结果你懂的!

显然,漏了”使用了 next-key locks!”这个条件后,意思完全改变,我们在该隔离级别下执行语句

1
select *  from tx_tb where pId >= 1;

是快照读,是不加任何锁的,根本不能解决幻读问题,除非你用

1
select *  from tx_tb where pId >= 1 lock in share mode;

这样,你就用上了next-key locks,解决了幻读问题!

串行读(SERIALIZABLE_READ)

在该隔离级别下,所有的 select 语句后都自动加上 lock in share mode。因此,在该隔离级别下,无论你如何进行查询,都会使用 next-key locks。所有的 select 操作均为当前读!
22

OK,注意看上表红色部分!就是因为使用了 next-key locks,innodb 将 PiD=1 这条索引记录,和 $(1,++∞)$ 这个间隙锁住了。其他事务要在这个间隙上插数据,就会阻塞,从而防止幻读发生!

有的人会说,你这第二次查询的结果,也变了啊,明显和第一次查询结果不一样啊?对此,我只能说,请看清楚啊。这是被自己的事务改的,不是被其他事物修改的。这不算是幻读,也不是不可重复读。

总结

上面罗里吧嗦一大堆,最后来一个表格做总结吧,你面试答这个表就行。上面的一切是为了这张表做准备!

隔离级别 脏读 不可重复读 幻读
读未提交
不可重复读
可重复读
串行化

转自:http://www.cnblogs.com/lzrabbit/p/4298794.html

四个命令

Expect中最关键的四个命令是send,expect,spawn,interact。

  • send:用于向进程发送字符串
  • expect:从进程接收字符串
  • spawn:启动新的进程
  • interact:允许用户交互

send命令

send命令接收一个字符串参数,并将该参数发送到进程。

1
2
expect1.1> send "hello world\n"
hello world

expect命令

基础知识

expect命令和send命令正好相反,expect通常是用来等待一个进程的反馈。expect可以接收一个字符串参数,也可以接收正则表达式参数。和上文的send命令结合,现在我们可以看一个最简单的交互式的例子:

1
2
expect "hi\n"
send "hello there!\n"

这两行代码的意思是:从标准输入中等到hi和换行键后,向标准输出输出hello there。

tips: $expect_out(buffer)存储了所有对expect的输入,<$expect_out(0,string)>存储了匹配到expect参数的输入。

比如如下程序:

1
2
3
expect "hi\n"
send "you typed <$expect_out(buffer)>"
send "but I only expected <$expect_out(0,string)>"

当在标准输入中输入

1
2
test
hi

是,运行结果如下

1
2
3
you typed: test
hi
I only expect: hi

模式-动作

expect最常用的语法是来自tcl语言的模式-动作。这种语法极其灵活,下面我们就各种语法分别说明。

单一分支模式语法:

1
expect "hi" {send "You said hi"}

匹配到hi后,会输出”you said hi”

多分支模式语法:

1
2
3
expect "hi" { send "You said hi\n" } \
"hello" { send "Hello yourself\n" } \
"bye" { send "That was unexpected\n" }

匹配到hi,hello,bye任意一个字符串时,执行相应的输出。等同于如下写法:

1
2
3
4
5
expect {
"hi" { send "You said hi\n"}
"hello" { send "Hello yourself\n"}
"bye" { send "That was unexpected\n"}
}

spawn命令

上文的所有demo都是和标准输入输出进行交互,但是我们跟希望他可以和某一个进程进行交互。spawn命令就是用来启动新的进程的。spawn后的send和expect命令都是和spawn打开的进程进行交互的。结合上文的send和expect命令我们可以看一下更复杂的程序段了。

1
2
3
4
5
6
7
8
9
10
set timeout -1
spawn ftp ftp.test.com //打开新的进程,该进程用户连接远程ftp服务器
expect "Name" //进程返回Name时
send "user\r" //向进程输入anonymous\r
expect "Password:" //进程返回Password:时
send "123456\r" //向进程输入don@libes.com\r
expect "ftp> " //进程返回ftp>时
send "binary\r" //向进程输入binary\r
expect "ftp> " //进程返回ftp>时
send "get test.tar.gz\r" //向进程输入get test.tar.gz\r

这段代码的作用是登录到ftp服务器ftp ftp.uu.net上,并以二进制的方式下载服务器上的文件test.tar.gz。程序中有详细的注释。

interact

到现在为止,我们已经可以结合spawn、expect、send自动化的完成很多任务了。但是,如何让人在适当的时候干预这个过程了。比如下载完ftp文件时,仍然可以停留在ftp命令行状态,以便手动的执行后续命令。interact可以达到这些目的。下面的demo在自动登录ftp后,允许用户交互。

1
2
3
4
5
6
spawn ftp ftp.test.com
expect "Name"
send "user\r"
expect "Password:"
send "123456\r"
interact

NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

安装模块

本地安装

  1. 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。
  2. 可以通过 require() 来引入本地安装的包。
1
npm install express

全局安装

  1. 将安装包放在 /usr/local 下或者你 node 的安装目录。
  2. 可以直接在命令行里使用。
1
npm install express -g

卸载模块

1
npm uninstall express (-g)

更新模块

1
npm update express (-g)

搜索模块

1
npm search express

查看过时的包

1
npm -g outdated

find是linux 命令,它将档案系统内符合 expression 的档案列出来。你可以指要档案的名称、类别、时间、大小、权限等不同资讯的组合,只有完全相符的才会被列出来。find 根据下列规则判断 path 和 expression,在命令列上第一个 - ( ) , ! 之前的部分为 path,之后的是 expression。

用法:find path -option [ -print ] [ -exec -ok command ] {} \;

使用说明:

  • -exec:对搜索的结构指令指定的shell命令。注意格式要正确:”-exec 命令 {} ;“。注意“{}” 与;之间有空格。
  • -mount,-xdev : 只检查和指定目录在同一个档案系统下的档案,避免列出其它档案系统中的档案
  • -amin -n : 在最近的 n 分钟内被读取过
  • -amin +n : 在 n 分钟之前被读取过
  • -anewer file : 比档案 file 更晚被读取过的档案
  • -atime -n : 在最近的 n 天内读取过的档案
  • -atime +n : 在 n 天前读取过的档案
  • -cmin -n : 在最近的 n 分钟内被修改过
  • -cmin +n : 在 n 分钟前被修改过
  • -cnewer file :比档案 file 更新的档案
  • -ctime -n : 在最近的 n 天内修改过的档案
  • -ctime +n : 在 n 天前修改过的档案
  • -empty : 空的档案-gid n or -group name : gid 是 n 或是 group 名称是 name
  • -ipath p,-path p : 路径名称符合 p 的档案,ipath 会忽略大小写
  • -name name,-iname name : 档案名称符合 name 的档案。iname 会忽略大小写
  • -size n[cwbkMG] : 档案大小 为 n 个由后缀决定的数据块。其中后缀含义为:
    • b: 代表 512 位元组的区块(如果用户没有指定后缀,则默认为 b)
    • c: 表示字节数
    • k: 表示 kilo bytes (1024字节)
    • w: 字 (2字节)
    • M:兆字节(1048576字节)
    • G: 千兆字节 (1073741824字节)
  • -type c : 档案类型是 c 的档案。
    • d: 目录
    • c: 字型装置档案
    • b: 区块装置档案
    • p: 具名贮列
    • f: 一般档案
    • l: 符号连结
    • s: socket
  • -pid n : process id 是 n 的档案

-depth参数

参数-depth 的意思是:在处理目录以前首先处理目录下的子内容。

也即是说在不加-depth的时候,处理顺序是首先处理目录本身,然后处理目录下的子内容。加不加-depth参数,会影响输出结构的输出顺序。


find 路径 -name 文件名 2>/dev/null: 过滤掉没有查看权限的文件

百度百科:

DS_Store 是用来存储这个文件夹的显示属性的:比如文件图标的摆放位置。删除以后的副作用就是这些信息的失去。(当然,这点副作用其实不是太大)。

删除当前目录的.DS_store

1
find . -name '*.DS_Store' -type f -delete

删除所有目录的.DS_store

1
sudo find / -name ".DS_Store" -depth -exec rm {} \;

禁止.DS_store生成:

1
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool TRUE

恢复.DS_store生成:

1
defaults delete com.apple.desktopservices DSDontWriteNetworkStores

  • #include< file >:编译程序会先到标准函数库中找文件
  • #include “file”:编译程序会先从当前目录中找文件

转自:http://blog.doyoe.com/2015/03/15/css/%E7%BD%AE%E6%8D%A2%E5%92%8C%E9%9D%9E%E7%BD%AE%E6%8D%A2%E5%85%83%E7%B4%A0/

内联的置换和非置换元素的宽度定义

对于内联的非置换元素,宽度设置是不适用的。

对于内联的置换元素来说,其宽度的设置需遵循以下几点:

  • 若宽高的计算值都为 auto 且元素有固有宽度,则 width 的使用值为该固有宽度;

    典型的例子是:拥有默认宽高的 input 当宽度的计算值为auto时,则宽度使用值为其默认的固有宽度

  • 若宽度的计算值为 auto 且元素有固有宽度,则 width 的使用值为该固有宽度;

    例子同上

  • 若宽度的计算值为 auto 且高度有 非auto 的计算值,并且元素有固有宽高比,则 width 的使用值为 高度使用值 * 固有宽高比;

    典型的例子:img 当只定义了其高度值时,其宽度将会根据固有宽高比进行等比设置

  • 除此之外,当 width 的计算值为 auto 时,则宽度的使用值为 300px

    典型的例子:比如iframe, canvas

其它类型的置换元素,其宽度的定义都参照行内置换元素的定义。

内联的置换和非置换元素的高度定义

对于行内级非置换元素,高度设置是不适用的。

对于行内级置换元素来说,其高度的设置需遵循以下几点:

  • 若宽高的计算值都为 auto 且元素有固有高度,则 height 的使用值为该固有高度;
  • 若高度的计算值为 auto 且元素有固有高度,则 height 的使用值为该固有高度;
  • 若高度的计算值为 auto 且宽度有 非auto 的计算值,并且元素有固有宽高比,则 height 的使用值为:宽度使用值 / 固有宽高比;
  • 若高度的计算值为 auto 且上述条件完全不符,则 height 的使用值 不能大于150px,且宽度不能大于长方形高度的2倍。

其它类型的置换元素,其高度的定义都参照行内置换元素的定义。

内联的非置换元素如何修改宽高

将内联元素变为块级元素。

设置css的display属性。

)。)。)。)。 )。)
描述
none 此元素不会被显示。
block 此元素将显示为块级元素,此元素前后会带有换行符。
inline 默认。此元素会被显示为内联元素,元素前后没有换行符。
inline-block 行内块元素。(CSS2.1 新增的值)
list-item 此元素会作为列表显示。
run-in 此元素会根据上下文作为块级元素或内联元素显示。
compact CSS 中有值 compact,不过由于缺乏广泛支持,已经从 CSS2.1 中删除。
marker CSS 中有值 marker,不过由于缺乏广泛支持,已经从 CSS2.1 中删除。
table 此元素会作为块级表格来显示(类似 ),表格前后带有换行符。
inline-table 此元素会作为内联表格来显示(类似 ),表格前后没有换行符。
table-row-group 此元素会作为一个或多个行的分组来显示(类似
table-header-group 此元素会作为一个或多个行的分组来显示(类似
table-footer-group 此元素会作为一个或多个行的分组来显示(类似
table-row 此元素会作为一个表格行显示(类似
table-column-group 此元素会作为一个或多个列的分组来显示(类似
table-column 此元素会作为一个单元格列显示(类似
table-cell 此元素会作为一个表格单元格显示(类似
table-caption 此元素会作为一个表格标题显示(类似
inherit 规定应该从父元素继承 display 属性的值。