docker 实现redis集群搭建(二)
三、运行redis集群1.运行redis容器[root@etcd3 docker_redis_nodes]# docker run -d --name redis-6379 -p 6379:6379 hakimdstx/nodes-redis:4.0.1
1673a7d859ea83257d5bf14d82ebf717fb31405c185ce96a05f597d8f855aa7d
[root@etcd3 docker_redis_nodes]# docker run -d --name redis-6380 -p 6380:6379 hakimdstx/nodes-redis:4.0.1
df6ebce6f12a6f3620d5a29adcfbfa7024e906c3af48f21fa7e1fa524a361362
[root@etcd3 docker_redis_nodes]# docker run -d --name redis-6381 -p 6381:6379 hakimdstx/nodes-redis:4.0.1
396e174a1d9235228b3c5f0266785a12fb1ea49efc7ac755c9e7590e17aa1a79
[root@etcd3 docker_redis_nodes]# docker run -d --name redis-6382 -p 6382:6379 hakimdstx/nodes-redis:4.0.1
d9a71dd3f969094205ffa7596c4a04255575cdd3acca2d47fe8ef7171a3be528
[root@etcd3 docker_redis_nodes]# docker run -d --name redis-6383 -p 6383:6379 hakimdstx/nodes-redis:4.0.1
73e4f843d8cb28595456e21b04f97d18ce1cdf8dc56d1150844ba258a3781933
[root@etcd3 docker_redis_nodes]# docker run -d --name redis-6384 -p 6384:6379 hakimdstx/nodes-redis:4.0.1
10c62aafa4dac47220daf5bf3cec84406f086d5261599b54ec6c56bb7da97d6d2.查看容器信息[root@etcd3 redis]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
10c62aafa4da hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 3 seconds ago Up 2 seconds 0.0.0.0:6384->6379/tcp redis-6384
73e4f843d8cb hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 12 seconds ago Up 10 seconds 0.0.0.0:6383->6379/tcp redis-6383
d9a71dd3f969 hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 20 seconds ago Up 18 seconds 0.0.0.0:6382->6379/tcp redis-6382
396e174a1d92 hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 3 days ago Up 3 days 0.0.0.0:6381->6379/tcp redis-6381
df6ebce6f12a hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 3 days ago Up 3 days 0.0.0.0:6380->6379/tcp redis-6380
1673a7d859ea hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 3 days ago Up 3 days 0.0.0.0:6379->6379/tcp redis-63793.运行 redis 集群容器1.通过远程连接,查看redis ?info replication 信息[root@etcd2 ~]# redis-cli -h 192.168.10.52 -p 6379
192.168.10.52:6379> info replication
NOAUTH Authentication required.
192.168.10.52:6379> auth 123456
OK
192.168.10.52:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:2f0a7b50aed699fa50a79f3f7f9751a070c50ee9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
192.168.10.52:6379>
# 其余基本信息同上 ?可以看到,客户连接之后,因为之前设置了密码,所以需要先输入密码认证,否则就无法通过。以上信息,我们知道所有的redis都是master角色 role:master ,这显然不是我们所希望的。2.在配置之前我们需要查看所有容器当前的IP地址[root@etcd3 redis]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
10c62aafa4da hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 3 seconds ago Up 2 seconds 0.0.0.0:6384->6379/tcp redis-6384
73e4f843d8cb hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 12 seconds ago Up 10 seconds 0.0.0.0:6383->6379/tcp redis-6383
d9a71dd3f969 hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 20 seconds ago Up 18 seconds 0.0.0.0:6382->6379/tcp redis-6382
396e174a1d92 hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 3 days ago Up 3 days 0.0.0.0:6381->6379/tcp redis-6381
df6ebce6f12a hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 3 days ago Up 3 days 0.0.0.0:6380->6379/tcp redis-6380
1673a7d859ea hakimdstx/nodes-redis:4.0.1 "/usr/local/redis/..." 3 days ago Up 3 days 0.0.0.0:6379->6379/tcp redis-6379
[root@etcd3 redis]#
[root@etcd3 redis]# docker inspect 10c62aafa4da 73e4f843d8cb d9a71dd3f969 396e174a1d92 df6ebce6f12a 1673a7d859ea | grep IPA
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.7",
"IPAMConfig": null,
"IPAddress": "172.17.0.7",
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.6",
"IPAMConfig": null,
"IPAddress": "172.17.0.6",
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.5",
"IPAMConfig": null,
"IPAddress": "172.17.0.5",
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.4",
"IPAMConfig": null,
"IPAddress": "172.17.0.4",
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.3",
"IPAMConfig": null,
"IPAddress": "172.17.0.3",
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAMConfig": null,
"IPAddress": "172.17.0.2",可以知道: ?redis-6379:172.17.0.2,redis-6380:172.17.0.3,redis-6381:172.17.0.4,redis-6382:172.17.0.5,redis-6383:172.17.0.6,redis-6384:172.17.0.7 ?3.配置redis4.ert4.Redis Cluster 的集群感知操作//集群(cluster)
CLUSTER INFO 打印集群的信息
CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。
//节点(node)
CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。
CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。
CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。
//槽(slot)
CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。
CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。
CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
CLUSTER SETSLOT <slot> NODE <node_id> 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽 slot 迁移到 node_id 指定的节点中。
CLUSTER SETSLOT <slot> IMPORTING <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。
CLUSTER SETSLOT <slot> STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。//键 (key)
CLUSTER KEYSLOT <key> 计算键 key 应该被放置在哪个槽上。
CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的键值对数量。
CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot 槽中的键。?redis 集群感知:节点握手——是指一批运行在集群模式的节点通过Gossip协议彼此通信,达到感知对方的过程。192.168.10.52:6379> CLUSTER MEET 172.17.0.3 6379
OK
192.168.10.52:6379> CLUSTER MEET 172.17.0.4 6379
OK
192.168.10.52:6379> CLUSTER MEET 172.17.0.5 6379
OK
192.168.10.52:6379> CLUSTER MEET 172.17.0.6 6379
OK
192.168.10.52:6379> CLUSTER MEET 172.17.0.7 6379
OK
192.168.10.52:6379> CLUSTER NODES
54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 172.17.0.3:6379@16379 master - 0 1528697195600 1 connected
f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 172.17.0.4:6379@16379 master - 0 1528697195600 0 connected
ae86224a3bc29c4854719c83979cb7506f37787a 172.17.0.7:6379@16379 master - 0 1528697195600 5 connected
98aebcfe42d8aaa8a3375e4a16707107dc9da683 172.17.0.6:6379@16379 master - 0 1528697194000 4 connected
0bbdc4176884ef0e3bb9b2e7d03d91b0e7e11f44 172.17.0.5:6379@16379 master - 0 1528697194995 3 connected
760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 172.17.0.2:6379@16379 myself,master - 0 1528697195000 2 connected?当前已经使这六个节点组成集群,但是现在还无法工作,因为集群节点还没有分配槽(slot)。1.分配槽信息查看172.17.0.2:6379 的槽个数192.168.10.52:6379> CLUSTER INFO
cluster_state:fail
cluster_slots_assigned:0 # 被分配槽的个数为0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0上面看到集群状态是失败的,原因是槽位没有分配,而且需要一次性把16384个槽位完全分配了,集群才可用。2..分配槽位分配槽位: CLUSTER ADDSLOTS ?槽位,一个槽位只能分配一个节点,16384个槽位必须分配完,不同节点不能冲突。所以通过脚本进行分配 addslots.sh:#!/bin/bash
# node1 192.168.10.52 172.17.0.2
n=0
for ((i=n;i<=5461;i++))
do
/usr/local/bin/redis-cli -h 192.168.10.52 -p 6379 -a 123456 CLUSTER ADDSLOTS $i
done
# node2 192.168.10.52 172.17.0.3
n=5462
for ((i=n;i<=10922;i++))
do
/usr/local/bin/redis-cli -h 192.168.10.52 -p 6380 -a 123456 CLUSTER ADDSLOTS $i
done
# node3 192.168.10.52 172.17.0.4
n=10923
for ((i=n;i<=16383;i++))
do
/usr/local/bin/redis-cli -h 192.168.10.52 -p 6381 -a 123456 CLUSTER ADDSLOTS $i
done?其中, -a 123456 ?表示需要输入的密码。192.168.10.52:6379> CLUSTER INFO
cluster_state:fail # 集群状态为失败
cluster_slots_assigned:16101 # 没有完全分配结束
cluster_slots_ok:16101
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:2
cluster_stats_messages_ping_sent:266756
cluster_stats_messages_pong_sent:266528
cluster_stats_messages_meet_sent:10
cluster_stats_messages_sent:533294
cluster_stats_messages_ping_received:266527
cluster_stats_messages_pong_received:266666
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:533194<br>
192.168.10.52:6379> CLUSTER INFO
cluster_state:ok # 集群状态为成功
cluster_slots_assigned:16384 # 已经全部分配完成
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:2
cluster_stats_messages_ping_sent:266757
cluster_stats_messages_pong_sent:266531
cluster_stats_messages_meet_sent:10
cluster_stats_messages_sent:533298
cluster_stats_messages_ping_received:266530
cluster_stats_messages_pong_received:266667
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:533198综上可知,当全部槽位分配完成之后,集群还是可行的,如果我们手欠,移除一个槽位,那么集群就立马那不行了,自己去试试吧 ——CLUSTER DELSLOTS 0 。5.如何变成高可用性以上我们已经搭建了一套完整的可运行的redis cluster,但是每个节点都是单点,这样子可能出现,一个节点挂掉,整个集群因为槽位分配不完全而崩溃,因此,我们需要为每个节点配置副本备用节点。前面我们已经提前创建了6个备用节点,搭建集群花了三个,因此还有剩下三个直接可以用来做备用副本。192.168.10.52:6379> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6 # 总共6个节点
cluster_size:3 # 集群为 3 个节点
cluster_current_epoch:5
cluster_my_epoch:2
cluster_stats_messages_ping_sent:270127
cluster_stats_messages_pong_sent:269893
cluster_stats_messages_meet_sent:10
cluster_stats_messages_sent:540030
cluster_stats_messages_ping_received:269892
cluster_stats_messages_pong_received:270037
cluster_stats_messages_meet_received:1
cluster_stats_messages_received:539930?查看所有节点的id192.168.10.52:6379> CLUSTER NODES
54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 172.17.0.3:6379@16379 master - 0 1528704114535 1 connected 5462-10922
f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 172.17.0.4:6379@16379 master - 0 1528704114000 0 connected 10923-16383
ae86224a3bc29c4854719c83979cb7506f37787a 172.17.0.7:6379@16379 master - 0 1528704114023 5 connected
98aebcfe42d8aaa8a3375e4a16707107dc9da683 172.17.0.6:6379@16379 master - 0 1528704115544 4 connected
0bbdc4176884ef0e3bb9b2e7d03d91b0e7e11f44 172.17.0.5:6379@16379 master - 0 1528704114836 3 connected
760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 172.17.0.2:6379@16379 myself,master - 0 1528704115000 2 connected 0-5461?编写脚本,添加副本节点[root@etcd2 tmp]# vi addSlaveNodes.sh
#!/bin/bash
/usr/local/bin/redis-cli -h 192.168.10.52 -p 6382 -a 123456 CLUSTER REPLICATE 760e4d0039c5ac13d04aa4791c9e6dc28544d7c7
/usr/local/bin/redis-cli -h 192.168.10.52 -p 6383 -a 123456 CLUSTER REPLICATE 54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c
/usr/local/bin/redis-cli -h 192.168.10.52 -p 6384 -a 123456 CLUSTER REPLICATE f45f9109f2297a83b1ac36f9e1db5e70bbc174ab?注意:1、作为备用的节点,必须是未分配槽位的,否者会操作失败 (error) ERR To set a master the node must be empty and without assigned slots 。 ? ? ? ? ? 2、需要从需要添加的节点上面执行操作,CLUSTER REPLICATE [node_id] ?,使当前节点成为 node_id 的副本节点。 ? ? ? ? ? 3、添加从节点(集群复制): 复制的原理和单机的Redis复制原理一样,区别是:集群下的从节点也需要运行在cluster模式下,要先添加到集群里面,再做复制。查看所有节点信息:192.168.10.52:6379> CLUSTER NODES
54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 172.17.0.3:6379@16379 master - 0 1528705604149 1 connected 5462-10922
f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 172.17.0.4:6379@16379 master - 0 1528705603545 0 connected 10923-16383
ae86224a3bc29c4854719c83979cb7506f37787a 172.17.0.7:6379@16379 slave f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 0 1528705603144 5 connected
98aebcfe42d8aaa8a3375e4a16707107dc9da683 172.17.0.6:6379@16379 slave 54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 0 1528705603000 4 connected
0bbdc4176884ef0e3bb9b2e7d03d91b0e7e11f44 172.17.0.5:6379@16379 slave 760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 0 1528705603000 3 connected
760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 172.17.0.2:6379@16379 myself,master - 0 1528705602000 2 connected 0-5461可以看到我们现在实现了三主三从的一个高可用集群。6.高可用测试——故障转移查看当前运行状态:192.168.10.52:6379> CLUSTER NODES
54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 172.17.0.3:6379@16379 master - 0 1528705604149 1 connected 5462-10922
f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 172.17.0.4:6379@16379 master - 0 1528705603545 0 connected 10923-16383
ae86224a3bc29c4854719c83979cb7506f37787a 172.17.0.7:6379@16379 slave f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 0 1528705603144 5 connected
98aebcfe42d8aaa8a3375e4a16707107dc9da683 172.17.0.6:6379@16379 slave 54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 0 1528705603000 4 connected
0bbdc4176884ef0e3bb9b2e7d03d91b0e7e11f44 172.17.0.5:6379@16379 slave 760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 0 1528705603000 3 connected
760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 172.17.0.2:6379@16379 myself,master - 0 1528705602000 2 connected 0-5461 ?以上,运行正常尝试关闭一个master,选择端口为6380的容器,停掉之后:192.168.10.52:6379> CLUSTER NODES
54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 172.17.0.3:6379@16379 master,fail - 1528706408935 1528706408000 1 connected 5462-10922
f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 172.17.0.4:6379@16379 master - 0 1528706463000 0 connected 10923-16383
ae86224a3bc29c4854719c83979cb7506f37787a 172.17.0.7:6379@16379 slave f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 0 1528706462980 5 connected
98aebcfe42d8aaa8a3375e4a16707107dc9da683 172.17.0.6:6379@16379 slave 54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 0 1528706463000 4 connected
0bbdc4176884ef0e3bb9b2e7d03d91b0e7e11f44 172.17.0.5:6379@16379 slave 760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 0 1528706463985 3 connected
760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 172.17.0.2:6379@16379 myself,master - 0 1528706462000 2 connected 0-5461
192.168.10.52:6379>
192.168.10.52:6379> CLUSTER INFO
cluster_state:fail
cluster_slots_assigned:16384
cluster_slots_ok:10923
cluster_slots_pfail:0
cluster_slots_fail:5461
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:5
cluster_my_epoch:2
cluster_stats_messages_ping_sent:275112
cluster_stats_messages_pong_sent:274819
cluster_stats_messages_meet_sent:10
cluster_stats_messages_fail_sent:5
cluster_stats_messages_sent:549946
cluster_stats_messages_ping_received:274818
cluster_stats_messages_pong_received:275004
cluster_stats_messages_meet_received:1
cluster_stats_messages_fail_received:1
cluster_stats_messages_received:549824 ?以上,发现整个集群都失败了,从节点没有自动升级为主节点,怎么回事??重启停掉的容器,经排查日志信息 [root@df6ebce6f12a /]# tail -f /var/log/redis/redis-server.log ?:1:S 11 Jun 09:57:46.712 # Cluster state changed: ok
1:S 11 Jun 09:57:46.718 * (Non critical) Master does not understand REPLCONF listening-port: -NOAUTH Authentication required.
1:S 11 Jun 09:57:46.718 * (Non critical) Master does not understand REPLCONF capa: -NOAUTH Authentication required.
1:S 11 Jun 09:57:46.719 * Partial resynchronization not possible (no cached master)
1:S 11 Jun 09:57:46.719 # Unexpected reply to PSYNC from master: -NOAUTH Authentication required.
1:S 11 Jun 09:57:46.719 * Retrying with SYNC...
1:S 11 Jun 09:57:46.719 # MASTER aborted replication with an error: NOAUTH Authentication required.
1:S 11 Jun 09:57:46.782 * Connecting to MASTER 172.17.0.6:6379
1:S 11 Jun 09:57:46.782 * MASTER <-> SLAVE sync started
1:S 11 Jun 09:57:46.782 * Non blocking connect for SYNC fired the event.可以看到,主从之间访问需要auth,之前忘记了配置 redis.conf ?中的 # masterauth ,所以导致主从之间无法通讯。修改配置之后,自动故障转移正常。有时候需要实施人工故障转移:登录6380端口的从节点:6383,执行 CLUSTER FAILOVER 命令:192.168.10.52:6383> CLUSTER FAILOVER
(error) ERR Master is down or failed, please use CLUSTER FAILOVER FORCE?发现因为master已经down了,所以我们需要执行强制转移192.168.10.52:6383> CLUSTER FAILOVER FORCE
OK查看当前 cluster node 情况:192.168.10.52:6383> CLUSTER NODES
0bbdc4176884ef0e3bb9b2e7d03d91b0e7e11f44 172.17.0.5:6379@16379 slave 760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 0 1528707535332 3 connected
ae86224a3bc29c4854719c83979cb7506f37787a 172.17.0.7:6379@16379 slave f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 0 1528707534829 5 connected
f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 172.17.0.4:6379@16379 master - 0 1528707534527 0 connected 10923-16383
98aebcfe42d8aaa8a3375e4a16707107dc9da683 172.17.0.6:6379@16379 myself,master - 0 1528707535000 6 connected 5462-10922
760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 172.17.0.2:6379@16379 master - 0 1528707535834 2 connected 0-5461
54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 172.17.0.3:6379@16379 master,fail - 1528707472833 1528707472000 1 connected?从节点已经升级为master节点。这时候,我们尝试重启了,6380节点的redis(其实是重新启动停掉的容器):192.168.10.52:6383> CLUSTER NODES
0bbdc4176884ef0e3bb9b2e7d03d91b0e7e11f44 172.17.0.5:6379@16379 slave 760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 0 1528707556044 3 connected
ae86224a3bc29c4854719c83979cb7506f37787a 172.17.0.7:6379@16379 slave f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 0 1528707555000 5 connected
f45f9109f2297a83b1ac36f9e1db5e70bbc174ab 172.17.0.4:6379@16379 master - 0 1528707556000 0 connected 10923-16383
98aebcfe42d8aaa8a3375e4a16707107dc9da683 172.17.0.6:6379@16379 myself,master - 0 1528707556000 6 connected 5462-10922
760e4d0039c5ac13d04aa4791c9e6dc28544d7c7 172.17.0.2:6379@16379 master - 0 1528707556000 2 connected 0-5461
54cb5c2eb8e5f5aed2d2f7843f75a9284ef6785c 172.17.0.3:6379@16379 slave 98aebcfe42d8aaa8a3375e4a16707107dc9da683 0 1528707556547 6 connected我们发现,6380节点反而变成了 6383节点的从节点。现在集群应该是完整的了,所以,集群状态应该已经恢复了,我们查看下:192.168.10.52:6383> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:6
cluster_stats_messages_ping_sent:19419
cluster_stats_messages_pong_sent:19443
cluster_stats_messages_meet_sent:1
cluster_stats_messages_auth-req_sent:5
cluster_stats_messages_update_sent:1
cluster_stats_messages_sent:38869
cluster_stats_messages_ping_received:19433
cluster_stats_messages_pong_received:19187
cluster_stats_messages_meet_received:5
cluster_stats_messages_fail_received:4
cluster_stats_messages_auth-ack_received:2
cluster_stats_messages_received:38631OK,没有问题。7.集群访问客户端在初始化的时候只需要知道一个节点的地址即可,客户端会先尝试向这个节点执行命令,比如 ?get key ,如果key所在的slot刚好在该节点上,则能够直接执行成功。如果slot不在该节点,则节点会返回MOVED错误,同时把该slot对应的节点告诉客户端,客户端可以去该节点执行命令192.168.10.52:6383> get hello
(error) MOVED 866 172.17.0.2:6379
192.168.10.52:6379> set number 20004
(error) MOVED 7743 172.17.0.3:6379 ?另外,redis集群版只使用db0,select命令虽然能够支持select 0。其他的db都会返回错误。192.168.10.52:6383> select 0
OK
192.168.10.52:6383> select 1
(error) ERR SELECT is not allowed in cluster mode近期,有网友询问docker redis集群连接报错的问题,具体报错如下:初步认为是,node节点没有全部添加进去,添加之后,依然有上述问题。想到是跨主机访问,应该是路由寻址不了导致的。当初写上述教程的时候,docker是以默认的网络模式bridge模式运行的,毕竟当初是以学习整理文档为主,主要是单机访问。但是,实际应用化场景中,多是公网跨主机访问,问题明朗了,想着集群这东西最好还是设置成共享主机公网ip比较好,于是解决如下:在docker运行时,执行网络模式为:host。端口冲突解决,毕竟host模式下,容器会占用宿主机的端口,于是,我们就从配置下手,在宿主机上生成配置redis-60001.conf,redis-60002.conf,redis-60003.conf...,有多少端口建多少个文件,最终运行一个容器,挂载一个配置到容器中用于覆盖主机中的配置。最终的运行方式如下:docker run -d --name redis-6380 --net host -v /tmp/redis.conf:/usr/local/redis/redis.conf hakimdstx/nodes-redis:4.0.1至此,网络问题得到解决。PS.生产环境需要注意防火墙问题,不然也是会报错的。 ?
PHP 7 新特性(二)
九、PHP CSPRNGCSPRNG(Cryptographically Secure Pseudo-Random Number Generator,伪随机数产生器)。PHP 7 通过引入几个 CSPRNG 函数提供一种简单的机制来生成密码学上强壮的随机数。random_bytes() - 加密生存被保护的伪随机字符串。random_int() - 加密生存被保护的伪随机整数。random_bytes()语法格式string random_bytes ( int $length )参数length - 随机字符串返回的字节数。返回值返回一个字符串,接受一个int型入参代表返回结果的字节数。实例<?php
$bytes = random_bytes(5);
print(bin2hex($bytes));
?>以上程序执行输出结果为:6f36d48a29
random_int()语法格式int random_int ( int $min , int $max )参数min - 返回的最小值,必须是大于或等于 PHP_INT_MIN 。
max - 返回的最大值,必须是小于或等于 PHP_INT_MAX 。返回值返回一个指定范围内的int型数字。实例<?php
print(random_int(100, 999));
print(PHP_EOL);
print(random_int(-1000, 0));
?>以上程序执行输出结果为:723
-64十、PHP 7 异常PHP 7 异常用于向下兼容及增强旧的assert()函数。它能在生产环境中实现零成本的断言,并且提供抛出自定义异常及错误的能力。老版本的API出于兼容目的将继续被维护,assert()现在是一个语言结构,它允许第一个参数是一个表达式,而不仅仅是一个待计算的 string或一个待测试的boolean。assert() 配置参数assertion断言。在 PHP 5 中,是一个用于执行的字符串或者用于测试的布尔值。在 PHP 7 中,可以是一个返回任何值的表达式, 它将被执行结果用于指明断言是否成功。description如果 assertion 失败了,选项 description 将会包括在失败信息里。exception在 PHP 7 中,第二个参数可以是一个 Throwable 对象,而不是一个字符串,如果断言失败且启用了 assert.exception 该对象将被抛出。实例将 zend.assertions 设置为 0:<?php
ini_set('zend.assertions', 0);
assert(true == false);
echo 'Hi!';
?>以上程序执行输出结果为:Hi!将 zend.assertions 设置为 1,assert.exception 设置为 1:实例<?php
ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);
assert(true == false);
echo 'Hi!';
?>以上程序执行输出结果为:Fatal error: Uncaught AssertionError: assert(true == false) in -:2
Stack trace:
#0 -(2): assert(false, 'assert(true == ...')
#1 {main}
thrown in - on line 2十一、PHP 7 use 语句PHP 7 可以使用一个 use 从同一个 namespace 中导入类、函数和常量:实例// PHP 7 之前版本需要使用多次 use
use some\namespace\ClassA;
use some\namespace\ClassB;
use some\namespace\ClassC as C;
use function some\namespace\fn_a;
use function some\namespace\fn_b;
use function some\namespace\fn_c;
use const some\namespace\ConstA;
use const some\namespace\ConstB;
use const some\namespace\ConstC;
// PHP 7+ 之后版本可以使用一个 use 导入同一个 namespace 的类
use some\namespace\{ClassA, ClassB, ClassC as C};
use function some\namespace\{fn_a, fn_b, fn_c};
use const some\namespace\{ConstA, ConstB, ConstC};
?>十二、PHP 7 错误处理PHP 7 改变了大多数错误的报告方式。不同于 PHP 5 的传统错误报告机制,现在大多数错误被作为 Error 异常抛出。这种 Error 异常可以像普通异常一样被 try / catch 块所捕获。如果没有匹配的 try / catch 块, 则调用异常处理函数(由 set_exception_handler() 注册)进行处理。 如果尚未注册异常处理函数,则按照传统方式处理:被报告为一个致命错误(Fatal Error)。Error 类并不是从 Exception 类 扩展出来的,所以用 catch (Exception $e) { ... } 这样的代码是捕获不 到 Error 的。你可以用 catch (Error $e) { ... } 这样的代码,或者通过注册异常处理函数( set_exception_handler())来捕获 Error。Error 异常层次结构Error
ArithmeticError
AssertionError
DivisionByZeroError
ParseError
TypeError实例<?php
class MathOperations
{
protected $n = 10;
// 求余数运算,除数为 0,抛出异常
public function doOperation(): string
{
try {
$value = $this->n % 0;
return $value;
} catch (DivisionByZeroError $e) {
return $e->getMessage();
}
}
}
$mathOperationsObj = new MathOperations();
print($mathOperationsObj->doOperation());
?>以上程序执行输出结果为:Modulo by zero十三、PHP intdiv() 函数PHP 7 新增加了 intdiv() 函数,接收两个参数,返回值为第一个参数除于第二个参数的值并取整。实例<?php
echo intdiv(9,3),PHP_EOL;
echo intdiv(10,3),PHP_EOL;
echo intdiv(5,10),PHP_EOL;
?>以上程序执行输出结果为:3
3
0十四、PHP 7 Session 选项PHP 7 session_start() 函数可以接收一个数组作为参数,可以覆盖 php.ini 中 session 的配置项。这个特性也引入了一个新的 php.ini 设置(session.lazy_write), 默认情况下设置为 true,意味着 session 数据只在发生变化时才写入。除了常规的会话配置指示项, 还可以在此数组中包含 read_and_close 选项。如果将此选项的值设置为 TRUE, 那么会话文件会在读取完毕之后马上关闭, 因此,可以在会话数据没有变动的时候,避免不必要的文件锁。把 cache_limiter 设置为私有的,同时在阅读完 session 后立即关闭。实例 'private', 'read_and_close' => true, ]); ?>php7 以前,我们使用 session 前都是要先代用 session_strat() 函数来初始化的,但这个函数是没有参数可以传的,session 的配置都在 php.ini 文件中。在 php7 后 session_start() 可以接受一个 array 作为参数, 用来覆盖 php.ini 文件中设置的会话配置选项。session_start([
'cache_limiter' => 'private', //在读取完毕会话数据之后马上关闭会话存储文件
'cookie_lifetime'=>3600, //SessionID在客户端Cookie储存的时间,默认是0,代表浏览器一关闭SessionID就作废
'read_and_close'=>true //在读取完会话数据之后, 立即关闭会话存储文件,不做任何修改
]);
$_SESSION['name']='quan';
echo $_SESSION['name'];十五、PHP 7 废弃特性PHP4 风格的构造函数在 PHP4 中类中的函数可以与类名同名,这一特性在 PHP7 中被废弃,同时会发出一个 E_DEPRECATED 错误。当方法名与类名相同,且类不在命名空间中,同时PHP5的构造函数(__construct)不存在时,会产生一个 E_DEPRECATED 错误。实例<?php
class A {
function A() {
print('Style Constructor');
}
}
?>以上程序执行输出结果为:以静态的方式调用非静态方法以静态的方式调用非静态方法,不再支持:实例<?php
class A {
function b() {
print('Non-static call');
}
}
A::b();
?>以上程序执行输出结果为:Deprecated: Non-static method A::b() should not be called statically in...
Non-static callpassword_hash() 随机因子选项函数原 salt 量不再需要由开发者提供了。函数内部默认带有 salt 能力,无需开发者提供 salt 值。capture_session_meta SSL 上下文选项废弃了 "capture_session_meta" SSL 上下文选项。 在流资源上活动的加密相关的元数据可以通过 stream_get_meta_data() 的返回值访问。十六、PHP 7 移除的扩展PHP 7+ 版本移除了以下扩展:eregmssqlmysqlsybase_ct十七、PHP 7 移除的 SAPIPHP 7+ 版本移除了以下 SAPI:aolserverapacheapache_hooksapache2filtercaudiumcontinuityisapimilternsapiphttpdpi3webroxenthttpdtuxwebjames
centos命令大全
系统信息arch 显示机器的处理器架构(1)uname -m 显示机器的处理器架构(2)uname -r 显示正在使用的内核版本dmidecode -q 显示硬件系统部件 - (SMBIOS / DMI)hdparm -i /dev/hda 罗列一个磁盘的架构特性hdparm -tT /dev/sda 在磁盘上执行测试性读取操作cat /proc/cpuinfo 显示CPU info的信息cat /proc/interrupts 显示中断cat /proc/meminfo 校验内存使用cat /proc/swaps 显示哪些swap被使用cat /proc/version 显示内核的版本cat /proc/net/dev 显示网络适配器及统计cat /proc/mounts 显示已加载的文件系统lspci -tv 罗列 PCI 设备lsusb -tv 显示 USB 设备date 显示系统日期cal 2007 显示2007年的日历表date 041217002007.00 设置日期和时间 - 月日时分年.秒clock -w 将时间修改保存到 BIOS关机 (系统的关机、重启以及登出 )shutdown -h now 关闭系统(1)init 0 关闭系统(2)telinit 0 关闭系统(3)shutdown -h hours:minutes & 按预定时间关闭系统shutdown -c 取消按预定时间关闭系统shutdown -r now 重启(1)reboot 重启(2)logout 注销文件和目录cd /home 进入 '/ home' 目录'cd .. 返回上一级目录cd ../.. 返回上两级目录cd 进入个人的主目录cd ~user1 进入个人的主目录cd - 返回上次所在的目录pwd 显示工作路径ls 查看目录中的文件ls -F 查看目录中的文件ls -l 显示文件和目录的详细资料ls -a 显示隐藏文件ls *[0-9]* 显示包含数字的文件名和目录名tree 显示文件和目录由根目录开始的树形结构(1)lstree 显示文件和目录由根目录开始的树形结构(2)mkdir dir1 创建一个叫做 'dir1' 的目录'mkdir dir1 dir2 同时创建两个目录mkdir -p /tmp/dir1/dir2 创建一个目录树rm -f file1 删除一个叫做 'file1' 的文件'rmdir dir1 删除一个叫做 'dir1' 的目录'rm -rf dir1 删除一个叫做 'dir1' 的目录并同时删除其内容rm -rf dir1 dir2 同时删除两个目录及它们的内容mv dir1 new_dir 重命名/移动 一个目录cp file1 file2 复制一个文件cp dir/* . 复制一个目录下的所有文件到当前工作目录cp -a /tmp/dir1 . 复制一个目录到当前工作目录cp -a dir1 dir2 复制一个目录ln -s file1 lnk1 创建一个指向文件或目录的软链接ln file1 lnk1 创建一个指向文件或目录的物理链接touch -t 0712250000 file1 修改一个文件或目录的时间戳 - (YYMMDDhhmm)file file1 outputs the mime type of the file as texticonv -l 列出已知的编码iconv -f fromEncoding -t toEncoding inputFile > outputFile creates a new from the given input file by assuming it is encoded in fromEncoding and converting it to toEncoding.find . -maxdepth 1 -name *.jpg -print -exec convert "{}" -resize 80x60 "thumbs/{}" ; batch resize files in the current directory and send them to a thumbnails directory (requires convert from Imagemagick)文件搜索find / -name file1 从 '/' 开始进入根文件系统搜索文件和目录find / -user user1 搜索属于用户 'user1' 的文件和目录find /home/user1 -name *.bin 在目录 '/ home/user1' 中搜索带有'.bin' 结尾的文件find /usr/bin -type f -atime +100 搜索在过去100天内未被使用过的执行文件find /usr/bin -type f -mtime -10 搜索在10天内被创建或者修改过的文件find / -name *.rpm -exec chmod 755 '{}' ; 搜索以 '.rpm' 结尾的文件并定义其权限find / -xdev -name *.rpm 搜索以 '.rpm' 结尾的文件,忽略光驱、捷盘等可移动设备locate *.ps 寻找以 '.ps' 结尾的文件 - 先运行 'updatedb' 命令whereis halt 显示一个二进制文件、源码或man的位置which halt 显示一个二进制文件或可执行文件的完整路径挂载一个文件系统mount /dev/hda2 /mnt/hda2 挂载一个叫做hda2的盘 - 确定目录 '/ mnt/hda2' 已经存在umount /dev/hda2 卸载一个叫做hda2的盘 - 先从挂载点 '/ mnt/hda2' 退出fuser -km /mnt/hda2 当设备繁忙时强制卸载umount -n /mnt/hda2 运行卸载操作而不写入 /etc/mtab 文件- 当文件为只读或当磁盘写满时非常有用mount /dev/fd0 /mnt/floppy 挂载一个软盘mount /dev/cdrom /mnt/cdrom 挂载一个cdrom或dvdrommount /dev/hdc /mnt/cdrecorder 挂载一个cdrw或dvdrommount /dev/hdb /mnt/cdrecorder 挂载一个cdrw或dvdrommount -o loop file.iso /mnt/cdrom 挂载一个文件或ISO镜像文件mount -t vfat /dev/hda5 /mnt/hda5 挂载一个Windows FAT32文件系统mount /dev/sda1 /mnt/usbdisk 挂载一个usb 捷盘或闪存设备mount -t smbfs -o username=user,password=pass //WinClient/share /mnt/share 挂载一个windows网络共享磁盘空间df -h 显示已经挂载的分区列表ls -lSr |more 以尺寸大小排列文件和目录du -sh dir1 估算目录 'dir1' 已经使用的磁盘空间'du -sk * | sort -rn 以容量大小为依据依次显示文件和目录的大小rpm -q -a --qf '%10{SIZE}t%{NAME}n' | sort -k1,1n 以大小为依据依次显示已安装的rpm包所使用的空间 (fedora, redhat类系统)dpkg-query -W -f='${Installed-Size;10}t${Package}n' | sort -k1,1n 以大小为依据显示已安装的deb包所使用的空间 (ubuntu, debian类系统)用户和群组groupadd group_name 创建一个新用户组groupdel group_name 删除一个用户组groupmod -n new_group_name old_group_name 重命名一个用户组useradd -c "Name Surname " -g admin -d /home/user1 -s /bin/bash user1 创建一个属于 "admin" 用户组的用户useradd user1 创建一个新用户userdel -r user1 删除一个用户 ( '-r' 排除主目录)usermod -c "User FTP" -g system -d /ftp/user1 -s /bin/nologin user1 修改用户属性passwd 修改口令passwd user1 修改一个用户的口令 (只允许root执行)chage -E 2005-12-31 user1 设置用户口令的失效期限pwck 检查 '/etc/passwd' 的文件格式和语法修正以及存在的用户grpck 检查 '/etc/passwd' 的文件格式和语法修正以及存在的群组newgrp group_name 登陆进一个新的群组以改变新创建文件的预设群组文件的权限 - 使用 "+" 设置权限,使用 "-" 用于取消ls -lh 显示权限ls /tmp | pr -T5 -W$COLUMNS 将终端划分成5栏显示chmod ugo+rwx directory1 设置目录的所有人(u)、群组(g)以及其他人(o)以读(r )、写(w)和执行(x)的权限chmod go-rwx directory1 删除群组(g)与其他人(o)对目录的读写执行权限chown user1 file1 改变一个文件的所有人属性chown -R user1 directory1 改变一个目录的所有人属性并同时改变改目录下所有文件的属性chgrp group1 file1 改变文件的群组chown user1:group1 file1 改变一个文件的所有人和群组属性find / -perm -u+s 罗列一个系统中所有使用了SUID控制的文件chmod u+s /bin/file1 设置一个二进制文件的 SUID 位 - 运行该文件的用户也被赋予和所有者同样的权限chmod u-s /bin/file1 禁用一个二进制文件的 SUID位chmod g+s /home/public 设置一个目录的SGID 位 - 类似SUID ,不过这是针对目录的chmod g-s /home/public 禁用一个目录的 SGID 位chmod o+t /home/public 设置一个文件的 STIKY 位 - 只允许合法所有人删除文件chmod o-t /home/public 禁用一个目录的 STIKY 位文件的特殊属性 - 使用 "+" 设置权限,使用 "-" 用于取消chattr +a file1 只允许以追加方式读写文件chattr +c file1 允许这个文件能被内核自动压缩/解压chattr +d file1 在进行文件系统备份时,dump程序将忽略这个文件chattr +i file1 设置成不可变的文件,不能被删除、修改、重命名或者链接chattr +s file1 允许一个文件被安全地删除chattr +S file1 一旦应用程序对这个文件执行了写操作,使系统立刻把修改的结果写到磁盘chattr +u file1 若文件被删除,系统会允许你在以后恢复这个被删除的文件lsattr 显示特殊的属性打包和压缩文件bunzip2 file1.bz2 解压一个叫做 'file1.bz2'的文件bzip2 file1 压缩一个叫做 'file1' 的文件gunzip file1.gz 解压一个叫做 'file1.gz'的文件gzip file1 压缩一个叫做 'file1'的文件gzip -9 file1 最大程度压缩rar a file1.rar test_file 创建一个叫做 'file1.rar' 的包rar a file1.rar file1 file2 dir1 同时压缩 'file1', 'file2' 以及目录 'dir1'rar x file1.rar 解压rar包unrar x file1.rar 解压rar包tar -cvf archive.tar file1 创建一个非压缩的 tarballtar -cvf archive.tar file1 file2 dir1 创建一个包含了 'file1', 'file2' 以及 'dir1'的档案文件tar -tf archive.tar 显示一个包中的内容tar -xvf archive.tar 释放一个包tar -xvf archive.tar -C /tmp 将压缩包释放到 /tmp目录下tar -cvfj archive.tar.bz2 dir1 创建一个bzip2格式的压缩包tar -xvfj archive.tar.bz2 解压一个bzip2格式的压缩包tar -cvfz archive.tar.gz dir1 创建一个gzip格式的压缩包tar -xvfz archive.tar.gz 解压一个gzip格式的压缩包zip file1.zip file1 创建一个zip格式的压缩包zip -r file1.zip file1 file2 dir1 将几个文件和目录同时压缩成一个zip格式的压缩包unzip file1.zip 解压一个zip格式压缩包RPM 包 - (Fedora, Redhat及类似系统)rpm -ivh package.rpm 安装一个rpm包rpm -ivh --nodeeps package.rpm 安装一个rpm包而忽略依赖关系警告rpm -U package.rpm 更新一个rpm包但不改变其配置文件rpm -F package.rpm 更新一个确定已经安装的rpm包rpm -e package_name.rpm 删除一个rpm包rpm -qa 显示系统中所有已经安装的rpm包rpm -qa | grep httpd 显示所有名称中包含 "httpd" 字样的rpm包rpm -qi package_name 获取一个已安装包的特殊信息rpm -qg "System Environment/Daemons" 显示一个组件的rpm包rpm -ql package_name 显示一个已经安装的rpm包提供的文件列表rpm -qc package_name 显示一个已经安装的rpm包提供的配置文件列表rpm -q package_name --whatrequires 显示与一个rpm包存在依赖关系的列表rpm -q package_name --whatprovides 显示一个rpm包所占的体积rpm -q package_name --scripts 显示在安装/删除期间所执行的脚本lrpm -q package_name --changelog 显示一个rpm包的修改历史rpm -qf /etc/httpd/conf/httpd.conf 确认所给的文件由哪个rpm包所提供rpm -qp package.rpm -l 显示由一个尚未安装的rpm包提供的文件列表rpm --import /media/cdrom/RPM-GPG-KEY 导入公钥数字证书rpm --checksig package.rpm 确认一个rpm包的完整性rpm -qa gpg-pubkey 确认已安装的所有rpm包的完整性rpm -V package_name 检查文件尺寸、 许可、类型、所有者、群组、MD5检查以及最后修改时间rpm -Va 检查系统中所有已安装的rpm包- 小心使用rpm -Vp package.rpm 确认一个rpm包还未安装rpm2cpio package.rpm | cpio --extract --make-directories *bin* 从一个rpm包运行可执行文件rpm -ivh /usr/src/redhat/RPMS/`arch`/package.rpm 从一个rpm源码安装一个构建好的包rpmbuild --rebuild package_name.src.rpm 从一个rpm源码构建一个 rpm 包YUM 软件包升级器 - (Fedora, RedHat及类似系统)yum install package_name 下载并安装一个rpm包yum localinstall package_name.rpm 将安装一个rpm包,使用你自己的软件仓库为你解决所有依赖关系yum update package_name.rpm 更新当前系统中所有安装的rpm包yum update package_name 更新一个rpm包yum remove package_name 删除一个rpm包yum list 列出当前系统中安装的所有包yum search package_name 在rpm仓库中搜寻软件包yum clean packages 清理rpm缓存删除下载的包yum clean headers 删除所有头文件yum clean all 删除所有缓存的包和头文件DEB 包 (Debian, Ubuntu 以及类似系统)dpkg -i package.deb 安装/更新一个 deb 包dpkg -r package_name 从系统删除一个 deb 包dpkg -l 显示系统中所有已经安装的 deb 包dpkg -l | grep httpd 显示所有名称中包含 "httpd" 字样的deb包dpkg -s package_name 获得已经安装在系统中一个特殊包的信息dpkg -L package_name 显示系统中已经安装的一个deb包所提供的文件列表dpkg --contents package.deb 显示尚未安装的一个包所提供的文件列表dpkg -S /bin/ping 确认所给的文件由哪个deb包提供APT 软件工具 (Debian, Ubuntu 以及类似系统)apt-get install package_name 安装/更新一个 deb 包apt-cdrom install package_name 从光盘安装/更新一个 deb 包apt-get update 升级列表中的软件包apt-get upgrade 升级所有已安装的软件apt-get remove package_name 从系统删除一个deb包apt-get check 确认依赖的软件仓库正确apt-get clean 从下载的软件包中清理缓存apt-cache search searched-package 返回包含所要搜索字符串的软件包名称查看文件内容cat file1 从第一个字节开始正向查看文件的内容tac file1 从最后一行开始反向查看一个文件的内容more file1 查看一个长文件的内容less file1 类似于 'more' 命令,但是它允许在文件中和正向操作一样的反向操作head -2 file1 查看一个文件的前两行tail -2 file1 查看一个文件的最后两行tail -f /var/log/messages 实时查看被添加到一个文件中的内容文本处理cat file1 file2 ... | command <> file1_in.txt_or_file1_out.txt general syntax for text manipulation using PIPE, STDIN and STDOUTcat file1 | command( sed, grep, awk, grep, etc...) > result.txt 合并一个文件的详细说明文本,并将简介写入一个新文件中cat file1 | command( sed, grep, awk, grep, etc...) >> result.txt 合并一个文件的详细说明文本,并将简介写入一个已有的文件中grep Aug /var/log/messages 在文件 '/var/log/messages'中查找关键词"Aug"grep ^Aug /var/log/messages 在文件 '/var/log/messages'中查找以"Aug"开始的词汇grep [0-9] /var/log/messages 选择 '/var/log/messages' 文件中所有包含数字的行grep Aug -R /var/log/* 在目录 '/var/log' 及随后的目录中搜索字符串"Aug"sed 's/stringa1/stringa2/g' example.txt 将example.txt文件中的 "string1" 替换成 "string2"sed '/^$/d' example.txt 从example.txt文件中删除所有空白行sed '/ *#/d; /^$/d' example.txt 从example.txt文件中删除所有注释和空白行echo 'esempio' | tr '[:lower:]' '[:upper:]' 合并上下单元格内容sed -e '1d' result.txt 从文件example.txt 中排除第一行sed -n '/stringa1/p' 查看只包含词汇 "string1"的行sed -e 's/ *$//' example.txt 删除每一行最后的空白字符sed -e 's/stringa1//g' example.txt 从文档中只删除词汇 "string1" 并保留剩余全部sed -n '1,5p;5q' example.txt 查看从第一行到第5行内容sed -n '5p;5q' example.txt 查看第5行sed -e 's/00*/0/g' example.txt 用单个零替换多个零cat -n file1 标示文件的行数cat example.txt | awk 'NR%2==1' 删除example.txt文件中的所有偶数行echo a b c | awk '{print $1}' 查看一行第一栏echo a b c | awk '{print $1,$3}' 查看一行的第一和第三栏paste file1 file2 合并两个文件或两栏的内容paste -d '+' file1 file2 合并两个文件或两栏的内容,中间用"+"区分sort file1 file2 排序两个文件的内容sort file1 file2 | uniq 取出两个文件的并集(重复的行只保留一份)sort file1 file2 | uniq -u 删除交集,留下其他的行sort file1 file2 | uniq -d 取出两个文件的交集(只留下同时存在于两个文件中的文件)comm -1 file1 file2 比较两个文件的内容只删除 'file1' 所包含的内容comm -2 file1 file2 比较两个文件的内容只删除 'file2' 所包含的内容comm -3 file1 file2 比较两个文件的内容只删除两个文件共有的部分字符设置和文件格式转换dos2unix filedos.txt fileunix.txt 将一个文本文件的格式从MSDOS转换成UNIXunix2dos fileunix.txt filedos.txt 将一个文本文件的格式从UNIX转换成MSDOSrecode ..HTML < page.txt > page.html 将一个文本文件转换成htmlrecode -l | more 显示所有允许的转换格式文件系统分析badblocks -v /dev/hda1 检查磁盘hda1上的坏磁块fsck /dev/hda1 修复/检查hda1磁盘上linux文件系统的完整性fsck.ext2 /dev/hda1 修复/检查hda1磁盘上ext2文件系统的完整性e2fsck /dev/hda1 修复/检查hda1磁盘上ext2文件系统的完整性e2fsck -j /dev/hda1 修复/检查hda1磁盘上ext3文件系统的完整性fsck.ext3 /dev/hda1 修复/检查hda1磁盘上ext3文件系统的完整性fsck.vfat /dev/hda1 修复/检查hda1磁盘上fat文件系统的完整性fsck.msdos /dev/hda1 修复/检查hda1磁盘上dos文件系统的完整性dosfsck /dev/hda1 修复/检查hda1磁盘上dos文件系统的完整性初始化一个文件系统mkfs /dev/hda1 在hda1分区创建一个文件系统mke2fs /dev/hda1 在hda1分区创建一个linux ext2的文件系统mke2fs -j /dev/hda1 在hda1分区创建一个linux ext3(日志型)的文件系统mkfs -t vfat 32 -F /dev/hda1 创建一个 FAT32 文件系统fdformat -n /dev/fd0 格式化一个软盘mkswap /dev/hda3 创建一个swap文件系统SWAP文件系统mkswap /dev/hda3 创建一个swap文件系统swapon /dev/hda3 启用一个新的swap文件系统swapon /dev/hda2 /dev/hdb3 启用两个swap分区备份dump -0aj -f /tmp/home0.bak /home 制作一个 '/home' 目录的完整备份dump -1aj -f /tmp/home0.bak /home 制作一个 '/home' 目录的交互式备份restore -if /tmp/home0.bak 还原一个交互式备份rsync -rogpav --delete /home /tmp 同步两边的目录rsync -rogpav -e ssh --delete /home ip_address:/tmp 通过SSH通道rsyncrsync -az -e ssh --delete ip_addr:/home/public /home/local 通过ssh和压缩将一个远程目录同步到本地目录rsync -az -e ssh --delete /home/local ip_addr:/home/public 通过ssh和压缩将本地目录同步到远程目录dd bs=1M if=/dev/hda | gzip | ssh user@ip_addr 'dd of=hda.gz' 通过ssh在远程主机上执行一次备份本地磁盘的操作dd if=/dev/sda of=/tmp/file1 备份磁盘内容到一个文件tar -Puf backup.tar /home/user 执行一次对 '/home/user' 目录的交互式备份操作( cd /tmp/local/ && tar c . ) | ssh -C user@ip_addr 'cd /home/share/ && tar x -p' 通过ssh在远程目录中复制一个目录内容( tar c /home ) | ssh -C user@ip_addr 'cd /home/backup-home && tar x -p' 通过ssh在远程目录中复制一个本地目录tar cf - . | (cd /tmp/backup ; tar xf - ) 本地将一个目录复制到另一个地方,保留原有权限及链接find /home/user1 -name '*.txt' | xargs cp -av --target-directory=/home/backup/ --parents 从一个目录查找并复制所有以 '.txt' 结尾的文件到另一个目录find /var/log -name '*.log' | tar cv --files-from=- | bzip2 > log.tar.bz2 查找所有以 '.log' 结尾的文件并做成一个bzip包dd if=/dev/hda of=/dev/fd0 bs=512 count=1 做一个将 MBR (Master Boot Record)内容复制到软盘的动作dd if=/dev/fd0 of=/dev/hda bs=512 count=1 从已经保存到软盘的备份中恢复MBR内容光盘cdrecord -v gracetime=2 dev=/dev/cdrom -eject blank=fast -force 清空一个可复写的光盘内容mkisofs /dev/cdrom > cd.iso 在磁盘上创建一个光盘的iso镜像文件mkisofs /dev/cdrom | gzip > cd_iso.gz 在磁盘上创建一个压缩了的光盘iso镜像文件mkisofs -J -allow-leading-dots -R -V "Label CD" -iso-level 4 -o ./cd.iso data_cd 创建一个目录的iso镜像文件cdrecord -v dev=/dev/cdrom cd.iso 刻录一个ISO镜像文件gzip -dc cd_iso.gz | cdrecord dev=/dev/cdrom - 刻录一个压缩了的ISO镜像文件mount -o loop cd.iso /mnt/iso 挂载一个ISO镜像文件cd-paranoia -B 从一个CD光盘转录音轨到 wav 文件中cd-paranoia -- "-3" 从一个CD光盘转录音轨到 wav 文件中(参数-3)cdrecord --scanbus 扫描总线以识别scsi通道dd if=/dev/hdc | md5sum 校验一个设备的md5sum编码,例如一张 CD网络 - (以太网和WIFI无线)ifconfig eth0 显示一个以太网卡的配置ifup eth0 启用一个 'eth0' 网络设备ifdown eth0 禁用一个 'eth0' 网络设备ifconfig eth0 192.168.1.1 netmask 255.255.255.0 控制IP地址ifconfig eth0 promisc 设置 'eth0' 成混杂模式以嗅探数据包 (sniffing)dhclient eth0 以dhcp模式启用 'eth0'route -n show routing tableroute add -net 0/0 gw IP_Gateway configura default gatewayroute add -net 192.168.0.0 netmask 255.255.0.0 gw 192.168.1.1 configure static route to reach network '192.168.0.0/16'route del 0/0 gw IP_gateway remove static routeecho "1" > /proc/sys/net/ipv4/ip_forward activate ip routinghostname show hostname of systemhost www.example.com lookup hostname to resolve name to ip address and viceversa(1)nslookup www.example.com lookup hostname to resolve name to ip address and viceversa(2)ip link show show link status of all interfacesmii-tool eth0 show link status of 'eth0'ethtool eth0 show statistics of network card 'eth0'netstat -tup show all active network connections and their PIDnetstat -tupl show all network services listening on the system and their PIDtcpdump tcp port 80 show all HTTP trafficiwlist scan show wireless networksiwconfig eth1 show configuration of a wireless network cardhostname show hostnamehost www.example.com lookup hostname to resolve name to ip address and viceversanslookup www.example.com lookup hostname to resolve name to ip address and viceversawhois www.example.com lookup on Whois databaseMicrosoft Windows networks (SAMBA)nbtscan ip_addr netbios name resolutionnmblookup -A ip_addr netbios name resolutionsmbclient -L ip_addr/hostname show remote shares of a windows hostsmbget -Rr smb://ip_addr/share like wget can download files from a host windows via smbmount -t smbfs -o username=user,password=pass //WinClient/share /mnt/share mount a windows network share
Ceph集群详细部署配置图文讲解(二)(上)
Ceph集群详细部署配置一、部署结构虚拟机创建三台服务器, CENTOS 版本为 7.6 , IP 网段 192.168.116.2/24 。三台主机名称为:CENTOS7-1: IP 为 192.168.116.141 , 既做管理节点, 又做子节点。CENTOS7-2: IP 为 192.168.116.142 , 子节点。CENTOS7-3: IP 为 192.168.116.143 , 子节点二、系统配置系统配置工作, 三台节点依次执行:1、修改主机名称[root@CENTOS7-1 ~]# vi /etc/hostname
CENTOS7-12、编辑hosts文件192.168.116.141 CENTOS7-1
192.168.116.142 CENTOS7-2
192.168.116.143 CENTOS7-3注意, 这里面的主机名称要和节点名称保持一致, 否则安装的时候会出现问题3、修改yum源vi /etc/yum.repos.d/ceph.repo , 为避免网速过慢问题, 这里采用的是清华镜像源:
[ceph]
name=Ceph packages for $basearch
baseurl=https://mirrors.tuna.tsinghua.edu.cn/ceph/rpm-mimic/el7/x86_64/
enabled=1
gpgcheck=1
type=rpm-md
gpgkey=https://download.ceph.com/keys/release.asc
[ceph-noarch]
name=Ceph noarch packages
# 官方源
#baseurl=http://download.ceph.com/rpm-mimic/el7/noarch
# 清华源
baseurl=https://mirrors.tuna.tsinghua.edu.cn/ceph/rpm-mimic/el7/noarch/
enabled=1
gpgcheck=1
type=rpm-md
gpgkey=https://download.ceph.com/keys/release.asc
[ceph-source]
name=Ceph source packages
baseurl=https://mirrors.tuna.tsinghua.edu.cn/ceph/rpm-mimic/el7/SRPMS/
enabled=1
gpgcheck=1
type=rpm-md
gpgkey=https://download.ceph.com/keys/release.asc4、安装ceph与ceph-deploy组件yum update && yum -y install ceph ceph-deploy安装完成, 如果执行 ceph-deploy 出现 ImportError: No module named pkg_resources安装 python2-pip :yum install epel-release -y
yum -y install python2-pip5、安装NTP时间同步工具 yum install ntp ntpdate ntp-doc -y 确保时区是正确, 设置开机启动:systemctl enable ntpd并将时间每隔1小时自动校准同步。编辑 vi /etc/rc.d/rc.local 追加:/usr/sbin/ntpdate ntp1.aliyun.com > /dev/null 2>&1; /sbin/hwclock -w配置定时任务, 执行crontab -e 加入:三、免密码SSH登陆1、官方建议不用系统内置用户, 创建名为ceph_user用户, 密码也设为ceph_user:useradd -d /home/ceph_user -m ceph_user
passwd ceph_user2、设置sudo权限echo "ceph_user ALL = (root) NOPASSWD:ALL" | sudo tee
/etc/sudoers.d/ceph_user
sudo chmod 0440 /etc/sudoers.d/ceph_user1 、 2 两个步骤依次在三台机器上执行。接下来在主节点, 继续执行:3、生成密钥:切换用户: su ceph_user执行 ssh-keygen ,一直按默认提示点击生成 RSA 密钥信息。4、分发密钥至各机器节点ssh-copy-id ceph_user@CENTOS7-1
ssh-copy-id ceph_user@CENTOS7-2
ssh-copy-id ceph_user@CENTOS7-35、修改管理节点上的 ~/.ssh/config 文件, 简化SSH远程连接时的输入信息:管理节点是会有root和ceph_user多个用户, ssh远程连接默认会以当前用户身份进行登陆, 如果我们是root身份进行远程连接, 还是需要输入密码, 我们想简化, 该怎么处理?切换root身份su root修改~/.ssh/config 文件Host CENTOS7-1
Hostname CENTOS7-1
User ceph_user
Host CENTOS7-2
Hostname CENTOS7-2
User ceph_user
Host CENTOS7-3
Hostname CENTOS7-3
User ceph_user 注意修改文件权限, 不能采用777最大权限:chmod 600 ~/.ssh/config进行ssh远程连接时, Host的主机名称是区分大小写的, 所以要注意配置文件的主机名称。6、开放端口, 非生产环境, 可以直接禁用防火墙: systemctl stop firewalld.service
systemctl disable firewalld.service7、SELINUX设置SELinux 设为禁用:setenforce 0永久生效:编辑 vi /etc/selinux/config 修改:SELINUX=disabled四、集群搭建配置采用root身份进行安装1、在管理节点创建集群配置目录,cd /usr/local:mkdir ceph-cluster
cd ceph-cluster注意: 此目录作为 ceph 操作命令的基准目录, 会存储处理配置信息。2、创建集群, 包含三台机器节点:ceph-deploy new CENTOS7-1 CENTOS7-2 CENTOS7-3创建成功后, 会生一个配置文件。3、 如果接下来集群的安装配置出现问题, 可以执行以下命令清除, 再重新安装:ceph-deploy purge CENTOS7-1 CENTOS7-2 CENTOS7-3
ceph-deploy purgedata CENTOS7-1 CENTOS7-2 CENTOS7-3
ceph-deploy forgetkeys将三台节点的mon信息也删除rm -rf /var/run/ceph/4、 修改配置文件, 有些配置后面需用到:vi /usr/local/ceph-cluster/ceph.conf加入:[global]
# 公网网络
public network = 192.168.88.0/24
# 设置pool池默认分配数量 默认副本数为3
osd pool default size = 2
# 容忍更多的时钟误差
mon clock drift allowed = 2
mon clock drift warn backoff = 30
# 允许删除pool
mon_allow_pool_delete = true
[mgr]
# 开启WEB仪表盘 mgr modules = dashboard第一项为副本数, 设为 2 份。第二项为对外 IP 访问网段,注意根据实际 IP 修改网段。第三、四项为允许一定时间的漂移误差。5、执行安装:ceph-deploy install CENTOS7-1 CENTOS7-2 CENTOS7-3如果出现错误:ceph_deploy][ERROR ] RuntimeError: Failed to execute command: ceph --version可以在各节点上单独进行安装:yum -y install ceph如果没有仓库文件 ceph.repo , 按上面的步骤手工创建。6、初始monitor信息:ceph-deploy mon create-initial ## ceph-deploy --overwrite-conf mon create-initial执行完成后, 会生成以下文件:7、同步管理信息: 下发配置文件和管理信息至各节点:ceph-deploy admin CENTOS7-1 CENTOS7-2 CENTOS7-38、安装mgr(管理守护进程), 大于12.x版本需安装, 我们装的是最新版,需执行: ceph-deploy mgr create CENTOS7-1 CENTOS7-2 CENTOS7-39、安装OSD(对象存储设备) 注意: 新版本的 OSD 没有 prepare 与 activate 命令。这里需要新的硬盘作为 OSD 存储设备, 关闭虚拟机, 增加一块硬盘, 不用格式化。重启, fdisk -l 查看新磁盘名称:执行创建OSD命令:ceph-deploy osd create --data /dev/sdb CENTOS7-1三台节点都需分别依次执行。ceph-deploy gatherkeys CENTOS7-110、验证节点: 输入 ceph health 或 ceph -s 查看, 出现 HEALTH_OK 代表正常。通过虚拟机启动, 如果出现错误: 在各节点执行命令, 确保时间同步一致:ntpdate ntp1.aliyun.com
Python 多任务3: 协程
一、总体内容1.1、协程的介绍1.2、迭代器以及迭代器的应用1.3、生成器(生成器与迭代器保存的都是生成数据的代码,而不是数据)1.4、gevent 来实现一个 图片下载器 & 视频下载器二、协程介绍2.1、协程是什么?协程,又称微线程,纤程。英文名Coroutine:英[k?ru:'ti:n]。协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。2.2、协程和线程差异在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。2.3、简单实现协程(暂时用yield,后面我们主要用 gevent 来实现 协程)import time
def work1():
while True:
print("----work1---")
yield
time.sleep(0.5)
def work2():
while True:
print("----work2---")
yield
time.sleep(0.5)
def main():
w1 = work1()
w2 = work2()
while True:
next(w1)
next(w2)
if __name__ == "__main__":
main()运行结果:---work1---
----work2---
----work1---
----work2---
----work1---
----work2---
---------等等-------三、迭代器3.1、迭代器的介绍迭代 是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器 对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。3.2、可迭代对象我们已经知道可以对list、tuple、str等类型的数据使用for...in...的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。是否所有的数据类型都可以放到for...in...的语句中,然后让for...in...每次从中取出一条数据供我们使用,即供我们迭代吗?>>> for i in 100:
... print(i)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable错误原因是: int整型不是iterable,即int整型不是可以迭代的我们再看一下一个类是否可以进行迭代class MyList(object):
def __init__(self):
self.container = []
def add(self, item):
self.container.append(item)
mylist = MyList()
mylist.add(1)
mylist.add(2)
mylist.add(3)
for num in mylist:
print(num)打印结果是:从下面可以看出不可迭代TypeError: 'MyList' object is not iterable我们自定义了一个容器类型MyList,在将一个存放了多个数据的MyList对象放到for...in...的语句中,发现for...in...并不能从中依次取出一条数据返回给我们,也就说我们随便封装了一个可以存放多条数据的类型却并不能被迭代使用。我们把可以通过for...in...这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象(Iterable)。3.3、如何判断一个对象是否可以迭代可以使用 isinstance()判断一个对象是否是 Iterable对象,如下判断>>> from collections import Iterable
>>> isinstance([],Iterable)
True
>>> isinstance(10,Iterable)
False3.4、可迭代对象的本质我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在for...in...中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator:[?t?'re?t?])。可迭代对象的本质 就是可以向我们提供一个这样的中间“人”即迭代器帮助我们对其进行迭代遍历使用。可迭代对象通过iter方法向我们提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据.那么也就是说,一个具备了__iter__方法的对象,就是一个可迭代对象。在一个类里面加上 __iter__就可以使这个类成为可迭代的对象class MyList(object):
def __init__(self):
self.container = list()
def add(self, item):
self.container.append(item)
def __iter__(self):
"""返回一个迭代器"""
# 我们暂时忽略如何构造一个迭代器对象
pass
mylist = MyList()
mylist.add(1)
mylist.add(2)
print(isinstance(mylist,Iterable))打印结果是:True, 这回测试发现添加了__iter__方法的mylist对象已经是一个 可迭代对象 了3.5、 iter()函数与next()函数list、tuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。iter()函数实际上就是调用了可迭代对象的__iter__方法。>>> li = [11, 22, 33, 44, 55]
>>> li_iter = iter(li)
>>> next(li_iter)
11
>>> next(li_iter)
22
>>> next(li_iter)
33
>>> next(li_iter)
44
>>> next(li_iter)
55
>>> next(li_iter)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration注意,当我们已经迭代完最后一个数据之后,再次调用next()函数会抛出StopIteration的异常,来告诉我们所有数据都已迭代完成,不用再执行next()函数了。3.6、如何判断一个对象是否是迭代器可以使用 isinstance() 判断一个对象是否是 Iterator 对象:>>> from collections import Iterator
>>> isinstance([], Iterator)
False
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter("abc"), Iterator)
True3.7、迭代器Iterator通过上面的分析,我们已经知道,迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__方法(Python3中是对象的__next__方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的__next__方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。from collections import Iterator
from collections import Iterable
class MyList(object):
def __init__(self):
self.name_list = []
self.all_count = 0
def add_name(self,temp):
self.name_list.append(temp)
def __iter__(self):
return self
def __next__(self):
if self.all_count < len(self.name_list):
ret = self.name_list[self.all_count]
self.all_count += 1
return ret
else:
raise StopIteration
my_list = MyList()
my_list.add_name("张三")
my_list.add_name("李四")
my_list.add_name("王五")
print("判断my_list是否是可以迭代的对象:",isinstance(my_list,Iterable))
my_list_iterator = iter(my_list)
print("判断my_list_iterator是否是迭代器:",isinstance(my_list_iterator,Iterator))
for name in my_list:
print(name)打印结果是:判断my_list是否是可以迭代的对象: True
判断my_list_iterator是否是迭代器: True
张三
李四
王五提示:一个实现了__iter__方法和__next__方法的对象,就是迭代器。3.8、for...in...循环的本质for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。3.9、迭代器的应用场景我们发现迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来供后续依次读取,这样可以节省大量的存储(内存)空间。举例:比如,数学中有个著名的 斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...class FibIterator(object):
"""斐波那契数列迭代器"""
def __init__(self,num):
"""
:param num: 指明生成数列的前num个数
"""
self.all_num = num
# 用来保存当前生成到数列中的第几个数了
self.num = 0
# 用来保存前前一个数,初始值为数列中的第一个数0
self.start_a = 0
# 用来保存前后一个数,初始值为数列中的第二个数1
self.start_b = 1
def __iter__(self):
# """迭代器的__iter__返回自身即可"""
return self
def __next__(self):
# """被next()函数调用来获取下一个数"""
if self.num < self.all_num:
self.num += 1
retValue = self.start_a
self.start_a,self.start_b = self.start_b,self.start_a+self.start_b
return self.start_a
else:
raise StopIteration
fibIterator = FibIterator(20)
for num in fibIterator:
print(num)3.10、并不是只有for循环能接收可迭代对象除了for循环能接收可迭代对象,list、tuple等也能接收。li = list(FibIterator(15))
print(li)
tp = tuple(FibIterator(6))
print(tp)打印结果[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
(1, 1, 2, 3, 5, 8)3.11、以后再见到 元组转列表,列表转元组,不要再认为是简简单单的转换了;比如下面的例子list1 = list((1,2,3,4,5))
print(list1)
打印结果是:[1, 2, 3, 4, 5]这可不是简简单单的转换,list()这就是利用了迭代器,把返回的每一个元组数据都放在了列表里面,生成一个新的列表;四、生成器4.1、生成器介绍利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。4.2、创建生成器方法创建生成器方法1In [15]: L = [ x*2 for x in range(3)]
In [16]: L
Out[16]: [0, 2, 4]
In [17]: G = ( x*2 for x in range(3))
In [18]: G
Out[18]: <generator object <genexpr> at 0x7f626c132db0>创建 L 和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是一个列表,而 G 是一个生成器。我们可以直接打印出列表L的每一个元素,而对于生成器G,我们可以按照迭代器的使用方法来使用,即可以通过next()函数、for循环、list()等方法使用。In [19]: next(G)
Out[19]: 0
In [20]: next(G)
Out[20]: 2
In [21]: next(G)
Out[21]: 4
In [26]: G = ( x*2 for x in range(5))
In [27]: for x in G:
....: print(x)
....:
0
2
4创建生成器方法2generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。我们仍然用 二 中提到的斐波那契数列来举例,回想我们在上一节用迭代器的实现方式:def fib(n):
current = 0
num1, num2 = 0, 1
while current < n:
yield num1
num1, num2 = num2, num1+num2
current += 1
return 'done'
fib1 = fib(5)
for i in fib1:
print(i)在使用生成器实现的方式中,我们将原本在迭代器next方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。简单来说:只要在def中有yield关键字的 就称为 生成器此时按照调用函数的方式( 案例中为F = fib(5) )使用生成器就不再是执行函数体了,而是会返回一个生成器对象( 案例中为F ),然后就可以按照使用迭代器的方式来使用生成器了。但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:while True:
try:
x = next(fib1)
print("value:%d" % x)
except StopIteration as e:
print("生成器返回值:%s" % e.value)
break总结使用了yield关键字的函数不再是函数,而是生成器。(使用了yield的函数就是生成器)yield关键字有两点作用:保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)Python3中的生成器可以使用return返回最终运行的返回值,而Python2中的生成器不允许使用return返回一个返回值(即可以使用return从生成器中退出,但return后不能有任何表达式)。4.3、使用send唤醒我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。例子:执行到yield时,gen函数作用暂时保存,返回i的值; temp接收下次c.send("python"),send发送过来的值,c.next()等价c.send(None)def send_use(num):
i = 0
while i < num:
temp = yield i
print(temp)
i += 1
s = send_use(5)
print(next(s))
print(next(s))
print(next(s))
print(next(s))
print(s.send("哈哈"))4.4、使用__next__()方法(不常使用)def send_use(num):
i = 0
while i < num:
yield i
i += 1
s = send_use(5)
print(s.__next__())
print(s.__next__())
print(s.__next__())4.5、greenlet为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单安装方式sudo pip3 install greenlet使用方式from greenlet import greenlet
import time
def test1():
while True:
print "---A--"
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print "---B--"
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
#切换到gr1中运行
gr1.switch()运行效果---A--
---B--
---A--
---B--
---A--
---B--
---A--
---B--4.6、gevent (重点)其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO安装 geventpip3 install geventgevent的使用import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent,i)
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()运行结果(可以看到,3个gevent是依次运行而不是交替运行)<built-in function getcurrent> 0
<built-in function getcurrent> 1
<built-in function getcurrent> 2
<built-in function getcurrent> 3
<built-in function getcurrent> 4
<built-in function getcurrent> 0
<built-in function getcurrent> 1
<built-in function getcurrent> 2
<built-in function getcurrent> 3
<built-in function getcurrent> 4
<built-in function getcurrent> 0
<built-in function getcurrent> 1
<built-in function getcurrent> 2
<built-in function getcurrent> 3
<built-in function getcurrent> 4gevent切换执行import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent,i)
# 用来模拟一个耗时操作,注意不是time模块中的sleep
gevent.sleep(0.3)
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()运行结果: 可以看到是交替执行的<built-in function getcurrent> 0
<built-in function getcurrent> 0
<built-in function getcurrent> 0
<built-in function getcurrent> 1
<built-in function getcurrent> 1
<built-in function getcurrent> 1
<built-in function getcurrent> 2
<built-in function getcurrent> 2
<built-in function getcurrent> 2
<built-in function getcurrent> 3
<built-in function getcurrent> 3
<built-in function getcurrent> 3
<built-in function getcurrent> 4
<built-in function getcurrent> 4
<built-in function getcurrent> 44.7、给程序打补丁(如果你在使用gevent的模块中sleep()用的是time模块,那么为了使gevent也起作用,那么就要进行打补丁)需要加上下面的代码# 有耗时操作时需要
monkey.patch_all() # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块完整的代码如下from gevent import monkey
import gevent
import time
# 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
monkey.patch_all()
def f(name,n):
for i in range(n):
print(gevent.getcurrent,"name=%s"%name,i)
# 用来模拟一个耗时操作,注意不是time模块中的sleep
time.sleep(0.3)
gevent.joinall([
gevent.spawn(f,"work1",5),
gevent.spawn(f,"work1",5)
])运行结果如下:<built-in function getcurrent> name=work1 0
<built-in function getcurrent> name=work1 0
<built-in function getcurrent> name=work1 1
<built-in function getcurrent> name=work1 1
<built-in function getcurrent> name=work1 2
<built-in function getcurrent> name=work1 2
<built-in function getcurrent> name=work1 3
<built-in function getcurrent> name=work1 3
<built-in function getcurrent> name=work1 4
<built-in function getcurrent> name=work1 4五、gevent 来实现一个图片下载器5.1、我们在下载网络的时候会用到 urllib.request,在用到打开一个链接urllib.request.urlopen的时候会报错:urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1051)>,这是我的报错原因是:查找相关资料后确定为Python 2.7.9 之后版本引入了一个新特性当你urllib.urlopen一个 https 的时候会验证一次 SSL 证书当目标使用的是自签名的证书时就会爆出一个上面的错误,处理办法如下:在全局添加下面代码import ssl
ssl._create_default_https_context = ssl._create_unverified_context5.2、使用协程 gevent 实现的图片下载器import urllib.request
import ssl
from gevent import monkey
import gevent
monkey.patch_all()
ssl._create_default_https_context = ssl._create_unverified_context
def down_loader_image(image_name,image_url):
"""
使用进程 gevent的图片下载器
:param image_name: 图片的名字
:param image_url: 图片的url
"""
req = urllib.request.urlopen(image_url)
image_content = req.read()
with open("/Users/wangchong/Desktop/%s"%image_name,"wb") as f:
f.write(image_content)
def main():
gevent.joinall([
gevent.spawn(down_loader_image,"20.png","https://rpic.douyucdn.cn/live-cover/appCovers/2018/11/15/5658062_20181115170406_small.jpg"),
gevent.spawn(down_loader_image,"21.png","https://rpic.douyucdn.cn/live-cover/roomCover/2018/09/27/b0983e7b6267f5b8371106ccf5f113ab_big.jpg")
])
if __name__ == '__main__':5.3、使用协程 gevent 实现多个视频下载import urllib.request
import ssl
from gevent import monkey
import gevent
#有IO才做时需要这一句,将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
monkey.patch_all()
ssl._create_default_https_context = ssl._create_unverified_context
def down_loader_image(video_name,video_url):
"""
使用进程 gevent的图片下载器
:param video_name: 视频的名字
:param video_url: 图片的url
"""
req = urllib.request.urlopen(video_url)
data = req.read()
with open("/Users/wangchong/Desktop/%s"% video_name,"wb") as f:
f.write(data)
print('%d bytes received from %s.' % (len(data), video_url))
def main():
gevent.joinall([
gevent.spawn(down_loader_image,"1.mp4","http://images.ciotimes.com/2.wx_test.mp4"),
gevent.spawn(down_loader_image,"2.mp4","http://images.ciotimes.com/3.wx_test.mp4")
])
if __name__ == '__main__':
main()提示 上面的url可以换为自己需要下载视频、音乐、图片等网址六、总结:进程、线程、协程对比6.1、对 进程、线程、协程 三者的描述有一个老板想要开个工厂进行生产某件商品(例如剪子)他需要花一些财力物力制作一条生产线,这个生产线上有很多的器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为:进程只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程这个老板为了提高生产率,想到3种办法:在这条生产线上多招些工人,一起来做剪子,这样效率是成倍増长,即 单进程 多线程 方式老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了,即多进程 多线程方式老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程中又有多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序 之后他才能再次工作) ,那么这个员工就利用这个时间去做其它的事情,那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其它事情,其实这就是:协程方式6.2、总体描述进程是资源分配的单位线程是操作系统调度的单位进程切换需要的资源很最大,效率很低线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)协程切换任务资源很小,效率高多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发协程依赖于线程,线程依赖于进程,进程一死,协程必挂
DNS协议解析
TCP/IP中使用的是IP地址和端口号来确定网络上某一台主机上的某一个程序,不免有人有疑问,为什么不用域名来直接进行通信呢?因为IP地址是固定长度的,IPv4是32位,IPv6是128位,而域名是变长的,不便于计算机处理。IP地址对于用户来说不方便记忆,但域名便于用户使用,例如www.baidu.com这是百度的域名。总结:IP地址是面向主机的,而域名则是面向用户的。DNS(域名系统)是因特网使用的命名系统,用于解决IP地址和域名的映射关系ps:域名和IP的对应关系保存在一个叫hosts文件中。最初,通过互联网信息中心来管理这个文件,如果有一个新的计算机想接入网络,或者某个计算IP变更都需要到信息中心申请变更hosts文件。其他计算机也需要定期更新,才能上网。但是这样太麻烦了,就出现了DNS系统。一个组织的系统管理机构, 维护系统内的每个主机的IP和主机名的对应关系如果新计算机接入网络,将这个信息注册到数据库中用户输入域名的时候,会自动查询DNS服务器,由DNS服务器检索数据库,得到对应的IP。在域名解析的过程中仍然会优先查找hosts文件的内容。DNS域名结构域名系统必须要保持唯一性。命名:每一个域名(本文只讨论英文域名)都是一个标号序列(labels),用字母(A-Z,a-z,大小写等价)、数字(0-9)和连接符(-)组成标号序列总长度不能超过255个字符,它由点号分割成一个个的标号(label)每个标号应该在63个字符之内,每个标号都可以看成一个层次的域名。级别最低的域名写在左边,级别最高的域名写在右边。域名服务主要是基于UDP实现的,服务器的端口号为53。其中顶级域名分为:国家顶级域名、通用顶级域名、反向域名。DNS域名服务器域名是分层结构,域名服务器也是对应的层级结构。有了域名结构,还需要有一个东西去解析域名,域名需要由遍及全世界的域名服务器去解析,域名服务器实际上就是装有域名系统的主机。由高向低进行层次划分,可分为以下几大类:注:一个域名服务器所负责的范围,或者说有管理权限的范围,就称为区。我们需要注意的是:每个层的域名上都有自己的域名服务器,最顶层的是根域名服务器;每一级域名服务器都知道下级域名服务器的IP地址为了容灾, 每一级至少设置两个或以上的域名服务器DNS域名解析过程域名解析总体可分为一下过程:输入域名后, 先查找自己主机对应的域名服务器,域名服务器先查找自己的数据库中的数据.如果没有, 就向上级域名服务器进行查找, 依次类推最多回溯到根域名服务器, 肯定能找到这个域名的IP地址域名服务器自身也会进行一些缓存, 把曾经访问过的域名和对应的IP地址缓存起来, 可以加速查找过程具体过程:主机先向本地域名服务器进行递归查询本地域名服务器采用迭代查询,向一个根域名服务器进行查询根域名服务器告诉本地域名服务器,下一次应该查询的顶级域名服务器的IP地址本地域名服务器向顶级域名服务器进行查询顶级域名服务器告诉本地域名服务器,下一步查询权限服务器的IP地址本地域名服务器向权限服务器进行查询权限服务器告诉本地域名服务器所查询的主机的IP地址本地域名服务器最后把查询结果告诉主机拓展:递归查询和迭代查询递归查询:本机向本地域名服务器发出一次查询请求,就静待最终的结果。如果本地域名服务器无法解析,自己会以DNS客户机的身份向其它域名服务器查询,直到得到最终的IP地址告诉本机迭代查询:本地域名服务器向根域名服务器查询,根域名服务器告诉它下一步到哪里去查询,然后它再去查,每次它都是以客户机的身份去各个服务器查询。通俗地说递归就是把一件事情交给别人,如果事情没有办完,哪怕已经办了很多,都不要把结果告诉我,我要的是你的最终结果,而不是中间结果;如果你没办完,请你找别人办完。迭代则是我交给你一件事,你能办多少就告诉我你办了多少,然后剩下的事情就由我来办。在浏览器中输入url地址 ->> 显示主页的过程简单来讲,分为域名输入、DNS解析、TCP连接、发送HTTP请求、响应HTTP请求,解析渲染、断开连接。主要经历以下流程:浏览器解析输入:地址栏会根据用户输入,做出如下判断:输入的是非 URL 结构的字符串,则会用浏览器默认的搜索引擎搜索该字符串;输入的是 URL 结构字符串,则会构建完整的 URL 结构,浏览器进程会将完整的 URL 通过进程间通信,即 IPC,发送给网络进程DNS解析:在网络进程接收到 URL 后,并不是马上对指定 URL 进行请求。首先进行DNS 解析域名得到对应的 IP,然后通过 ARP 解析 IP 得到对应的 MAC地址。DNS解析域名(寻址)的过程:浏览器缓存:询问浏览器 DNS 缓存本地系统缓存:询问本地操作系统 DNS 缓存(即查找本地 hosts 文件)路由器缓存:查询路由器的DNS缓存ISP DNS 缓存:询问 ISP(Internet Service Provider)互联网服务提供商(例如电信、移动)的 DNS 服务器询问根服务器,这个过程可以进行递归和迭代两种查找的方式,两者都是先询问顶级域名服务器查找(DNS服务器先问根域名服务器.com域名服务器的IP地址,然后再问.com域名服务器,依次类推)浏览器向服务器发起tcp连接:解析出IP地址后,根据IP地址和默认端口,和服务器三次握手建立TCP连接。发送HTTP请求:浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求。cookie会随着请求发给服务器。服务器处理请求并返回HTTP报文:服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器浏览器解析渲染页面:显示html文本内容释放TCP连接:连接结束(1)现代浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开?什么情况下会断开?在 HTTP/1.0 中,一个服务器在发送完一个HTTP 响应后,会断开 TCP 链接。但是这样每次请求都会重新建立和断开 TCP 连接,代价过大。HTTP/1.1 就把 Connection:keep-alive 头写进标准,并且默认开启持久连接(长连接),默认情况下建立 TCP 连接不会断开,只有在请求报头中声明 Connection: close 才会在请求完成后关闭连接。(2)一个 TCP 连接可以对应几个 HTTP 请求?如果维持连接,一个 TCP 连接是可以发送多个 HTTP 请求的。可以理解为复用。(3)一个 TCP 连接中 HTTP 请求发送可以一起发送么(比如一起发三个请求,再三个响应一起接收)?HTTP/1.1 存在一个问题,单个 TCP 连接在同一时刻只能处理一个请求,意思是说:两个请求的生命周期不能重叠,任意两个 HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。虽然 HTTP/1.1 规范中规定了 Pipelining 来试图解决这个问题,但是这个功能在浏览器中默认是关闭的。但是,一些代理服务器不能正确的处理 HTTP Pipelining。正确的流水线实现是复杂的。HTTP2 提供了 Multiplexing 多路传输特性,可以在一个 TCP 连接中同时完成多个 HTTP 请求。多个 HTTP 请求可以在同一个 TCP 连接中并行进行。(4)为什么有的时候刷新页面不需要重新建立 SSL 连接?TCP 连接有的时候会被浏览器和服务端维持一段时间。TCP 不需要重新建立,SSL 自然也会用之前的。
Socket原理与基本操作
SOCKET原理与连接?(1)基本概念:在TCP/IP协议栈中,在网络层IP地址可以代表唯一的一台主机,但是实际上网络通信是主机应用程序之间的通信,一个主机可能有很多进程并发执行,因此还需要端口号来区分进程。故,网络中的进程可以用 IP+端口号+协议 进行唯一标识。socket是应用层和传输层的一种抽象层,是应用层与传输层的接口,是支持TCP网络通信的基本通信单元,完成了不同进程间的通信。在互联网中,通信模式是客户端(client)与服务器(server)的端点对端点的通信模式。标识每个端点IP地址和端口号称为套接字。socket={IP:PORT}。标识源IP地址,源port,目的IP 地址,目的port,协议统称为套接字对。(2)网络进程通信:拓展:本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:消息传递(管道、FIFO、消息队列)同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)共享内存(匿名的和具名的)远程过程调用(Solaris门和Sun RPC)网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。(3)建立socket连接:建立socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。(3)SOCKET连接与TCP连接:创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。(4)Socket连接与HTTP连接:由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用 中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导 致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给 客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以 保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。socket的基本操作既然socket是“open—write/read—close”模式的一种实现,那么socket就提供了这些操作对应的函数接口。下面以TCP为例,介绍几个基本的socket接口函数。(1)socket()函数int socket(int domain, int type, int protocol);socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。bind()函数正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);函数的三个参数分别为:sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同addrlen:对应的是地址的长度。通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。网络字节序与主机字节序主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。listen()、connect()函数如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。int listen(int sockfd, int backlog);int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。accept()函数TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);</pre>accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。read()、write()等函数万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:read()/write()recv()/send()readv()/writev()recvmsg()/sendmsg()recvfrom()/sendto()我推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。它们的声明如下:#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); </pre>read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。其它的我就不一一介绍这几对I/O函数了,具体参见man文档或者baidu、Google,下面的例子中将使用到send/recv。close()函数在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。int close(int fd);close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。Cookie和Session作用是什么,有什么区别?Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。Cookie 是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。Session是存在服务器的一种用来存放用户数据的类HashTable结构。当浏览器第一次发送请求时,服务器自动生成了一个HashTable和一个Session ID用来唯一标识这个HashTable,并将其通过响应发送到浏览器。当浏览器第二次发送请求,会将前一次服务器响应中的Session ID放在请求中一并发送到服务器上,服务器从请求中提取出Session ID,并和保存的所有Session ID进行对比,找到这个用户对应的HashTable。Session是服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库或文件中。两者主要区别:应用场景不同:Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。Cookie 一般用来保存用户信息。比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。Session 的主要作用就是通过服务端记录用户的状态,即用户的验证。 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。数据存储位置不同:Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。数据安全性不同:Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。存储数据类型不同:Cookie 只能存储 ASCII 码,而 Session 可以存储任何类型的数据。补充:session 的运行依赖 session id,而 session id 存在 cookie 中,叫做 JSESSIONID。如果浏览器禁用了 cookie ,同时 session 也会失效(可以通过其它方式实现,比如在 url 中传递 session_id)
飞天加速计划·高校学生在家实践 续费任务文章
本人是软件开发专业,通过阿里云app首页了解到飞天加速计划·高校学生在家实践”活动通过两天的上手使用我认识到,使用一个ECS中的镜像系统需要以下几个基础步骤:1.选择镜像包并安装。2.通过PUTTY等软件来访问命令行模式系统。3.设置安全组,使得公网IP可以被访问。4.安装图形界面和VNC包,可以通过VNC等软件访问图形界面。5.更换时需要将已有镜像包,添加快照,从而创建自定义镜像,并选择其他镜像包更换。CENTOS6.5这是我安装的第一个镜像版本,也是阿里云官网上有视频教程的版本。第一次选择时没有设置密码就安装了镜像,发现之后赶紧设置了密码。随后发现无法使用SSH方式进入,原来设置密码后需要重启才能进入。重启后进入成功,通过原有的PUTTY以SSH形式访问,端口22,需要输入公网地址。随后进入PUTTY界面,输入用户名和密码进入命令行模式。教程推荐使用FileZilla通过sftp的方式传输管理文件,并且建议将Windows默认的文本编辑器改为vscode,这点确实很有必要。随后,设置了安全组,并且暂且开放所有端口并允许所有IP访问。下面是激动人心的一幕:为系统安装图形界面!我们选用了Gnome桌面+VNC一键包,并且设置了VNC连接密码,教程推荐了vnc viewer作为连接软件,实测比较好用。注意安装过程中要设置VNC密码。然后我们就可以用VNC方式连接CENTOS的图形界面辣,还可以在安卓手机和平板上连接,简单实用。安卓手机的SSH连接软件我使用了juiceSSH,界面友好且美观,很好用。至于VNC软件,我随便下载了一个bVNC,不太好用,但是也勉强,不知道微软的Microsoft Remote Desktop能否当做VNC使用。(实测不能,都不是一种连接方式)进入图形界面,打开命令行,突然发现字体重叠,上网搜索发现是字体问题导致的,于是下载了一个新的字体,更换了字体之后就正常了,顺带说一下,也就是新字体宽一点而已。CENTOS自带的python2.7,所以需要自己安装python3.6,这里安装的方式简述如下。WINDOWS SERVER2019WINDOWS系统简直差点折磨死我了,我一直无法用SSH的形式连接上去,VNC也不行,只能用官方的远程桌面连接了,进入后首先需要开启远程控制,并且关闭浏览器保护模式(差点烦死我了),这样就可以随意安装第三方软件了。然后可以安装SSH服务组件,虽然安装了也没连上。随后设置安全组,将RDP,SSH,HTTP都勾上,范围填0.0.0.0/0下载Microsoft Remote Desktop,就可以在电脑和手机端都连接上桌面了。至于ssh我还是没有解决。。。。。。。过两天来看,WIN可以说最省心了,但是也确实没啥用。。。。UBUNTU16.04 LAMP环境 PHP5.4.45这是我用的第一个LAMP环境,初始使用没有遇到太大问题。首先,我安装了一个桌面环境xfce,和之前用过的双系统中的UNITY桌面有很大不同,不过也勉强够用最后简单总结一下,阿里云的服务器听稳定,使用起来也简单,遇到不会的还可以找售后工程师解决,他们也会耐心的解决问题。
服务器使用
我是电气专业的,现在大三,刚开始接触云服务器。一切的起源是我在跟B站的一名UP主学习一个泰拉瑞亚联机教程,里边需要用到服务器,UP主使用的服务器也是阿里云服务器,所以就开始去了解阿里云服务器。在官网的主界面看到飞天加速计划·高校学生在家实践这个活动,对于我等学生党来说,真的是很大一个福利,就参加学习实践一下。云服务器一般都会安装Linux操作系统,它不同于平常使用的Windows,命令行操作是它的灵魂所在,虽然也可以安装有操作界面的版本,但既然是要学习云服务器,学习Linux,最好还是直接学习它的核心所在,乖乖的使用命令行的方式进行操作。我安装的是Ubuntu 20.04最新版本,按照某位老师所说,只有新手才会经常追求最新版本的软件,应为新版本意味着与可能会有Bug,大公司企业对于操作系统的第一要求是稳定性和可靠性。这些版本的迭代,功能越来越强大,但它的核心基本不变,所以不论哪个版本,对于初学者来说都是足够的。操作云服务器,一般用到远程控制工具,例如短小精悍的PUTTY,或者国产软件Finalshell,当然还有很多其他的相关软件,我目前接触使用的也就是这两个。我目前在学习MQTT协议,安装网上教程首先在Linux中安装EMQ,我使用的是Shell命令直接安装的方式,很轻松就安装成功。然后是购买域名,申请SSL证书,域名解析,安装证书,域名备案(备案需要云服务器还剩余有三个月的使用期才可以,比如我是使用该计划赠送的云服务器,目前也就两个星期的时间,是不可以进行备案的),当然也是需要实名认证的,等等。我个人比较喜欢物联网,嵌入式这些东西,一点一点的接触,感觉需要学的东西真的很多,希望借助这次活动,能够对云服务器和Linux操作系统有更深的一个了解。
阿里云ecs使用体验
我是一名软件工程专业的大三学生,最大的特质就是执着,只要是我所选择的事情,我所选择的道路,我都会非常坚定的走下去。我喜欢挑战自我,喜欢在强手之中找到自己的位置,如果失败我会得到更大的动力,争取更大的成功。刚买了服务器,建议先通过ssh连接,市面上有很多产品,自己可以谷歌百度一下,本人用的是Xshell家族,首先确认下安全组里面的22端口放开(已默认打开)IP为公网IP,用户名为root,密码为你设置的密码,如果忘记了,找到管理控制台的实例菜单下如果发现网站打不开,首先应该检查下阿里云的安全组端口是否开放,不然费时费力,最后才想起,基本软件的安装可以自行百度一下都有。在云服务器ecs使用中除了端口开放之外,还有一个问题就是邮件发送问题,阿里云服务器默认关闭25的邮件端口,如需要邮件发送功能需改成465端口才可以。总而言之,通过“高校学生在家实践”计划,让我可以体验到阿里云服务器ecs的方方面面,确实服务器的体验上是挺好的,在这个过程中,为了部署项目,到处查阅资料,感受颇深,不过也学习到了很多的东西,有了经验,以后就能在运维上更加游刃有余了。