0%

在 PHP 中,constdefine 都可以用来定义常量。但二者之间有什么区别呢?

const 和 define 的区别

  • const 是在编译阶段定义的,define 是在运行时定义的。 因此,const 不可以有条件的定义,但是 define 可以。

    1
    2
    3
    4
    5
    6
    7
    if (...) {
    const FOO = 'BAR'; //非法的
    }
    //然而
    if (...) {
    define('FOO', 'BAR');//合法的
    }
  • const 只能接受静态标量(数字,字符串或者像 true、false、null、__FILE__等其他常量),而 define 接受任何表达式。不过从 PHP 5.6 开始,const 也开始支持表达式。

  • const 采用普通的常量名称,而 define 接受任何表达式作为名称。他可以做这样用:

    1
    2
    3
    for ($i = 0; $i < 32; ++$i) {
    define('BIT_' . $i, 1 << $i);
    }
  • const 的名称是大小写敏感的,但是 define 可以通过传递第三个参数为 true 来定义不区分名称大小写的常量。

    1
    2
    3
    define('FOO', 'BAR', true);
    echo FOO; // BAR
    echo foo; // BAR

为什么我更赞成使用 const

  • const 可读性更好。const 是一种语言结构而不是函数,而且能够在类中进行定义。

  • const 作为一种语言结构,可以通过自动化工具进行静态分析。

  • const 在当前命名空间中定义常量,而 define 必须传进去整个命名空间名。

    1
    2
    3
    4
    namespace A\B\C;
    // 比如要设置 A\B\C\FOO:
    const FOO = 'BAR';
    define('A\B\C\FOO', 'BAR');
  • const 从 PHP 5.6 开始支持被定义为数组,但是 define 还不支持。不过从 PHP 7 开始,两者都支持定义为数组了。

最后,请记住 const 可以在类和接口中定义类常量或者接口常量,但是 define 不能这样做 。

1
2
3
4
5
6
class Foo {
const BAR = 2; // 合法
}
class Baz {
define('QUX', 2); // 非法
}

总结

除非你需要根据条件和表达式来定义常量,否则使用 const 而不是 define - 只是为了便于阅读!

翻译自 define() vs const,版权归原作者所有。

进程的状态

在操作系统中,我们学习过进程的几种状态,分别为就绪态、运行态和阻塞态。然而在实际的系统中,往往还会有两种状态,即新建态和终止态。 因此,一共有新建态、就绪态、运行态、阻塞态和终止态 5 种进程状态。

Linux 下使用 ps 能够查询的状态

在 linux 和 unix 中,可以使用 ps -l 命令来查看进程状态,在实际的操作系统中,进程的状态要比预期的复杂的多。

1
2
3
4
5
6
7
8
9
10
11
12
D  不可中断睡眠 (通常是在IO操作) 收到信号不唤醒和不可运行, 进程必须等待直到有中断发生  
R 正在运行或可运行(在运行队列排队中)
S 可中断睡眠 (休眠中, 受阻, 在等待某个条件的形成或接受到信号)
T 已停止的 进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行
X 死进程 (未开启)
Z 僵尸进程 进程已终止, 但进程描述符存在, 直到父进程调用
< 高优先级
N 低优先级
L 有些页被锁进内存
s 有子进程
l 多线程,克隆线程
+ 前台进程

fork 产生子进程

在多线程中,我们需要生成新的进程,会通过使用 fork 来产生子进程。在子进程产生后,如果父进程处理方式不正确或者提前退出,则可能会出现僵尸进程孤儿进程

在进程运行过程中,会将进程的一些信息(进程号,执行时间,退出码,打开的文件等)存入进程表,进程结束时会传递给父进程,父进程调用 wait 或者 waitpid 来释放符号表中的信息。

孤儿进程

在父进程创建了子进程后,子进程会在结束时将进程表中的信息传递给父进程,然后将进程表中的信息移除。当子进程还没有结束,父进程提前退出时,子进程这时就被称为”孤儿进程”。孤儿进程会被进程号为1的init进程收养,代替其父进程完成善后工作。因此,孤儿进程不会对系统造成危害。

僵尸进程

僵尸进程产生的原因

在父进程创建子进程后,子进程已经结束,但是仍然占用符号表的状态,被称作”僵尸进程”。 这一过程任何进程都会遇到(除了 init 进程),但是如果父进程立即进行处理,这一时间会很短暂的。如果父进程迟迟不做处理,这时候就可以看到很多状态为 Z 的僵尸进程。

僵尸进程无疑是有危害的,因为僵尸进程长时间占用进程号,如果系统长时间运行,可能会导致进程号耗尽的情况。

如何消除僵尸进程

僵尸进程本身已经结束了,所以调用 kill 命令去结束僵尸进程是没有用的。其实僵尸进程的根源是父进程,因为父进程不去收养这些进程而形成了长时间存在的僵尸进程。

因此,杀死父进程可以结束僵尸进程。父进程被杀死后,init 进程会收养这些进程并移除其进程表中的信息。

使用两次 fork 避免僵尸进程

当我们 fork() 一次后,存在父进程和子进程。这时有两种方法来避免产生僵尸进程:

  • 父进程调用 waitpid() 等函数来接收子进程退出状态。
  • 父进程先结束,子进程则自动托管到 init 进程(pid = 1)

第一种方法,有两种情况:

  • 父进程在创建子进程后等待子进程结束,然后执行自身业务 => 如果子进程时间比较长,父进程的业务无法完成;
  • 父进程在执行自己业务后等待子进程结束 => 子进程会长时间处于僵尸进程状态。

第二种方法父进程如果有自身业务将无法进行,不可取。

因此,我们可以通过两次 fork 来避免僵尸进程。

  • 第一次 fork: 父进程产生子进程 ,等待子进程结束;
  • 第二次 fork: 子进程产生孙子进程;
  • 子进程立即退出,孙子进程变成孤儿进程;
  • 父进程收到子进程结束信号,继续执行后续逻辑;

这样,孙子进程由 init 进程负责收尸,子进程在创建孙子进程后立即由父进程收尸,便不会产生僵尸进程的问题了。

问题

当在 a.com 进行访问时,如何向 b.com 携带 b.com 的 cookie 发送一个请求?

情境

a.com 是一个第三方网站,需要通过访问 b.com 的接口来获取用户的一些信息。这时候,b.com 上的用户已经登录了。

跨域请求

我们知道,在发送跨域请求时,需要后端设置一些请求头,否则浏览器不会允许客户端跨域发送请求。

1
Access-Control-Allow-Origin: a.com

这样,a.com 便可以调用 b.com 的接口了。

但是,这样调用过去会发现,b.com 会返回用户未登录。原因是 b.com 的 cookie 没有发送过去。

跨域携带 Cookie

这时候需要后端添加另外一个请求头:

1
Access-Control-Allow-Credentials: true

前端在发送请求时也需要设置

1
2
3
4
xhr = new XMLHttpRequest();
xhr.withCredentials= true; //关键句
xhr.open("GET", url);
xhr.send();

这样,后端就可以接收到前端携带的 Cookie 了。

总结

综上所述,前端需要在发送 XMLHttpRequest 的时候加上

1
xhr.withCredentials= true;

后端需要设置请求头:

1
2
Access-Control-Allow-Origin: a.com //这里需要换成相应的发起请求的域名
Access-Control-Allow-Credentials: true

客厅

客厅效果

客厅效果图

客厅窗户效果

客厅窗户图

餐厅

餐厅效果图

厨房

厨房效果图

卫生间

卫生间效果图

卧室

卧室效果 或者 卧室效果2

今天阅读了 Redis 的分布式锁相关部分,在这里做一下笔记。

Redis 的分布式锁主要依赖于 Watch 命令。

Watch 命令的语法为

1
watch key

当进入 watch 状态后,修改该 key 时会去检查 key 在执行过程中是否已经被修改。如果已经被修改,则命令会执行失败。

1
2
3
4
5
6
set amount 1000

watch amount
multi
decr amount
exec

watch amount 后如果有别人已经修改了 amount 值,那么之后的事务将会失败。这样就实现了简单的乐观锁。

watch 执行之后可以执行 unwatch操作来取消观察状态。例如 watch 之后去判断库存数量,如果已经是 0,可以取消观察并直接向用户返回库存消耗完毕。

为什么要使用Expect ?

在执行 ssh 远程登录时,有时候需要在配置了密钥文件的情况下仍然需要输入密码,这一步骤无法省略,而密码冗长有让我们有必要采取措施来绕过密码输入的步骤。而 Expect 就能够非常简单的完成此任务。

Expect 简介

Expect是Unix系统中用来进行自动化控制和测试的软件工具,由DonLibes制作,作为Tcl脚本语言的一个扩展,应用在交互式软件中如telnet,ftp,Passwd,fsck,rlogin,tip,ssh等等。该工具利用Unix伪终端包装其子进程,允许任意程序通过终端接入进行自动化控制;也可利用Tk工具,将交互程序包装在X11的图形用户界面中。   简单地说,expect是一个工具,可以根据用户设定的规则和系统进程进行自动化交互,例如远程登陆的密码输入、自动化的执行远程命令。

Expect 基本命令

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

编写脚本

1
2
3
4
5
6
 #!/usr/bin/expect
spawn ssh user@host -i ~/.ssh/user.pem
# 将下方字符串替换为自己的终端输出的提示
expect "Enter passphrase for key '/Users/phpcyy/.ssh/user.pem':"
send "$PASSWORD\r"
interact

ps: 注意将上方的参数替换成自己相应的设置。

执行脚本

添加可执行权限,并执行该脚本,就可以自动连上远程的服务器了。

创建 redis.service

1
2
cd  /usr/lib/systemd/system
touch redis.service

编辑内容

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Redis Server
After=network.target

[Service]
PIDFile=/var/run/redis_6379.pid
ExecStart=/usr/local/bin/redis-server /etc/redis.conf
ExecStop=/usr/local/bin/redis-cli -a passwd shutdown
ExecReload=/bin/kill -s HUP $MAINPID

[Install]
WantedBy=mutli-user.target

创建软链接

1
ln -s `pwd`/redis.service /etc/systemd/system/multi-user.target.wants

重新载入 systemctl 并启动服务

1
2
systemctl daemon-reload
systemctl start redis.service

查看状态

1
systemctl status redis.service

关闭 Redis

1
systemctl stop redis.service

开机自启

1
systemctl enable redis.service

是否开机自启

1
systemctl is-enabled redis.service

CURL 是最简单的发送 http 请求的方法之一,在 Linux 上用来模拟请求和测试 API 十分有用,这里记录一下 CURL 常见的使用方法。

发送 GET 请求

发送 GET 请求非常简单,只需要在命令后直接跟上 URL 就可以了。

1
curl "http://www.baidu.com"

发送其他 HTTP 动词

除了 GET 以外,我们经常还会用到诸如 POST,PUT,DELETE 等动词,只要加上 -x 参数就可以了。

1
curl -X POST "http://www.weibo.com/login"

添加请求参数

发送 GET 请求非常简单,只需要在 URL 后添加所需参数即可。

1
curl "http://www.baidu.com?s=hello"

发送其他类型的请求可以用 --data 或者 -d 选项指定参数。

1
curl -X "http://www.weibo.com/login" -d "name=master&password=123456"

发送带有文件需要使用 multipart/form-data 类型的 Content-Type。使用 CURL 发送文件也很简单,使用 –form 参数即可。其中文件类型需要加上@,然后指定文件位置;字符串类型则直接使用字符串值即可。

1
curl --form "headimg=@1.jpg" --form "user=master" "http://www.weibo.com/profile"

获取 Header 信息

有时候我们想看到响应的头信息来进行调试。只需要添加 -I 参数即可。

1
curl -I "http://www.baidu.com"

显示连接过程

使用 -V 参数可以得到连接的详细信息。包括端口连接和 HTTP 请求的全文。

1
curl -v "https://www.baidu.com"
1
curl --cookie "SESSIONID=2123112312" "http://user.qq.com"

添加 Header

1
curl --header "Content-Type:application/json" "http://test.qq.com"

HTTP 认证

有些网站可能启用了 Http 基本认证,需要指定用户名和密码才可以访问。这时使用 --user 参数就可以了。

1
curl --user "foo=bar" "http://auth.test.com"

当表中数据很大的时候,根据条件进行查询时会变的很慢。因此,通过设置索引来加快查询速度就非常有必要了。

索引为什么快?

  1. 索引是有序的,增加索引后可以使用定位算法迅速找到查询的值。
  2. JOIN 时可以快速的根据索引值来判断 JOIN 条件,大大减少了查询次数
  3. 索引空间小,可以放在内存中,加快查询速度。

索引的作用

  1. 加快条件查询(where 和 join)的速度。
  2. 快速找到索引列的最大值和最小值(大大加快 MIN() 和 MAX() 函数的速度)。
  3. 高效完成分类、分组和排序操作(加快 ORDER BY 和 GROUP BY 的速度)。
  4. 如果只查询索引列包含的值时,可以直接从索引中获取相关信息。

索引的注意事项

  1. 为用于搜索、排序和分组的列创建索引,而对于用作输出显示的列不用创建索引。
  2. 认真考虑数据列基数。重复数过高的列不要设置索引。
  3. 让索引尽量短小。索引列的数据类型长度尽量短。
  4. 索引字符串值的前缀。
  5. 利用最左前缀。
  6. 不要建立过多的索引。
  7. 让索引类型和比较类型相匹配。如果是精确查找,散列索引更加高效;如果是范围查找,B 树索引则更加高效。
  8. 利用慢日志查出来性能低下的查询。

m * n 就是将 m 进行 n 次累加。如果简单的使用 for 循环,当循环次数很大时无疑很慢。

因此我们可以考虑使用迭代。“一生二,二生三,三生万物”,这句话在这里可以得到体现。比如 5 * 9,1 * 9 = 9;2 * 9 = 9 + 9 =18;4 * 9 = 18 + 18 = 36;5 * 9 = 36 + 9 = 45;

因此 5 * 9 最终转化为了 4 * 9 + 1 * 9。乘法转化为了加法。通过观察可以发现 5 的 二进制为 0b101,刚好符合 4 * 9 + 1 * 9。

因此任何乘法都可以转换为加法。m * n,将 m 转为 2 进制,然后逐位求出其位数对应的乘积,相加便是最终的结果。

例如 13 * 16, 13可以转化为 0b1101,依次迭代,

1
2
1      1     0     1
128 64 32 16

结果相加为 128 + 64 + 16 = 208 。

转化为算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func mul(m int, n int) int {
//将较小者进行迭代可以减少循环次数
if m > n {
m, n = n, m
}
result := 0
for m > 0 {
//取余运算
if m&1 == 1 {
result = n + result
}
//n = 2*n,对应例子中的第二行
n = n << 1
//m = m/2,对应例子中的第一行
m = m >> 1
}
return result
}