为什么需要集群和高可用

为什么需要主从复制

主要是安全性和可用性的考虑,如果只有一个redis服务,一旦服务宕机,那么所有的客户端无法访问,会对业务造成很大影响,另一个是一旦硬件损坏,单机无法恢复,会对数据带来灾难性影响。另一个考虑是性能的提升,主从复制模式,写请求只能由master节点来处理,但是读请求可能用从节点来分担请求,提高效率。

可用性、数据安全、性能都可以通过搭建多个 Reids 服务实现。其中有一个是主节点(master),可以有多个从节点(slave)。主从之间通过数据同步,存储完全相同的数据。如果主节点发生故障,则把某个从节点改成主节点,访问新的主节点

使用哨兵模式来实现主从复制架构是为了主从节点的切换故障转移能够自动进行。

为什么需要集群

Redis 本身的 QPS 已经很高了,但是如果在一些并发量非常高的情况下,性能还是会受到影响。这个时候我们希望有更多的 Redis 服务来完成工作。

第二个是出于存储的考虑。因为 Redis 所有的数据都放在内存中,如果数据量大,很容易受到硬件的限制。升级硬件收效和成本比太低,所以我们需要有一种横向扩展的方法,使用分片技术,把数据分配到一个个集群redis组中。

Redis主从复制

实现主从复制架构的手段:
一主多从:

  1. 从节点的配置文件添加slaveof 192.168.18.140 6379 #主节点的ip端口,表示以该节点为主节点实现主从复制。
  2. 从节点启动服务的时候指定master节点,./redis-server --slaveof 192.168.18.140 6379
  3. 从节点启动后,在客户端使用命令 slaveof 192.168.18.140 6379 指定master节点

info replication 查看主从架构的状态。

从节点不能写入数据(只读),只能从 master 节点同步数据。get 成功,set 失败。

从节点取消指定主节点,自己成为主节点,也就是从节点断开slave模式:
slave no one 命令。

主从复制原理

连接阶段:

  1. slave 节点启动时,会在自己本地保存master 节点的信息,包括ip port等。
  2. slave节点内部有个定时任务 replicationCron,每隔1秒钟检查是否有新的master节点要连接和复制,如果有,就跟master节点建立一个Socket连接,如果连接成功,从节点为该Socket连接创建一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB命令,接收命令传播等。
  3. 当从节点变成了主节点的一个客户端之后,会给主节点发送 ping 请求。

数据同步阶段:
4. slave节点第一次会执行全量复制,master节点通过bgsave命令在本地生成一个RDB数据快照文件,然后将RDB文件通过Socket连接传送给从节点,然后从节点先清空自身数据,然后使用接收到的RDB文件加载数据。 传送文件会有个超时时间,如果超时了会进行重连,可以设置大一点,防止超时循环重连,配置文件修改repl-timeout配置项设置。
5. 在开始生成RDB文件时,master节点还会接收新的写命令,会把新的写命令缓存在内存中,在slave 节点通过RDB文件加载数据成功后,再把新的写命令发送给slave节点。
6. 命令传播阶段:从节点数据同步完成后,可以开始对外提供读服务,主节点还是会继续执行写命令,然后异步将命令发送给slave 节点用于同步数据。是异步的,所以数据一致性必然会存在延时,与Zookeeper采用CP模式保证数据分布式一致性不一样,redis使用的是AP保证服务可用性。
7. 如果从节点由于某些原因,比如宕机啥的,与主节点断开连接了一段时间,在重新连接后会使用增量复制来同步数据,主节点和从节点都有master_repl_offset命令来记录数据的偏移量,偏移量越大,数据越新,一般主节点都要大于等于从节点的偏移量,可以使用该偏移量来进行增量复制,增量的数据就是主节点与从节点直接的数据偏移量之差。

数据一致性延迟是不可避免的,只能通过优化网络来尽可能减少数据延时时间。
repl-disable-tcp-nodelay no
上面配置的作用是:当设置为yes时,TCP会对多个写命令TCP包进行合并一次性发送从而减少带宽,但是发送的频率会降低,从节点的数据延时会增加,一致性变差,具体发送频率与linux内核参数有关,默认为40ms,当设置为no时,TCP会立马把主节点的数据发送给从节点,带宽增加但延时变小。

一般来说,只有当应用对 Redis 数据不一致的容忍度较高,且主从节点之间网络状况不好时,才会设置为 yes;多数情况使用默认值 no。

主从复制的不足:

  1. RDB文件过大时,同步耗时。
  2. 在一主一从或者一主多从情况下,如果主节点挂了,对外提供的写服务就是不可用了(从节点仍然可以对外提供读服务),单点问题没有解决,需要手动进行主从的切换才行,但是这样比较费事费力,还会导致服务一段时间不可用。所以可以使用哨兵模式来实现自动的主从切换和故障转移。

哨兵模式

哨兵Sentinel模式的大致思路是创建一台服务器来监控所有的Redis服务节点状态,比如master节点超过一定时间没有给监控服务器发送心跳报文,就把master标记为下线,然后把某个slave节点变成master节点,重写进行数据的同步。应用程序(客户端)每一次都是从这个监控服务器拿到master的地址。

如果监控服务器本身出问题了怎么办?那我们就拿不到 master 的地址了,应用也没有办法访问。可以对sentinel服务器进行集群。

从 Redis2.8 版本起,提供了一个稳定版本的 Sentinel(哨兵),用来解决高可用的问题。它是一个特殊状态的 redis 实例。

我们会启动一个或者多个 Sentinel 的服务(通过 src/redis-sentinel),它本质上只是一个运行在特殊模式之下的 Redis,Sentinel 通过 info 命令得到被监听 Redis 机器的master,slave 等信息。

为了保证监控服务器的可用性,我们会对 Sentinel 做集群的部署。Sentinel 既监控所有的 Redis 服务,Sentinel 之间也相互监控。
注意:Sentinel 本身没有主从之分,只有 Redis 服务节点有主从之分。

此时就要以下角色

  1. sentinel集合。
  2. redis服务节点group(一主多从为一个group)。

服务下线:
Sentinel默认以每秒一次的频率向各Redis服务节点发送PING命令,如果在down-after-milliseconds 内都没有收到有效回复,Sentinel 会将该服务器标记为主观下线。

sentinel down-after-milliseconds <master-name> <milliseconds>  #sentinel.conf

这个时候 Sentinel 节点会继续询问其他的 Sentinel 节点,确认这个节点是否下线,如果多数(超过半数) Sentinel 节点都认为 master 下线,master 才真正确认被下线(客观下线),这个时候就需要重新选举 master。

由于Sentinel实现了集群,所以首先需要通过Raft算法在Sentinel集群中选取出一个临时Leader来对下线的master进行故障转移。

故障转移:

  1. 选举出的Leader Sentinel会向某个从节点发送 slaveof no one 使它成为一个独立主节点,然后向其余从节点发送slaveof xxx xxx ,使得这些从节点成为新主节点的从节点,并进行数据的同步,然后故障转移完成。

如何从多个slave节点中选择一个成为新的master节点:
关于从节点选举,一共有四个因素影响选举的结果,分别是断开连接时长、优先级排序、复制数量、进程 id。

如果与哨兵连接断开的比较久,超过了某个阈值,就直接失去了选举权。如果拥有选举权,那就看谁的优先级高,这个在配置文件redis.conf里可以设置(replica-priority 100),数值越小优先级越高。

如果优先级相同,就看谁从 master 中复制的数据最多(复制偏移量最大),选最多的那个,如果复制数量也相同,就选择进程 id 最小的那个。

Sentinel搭建实战

环境:
需要三个服务节点一主二从和三个sentinel集群节点,由于虚拟机不够,这里就只用三台虚拟机,每台虚拟机部署一个服务节点和一个sentinel节点。

ip 节点角色/端口
192.168.18.140 master 6379 / sentinel 26379
192.168.18.141 salve 6379 / sentinel 26379
192.168.18.142 slave 6379 / sentinel 26379
  1. 修改从服务节点141、142的配置文件redis.conf,添加 slaveof 192.168.18.140 6379。
  2. 修改三个sentinel节点的配置文件sentinel.conf:
daemonize yes    #以服务进程模式启动
port 26379  #端口
protected-mode no  # 是否允许外部网络访问
dir "/usr/local/redis-5.0.5/sentinel-tmp"   #sentinel的工作目录
sentinel monitor redis-master 192.168.18.140 6379 2  #要监控的服务节点 master节点的ip port  从节点个数
sentinel down-after-milliseconds redis-master 30000 #master宕机多久,才会被 Sentinel 主观认为下线。
sentinel failover-timeout redis-master 180000 #故障转移的超时时间
#指定了在进行主从切换故障转移时,最多可以有多少个salve同时对新的master进行数据同步,
#这个设置的越少,故障转移需要的时间越长,因为能够同时进行同步的slave越少,这个设置的越大,
#代表在故障转移期间能够对外提供读服务的slave节点就越少,因为在同步期间服务不可用。
#比如故障转移时有4个slave节点向新的master节点同步数据,如果设置为1,那么同步将串行执行,
#同一时间只有一台salve进行同步,另外三台还能对外提供服务,
#如果设置为4,那么会4台slave同时进行同步,那么此时就没有从服务器对外提供服务了。
sentinel parallel-syncs redis-master 1

先启动三台服务节点再启动三台sentinel节点:

./redis-server ../redis.conf./redis-sentinel ../sentinel.conf

验证:

  • 使用info replication命令查看服务节点的状态。
  • 故意关闭shutdown主节点。
  • 经过一定时间后,某一台从节点会被升为主节点。自动故障转移成功。

哨兵机制的不足:

  • 主从切换时会丢失数据,主从复制是异步执行的,master节点写入数据后没来得及同步到slave节点就发送了故障,进行主从切换,该没有同步的数据就会丢失。
  • 只能单点写,没有解决水平扩容的问题。
  • 这种主从模式所以节点保存的数据都是一样的,无法对数据进行水平扩容,如果数据量非常大,这个时候我们需要多个 master-slave 的 group,把数据分布到不同的 group 中。

Redis分布式集群

集群的架构图

通过对Redis进行分布式集群,可以达到以下效果:

  1. 水平扩展Redis的写性能,分布式集群实际上就是多个主从为一个组,然后多个组组成一个集群,每个组的master节点可以进行写操作,水平扩展了redis的写性能,这是哨兵机制做不到的。
  2. 水平扩展能存储的数据量,把所有数据通过分片技术分布在一个个Group里面,像上图有两个Group之间的数据时不一样的,但是同一Group的主从节点数据时一样的。这样就可以水平扩展数据。
分片技术

有三种方案对数据进行分片:

  1. 在客户端分片,就是在客户端对数据的key进行hash,再取模,就能确定落到哪个Group里面,取数据的时候也用同样的算法路由到对应的Group进行取数据,这种方案分片逻辑集中在客户端,不依赖于中间件,分区逻辑可以自定义,但是不能动态增减服务器,也就是增加或减少Group就要修改相应的代码。
  2. 第二种思路就是把分片的代码抽取出来,做成一个公共服务,所有的客户端都连接到这个代理层。由代理层来实现请求和转发。

    典型的代理分区方案有 Twitter 开源的 Twemproxy 和国内的豌豆荚开源的 Codis。

以上两种方案都是对服务端透明的,也就是redis集群中的每个Group都不会知道其他Group的存在,只是有客户端或者中间件进行分片调配。

第三种方案是通过服务端实现集群,在Redis3.0之后,推出了Redis Cluster用来解决分布式的需求,同时也可以实现高可用。跟 Codis 不一样,它是去中心化的,客户端可以连接到任意一个可用节点。

Redis Cluster

数据分片的关键考虑问题:

  1. 数据怎么均匀的分配,也就是数据怎么均匀地分配到每个Group而不会出现某个Group的数据量很大,另一个却很小的情况。
  2. 客户端怎么访问到相应的节点和数据。
  3. 重新分片过程,怎么保证正常服务。

Redis Cluster 可以看成是由多个 Redis 实例组成的数据集合。客户端不需要关注数据的子集到底存储在哪个节点,只需要关注这个集合整体。
下面是三主三从,三个Group组成的redis集群,集群中的所有节点都是两两间互相通信。

数据分布的策略:

哈希取模:

如果想要数据分布相对均匀,可以考虑哈希后取模,hash(key)%N,N为Group的个数,根据余数决定把数据存储到哪个Group里面,取数据的时候也是使用该算法路由到指定Group取数,这种方式比较简单,属于静态分片,一旦Group数量发生变化,由于取模的N发生变化,所有数据需要全部重新分布。

一致性哈希:

把所以哈希值空间组成一个虚拟的圆环(哈希环),整个空间按顺时针方向组织,因为是环形空间,所以0和2^32-1是重叠的。

假设有四台机器要使用hash环来实现数据分布,我们可以先根据机器的名称、Ip等计算出一个哈希值,然后分布到哈希环中。

然后添加一个key,先把key进行哈希计算,得到一个哈希值,然后看看哈希值会落到圆环中的哪个位置,然后从那个位置开始顺时针遇到的第一个集群节点,该key就会落到该节点上。

如果新增了一个节点,只会影响新增节点顺时针方向遇到的第一个节点。

如上图,新增的Node5在Node1和Node4之间,只会影响Node1,假设Node4的哈希值是1,Node1的哈希值是100,Node5的哈希值是60,在没有Node5之前,对key进行哈希得到的哈希值为2-100的key会落到Node1上,新增了Node5之后,对key进行哈希得到的哈希值为2-60的key会落到Node5。61-100的key会落到Node1,因为之前有一些2-60的数据落到了Node1上,所以只需把Node1这部分key迁移到Node5,就完成了新增节点的数据重新分布,只影响了一个节点。

假如删除了一个节点,就把该节点的数据全部迁移到该节点顺时针方向遇到的第一个节点就完成了数据重新分布。

一致性哈希解决了动态增减节点时,所有数据都需要重新分布的问题,它只会影响到下一个相邻的节点,对其他节点没有影响

但是这样的一致性哈希算法有一个缺点,因为节点不一定是均匀地分布的,特别是在节点数比较少的情况下,所以数据不能得到均匀分布。解决这个问题的办法是引入虚拟节点(Virtual Node)。

比如没有使用虚拟节点时有两个节点:
极端的情况是Node1通过哈希计算得到值为1,而Node2通过哈希计算得到了2^32-3次方,两个节点在圆环的位置就会顺时针Node1到Node2之间大量的key,而Node2到Node1却只有一点点,大量的key就会落到Node2上。

Node1 设置了两个虚拟节点,Node2 也设置了两个虚拟节点(虚线圆圈)。

定位到Node1_1、1_2、1_3的key会落到Node1节点,定位到Node2_1、2、3key会落到Node2节点,使得分布比较均匀。

Redis虚拟槽分区

Redis 既没有用哈希取模,也没有用一致性哈希,而是用虚拟槽来实现分片的。

Redis 创建了 16384 个槽(slot),每个节点负责一定区间的 slot。比如 Node1 负责 0-5460,Node2 负责 5461-10922,Node3 负责 10923-16383。

然后通过CRC16算法对key计算再对16384进行取模,看计算的结果会落在哪个槽里面,然后这个槽由哪个节点负责,该key就落入到这个节点上。

Redis 的每个 master 节点维护一个 16384 位(2048bytes=2KB)的位序列,比如:序列的第 0 位是 1,就代表第一个 slot 是它负责;序列的第 1 位是 0,代表第二个 slot不归它负责。

对象分布到 Redis 节点上时,对 key 用 CRC16 算法计算再%16384,得到一个 slot的值,数据落到负责这个 slot 的 Redis 节点上。

key 与 slot 的关系是永远不会变的,会变的只有 slot 和 Redis 节点的关系。也就是同一个key计算得到的slot值是用于不会变的,变的只是Redis节点与它负责的slot之间的关系。

新增或者下面master节点,数据怎么迁移呢:
因为 key 和 slot 的关系是永远不会变的,当新增了节点的时候,需要把原有的 slot分配给新的节点负责,并且把相关的数据迁移过来。

比如 Node1 负责 0-5460,Node2 负责 5461-10922,Node3 负责 10923-16383。现在新增一个Node4,然他负责13000-16383槽的数据,只要把原本在Node3的相关slot分配给Node4负责,并把Node3这些槽的数据迁移到Node4即可。

节点下线后,把该节点的slot分配给其他在线的节点,并把对应数据迁移过去即可,只有所以slot都有节点负责的情况下,集群才能对外提供服务。

怎么让相关数据落到同一节点
在key里面假如{hash tag}可以使得相关key落到同一节点上,Redis在计算槽编号的时候只会获取{}之间的字符来进行槽编号计算,这样由于两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽。

比如下面两key会被计算出相同的槽,因为{bb}是一样的。
aaa{bb}cccc
pppp{bb}eeee

Redis集群搭建实战

为了节省机器,我们直接把6个Redis实例安装在同一台机器上(3主3从),只是使用不同的端口号。
机器IP192.168.18.140
六个节点的端口分别为9001 9002 9003 9004 9005 9006

新版的cluster已经不需要通过ruby脚本创建,删掉了ruby相关依赖的安装

  1. 在redis主目录下创建一个redis-cluster目录,然后到redis-cluster下创建6个目录,分别为9001 9002 9003 9004 9005 9006。
  2. 把主目录的redis.conf配置文件复制到9001目录下,并进行修改:
port 9001  #端口号
daemonize yes  #以守护进程启动
protected-mode yes #不允许外网访问
dir /usr/local/redis5.0.9/redis-5.0.9/redis-cluster/9001  #工作目录
cluster-enabled yes  #是否允许开启集群
cluster-config-file nodes-7291.conf #集群节点的配置文件,不用自己创建,redis会自动创建并更新。
cluster-node-timeout 5000 #集群节点超时后认为主观下线
appendonly yes  #开启AOF持久化
pidfile /var/run/redis_7291.pid  #PID文件

修改好后把该文件复制到9002 9003 9004 9005 9006目录,并做一些修改 有关9001的都改为对应的。

  1. 启动redis服务
./redis-server ../redis-cluster/9001/redis.conf
./redis-server ../redis-cluster/9002/redis.conf
./redis-server ../redis-cluster/9003/redis.conf
./redis-server ../redis-cluster/9004/redis.conf
./redis-server ../redis-cluster/9005/redis.conf
./redis-server ../redis-cluster/9006/redis.conf

使用ps -ef|grep redis查看进程启动情况:

4. 使用命令创建集群

./redis-cli --cluster create 192.168.18.140:9001 192.168.18.140:9002 192.168.18.140:9003 192.168.18.140:9004 192.168.18.140:9005 192.168.18.140:9006 --cluster-replicas 1#cluster-replicas 1  为每个master节点配置一个slave节点

然后会自动为你生成一套集群方案


M表示master节点,S表示Slave节点,后面的一串表示集群中的节点标志,是唯一的。再后面是该节点的IP和端口,replicates 一串Id 表示该从节点是这串Id的节点的从节点。

主节点上都有显示展示的负责的slot槽。

  1. 输入yes接受该分配,然后生成集群。

    自此,集群创建完毕。
附录

集群命令:

cluster info :打印集群的信息
cluster nodes :列出集群当前已知的所有节点(node),以及这些节点的相关信息。
cluster meet :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
cluster forget <node_id> :从集群中移除 node_id 指定的节点(保证空槽道)。
cluster replicate <node_id> :将当前节点设置为 node_id 指定的节点的从节点。
cluster saveconfig :将节点的配置文件保存到硬盘里面。

slot命令:

cluster addslots [slot …] :将一个或多个槽(slot)指派(assign)给当前节点。
cluster delslots [slot …] :移除一个或多个槽对当前节点的指派。
cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
cluster setslot node <node_id> :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
cluster setslot migrating <node_id> :将本节点的槽 slot 迁移到 node_id 指定的节点中。
cluster setslot importing <node_id> :从 node_id 指定的节点中导入槽 slot 到本节点。
cluster setslot stable :取消对槽 slot 的导入(import)或者迁移(migrate)。

key相关命令:

cluster keyslot :计算键 key 应该被放置在哪个槽上。
cluster countkeysinslot :返回槽 slot 目前包含的键值对数量。
cluster getkeysinslot :返回 count 个 slot 槽中的键

Redis主从复制、哨兵模式和分布式集群相关推荐

  1. redis搭建主从哨兵模式+分片集群部署(redis系列二)

    前言:在前一章了解redis的基本介绍后,这一章主要介绍redis的实战部署,文章有点长请一步步耐心看完,我相信肯定会有收获的,这里用的资源包是2022年最新的redis版本可能会跟旧版本不同,在此章 ...

  2. Redis07:redis的主从复制(原理与哨兵模式)、集群(搭建与它的优缺点)

    Redis的主从复制 什么的主从复制 主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,slave以读为主 能干什么? 读写分离,性能扩展 容灾快速 ...

  3. Redis 单机模式,主从模式,哨兵模式(sentinel),集群模式(cluster),第三方模式优缺点分析

    Redis 的几种常见使用方式包括: 单机模式 主从模式 哨兵模式(sentinel) 集群模式(cluster) 第三方模式 单机模式 Redis 单副本,采用单个 Redis 节点部署架构,没有备 ...

  4. redis主从复制、高可用和集群

    redis简介:     redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zse ...

  5. 2.6_11 Redis主从复制、哨兵模式、分片集群

    相关链接 Excel目录 redis官网 redis中文网 Redis集群 1) 主从复制replication:一个master,多个slave,要几个slave取决于要求的读吞吐量[10万QPS/ ...

  6. Redis高可用方案:sentinel(哨兵模式)和集群

    一. redis高可用方案–sentinel(哨兵模式) 当我们搭建好redis主从复制方案后会发现一个问题,那就是当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力, ...

  7. Redis高可用解决方案:sentinel(哨兵模式)和集群

    一. redis高可用方案–sentinel(哨兵模式) 当我们搭建好redis主从复制方案后会发现一个问题,那就是当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力, ...

  8. Redis 高级特性(5)— 集群模式(主从模式、哨兵模式、cluster 集群模式)

    Redis 是如何做到高可用的呢? 它主要通过支持主从模式.哨兵模式.集群模式这三种模式,来满足不同业务特点和可用等级的需求. 其中,主从模式部署最简单,用得也最多,集群模式比较复杂,但可用性最高. ...

  9. Redis主从复制哨兵模式自动切换

    Redis主从复制 1.概念 主从复制,是指将一台Redis服务器的数据复制到其他的redis服务器上.前者为主节点master,后者为从节点slave,数据的复制是单向的,由主节点复制到从节点(主节 ...

最新文章

  1. Google AI的焦虑:拆分搜索和人工智能部门,Jeff Dean任AI业务负责人
  2. SDL及扩展库在ARM-Linux 完整移植
  3. 怎么让电脑变成无线路由器
  4. html5 video在uc不自动播放,uc浏览器无法播放视频怎么办
  5. html meta标签作用
  6. 利用before伪元素创建图标
  7. 数据恢复软件真的可以恢复硬盘数据吗,有哪些数据恢复软件推荐?
  8. 这份PDF让你知Spring其然,“Spring揭秘”更知其所以然
  9. CentOS7下EasyDarwin的安装搭建
  10. 无源滤波器和有源滤波器有什么区别?-道合顺大数据infinigo
  11. vscode 离线安装.vsix(window 全教程)
  12. 004 ZeroMQ PUB XSUB-XPUB SUB模式
  13. 记一次CAN报文过滤器组调试过程
  14. 强化学习方法(一):探索-利用困境exploration exploitation,Multi-armed bandit
  15. 开发脚手架与自动化构建
  16. google GMS
  17. 用英雄联盟的方式讲解JavaScript设计模式!
  18. C#使用表达式树不能包含动态操作,使用反射的方式来实现T类型
  19. JS Date时间各种格式互转
  20. Redis以及Jedis的GEO地图功能

热门文章

  1. 数据库笔记13:创建与使用游标
  2. c++ namespace_c++语法2、c执行命名空间输入输出
  3. 每日小记2017.9.4
  4. Intel Core Enhanced Core架构/微架构/流水线 (3) - 流水线概述
  5. nginx反向代理配置去除前缀
  6. centos php mcrypt_面试经常问你什么是PHP垃圾回收机制?
  7. wow修改人物模型_玻璃钢气球狗模型景观雕-东莞气球树脂雕塑
  8. python fortran混合编程输入矩阵_如何将动态数组从Python传递到Fortran动态链接库
  9. jupyter怎么调字体_夏天冰箱调到几档最好 冷藏调多少度合适
  10. pyqt 把控制台信息显示到_(基础篇 01)在控制台创建对应的应用