Redis

数据类型

概述

redis任何数据类型都有一个key作为键标识这个数据,一个键对应一个数据类型,数据类型可以为string、hash、list、set和sset五种。

string

能够储存字符串、整数和浮点数,储存整数或浮点数时支持自增自减操作,一般用于简单的键值对缓存。

常见指令

(m)set key value 设置一个键值对,value为string类型,加m表示批量设置
(m)get key 通过key获取值,获取到的值为value类型,加m表示批量获取
getrange key start end 获取范围内的值,当类型不为string时会返回转化成string后的范围内值
incr key 令值自增,仅仅对于整型有效
decr key 令值自减,同上

Hash

存放多个field和value的映射,类似于C++中的unordered_map,底层通过哈希表实现。

常见指令

h(m)set key field value 设置值,加m表是批量操作,键已存在会覆盖
h(m)get key field 获取值
hkeys key 获取哈希表的所有field
hvals key 获取哈希表中所偶的value
hgetall 获取所有键值对

List

一个存放若干字符串的列表,可以在列表的头部或尾部添加元素,类似于双端队列。

常见指令

lpush key values 将多个元素插入表头
bl(r)pop keys timeout 从列表的左边(右边)取出一个元素,如果没有元素会等待,直到超时或有元素为止,多个key时优先删除左边的
brpoplpush source destination timeout 从source右边取出一个加到destination左边,没有元素同上
lindex key index 获取从左起第index个数,index从0开始
linsert key before(after) index value 在列表第index个前(后)插入一个value
llen key 获取列表长度
lrange begin end 获取范围内元素
lrem key count value 删除从左起count个值为value的元素
ltrim key begin end 修剪列表,保留begin到end间的元素
lset key index value 设置值

Set

同C++/Java等中的set类似,都是存放唯一元素的哈希表。

常用指令

sadd key values 增加元素
scard key 获取元素数量
sdiff key1 keys 返回key1与其他集合的差异
sdiffstore destination key1 key2 将差异储存在destination中
sinter keys 返回所有集合的交集
sunion(store destionation) 返回并集(并储存在destination中)
sismember key value 返回value是否是集合的元素
spop key 移除并返回集合中的随机一个元素
srem key values 移除集合中的指定元素

Sorted set

在set基础上关联每个元素一个double类型的分数,数据按分数大小排序,元素一定唯一,分数可以不唯一

常用指令

zadd key score member 添加成员或者更新成员分数
zcard key 获取数量
zcount key min max 获取分数在范围内的元素
zrevrank key value 返回value的排名,按从大到小排序

计算机网络

应用层

应用程序之间的报文通信。

DNS协议

域名解析协议:用于通过域名获取IP地址,从而使得数据能够发送到目的地址去。

DNS解析过程

  1. 先在本地hosts中查找是否存在本地映射,如果存在,则解析完成,否则进入下一步
  2. 查询本地DNS缓存,如果存在,则解析完成,否则下一步
  3. 向本地DNS服务器查询,本地DNS服务器查看是否缓存了域名映射,如果存在则返回给主机解析完成,否则下一步
  4. 本地DNS服务器把请求发送给根DNS服务器,根DNS服务器将负责该域名的顶级域名服务器返回给本地DNS服务器IP
  5. 本地DNS服务器得到顶级域名服务器IP后,将请求发送给对应的顶级域名服务器,服务器返回权威DNS服务器
  6. 本地DNS服务器将请求发送给权威DNS服务器,得到域名的IP地址

递归查询:客户端发送一次查询,要求服务端返回查询结果,如主机向本地DNS服务器发送的查询请求。
迭代查询:客户端发送每一次查询,服务端如果没有授权回答,就返回一个能解答查询的其他服务器列表,如本地DNS服务器的查询过程。

http协议

超文本传输协议,用于应用程序之间的超文本通信,在下层使用TCP/IP协议传输。
常见的请求方式有:

  1. get请求:请求指定页面信息,返回实体主体。
  2. head请求:和get类似,不返回实体内容,用于获取报头。
  3. post请求:用于向服务器传输信息,传递的信息存在于请求体中,可能导致新的资源的建立或者已有资源的修改。
  4. put请求:向服务器发送数据取代指定内容。
  5. delete请求:请求删除指定页面。

get、post和put三者的对比:
使用get向服务器传递信息的时候,传递的信息储存在url中,如“https://cn.bing.com/search?q=http%E8%AF%B7%E6%B1%82%E6%96%B9%E6%B3%95%E6%AF%94%E8%BE%83” 问好后面的部分即为搜索时需要查询的内容,通过url将需要查询的内容传递给服务器,具有幂等性(多次重复的操作产生的效果一样)且长度收到限制,当请求被截获是,传递的信息会被直接看见,因此安全性较低。
使用post向服务器传递信息时,将需要传递的信息储存在请求体中,因此安全性相对较高(其实也不安全)且没有长度限制,数据类型也不再被限制为ascll码,且不具有幂等性,因为当你刷新post请求的网页时,数据会被重新提交,此时浏览器应该告知用户数据会被重复提交。
使用put请求和post大致相同,区别在于put请求的url后包含一个id作为标识,当重复提交时,由于id相同,服务器会将新提交的内容替换原id相同的内容,而post没有id作为标识,因此重复提交会重复创建,可知put也具有幂等性,适用于修改数据,而post适用于新建数据。

get 和 post的区别

  1. get传递信息时传递信息放在url中,而post放在请求体中
  2. get传递信息的内容有长度限制格式(urlencode编码),post没有
  3. get具有幂等性,post不具有,即重复提交时,get产生的效果一样,而post不一样。
  4. get方法提交的数据显示在url上,而页面会被浏览器保存,因此历史记录会显示提交的信息,而post不会

https协议

在http协议的基础上增加了SSL加密来保证传输的可靠性。

加密过程

服务器拥有非对称加密的公钥A和私钥A’
服务器接收到请求时将公钥A以明文的方式传递给客户端
客户端生成一个随机密钥S,利用公钥A进行加密后传给服务器
服务器得到加密后的密钥后使用私钥A’进行解密
此时双方共有密钥S,可用S进行加密通信

数字签名的制作

CA机构拥有非对称加密的公钥和私钥
CA机构对证书的明文进行hash
对hash后的数据使用私钥加密得到数字签名
明文和数字签名共同组成数字证书,发给网站

浏览器拿到证书后进行验证

拿到证书后得到明文和签名
使用CA机构提供的公钥进行解密得到S
按照证书中的hash算法对解密后的明文进行hash得到T
此时T应该和S相等

cookie和session

cookie是由服务器发送,要求浏览器保存到本地的信息,当再次访问网站时,浏览器会将cookie信息提交给服务器进行验证,以便于简化连接过程。由于cookie是保存到本地的,所以数据可能被本地用户更改,因此信息并不是完全可靠的,可以通过session解决。
session是服务器用来维护和客户通信的信息,session会保存一定的通信信息,如用户信息和通信密钥,当浏览器再次访问时,服务器会对比session中的信息,如果数据匹配,则会使用原来的密钥,而不需要重新进行密钥传输。

http返回码

1:服务器接收到请求,需要请求者进行进一步请求
举例:100——继续请求 101——切换到更高级的协议,如http高版本
2
:请求成功
举例:200——ok,请求成功,一般用于get、post 201——Created,已创建 202——Accpeted,已接受
3:重定向,需要进一步操作
举例:300——多种选择,请求资源包含多个位置 301——永久移动 302——临时移动
4
:客户端错误
举例:400——客户端请求语法错误 401——请求身份认证 403——forbidden,拒绝请求 404——not found,没有找到资源
5**:服务器错误
举例:500——服务器内部错误 501——不支持请求功能

其他应用层协议

FTP:文件传输协议
SMTP:邮件传输协议
SNMP:简单网络管理协议,用于在IP网络管理网络节点的协议
DHCP:动态网络分配协议,用于在主机连接到网络后动态分配ip地址
RIP:路由选择协议,见网络层

运输层

该层规定了进程之间的通信协议,传输基本单位为报文段

TCP协议

可靠性传输协议,在数据进行传输时,先通过三次握手建立连接,然后按序发送数据,数据发送完成后通过四次分手断开连接。

三次握手过程

第一次:客户端向服务器发送SYN=1的报文段作为连接请求,并随机长生一个序列号,不附带应用层数据。此时发送方进入SYN_SEND状态。
第二次:服务器收到连接请求后,向客户端发送一个SYN=1的报文段作为SYNACK确认报文段且序列号加一,同时由于TCP连接时全双工,该报文段还有进行连接请求的含义,此时客户端向服务端的方向已经建立好连接。此时接收端进入SYN_RCVD状态。
第三次:客户端对第二次的请求向服务器发送一个ACK,并且此时客户端可以附带传送应用层的数据给服务器,到此,三次握手完成。接收方接收到之后进入ENSTABLISHED状态。

四次分手过程

第一次:假定由客户端主动断开连接,此时客户端向服务器发送一个FIN报文段,此时客户端进入FIN_WAIT_1状态。
第二次:服务器发送一个ACK对FIN进行确认,此时只关闭了客户端向服务器传输的通道,服务器依然还可以传输数据,服务端接受后进入CLOSE_WAIT状态,客户端接受后进入FIN_WAIT_2状态。
第三次:在等待两个rtt后,服务器向客户端发送一个FIN报文段。此时服务端进入LAST_ACK状态
第四次:客户端向服务器发送一个ACK确认。发送后客户端进入TIME_WAIT状态并等待2倍MSL后结束

为什么握手是三次而分手是四次

本质上三次握手和四次分手进行的操作是一样的,都是分别关闭双向传输通道,但是在分手的第二次和第三次之间,服务器还可以向客户端发送数据,如果服务器还有数据需要传输,服务器依然可以传输数据直到数据发送完成。如果服务器没有数据需要发送,分手也可以三次完成。

为什么要等待两个MSL(报文段最长预留时间)

等待两个rtt是为了防止ACK丢失,如果第四次分手的数据丢失,会触发服务端的重传,这个重传的时间正好是两个MSL。

TCP如何保证传输可靠性

序列号:数据按序发送,在确认前面的数据被送达之后,后续的数据才会被继续发送。
重传机制:当数据发生丢失是重新传输数据。
确认应答:接收到数据后会发送一个确认号告知发送方数据被正确接受。

TCP拥塞控制

  1. 慢启动:开始时将窗口大小设置为1MSS,然后每过一个RTT成倍增长,直到达到慢启动阈值后增长幅度下降为1MSS,当出现丢包事件时进行快速恢复,当出现超时时进入拥塞避免。
  2. 拥塞避免:将窗口值减半且每过一个rtt增长1MSS;
  3. 快速回复:将窗口值置为1且将慢启动阈值置为当前窗口值的一半,重新进入慢启动。

TCP粘包

TCP不直接将接收到的数据传给应用层,而是将数据放入缓冲区,等待被应用层处理。当应用层处理速度小于TCP接受速率时,可能出现多个数据包连在一起的情况。同时,发送方使用Nagle算法也会导致粘包,Nagle算法会将数据量小的数据包合在一起发送给客户端导致粘包。

解决方法:

  1. 格式化数据:为数据提供开始和结束标识符标识数据的开始和结束,前提是数据内不包含这样的结束符号
  2. 发送长度:发送每一条数据的同时发送数据的长度,应用层根据长度获取数据

为什么UDP不会出现粘包问题?
因为TCP是基于数据(字节)流的,不知道一条消息的具体长度,而UDP是面向消息的,每次发送都是以消息为单位发送数据,通过消息保护边界保护数据。

半连接全连接队列

三次握手过程中,客户端向服务器发送连接请求后,客户端变成SYN_SENT状态,服务器收到连接请求后向客户端发送SYN+ACk,此时服务器处于SYN_RCVD状态,服务器会将此连接信息存入SYN队列(即半连接队列)中,当服务器收到客户端的确认ack时,将对应的连接从SYN队列移动到accept队列(即全连接队列)中,此时服务器处于ESTABLISHED状态。
简单说就是:半连接队列存放TCP连接建立到一半时的TCP连接,全连接队列存放成功连接的TCP请求。
当SYN队列满且服务器再次收到SYN时,服务器不会立马丢弃SYN包,而是利用TCP_syncookies生成cookie,这种方法也用于防止SYN洪泛攻击,服务器将源目的IP、源目的端口号、服务端初始序列号和其他安全数值信息进行哈希并加密后得到sever端初始序列号,称作cookie,服务器向客户端发送初始序列号为cookie的SYN+ACK给客户端,然后释放连接请求块,为新连接的请求腾出资源,服务端收到ACK时,将序列号-1,并用同样方法哈希加密,与原cookie比较,如果结果一致,则完成TCP连接建立,
当accept队列满时,如果服务器收到客户端发送的ACK,服务器不会立即丢弃,而是会检测tcp_abort_on_overflow的值,如果为0,则丢弃并启动定时器触发重传机制重新发送SYN+ACK数据包,发送达到一定次数后,如果依然队列为满,则发送RST包,并将连接从SYN队列中移除,如果tcp_abort_on_overflow值为1,则直接发送RST包并将连接移除,(客户端多次发送连接而得不到响应会抛出异常)

长连接短连接

长连接指双方建立连接后不马上断开,直到一方主动断开连接,连接过程中双方都可以进程数据传输而不受影响。一般适合需要频繁传输数据的情况下使用,避免了多次建立连接需要的代价。
短链接指双方在需要数据传输时才建立连接,数据发送完成后一方(一般是客户端)主动断开连接。适合有大量用户进行短期访问时使用,可以防止在不需要进行数据传输时保持连接的代价。

TCP首部内容

16位源端口号和16位目的端口号

表示源和目的主机的端口号

32位序号

TCP传输是面向字节流的,通过该字段标识报文段第一个数据的字节序号。该字段表示一个32位无符号整数,超过最大值后从0重新开始计数,用此字段对数据流计数。

32位确认号

只有在ACK位置1时有效,由接收方向发送方发送时,表示自身需要收到的下一条数据的序号同时前面的数据已经被接受。

4位首部长度

表示首部行占用多少个32bit位,由于任选字段长度可变,因此需要该值作为限制。一般为20字节,即首部长度为5

6位保留位

留给后续用于扩展。

6位标志位

每一位置1时表示各自的含义:
URG:紧急标志位
ACK:确认标志位
PSH:接收方应尽快将报文段交给应用层
RST:重新建立连接
SYN:同步序号,用来发起连接
FIN:用于完成发送任务标识

16位窗口大小

在拥塞控制时,需要控制窗口的大小。窗口大小的值表示字节数,最大为65535。

16位校验和

用于校验数据是否正确,具体过程如下:

  1. 校验和置0
  2. 每十六位作为一组数据得到若干组数据
  3. 将若干组数据相加
  4. 若结果大于16位,则高16位加低16位直到结果小于16位
  5. 将16位结果作为校验和

SACK

用于选择重传时使用,需要在建立连接的时候在SYN包中SACK-permitted选项设置为true,同时双方都应该支持这个功能。
在接受异常之后,接收方会发送所有接收队列中没有确认的数据信息,双方需要遵循以下准则。
接收方:

  1. 第一个block指定触发SACK的数据段。
  2. 尽可能填满block。
  3. 报告最近接受的不连续的ACK。
    发送方:
  4. 数据被确认前都应保存在窗口内。
  5. 每个数据段都有一个SACKed标志,重传时用来忽略。
  6. 如果SACK丢失,会触发超时重传重发所有需要发送的数据包。

UDP协议

面向无连接的非可靠性传输协议,数据传输不需要建立连接,发送发只管数据进行发送,不管数据是否被成功送达。
由于UDP面向无连接发送,因此可以支持一对多,多对多等发送方式,而TCP只支持一对一的发送。

网络层

定义了子网之间数据包传输的协议

IP协议

分为IPv4和IPv6协议,一个IPv4地址由8×4个比特位组成,占四个字节,一个IPv6地址由32×4个比特位组成,占16个字节,能表示更大的范围。

RIP协议

通过向周围节点发送更新请求,更新自身表中的路由表。由于每次更新请求都会使用UDP进行传输,因此该协议属于应用层协议,表中储存该节点到其他节点的最短路径,由于维护路径时坏消息(节点间断开连接的消息)传递速度慢,因此维护的速度慢,适合较小的子网进行传输。

OSPF协议

协议维护网络中各个节点的连接信息,然后根据最短路径算法求出最短路径即可,由于每次只需要传递连接信息,因此在较大的网络同样适用。

ARP协议

地址解析协议,通过IP地址获取mac地址

RARP协议

反向地址解析协议,通过mac地址反向获取IP地址

ICMP协议

网络控制报文协议,TCP协议的子协议,用于控制TCP报文传输,传输控制报文出错信息。

链路层

节点到节点的传输协议(如主机到路由器),传输单位为帧,网络层将数据交付给链路层,由链路层发送。

PPP协议

点对点协议,节点和节点之间的信息传输协议

多路访问协议

当多个节点需要在同一个信息通道通信时需要的协议
包括信道划分协议、随机接入协议和轮流协议

物理层

定义了数据以比特流形式在何种物理介质中传输,如光纤、双绞铜线

补充知识

token令牌

当用户频繁的向服务端请求数据时,服务器需要频繁的去数据库查询用户名和密码进行对比,判断用户名和密码是否正确,使用token令牌解决频繁的数据库访问。

原理

服务器为客户端随机生成一个字符串作为令牌发送给客户端,并将令牌保存在session中,当用户再次请求数据时,客户端将令牌发送给服务器,服务器通过验证session中的令牌和客户端发送过来的令牌是否一致来进行验证。

用于防止表单重复提交

当服务器收到客户端发送过来的数据后,服务端修改session中的token令牌为一个新值,当客户端重复发送请求时,客户端的令牌不变而服务器的令牌已经更改,因此令牌不正确,后续重复请求都会被拒绝。

用于防止crsf攻击(跨站点请求伪造)

服务器通过验证token的方式检测是否为正常请求。csrf攻击方式见下方,由于浏览器会默认将cookie添加到攻击者要攻击的请求中,因此需要在请求字段增加token令牌,通常在加载页面时利用js遍历整个dom树将所有a和form标签中添加token,这样,用户在自身访问后续内容时自动添加了token令牌,而攻击者的请求中不包含该令牌,因此攻击者的请求会失效。而如果是动态网页,就需要程序员手动将token加入其中。

csrf攻击

指攻击者利用真实用户身份借用户的手访问网站并进行攻击,过程如下:

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

设计模式

工厂模式

分为用户对象和工厂对象,用户对象根据自己的需求向工厂索要产品,工厂根据需求提供产品,这个产品可能来自不同的实例工厂,具体选择取决于用户需求。

简单工厂模式

实现了对产品的简单请求,缺点在于当需要增加实例工厂的时候需要修改工厂的源代码,打破了工厂的封装。

工厂方法模式

在简单工厂的基础上,在工厂类和实例工厂之间增加一层抽象类,当需要扩展工厂时,只需要新增工厂抽象类和实例类,通过继承即可添加工厂。

抽象工厂模式

前两种模式只支持一件特定产品的生产,抽象工厂模式支持多种产品的生产。

单例模式

某一个类只能产生一个实例对象,便于管理,减小系统资源开销,同时由于其没有抽象层,因此扩展需要修改源代码。
用于在只能产生一个实例的情况。

装饰模式

装饰者对象持有被装饰者对象实例,可以动态扩展被装饰者的功能。

策略模式

某种功能可以由多种算法实现时,可以用户可以选择算法决定功能的具体实现方式。

代理模式

在用户和主机之间增加一层代理层,代理层有选择的将用户请求发送给主机。

观察者模式

被观察者只需要将自己的状态发送出来,不用管发出来的信息的接受。观察者只需要处理自己所需要观察的信息即可,不需要管信息的来源。当观察者太多时,可能导致信息的分发变得十分复杂,降低效率。

建造者模式

当事务的多个部分可以各自有多种实现方式时,用户可以选择每个部分的实现方式,时最终结果呈现出不同的样子。

MVC模式

Model 模型,代表一个对象
View 视图:代表一个模型内数据的可视化
Controller 控制器,作用在模型和视图上,控制数据流入模型,当模型数据变化时,控制视图发生变化,将模型和视图分开

MySQL

索引

索引是对数据库表中一列或多列的值进行排序的数据结构,他们的底层使用的都是BTree,使用索引可以快速定位到特定的信息,提高查找效率。
创建索引分为以下几种方式:

1
2
3
4
5
6
create index on tablename(line1,line2);//直接创建索引
alert table tablename add index indexname(line);//修改表结构
create table tablename(
id int not null,
index indexname(linename)
);

分类如下

普通索引

最基本的索引之一,没有唯一性的限制。
创建方式:create index indexname on tablename(line1,line2,...)

唯一索引

在普通索引的基础上,增加了唯一性的限制,即索引列中不可能出出现相同的值。
创建方式:create unique index indexname on tablename(line1,line2,...)

主键索引

在主键上增加索引,在MySQL中默认会为主键增加索引,如果没有指定主键,则会将默认生成一个以行号为标志的索引。具有唯一性且不为空。
主键索引也是一种聚集索引。

联合索引

primary key(id,name):联合主键索引
unique(id,name):联合唯一索引
index(id,name):联合普通索引

全文索引

当一条数据的某个字段可能会很长的时候,如果直接进行匹配,消耗的时间是比较多的,因此引入全文索引,它通过对每个词的建立倒排索引的方式进行快速查找,每次查找时只需要根据其索引中是否存在查找的词即可。
创建索引的方法如下:

1
2
fulltext key content_index(content)//在创建表时添加索引
create fulltext index content_tag_fulltext on fulltext_test(content,tag);//对已有的表创建全文索引,支持全文索引

使用全文索引查询时,可使用如下方法查询匹配最小搜索长度:

1
2
select * from fulltext_test 
where match(content,tag) against('xxx xxx');

使用全文索引查询的速度比like+%的模糊查询要快很多,当文本内容较大时尤其明显,但是有可能出现精度问题,因此当文本数据大时应使用全文索引
使用全文索引时应该保证数据数量大于4,否则查询可能遇到意想不到的结果
查询的字段长度应在一定范文内,在INNODB(MySQL5.6版本后支持)中长度为(3.84),在MYISAM中为(4,84)
查询时又分为自然语言查询和布尔全文索引,自然语言查询匹配相关度,查询对应的单词出现的次数,布尔查询可以支持通配符查询等控制关联度查询。

聚集索引

建立索引后数据的物理关系和索引的逻辑关系是相同的。

覆盖索引

指查询时索引覆盖的查询条件的内容,只需要通过索引就可以获取到查询的内容的情况

数据库事物

指单个逻辑单元执行一系列操作,这些操作要么全部执行,要么全部不执行,它确保每次处理成为一个整体。

ACID原则

  1. 原子性(Atomicity)
    原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

  2. 一致性(Consistency)
    一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

  3. 隔离性(Isolation)
    隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
    即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
    多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。
    不同的隔离级别:
    Read Uncommitted(读取未提交内容):最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
    Read Committed(读取提交内容):只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
    Repeated Read(可重复读):在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
    Serialization(可串行化):事务串行化执行,隔离级别最高,牺牲了系统的并发性。可以解决并发事务的所有问题。

  4. 持久性(Durability)
    持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
    例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

MySQL如何保证ACID原则

原子性:通过undo log进行回滚。
隔离性:通过四个隔离级别保证。
持久性:redo log记录了每次提交的操作,每次提交后不仅更新内存中的数据,此时磁盘中的数据可能还没有发生改变,还进行刷盘更新磁盘中的redo log,当出现宕机时,只需要通过redo log进行恢复。

事务隔离级别

  1. 读未提交内容:在其他事务执行一系列操作并且还没有提交时,本事物已经获取了这一系列操作的结果,并且会在后续的读取中读取到这些内容,这种操作很明显的暴露出来了一个缺点,由于事物是支持回滚的,如果另一个事务执行操作后因为某种原因回滚到执行前的状态,但是本事物并不知道另一个事务已经回滚,则后续读出来的数据可能就是“脏数据”,因此出现了脏读的问题。
  2. 读提交内容:为了解决上述问题,本隔离级别不允许事务获取其他事务未提交的内容,使得如果其他事务出现了回滚操作时结果是一样的,这也是许多数据库使用的隔离级别,但如果本事务在执行的时候其他事务提交了内容,那么本次事务在另一事务提交前后读取到的内容是不一样的,这种现象称之为不可重复读取。
  3. 可重复读:为了解决不可重复读的问题,采取MVCC版本控制机制进行版本控制,当一个事务不进行更新等可能修改数据的操作时,事务读取的数据始终停留在原来的版本,无论其他事务是否提交了修改数据指令,本事务两次读取的内容都控制在同一个版本中,因此重复读取到的结果是一样的。当本次事务出现了数据更新时,数据库将更新操作应用于当前表格的最新版本上,同时将版本设置为修改数据后的版本,这样也保证了并发事务间的同步。但是当本事务两次读取行为间增加一个更新操作,而更新操作执行前另一个事务提交了一个插入操作,这个更新不会应用到插入和行中,就好像产生了幻觉一样,则称这种现象为“幻读”。
  4. 串行化读取:为了解决幻读问题,通过使用锁机制对数据进行访问控制,具体使用的锁为行锁和范围锁,当某一事务执行了读取操作后,会将读取的部分“锁定”,即锁住对应的行(行锁)不允许更改,也锁定读取的范围(范围锁),不允许插入操作,使得其他事务无法对读取到的部分进行任何操作从而解决这一问题。

存储引擎

对于MySQL来说,最常用的两个引擎分别是MAISAM和INNODB。

INNODB

事务型存储引擎,有行级锁和外键约束。
提供数据库ACID事务支持,实现了SQL标准四种隔离级别,不保存表的行数,统计行数时要访问整个表,锁的粒度小,写操作不会锁定全表。
使用场景:适用于经常更新的表,适合处理多重并发请求。
特点:B+树数据存储索引值,因此是聚集索引。

MYISAM

MySQL的默认引擎,不提供数据库事务,也不提供行级锁和外键,因此在执行写操作时会锁定整个表。
独立于操作系统,可以比较简单的进行数据转移。
特点:查询速度快,表保存了行数,统计行数时不需要访问整个表,只需要输出表的行数即可,但是如果加了where条件,一样需要遍历整个表。索引的B+树数据域存储实际数据地址,因此是非聚集索引。

二者区别

  1. 事务:MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持,提供事务支持已经外部键等高级数据库功能。

  2. 性能:MyISAM类型的表强调的是性能,其执行速度比InnoDB类型更快。

  3. 行数保存:InnoDB 中不保存表的具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含where条件时,两种表的操作是一样的。
    因为InnoDB是事务型的,每个事务的行数可能不同。

  4. 索引存储:InnoDB使用聚簇索引且支持外键,而MyISAM是非聚簇索引且不支持外键,因此InnoDB必须要有一个主键索引,辅助索引需要通过主键索引二次查找,如果没有主键索引会产生自增的行号作为索引。MyISAM可以不增加主键索引,主键索引和辅助索引相互独立。

  5. 服务器数据备份:InnoDB必须导出SQL来备份,LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。
    MyISAM应对错误编码导致的数据恢复速度快。MyISAM的数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作。
    InnoDB是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了。

  6. 锁的支持:MyISAM只支持表锁。InnoDB支持表锁、行锁 行锁大幅度提高了多用户并发操作的新能。但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的。

三大范式

第一范式

列不可再分

第二范式

不存在非主属性对码的部分依赖

第三范式

不存在非主属性对码的部份依赖和传递依赖

BCNF

不存在主属性对码的部份依赖和传递依赖

为什么MySQL要用B+树而不是B树或者红黑树

不选择红黑树的理由

相同数据量下红黑树高度过高,进行搜索时io次数多很多。
io次数多的理解:由于数据库中的数据都是存在于硬盘中的,想要对数据进行查找时就需要读取硬盘中的信息,数据很大时并不能一次读取所有信息,因此必须层层查找,每一次获取一个节点,并将节点中的信息与要查找的信息作对比,获得下一个需要得到的节点,如果使用红黑树,io的复杂度高达log(n),而使用B+树,只需要几次io即可找到。

不选择B树

  1. 由于B树的非叶子节点也会存放数据,因此相同内存大小下B树所能存放的节点更少,导致相同内存B树储存的数据量小。
  2. B树范围索引需要跨层查找。

B+树的特点

  1. 所有数据存在叶子节点中,非叶子节点只存关键字和子节点地址,因此每个节点能够存放的子节点数量更大。
  2. 各个叶子节点最后通过链表的形式连接起来,对于链表来说,数据是有序的,因此B+树的查找类似一次二分查找,且支持范围查找
  3. 由于数据都存放在叶子节点中,查找的速度十分稳定,可以认为用固定次数即可查找出来。

MySQL中的锁

乐观锁

乐观锁对并发保持乐观态度,认为数据不存在并发冲突,只在事务提交前检查数据是否被其他事务更改,一般采用版本号机制或CAS(比较并交换)实现。

悲观锁

悲观锁对并发持悲观态度,认为并发很容易对出现冲突,每次修改数据时都对数据进行加锁。

行锁

行锁是通过索引上的索引项加锁来实现的,只有通过索引条件检索数据,innoDB才使用行锁,否则将使用next-key lock

间隙锁

又叫范围锁,锁定一个范围,范围内不可插入和修改数据,用来解决幻读时有用。

next-key lock

使用行锁+间隙锁实现加锁

排他锁

又叫写锁,在执行写入操作时将表格锁住不允许其他事务操作。
在select语句后加for update可实现查询时加排他锁

共享锁

又叫读锁,可以被多个事务共同获取,但是被完全释放前数据不可被修改,即只允许读取。
在select语句后面加in share mode可实现查询时加共享锁

回表

辅助索引的叶子节点只存放存储列值和主键id,想要查找数据还需要在主键中二次查找
解决方法——覆盖索引:将要查找的字段添加到联合索引中

联合索引的最左原则

从左到右依次作为索引条件,但是遇到范围查询将终止。

主键非自增带来的问题

会使得主键索引的插入效率降低,因为当插入数据的主键应在某个已经满的块中时,需要移动数据块后插入。

SQL优化

  1. 为经常需要查询、链接、集合操作和group by或order by的字段建立索引
  2. 避免使索引失效的语句,’or’ ‘!=’ ‘link’
  3. 避免使用select *
  4. 使用覆盖索引避免二次查询
  5. 使用join代替嵌套查询,嵌套查询会产生临时表,开销大
  6. 小表驱动大表,先查小表再查大表
  7. 避免使用null(索引失效)

索引失效的情况

  1. 有or
  2. 复合索引未用到左列字段
  3. like以%开头
  4. 需要类型转换
  5. where中索引列有运算/函数

MySQL绑定变量

当你可能需要指令大量重复且类似的指令时,如果这些指令只有部分参数不同,可以使用绑定变量简化操作,用户只需要提交参数给服务器就可完成操作,大大提高的信息传输的效率。

当创建一个绑定变量 SQL 时,客户端会向服务器发送一个SQL语句的原型。服务器端收到这个SQL语句框架后,解析并存储这个SQL语句的部分执行计划,返回给客户端一个 SQL 语句处理句柄。以后每次执行这类查询,客户端都指定使用这个句柄。
绑定变量的SQL,使用问号标记可以接受参数的位置,当真正需要执行具体查询的时候,则使用具体值来替代这些问号。例如,下面是一个绑定变量的SQL语句:
INSERT INTO tb1(col1, col2, col3) VALUES(?,?,?);
可以通过向服务器端发送各个问号的取值和这个SQL的句柄来执行一个具体的查询。反复使用这样的方式执行具体的查询,正是使用绑定变量的优势所在。具体如何发送取值参数和SQL句柄,这各个客户端的编程语言有关。

因为如下的原因,MySQL可以在使用绑定变量的时候高效执行大量的重复语句:

  1. 在服务器端只需要执行一次SQL语句
  2. 在服务器端某些优化器的工作只需要执行一次,因为它会缓存一部分的执行计划
  3. 以二进制的方式只发送参数和句柄,比每次都发送 ASCII 码文本效率更高
  4. 仅仅是参数而不是整个查询语句,因此网络开销会更小
  5. MySQL在存储参数的时候,直接将其存放到缓存中,不再需要在内存中多次复制
    绑定变量相对也更加安全。因为无需在应用程序中处理转义,一则更加简单了,二则也大大减少了SQL指令被攻击的风险。

绑定变量的优化

对于绑定变量的SQL,MySQL 能够缓存其部分执行计划,如果某些执行计划需要根据传入的参数来计算时,MySQL就无法缓存这部分的执行计划。根据优化器什么时候工作,可以将优化分为三类:

准备阶段

服务器解析SQL语句,移除不可能的条件并且重写子查询

在第一次执行的时候

如果可能,服务器先简化嵌套循环的关联,并将外关联转化成内关联

在每次SQL语句执行时

服务器会做如下事情:

  1. 过滤分区
  2. 如果可能的话,尽可能移除COUNT()、 MIN() 和 MAX()
  3. 移除常数表达式
  4. 检测常量表
  5. 做必要的等值传播
  6. 分析和优化 ref、 range和索引优化等访问数据的方法
  7. 优化关联顺序

在4.1 和更新版本中,MySQL 支持了SQL接口的绑定变量。不使用二进制传输协议也可以直接以SQL 的方式使用绑定变量:
sql接口的绑定变量

1
2
3
prepare actor_name from 'select * from table_name where id=?';//创建绑定变量SQL
set @test_id := 1;//创建变量
execute actor_name using @test_id;//使用绑定变量

SQL接口的绑定变量的最主要用途就是在存储过程中使用。在MySQL5.0 版本中,这就可以在存储过程中使用绑定变量,其语法和前面介绍的SQL接口的绑定变量类似。这意味着可以在存储过程中构建并且执行动态的SQL语句,即可以通过灵活的拼接字符串等参数构建SQL语句。

绑定变量的限制

关于绑定变量的一些限制和注意事项如下:

  1. 绑定变量是会话级别的,所以链接之间不能公用绑定变量句柄。同样的,一旦连接断开,则原本的句柄也不能再使用了。(连接池和持久化连接可以在一定程度上缓解这个问题)

  2. 并不是所有的时候使用绑定变量都能获得更好的性能。如果只是执行一次的SQL,那么使用绑定变量方式无疑比直接执行多了一次额外的准备阶段消耗,而且还需要一次额外的网络开销。

  3. 绑定变量SQL总数的限制是一个全局限制,如果总是忘记释放绑定变量资源,则在服务器端很容易发生资源“泄漏”。

不过使用绑定变量最大的障碍可能是:它是如何实现以及原理是什么样的,这两点很容易让人困惑。有时候,很难解释如下三种绑定变量类型之间的区别是什么:

客户端模拟的绑定变量

客户端的驱动程序接收一个带参数的SQL,再将指定的值带入其中,最后将完整的查询发送到服务器端。

服务器端的绑定变量

客户端使用特殊的二进制协议将带参数的字符串发送到服务器端,然后使用二进制协议将具体的参数值发送给服务器端并执行。

SQL接口的绑定变量

客户端先发送一个带参数的字符串到服务器端,这类似于使用 PREPARE 的 SQL 语句,然后发送设置参数的SQL,最后使用 EXECUTE 来执行SQL。所有这些都是使用普通的文本传输协议。

redis

跳表

在有序链表的基础上,通过层层索引的方式提高查询效率。
每一层索引都是在前一层的基础上随机选取一半的数据作为下一级的索引,因此查询效率可以降低至log n,且每个节点都有两个下一级指针,一个指向下一层的节点一个指向同一层右边的节点,根节点为最上层最左节点。

跳表相对于红黑树

跳表可以支持范围查询,只需要找到范围的开头和结尾节点即可,而红黑树需要找到每个范围内的节点。
且跳表实现更加简单,不容易出错。

跳表相对于B+树

跳表和红黑树一样,都是在基于内存时效率高,而B+树基于硬盘时效率高(硬盘内B+树io次数更少,但内存内查询时间复杂度高于跳表等)

MySQL层次结构

原文链接
MySQL分为连接层、服务层、引擎层和存储层,每一层具体功能如下:

连接层

功能:管理用户连接,为连接分配权限
注意事项:

  1. 登录成功后,用户权限就被用户获取,如果权限被修改,重新连接后才能生效
  2. 连接分为长连接、短链接,

服务层

提供用户使用的接口和优化器,用于对用户请求提供服务。

引擎层

提供存储引擎,不同引擎可能使用不同的存储方式。

存储层

用于存放数据。

MySQL日志

error_log(错误日志)

记录运行过程中的Error、Worning、Note信息
可通过修改/etc/my.conf中添加–error-log = [filename]开启错误日志
可使用show variables like "log_error";查看错误日志路径

General Query Log(日常日志)

记录使用的sql语句,报错select、delete和update
如果开启日志功能但不能成功写入日志,可能是因为权限不够,运行如下指令赋予权限:
chown mysql:mysql /tmp/mysql_query.log
最后是日志地址,可用查询错误日志的方式查询地址。

Bing Log(二进制日志)

记录所有可能修改表格的sql语句,属于逻辑日志,可以用来查看数据库变化情况。

Slow Query Log(慢查询日志)

用于记录查询时间超过一定时间的查询记录,可以知道哪些查询语句执行速度较慢,便于优化。

redolog

innoDB引擎的日志,用于记录事务操作的变化,记录修改后的值,不论事务是否提交,都会被记录。
每个事务在提交到数据库前都先在redolog中记录,如果因为某种原因出现异常,则通过redolog进行数据恢复。
属于物理日志,记录的是每次修改的值。
redolog有大小限制,当写到结尾时会回到开头循环写。

undolog

保证事务原子性的关键,所有的更新语句都会在undolog中存放他的反向指令,如insert对应delete,当事务需要回滚时,就通过undolog进行回滚。
同时在MVCC机制中也使用undolog回退版本。

数据库

原文链接

安装

直接使用指令安装即可
yum install mysql-community-server

然后启动以下命令验证

1
2
3
4
service mysqld start        #开启MySQL服务    只要没有错误信息就表示已经正常启动了。
service mysqld stop        #关闭MySQL服务
service mysqld restart      #重启MySQL服务
service mysqld status      #查看服务状态

默认临时密码有以下两种方式查看

1
2
3
4
5
6
7
####打开日志文件查看
cat /var/log/mysqld.log

或者

####搜索临时密码,在日志文件中定位
grep 'temporary password' /var/log/mysqld.log

用户管理

登录用户

-u是用户 -p后面是密码

1
mysql -uroot -pve#LoVkeU2u!

更改密码

1
set password =password('123456');

数据库管理

新建数据库

create database database_name;

删除数据库

drop database database_name;

数据管理

创建数据库表

1
create table tablename(column_name column_type);

删除数据表

1
drop table tablename;

插入数据

1
2
3
insert into table_name( field1, field2,...fieldN )
VALUES
( value1, value2,...valueN );

删除语句

1
DELETE FROM <表名> [WHERE 子句] [ORDER BY 子句] [LIMIT 子句];

其中:

  1. <表名>:指定要删除数据的表名。
  2. ORDER BY 子句:可选项。表示删除时,表中各行将按照子句中指定的顺序进行删除。
  3. WHERE 子句:可选项。表示为删除操作限定删除条件,若省略该子句,则代表删除该表中的所有行。
  4. LIMIT 子句:可选项。用于告知服务器在控制命令被返回到客户端前被删除行的最大值。

举例:

1
2
delete from person
where fullname='李大锤';

更新语句

1
2
UPDATE table_name SET field1=new-value1, field2=new-value2
[WHERE Clause]

例如:
update person set telephone = '13607176668' where id = 1 and fullname = '张小敏';

查询语句

1
2
3
4
SELECT column_name,column_name
FROM table_name
[WHERE Clause]
[LIMIT N][ OFFSET M]

其中:
查询语句中你可以使用一个或者多个表,表之间使用逗号(,)分割,并使用WHERE语句来设定查询条件。
SELECT 命令可以读取一条或者多条记录。
你可以使用星号(*)来代替其他字段,SELECT语句会返回表的所有字段数据
你可以使用 WHERE 语句来包含任何条件。
你可以使用 LIMIT 属性来设定返回的记录数。
你可以通过OFFSET指定SELECT语句开始查询的数据偏移量。默认情况下偏移量为0。

例如:


linux

npm

更新包

安装npm-check 检查npm是否有更新
npm install -g npm-check
检查npm包状态
npm-check -u -g
点击空格后可选择要更新的包,然后点击回车即可更新

screen–服务器后台持续运行程序

原文链接
screen是一个用于建立会话窗口的工具,它可以启动一个新的会话窗口,当服务器需要进行一些长时间的操作而你不得不断开连接时,可以使用screen新建一个会话窗口然后将运行你所需要的程序,程序会在该窗口一直运行直到程序结束或者你关闭该窗口,非常好用。

安装screen

直接使用指令安装

1
2
yum install screen#centos
apt-get install screen#ubuntu

相关指令

  1. 创建窗口:screen -S name
  2. 分离窗口(窗口放到后台) ctrl+a+d
  3. 查看所有会话: screen -ls
  4. 进入会话: screen -r name
  5. 杀死会话: kill -9 name
  6. 清楚死去的窗口: screen -wipe
  7. 完全退出会话: `exit

git安装与使用

安装

yum install git

相关指令

  1. git init (–bare name.git) 初始化空仓库,拥有origin起点分支
  2. git remote add origin locate 设置远程地址
  3. git remote rm origin 删除远程地址
  4. git remote set-url origin locate 修改远程地址
  5. git add ./filename 将工作区的文件添加到暂存区
  6. git commit -m “message” 将暂存区保存到本地仓库
  7. git push origin master 将分支上传到远程仓库
  8. git pull origin master 将本地仓库同步到工作区
  9. git checkout

redis

安装

直接指令安装yum install redis

相关指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 启动redis
service redis start
# 停止redis
service redis stop
# 查看redis运行状态
service redis status
# 查看redis进程
ps -ef | grep redis
# 设置为开机自启动
chkconfig redis on
# 进入本机redis
redis-cli
# 列出所有key
keys *

hexo编辑经验

创建分类目录categories

原文链接
我是用的主题不同,所以步骤有一些偏差(其实就是跳过了后面的步骤)

  1. 新建categories页面
    hexo new page categories
  2. 修改categories.md
    1
    2
    3
    4
    5
    6
    7
    ---
    title: 文章分类
    date: 2018-06-11 10:13:21
    type: "categories"
    layout: "categories"
    comments: false
    ---
  3. 新建 >主题/layout/categories.ejs 如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <article class="article article-type-post show">
    <header class="article-header">
    <h1 class="article-title" itemprop="name">
    <%= page.title %>
    </h1>
    </header>

    <% if (site.categories.length){ %>
    <div class="category-all-page article-type-post show">
    <h3>共计&nbsp;<%= site.categories.length %>&nbsp;个分类</h3>
    <ul class="category-list">
    <% site.categories.sort('name').each(function(item){ %>
    <% if(item.posts.length){ %>
    <li class="category-list-item">
    <a href="<%- config.root %><%- item.path %>" title="<%= item.name %>"><%= item.name %><sup>[<%= item.posts.length %>]</sup></a>
    </li>
    <% } %>
    <% }); %>
    </ul>
    </div>
    <% } %>
    </article>
  4. 生成 >hexo g

制作鼠标特效

单机特效

原文链接

  1. 在 /themes/主题名称/source/js 新建一个clicklove.js文件,并把下方的代码放进去。

    ps:原文的文件位于 /themes/主题名称/source/js/src 下,版本和主题不同可能导致位置不同
    !function(e,t,a){function n(){c(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"),o(),r()}function r(){for(var e=0;e<d.length;e++)d[e].alpha<=0?(t.body.removeChild(d[e].el),d.splice(e,1)):(d[e].y--,d[e].scale+=.004,d[e].alpha-=.013,d[e].el.style.cssText="left:"+d[e].x+"px;top:"+d[e].y+"px;opacity:"+d[e].alpha+";transform:scale("+d[e].scale+","+d[e].scale+") rotate(45deg);background:"+d[e].color+";z-index:99999");requestAnimationFrame(r)}function o(){var t="function"==typeof e.onclick&&e.onclick;e.onclick=function(e){t&&t(),i(e)}}function i(e){var a=t.createElement("div");a.className="heart",d.push({el:a,x:e.clientX-5,y:e.clientY-5,scale:1,alpha:1,color:s()}),t.body.appendChild(a)}function c(e){var a=t.createElement("style");a.type="text/css";try{a.appendChild(t.createTextNode(e))}catch(t){a.styleSheet.cssText=e}t.getElementsByTagName("head")[0].appendChild(a)}function s(){return"rgb("+~~(255*Math.random())+","+~~(255*Math.random())+","+~~(255*Math.random())+")"}var d=[];e.requestAnimationFrame=function(){return e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)}}(),n()}(window,document);

  2. 在 /themes/主题名称/layout/layout.ejs 文件中的文件末尾添加如下代码。

    ps:原文中的文件名字叫做_layout.swig 主题和版本不同,后缀可能不同

    1
    2
    <!-- 页面点击小红心 -->
    <script type="text/javascript" src="/js/src/clicklove.js"></script>

各种特效可以用相同的方式添加 只需要把js文件中的代码改成你想要的特效,然后ejs文件新增的代码中的src=“路径”中的路径改成js路径即可。
并且可以多个特效重叠使用,只要额外加一行代码即可
下面提供烟花特效代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
class Circle {
constructor({ origin, speed, color, angle, context }) {
this.origin = origin
this.position = { ...this.origin }
this.color = color
this.speed = speed
this.angle = angle
this.context = context
this.renderCount = 0
}

draw() {
this.context.fillStyle = this.color
this.context.beginPath()
this.context.arc(this.position.x, this.position.y, 2, 0, Math.PI * 2)
this.context.fill()
}

move() {
this.position.x = (Math.sin(this.angle) * this.speed) + this.position.x
this.position.y = (Math.cos(this.angle) * this.speed) + this.position.y + (this.renderCount * 0.3)
this.renderCount++
}
}

class Boom {
constructor ({ origin, context, circleCount = 16, area }) {
this.origin = origin
this.context = context
this.circleCount = circleCount
this.area = area
this.stop = false
this.circles = []
}

randomArray(range) {
const length = range.length
const randomIndex = Math.floor(length * Math.random())
return range[randomIndex]
}

randomColor() {
const range = ['8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
return '#' + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range)
}

randomRange(start, end) {
return (end - start) * Math.random() + start
}

init() {
for(let i = 0; i < this.circleCount; i++) {
const circle = new Circle({
context: this.context,
origin: this.origin,
color: this.randomColor(),
angle: this.randomRange(Math.PI - 1, Math.PI + 1),
speed: this.randomRange(1, 6)
})
this.circles.push(circle)
}
}

move() {
this.circles.forEach((circle, index) => {
if (circle.position.x > this.area.width || circle.position.y > this.area.height) {
return this.circles.splice(index, 1)
}
circle.move()
})
if (this.circles.length == 0) {
this.stop = true
}
}

draw() {
this.circles.forEach(circle => circle.draw())
}
}

class CursorSpecialEffects {
constructor() {
this.computerCanvas = document.createElement('canvas')
this.renderCanvas = document.createElement('canvas')

this.computerContext = this.computerCanvas.getContext('2d')
this.renderContext = this.renderCanvas.getContext('2d')

this.globalWidth = window.innerWidth
this.globalHeight = window.innerHeight

this.booms = []
this.running = false
}

handleMouseDown(e) {
const boom = new Boom({
origin: { x: e.clientX, y: e.clientY },
context: this.computerContext,
area: {
width: this.globalWidth,
height: this.globalHeight
}
})
boom.init()
this.booms.push(boom)
this.running || this.run()
}

handlePageHide() {
this.booms = []
this.running = false
}

init() {
const style = this.renderCanvas.style
style.position = 'fixed'
style.top = style.left = 0
style.zIndex = '999999999999999999999999999999999999999999'
style.pointerEvents = 'none'

style.width = this.renderCanvas.width = this.computerCanvas.width = this.globalWidth
style.height = this.renderCanvas.height = this.computerCanvas.height = this.globalHeight

document.body.append(this.renderCanvas)

window.addEventListener('mousedown', this.handleMouseDown.bind(this))
window.addEventListener('pagehide', this.handlePageHide.bind(this))
}

run() {
this.running = true
if (this.booms.length == 0) {
return this.running = false
}

requestAnimationFrame(this.run.bind(this))

this.computerContext.clearRect(0, 0, this.globalWidth, this.globalHeight)
this.renderContext.clearRect(0, 0, this.globalWidth, this.globalHeight)

this.booms.forEach((boom, index) => {
if (boom.stop) {
return this.booms.splice(index, 1)
}
boom.move()
boom.draw()
})
this.renderContext.drawImage(this.computerCanvas, 0, 0, this.globalWidth, this.globalHeight)
}
}

const cursorSpecialEffects = new CursorSpecialEffects()
cursorSpecialEffects.init()

后台保持启动

pm2代理

原文

  1. 安装 yum install -g pm2
  2. 在博客根目录写一个名为hexo_run.js脚本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //run
    const { exec } = require('child_process')
    exec('hexo server',(error, stdout, stderr) => {
    if(error){
    console.log('exec error: ${error}')
    return
    }
    console.log('stdout: ${stdout}');
    console.log('stderr: ${stderr}');
    })
  3. 到博客根目录执行pm2 start hexo_run.js
  4. 其他相关指令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    pm2 start hexo_run.js	#启动
    pm2 list #查看pm2管理的所有服务

    pm2 stop all #停止pm2列表的所有服务
    pm2 stop 0 #停止进程为0的进程

    pm2 reload all #重新载入列表所有进程
    pm2 reload 0 #重载列表中进程为0的进程

    pm2 restart all #重启列表中所有的进程
    pm2 restart 0 #重启列表中进程为0的进程

    pm2 delete 0 #删除列表中进程为0的进程
    pm2 delete all #删除列表中所有的进程

STL

容器

map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<iostream>
#include<map>
#include<string>
using namespace std;

int main(){
map<int,string> m;

//迭代器
cout << "iterator:" << endl;
map<int,string>::iterator it;
m[1] = "123";
it = m.begin();
cout << it->first << " " << it->second << endl;//迭代器first对应key second对应value

//插入
cout << "insert:" << endl;
m[1] = "123";//数组方式写数据对
m.insert(pair<int,string> (2,"456"));//插入数据对,若存在key则不能插入
m.insert(map<int,string>::value_type(3,"789"));//同上

//查找
cout << m[1] << endl;//索引找值,不存在则返回对应类的初始值
cout << m.find(1)->second << endl;//通过key找迭代器 找不到则返回end迭代器

//删除
cout << "delete:" << endl;
m.erase(m.begin(),m.end());//删除begin到end之间的元素,end删除则表示删除单个元素
m.clear();//清空所有元素

//其他属性
m[1]="123";
m[2]="456";
cout << "others:" << endl;
cout << m.size() << endl;//返回元素个数
cout << m.max_size() << endl;//返回可容纳的最大个数
cout << m.empty() << endl;//返回是否为空,为空则返回true
map<int,string>::reverse_iterator rit = m.rend();//返回反向迭代器
it = m.upper_bound(0);//返回第一个大于0的元素的迭代器,找不到返回end
it = m.lower_bound(0);//同上 大于等于 同func.cpp中用法
it = m.equal_range(0).first;//返回lower
it = m.equal_range(0).second;//返回upper,都是二分查找
getchar();
return 0;
}

stack&queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
#include<stack>
#include<queue>
using namespace std;
void stk();
void que();
int main(){
stk();
que();
getchar();
return 0;
}
void stk(){
int a=0,n=1;
stack<int> s,s2;//声明栈
s.push(a);
cout << "stack:" << endl;
s.push(a);//栈顶压入a 无返回值
s.pop();//弹出栈顶元素 无返回值
cout << s.top() << endl;//返回栈顶元素 不会删除栈顶元素 注意判断是否为空
cout << s.empty() << endl;//判断是否为空栈 空栈返回true
cout << s.size() << endl;//返回栈中元素个数
s.emplace(a);//栈顶加入元素,可直接写构造函数参数 内部构造,如下 队列同理
stack<string> s1;
s1.emplace(3,'a');
cout << s1.top() << endl;
s.swap(s2);//交换两个栈的内容,队列同理
}
void que(){
queue<int> q,q2;//声明队列
int a=0;
q.push(a);
q2.push(a);
cout << "queue:" << endl;
q.push(a);//队尾增加元素
q.emplace(a);//队尾增加元素,可直接写对象的构造函数参数,在内部构造
q.pop();//删除队首元素
cout << q.empty() << endl;//判断是否为空队列 空队列返回true
cout << q.size() << endl;//返回队列元素个数
cout << q.front() << endl;//返回队首元素
cout << q.back() << endl;//返回队尾元素
q.swap(q2);//交换q和q2
}

string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//学习链接“https://blog.csdn.net/tengfei461807914/article/details/52203202”
#include<iostream>
#include<string>
#include<cstring>
using namespace std;

int main(){
char ch[]={"world!"};
cout << sizeof(ch) << endl;
string s1="Hello ",s2=ch,s("ssss");//初始化,用字符串或字符数组直接赋值或者利用构造函数直接赋值
s=s1+s2;//直接将s2接在s1后面
cout << s << endl;//输出应为“Hello world!”
cout << s.front() << s.back() << endl;//返回第一个/最后一个元素 类似与栈顶的作用
cout << "length:" << s.size() << endl;//获取s的长度 不包括 '\0'
cout << "cstring:" << s.c_str() << " length:" << strlen(s.c_str()) << endl;//返回包括'\0'的字符数组

//begin(),end()
string::iterator be = s.begin(),en = s.end();//返回s的头尾迭代器

//substr
cout << "substr():" << endl;
s="012345";
int begin=0,end=3,a=0,n=1;
cout << s.substr(a) << endl;//返回从a到尾部的string类型
cout << s.substr(a,n) << " : " << s.substr(0,8) << endl;
//返回从a开始长度为n的string 如果a+n大于总长度 会抛出out_of_range异常,但运行时返回从a到尾部的string类型
//cout << s.substr(s.begin(),s.end());不可以用迭代器

//insert,返回插入后的string
cout << "insert():" << endl;
cout << s.insert(begin,s1) << endl; // 在begin处插入s1(可用cstr代替) 原begin处接到尾部s1尾部
cout << s.insert(begin,s1,a,n) << endl;//在begin处插入s1中从a开始长度为n的string
cout << s.insert(begin,"hello",n) << endl;//在begin处插入cstr从头开始的n个字符
cout << s.insert(begin,n,'a') << endl;//在begin处插入n个a
string::iterator it = s.insert(s.begin(),'a');//在迭代器处插入字符,返回一个指向插入位置的迭代器
cout << *it << endl;
it = s.insert(s.begin(),n,'b');//在迭代器处插入n个字符,返回指向插入位置的迭代器
cout << *it << endl;
it = s.insert(s.begin(),s1.begin(),s1.end());//在s.begin中插入从s1.begin()到s1.end()的字符串,返回插入位置的迭代器
cout << *it << endl;

//erase
cout << "erase():" << s << endl;
cout << s.erase(a,n) << endl;//删除s从a处开始的n个字符,返回删除后的string
it = s.erase(s.begin()+5);//删除迭代器对应位置的字符,返回删除前参数迭代器指向
cout << *it << " " << s << endl;
it = s.erase(s.begin()+1,s.end());//删除迭代器到迭代器中间的字符,不包括后迭代器指向的位置,返回删除前的s.begin()+1迭代器
cout << *it << " " << s << endl;

//append
s = s1;
cout << "append():" << endl;
cout << s.append(s2) << endl;//直接追加s2,可用cstr和形参代替
cout << s.append(s2,a,n) << endl;//追加s2第a个开始的n个字符
cout << s.append("123456",n) << endl;//追加形参的n个字符
cout << s.append(n,'.') << endl;//追加n个char
cout << s.append(be,en) << endl;//追加be迭代器到en迭代器的字符不包括en
cout << s.append(n,56) << endl;//追加n个ascll码为56的字符
cout << s.append(1,'1');
s+="123";//函数重载为+

//replace,可用迭代器代替用于表示位子的数字或数字组合
s=s1;
cout << "replace():" << endl;
cout << s.replace(a,n,s2) << endl;//第a个后面的n-1个字符被s2代替,s2可用cstr和形参代替
cout << s.replace(a,n,s2,a,n) << endl;//第a个后面的n-1个字符被s2的第a个字符后面的n-1个字符代替 可不等长
cout << s.replace(a,n,"123",n) << endl;//第a个后面的n-1个字符被形参的前n个字符代替
cout << s.replace(a,n,n,'a') << endl;//第a个后面的n-1个字符被n个ch代替

//assign
cout << "assign:" << endl;
cout << s.assign(s1) << endl;//s赋值为s1 s1可用cstr和常量代替
cout << s.assign(n,'a') << endl;//s赋值n个ch
cout << s.assign(s1.begin(),s1.end()) << endl;//begin迭代器到end迭代器的值赋给s

//find/rfind,rfind用法同find 结果返回最后一个位置
cout << "find/rfind:" << endl;
s= s1;
cout << s.find(s1) << " " << s.find(s2) << endl;//找到s1第一次在s中出现的位置,不存在则返回结尾std::string:npos,s1可用cstr、常量和字符代替
cout << s.find(ch,a,n) << endl;//从a处开始找常量或ch的前n个字符,返回同上

//find_of 返回同find
cout << "find_of:" << endl;
cout << s.find_first_of(s1) << endl;//找到s1中任何一个字符第一次出现的位置
cout << s.find_first_not_of(s1) << endl;//找到第一个不在s1中出现过的字符
cout << s.find_last_of(s1) << endl;//找到任何一个字符最后一次出现的位置
cout << s.find_last_not_of(s1) << endl;//找到最后一个不在s1中出现的字符 的位置

//比较与转换
s="123";
n=10;
cout << "compare and transfer:" << endl;
cout << s.compare(a,n,s1,a,n) << endl;//s从a起第n-1个字符与s1从a起底n-1个字符比较,相等为0,s>s1返回1 否则返回-1 a、n组合均可删除
cout << to_string(n) << endl;//n转化为string n支持多种类型
cout << stoi(s,0,n) << endl;//s从0开始转化为n进制 i:int l:long ul:unsigned long ll::long long ull::u longlong f::float d:double ld:long double

getchar();
return 0;

}

vector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<iostream>
#include<vector>

using namespace std;

int main(){
int n=5,a=1;
vector<int> v(n,a);//构造含有n个a的vector,a,n均可根据情况删除
vector<int> v1(v);//赋值v到v1
vector<int> v2(v.begin(),v.end());//赋值begin到end之间的元素到v2 不包括右边界
//增加元素
cout << "add element: " << endl;
v.push_back(a);//尾部增加元素a
cout << *v.insert(v.begin(),n,a+1) << endl;//在迭代器处插入n个a n可省,返回插入位置迭代器
cout << *v.insert(v.begin(),v1.begin(),v1.end()) << endl;//在迭代器位置处插入begin到end间的元素
cout << *v.emplace(v.begin(),5) << endl;//在迭代器前插入单个元素

//减少元素
cout << "delete element: " << endl;
v.pop_back();//删除最后一个元素
cout << *v.erase(v.begin()+1,v.end()-1) << endl;//删除迭代器到迭代器间的元素,后者可删除
v1.clear();//清空元素

//访问元素
cout << "get element:" << endl;
v.assign(n,a);
cout << v.at(n-1) << endl;//返回位置n-1的元素
cout << v.front() << " " << v.back() << endl;//返回开头和结尾的元素
cout << *v.begin() << *v.end() << endl;//返回头尾迭代器
cout << *v.rbegin() << *v.rend() << endl;//返回反向迭代器

//大小
cout << "size:" << endl;
cout << v.empty() << endl;//判断是否为空,空则返回true
cout << v.size() << v.max_size() << v.capacity() << endl;//分别返回向量元素个数、最大可允许个数、当前所能容纳最大个数

//其他
cout << "others:" << endl;
v.swap(v1);//交换所有元素
v.assign(n,a);//设置前n个元素为a
v.assign(v1.begin(),v1.end());//设置为
cout << v[0] << endl;
getchar();
return 0;
}

迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
#include<string>
#include<vector>
#include<deque>
#include<list>
#include<set>
#include<map>
using namespace std;
int main(){
string s="Hello world!";
string::iterator it;//声明随机访问迭代器 容器类型::iterator
string::const_iterator c_it;//const类型迭代器
string::reverse_iterator r_it;//反向迭代器
string::const_reverse_iterator c_r_it;//常量反向迭代器
it = s.begin();//初始化迭代器
it++;//令迭代器指向下一个元素
cout << *it << " " << *(it+2) << " " << it[2] << endl;//it指向对应字符 类似指针

//可用迭代器:随机访问迭代器可事项双向迭代器所有功能,且可随机访问容器的任何位置 双向迭代器支持--和++操作 单项迭代器只支持++
string::iterator strit;//随机访问迭代器
vector<int>::iterator vit;//随机访问迭代器
deque<int>::iterator qit;//随机访问迭代器
list<int>::iterator lit;//双向迭代器
set<int>::iterator sit;//双向迭代器
map<int,int>::iterator mit;//双向迭代器

//迭代器辅助函数
string::iterator p1,p2;
int n=2;
p1 = s.begin();
p2 = s.end();
advance(p1,n);//迭代器向前或向后移动n个元素
cout << distance(p1,p2) << endl;//返回p1需要经过多少次++才能等于p2 如果p1在p2后面则会死循环
iter_swap(p1,p2);//交换p1 和p2
getchar();
return 0;
}

算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
#include<string>
#include<random>
#include<time.h>
using namespace std;

int main(){
string s="123";
reverse(s.begin(),s.end());//#include<algorithm> 反转迭代间内容 不包括s.end()
cout << s << endl;

s="1234";
cout << *lower_bound(s.begin(),s.end(),'2') << endl;//查找两个迭代器范围内第一个大于等于‘2’的元素
cout << *upper_bound(s.begin(),s.end(),'2') << endl;//同上 大于 注意:本函数使用二分法,默认是有序排列的,所以可能出错
cout << *equal_range(s.begin(),s.end(),'2').first << " " << *equal_range(s.begin(),s.end(),'2').second << endl;//找上述两个,lower先

cout << max(0,1) << endl;//支持多种数据比较,返回最大值
cout << min(0,1) << endl;//同上,返回最小值

sort(s.begin(),s.end(),[](auto a,auto b){return a>b;});//从begin到end排序,第三个参数为排序方式,可以是函数名,不写默认升序
//也可以是实例中的函数形式 返回值为1取前一个数,否则取后一个数

cout << s << endl;

//随机产生浮点数#include<random>
random_device rd;//随机种子
mt19937 mt(rd());//伪随机数生成器,参数为种子
uniform_real_distribution<> dis(1.0,2.0);//连续均匀分布,浮点类型
cout << dis(mt) << endl;

getchar();
return 0;
}
,