0%

虽然PHP做业务后端逐步在被Go等语言替代,但使用PHP做简单业务封装和数据组装时,开发效率依然是比较高效的。使用Nginx运行PHP的常用方法是FastCGI模块。PHP-FPM (FastCGI进程管理器)极大地提高了你的Nginx+PHP环境的性能,所以这对高负载的网站很有用。本教程介绍在CentOS8上安装Nginx并配置PHP-FPM的步骤,以便后续参考。

依赖环境

  • CentOS8
  • 拥有sudo权限
  • 更新dnf
1
sudo dnf update 

Step1 安装Nginx

Nginx在仓库中已经存在,可以使用dnf工具直接安装:

1
sudo dnf install nginx

启动Nginx服务,同时让Nginx服务在系统启动时自动启动。

1
2
sudo systemctl enable nginx
sudo systemctl start nginx

检查nginx是否已启动

1
sudo systemctl status nginx

如果您的系统上启用了防火墙,请确保打开HTTP端口以供远程系统访问。HTTP为80端口,HTTPS,为443端口。

1
2
3
sudo firewall-cmd --zone=public --permanent --add-service=http
sudo firewall-cmd --zone=public --permanent --add-service=https
sudo firewall-cmd --reload

在浏览器使用ip访问主机器,看是否能访问到nginx的默认页面了呢?

Read more »

最近半年主要在对一个10余年的商业化系统进行重构。对于商业化系统来讲,普遍存在状态多、链路长、业务逻辑复杂等特点。加上系统设计之初并未考虑到后续业务的发展形态,在承接业务需求时,不断打补丁,存在很多ad hoc方案,系统最终变成了一个”大泥球“,导致的问题是业务迭代效率低,代码变更故障率高。这篇文章主要对如何解决复杂问题谈谈自己的一些思考,供大家参考。

思考框架

首先谈谈什么是复杂问题。复杂问题是一个系统性问题,它的特点是规模大、维度多、关系复杂。在面对大型或复杂问题时,我们需要先找一样思想武器,或者说思考框架,可能简单归结为:分治知识抽象

分治可以把问题分割为规模更小且易于处理的若干子问题,这样就可以运用相似的知识来解决这些子问题,而使用抽象有助于进行推理和判断。分治、知识和抽象的有效性在于它们能够帮助我们在不变的智力条件理解和解决不断增长的问题

Read more »

charles

Charles 是一个功能强大的网络代理工具,常用于抓包、分析网络请求、模拟网络环境等操作。本入门指南将帮助你快速上手 Charles,掌握其基本用法和常用功能。

注: Charles是付费软件,白嫖党自行搜索破解教程。

下载安装

访问 Charles 官方网站下载对应操作系统的安装包,按照提示完成安装。

配置Charles

Mac端配置

1、链接Wifi,正常上网

2、安装Charles根证书

Help > SSL Proxying > Install Charles Root Certificate

3、信任Charles根证书

打开钥匙串,搜索刚才安装好的Charles根证书,点击Charles Proxy CA,展开的Trust一栏,点击Always Trust

4、开启SSL代理

Proxy > SSL Proxying Setting > SSL Proxying > Enable SSL Proxying

5、开启HTTP代理

Proxy > Proxy Settings > 将HTTP端口号设置为8888

6、打开Charles,开启录制

Proxy > Start Recoding

移动端设备端配置

1、移动设备和Mac端链接到同一Wifi,确保它们在同一网段。

2、查看Charles代理服务器地址和端口

Help > SSL Proxying > Install Charles Root Certificate > Install Charles Root Certificate on a Mobile Device or Remote Browser

charles

3、配置代理

找到链接的Wifi > 设置(i圆圈图标) > 配置代理 > 手动 > 填写第2步中服务器地址和端口号

charles

移动设备端链接到代理服务器后,Mac端会出现链接认证提示,点击允许。

4、打开Safari,并输入网址chls.pro/ssl,下载证书

5、手机的设置 > 通用 > 描述文件与设备管理,找到Charles,点击验证

6、手机设置 > 通用 > 关于本机 > 证书信任设置,确认Charles Proxy CA的信任开关处于打开状态

抓取HTTPS包

Mac端为指定请求设置代理

Proxy > SSL Proxying Setting > SSL Proxying > Enable SSL Proxying > Add > *:443

过滤网络请求

通常情况下,我们需要对网络请求进行过滤,只监控向指定服务器上发送的请求。

Proxy > Recording Settings > Include > Add > 填写要过滤的主机地址(域名)和 端口号。

限制网速

Charles可以模拟慢速网络或者高延迟的网络。

Proxy > Throttle Setting > 勾选 Enable Throttling > only for selected host 可以设置指定的主机访问进行限制网络

Map

Charles的Map功能分 Map RemoteMap Local 两种。Map Remote 是将指定的网络请求重定向到另一个网址请求地址,Map Local 是将指定的网络请求重定向到本地文件。

Map Remote

需要分别填写网络重定向的源地址和目的地址,对于不需要限制的条件,可以不填。这个功能在做Web开发是特别有用,可以比较方便地将移动设备的请求转发至后端服务器,比如开发机。

Tool > Map Remote > Enable Map Remote > Add

Map Local

对于 Map Local 功能,我们需要填写的重定向的源地址和本地的目标文件。对于有一些复杂的网络请求结果,我们可以先使用 Charles 提供的 “Save Response…” 功能,将请求结果保存到本地(如下图所示),然后稍加修改,成为我们的目标映射文件。

然后将一个指定的网络请求通过 Map Local 功能映射到了本地的一个经过修改的文件中。

Tool > Map Local > Enable Map Local > Add

Rewrite

Map Local 有一个潜在的问题,就是其返回的 Http Response Header 与正常的请求并不一样。这个时候如果客户端校验了 Http Response Header 中的部分内容,就会使得该功能失效。解决办法是同时使用 Map Local 和 Rewrite 功能,将相关的 Http 头 Rewrite 成我们希望的内容。

Rewrite 功能是对某一类网络请求进行一些正则替换,以达到修改结果的目的。

参考资料

channel是一种数据结构,在Go语言中用于协程间通信,是Go语言区别于其它语言的重要特性。Go语言原生支持channel,配合Go语言原生对并发的支持,让并发编程变得简单。正如Share Memory By Communicating对Go并发编程的建议:

Do not communicate by sharing memory; instead, share memory by communicating.

和传统并发编程使用同步原语共享内存不同,Go并发编程强调通过channel在协程间通信来共享内存,这实际上是对CSP(Communicating Sequential Processes)并发模型的一种实现。

从应用层面来讲,channel和协程(goroutine)是配合使用的,本文主要从实现层面介绍channel的内部原理,但不会涉及太多协程管理调度相关的知识。本文先介绍channel的基本用法,然后介绍channel内部数据结构以及实现原理,最后介绍使用channel时应该注意的一些问题。

Read more »

map又称映射表或关联数组,是一种常用的数据结构。它由一组<key, value>组成,每个key只出现一次。Go语言原生支持了map数据结构,正如 Go maps in action 中所说:

One of the most useful data structures in computer science is the hash table. Many hash table implementations exist with varying properties, but in general they offer fast lookups, adds, and deletes. Go provides a built-in map type that implements a hash table.

简单来说就是因为 hash table 是非常有用,并且提供了快速的查找、添加、删除特性,所以Go语言提供了map类型,它的底层是使用的hash table

刚接触Go语言时,有两点比较困惑:

  • 对map排序时,需要先对取出map中所有的key,对key排好序后,通过排好序的key查找value。
  • 遍历map时,每次得到的结果顺序都不一样。

随着对Go语言的了解,虽然这两个问题早以有了答案,但仍然想要了解一下map的内部实现原理,于是便有了这篇文章。

在这篇文章中,先简要介绍map的基本用法,再深入map的内部实现原理,最后介绍使用map时需要注意的问题。

Read more »

今年最后一天上班,下午没啥事,总结下Go语言的接口。接口是Go语言区别与其它语言的重要语言特性,也是我最喜欢的Go语言特性之一。Go的接口除了提供多态的能力外,它还是Go反射的基础。Go是一门静态语言,Go接口能够在编译期检查语法规则,同时具备像动态语言那样使用 ducking typing 实现接口。

我一直比较好奇Go的接口内部是如何实现。看了几篇文章,也看了runtime包中的部分实现。发现大部分文章都是从汇编的角度去分析,对我来说,我对汇编了解的不多,也不需要深入那么细节,我最终的目的是想通过了解Go接口的内部实现,让自己避免一些语言上的坑,同时能够写出更高效的代码。最终发现还是 Russ Cox 的文章 Go Data Structures: Interfaces 在一个相对高的层次介绍了Go的接口特性,但又没有深入汇编细节,比较容易理解,也达到了我的目的。

本文通过示例介绍接口的基本用法,然后引出接口的内部数据结构,再介绍接口的动态类型转换和接口派发原理以及效率,最后介绍一些需要注意的问题。

接口初识

接口是对实现的抽象。在Go语言中,接口是一组方法签名,使用interface关键字表示,Go中接口具有如下特点:

  • 接口实现是隐式的。
  • interface{}是一种静态类型。
  • 接口类型可以动态检查和转换
Read more »

在Go语言中,string是一种非常重要的数据结构。了解string的内部实现原理,有利于我们写出更高效的代码。本篇文章首先介绍string的实现原理,然后介绍string、bytes、runes和characters之间的关系,同时引出字符篇码Unicode和UTF-8,最后介绍使用string应该注意的一些问题。

先来看下面两个例子。

示例一

1
2
3
4
str := "中文abc"
for i := 0; i < len(str); i++ {
fmt.Printf("%d: %x\n", i, str[i])
}

示例二

1
2
3
4
str := "中文abc"
for index, runeValue := range str {
fmt.Printf("%d: %#U\n", index, runeValue)
}

上面例子中,%x 表示以16进行输出,%#U 表示输出Unicode字符的码点(code point)和其表现。你知道上面例子输出的结果是什么吗?如果理解了上面例子的输出,那基本了解了Go语言内部是如何实现的string,先来看string的内部数据结构。

Read more »

在Go语言中,slice是一种非常重要的数据结构,用于描述数组存储的连续部分,它本质上是对数组的引用。slice是建立在数组基础上,是对数组的一种封装,它屏蔽了数组底层细节,但使用起来更方便、灵活,再配合内建函数append和copy使得slice具有更强的表现力。

如果不了解slice的内部实现原理,可能会写出低效的代码,甚至会写出一些让你意想不到的BUG。本文先介绍slice的实现原理,再介绍slice一些经验和需要避免的问题。

在开始之前,先来看下面示例输出的结果是什么呢?如果你知道答案,说明你已经完全明白slice的实现原理,可以跳过原理部分介绍,后面的经验之谈可能会对你有帮助。

1
2
3
4
a := []int{1, 2, 3, 4, 5}
s1, s2 := a[:3], a[:3:3]
s1, s2 = append(s1, 100), append(s2, 101)
fmt.Println(a, s1, s2)

实现原理

由于slice是建立在数组的基础上,需要先了解Go中数组的一些概念,然后介绍slice的内部结构和分片操作,最后介绍内建append和copy函数在slice中的应用。

Read more »

上一篇 Lab2C: 持久化 中介绍了Raft持久化的实现,本篇介绍Raft日志压缩原理和实现。实验二共包含四个子实验:

本文是第四个子实验,需要实现Raft日志压缩,在开始实验前,先阅读以下材料:

基本原理

Raft的日志会随着接收客户端的请求数而不断增长,对实际生产环境中的系统而言,内存是有限的,较大的日志,在服务崩溃重启时需要较长时间加载恢复,这会造成服务的可用性问题。日志压缩主要解决这些问题。

Raft采用快照(Snapshot)来实现日志压缩。主要原理时,将当前整个系统的状态作为一个快照(可以理解为一个复本,相当于保证了当前的系统状态)持久化到稳定存储中,然后就可以丢弃这个快照点之前的所有日志了。论文中给出了一个快照示例如下:

上图中,在快照上,日志长度为7。在index=5处生成了一个快照(snaphot),快照中除记录了状态机的状态:x <- 0, y <- 9外,还记录了本次快照包含的最后一个日志记录的index和term(下面会解释为什么需要存储这两个信息)。快照成功后,我们就可以丢弃index=5之前(包括index=5)的所有日志了,日志压缩后长度为2,内存得以释放。

Read more »

上一篇 Lab2B: 日志复制 中介绍了Raft日志复制的实现,本篇介绍Raft持久化的原理和实现。实验二共包含四个子实验:

本文是第三个子实验,需要实现Raft持久化。持久化是指将Raft的部分状态存储到非易失的存储介质中,这样即便节点崩溃,也不至于丢失数据,当服务恢复后,可以从之前持久化的状态处开始工作。在开始实验前,先阅读以下材料:

Raft持久化本来是属于日志复制的一部分,但上一篇中我们已经看到,日志复制的原理和实现已经比较复杂了,所以课程将持久化单独拆分成了独立的一个子实验。

Lab2C比想象的简单,不是它本身就简单,而课程设置的比较简单。在实际生产环境中,持久化一般会将日志写入磁盘,涉及到磁盘I/O操作以及对内存状态的序列化与反序列化。在这个实验中不需要进行这样操作,实验中已经实现了一个Persister对象,将Raft状态的持久化封装成接口。它的实现也比较简单,只是模拟了持久化(实际上还是存储在内存)。

可能这个实验面向的人群主要是学生,主要实验目的是想让学生明白Raft持久化原理和意义,认为持久化到磁盘只是一种实现方式,并不是本实验的重点吧。但对一个软件工程师而言,熟练掌握磁盘I/O操作是一项基本功。

基本原理

Raft持久化是指将Raft状态序列化后存储到存储介质,本实验无需关心存储介质的问题,所以我们只需要对Raft状态进行序列化和反序列化。实现中提供了labgob包可能对任意类型的状态进行序列化和反序列化,这里就不展开了,大家看看labgob包的测试用例就会使用。

那我们需要持久化哪些Raft状态呢?

再次回顾论文Figure 8(这个图非常非常重要)中的左上图,需要对以下三个状态进行持久化:

  • currentTerm:当前任期,在Lab2A中已经介绍过了。
  • votedFor:当前任期中,投票给了谁,在Lab2A中已经介绍过了。
  • log[]:日志,每一项是一个LogEntry,在上一篇中已经定义过了。

那我们什么时候持久化Raft状态呢?

按照论文中的说明,节点在追加日志后,需要将日志持久化。追加日志有两种情况:一种是Leader接受客户端命令,将命令作为一条新的日志记录追加到日志中;另一种是Follower复制Leader的日志记录追加日志。所以我们只需要在这两种追加日志的情况下,持久化Raft状态即可。

Read more »