LeeYzero的博客

业精于勤,行成于思

0%

cover


最近本来想写一篇如何应对复杂系统的文章,偶然读到John Ousterhout软件设计哲学,读完后,发现我没有必要再写了。这篇文章对原文做一个概要性总结并谈谈自己的理解和思考。

对于一个软件系统,需求可以粗略的分为两类:

  • 功能性需求
  • 非功能性需求

功能性需求是面向用户的,非功能性需求是面向系统的,但非功能性需求最终是服务于功能性需求。我们通常会用非功能性需求去评估一个系统的质量,比如扩展性、维护性、安全性等。

那什么是软件设计呢?软件设计就是折衷(Trade Offs)。实际情况下,资源是受限的,需求面临着各种因素的制约,软件设计就是在有限资源条件下对影响因素的一种权衡。而这本书从另一个角度阐明了:软件设计即管理复杂性。在我看来,它更偏向于对非功能性需求的一种定义。

围绕着复杂性这个主题,这本书回答了以下三个问题:

  • 什么是复杂性?
  • 软件系统为什么会变得复杂?
  • 如何降低软件系统的复杂性?
Read more »

Leslie Lamport 在1978年发表了一篇论文Time, Clocks, and the Ordering of Events in a Distributed System,对分布式系统领域产生的深远影响,这篇论文也成为分布系统领域引用最高的文献之一。

论文中定义了分布式系统中事件的”happen before”关系,并引入了逻辑时钟解决分布式系统中事件同步的问题。但由于系统之外的信息传递并不受系统内部逻辑时钟的约束,所以会出现因果不一致的问题。于是论文中又提出物理时钟,只要能保证各进程中物理时钟同步在一个合理误差范围内,就能保证系统的全局排序。

读完后,除了对分布式系统中事件同步有了新的认识外,也对系统设计有了一些新的思考。这篇文章主要对论文做一些解读并谈谈自己的解理。

相对论的启示

据Leslie Lamport本人回忆,当他看到Paul Johnson和Bob Thomas的论文The Maintenance of Duplicate Databases中使用时间戳来提供分布式系统中的全局一致性时,他立即看出了算法中存在的问题。他之所以能够一下看出其本质,原因在于他对相对论有深刻的认识:

Special relativity teaches us that there is no invariant total ordering of events in space-time; different observers can disagree about which of two events happened first. There is only a partial order in which an event e1 precedes an event e2 iff e1 can causally affect e2.

狭义相对论告诉我们,时空中事件不存在绝对的全局顺序;不同的观察者可能对两件事件中哪件先发生持有不同的看法。事件在时空中只存在部分有序,只有当 e1 对 e2 产生因果影响时,事件 e1 才先于事件 e2。

Read more »

有些人在碰到问题时,就想:“我知道,我可以使用正则表达式。”现在,他们就有了两个问题。
– by Jamie “jwz” Zawinski, 1997年 8月

什么是正则表达式

正则表达式(Regular Expression),通常缩写为regex或regexp,是一种在文本中进行搜索和替换的模式描述语言。它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。正则表达式是由普通字符(例如:字母和数字)以及元字符组成的。

这篇slide 描述正则表达式的发展历史。
regular-expressions是学习正则表达式的一个非常好的网站。

初识正则表达式

我们在日常中可能已经使用过正则表达式,比如我们在电脑中查找所有doc文档时,会使用*.doc搜索,这个就是正则表达式。其中*为元字符,表示可以匹配任意字符。.doc是普通字符,表示后续为.doc的文档。

当然这是一个比较简单的例子,正则表达式的功能非常强大,它提供了很多的特性,非常灵活。

在自己的日常开发过程中,有两种场景使用正常表达式比较多:

  • 使用Linux命令,如grepsedawk中的提供的正则表达式能力查询日志。
  • 使用各语言,如PythonPHPGo中提供的正则表达式库在大文本中查找目标字符串。

熟练掌握正则表达式,在处理字符匹配的问题上可以事半功倍。

Read more »

工欲善其事,必先利其器。作为一个曾经的资深vim党,在使用过CodeServer后,基本上不再使用vim了。CodeServer作为vscode的云端IDE,再也不用搭建本地开发环境了。之前介绍了 CentOS搭建CodeServer环境,只需要几步,就可以搭建自己的云端IDE,非常方便。

vscode-go是vscode中针对Go语言的扩展,提供了丰富的特性,包括:

  • 智能感知(IntelliSense)
  • 代码导航(Code navigation)
  • 代码编辑(Code editing)
  • 诊断(Diagnostics)
  • 增强的测试和调试支持(Enhanced support for testing and debugging)

vscode提供了语言无关的调试适配协议 Debug Adapter Protocol,使用各语言可以基于vscode的扩展实现各自的调试器。对于Go语言,其实现为dlv-dap

本文主要介绍在vscode如何使用vscode-go提供的debugging能力对go代码进行调试。

Read more »

缘起

最近出现一例json.Unmarshal导致的精度丢失引发的线上问题,虽然这个问题在被及时发现,未对业务造成损失,但细挖这个问题的原因仍然比较有意思。这篇文章会从技术层面深入分析json.Unmarshal精度丢失的原因以及处理建议,以避免后续开发过程中再次踩坑。

Part1 中,我们着重说明了json.Unmarshal处理大整数可能出现精度丢失的问题,但遗留了一个问题,即大整数置换成浮点数时,为什么会造成精度丢失,在这篇文章中我会详细解释原因。

Go语言对浮点数的处理遵循IEEE-745标准,该标准规定了浮点数在计算机中的二进制表示以及舍入方式,下面先补充一些基础知识,然后再结合 Part1 中的case,分析精度丢失的原因。

十进制与二进制

十进制用0-9表示,逢十进一,同时二进制用0和1表示,逢二进一。而每个位可以使用位的值乘以位的权重表示,直接看例子:

十进制12.34可以表示为:
12.34 = 1×101 + 2×100 + 3×10−1 + 4×10−2 = 12.34

同理二进制101.11也可以表示为:

101.11 = 1×22 + 0×21 + 1×20 + 1×2−1 + 1×2−2 = 4 + 0 + 1 + 1/2 + 1/4 = 5.75

十进制小数点向左移动1位相当于将该数除以10,向右移动1位相当于将该数乘以10。
例如:123/10 = 12.3,12.3×10 = 123。

同理,二进制小数点向左移动1位相当于将该数除以2,向右移动1位相当于将该数乘以2。

例如:11/2 = 1.1,1.1x2 = 11

Read more »

缘起

最近出现一例json.Unmarshal导致的精度丢失引发的线上问题,虽然这个问题在被及时发现,未对业务造成损失,但细挖这个问题的原因仍然比较有意思。这篇文章会从技术层面深入分析json.Unmarshal精度丢失的原因以及处理建议,以避免后续开发过程中再次踩坑。

在分析这个问题的过程中,发现涉及Go对浮点数数值的处理,又涉及IEEE-745标准中的一些细节,放在一篇文章中会增大文章的阅读难度,故拆分成了两个部分:

  • Part1: 引出json.Unmarshal处理大整数可能出现精度丢失的问题,并浅层次分析原因以及解决办法。
  • Part2: 先补充IEEE-745的背景知识,然后解释为什么json.Unmarshal处理大整数可能会出现精度丢失。

示例

这个问题的现象是,原始json string是一个字典,其中包含了一个大整数,在业务场景中,需要向该字典中追加一些字段,然后再序列化后进行存储。为了使用上的方便,代码中使用map[string]any去接收json.Unmarshal的结果,然后再使用json.Marshal序列化,结果发现序列化后的大整数跟原始大整数不致。

下面代码片段做了一些简化,同时忽略错误处理细节:

1
2
3
4
5
6
7
str := `{"id":16505201442738640729}`

var m map[string]any
json.Unmarshal([]byte(str), &m)

data, _ := json.Marshal(&m)
fmt.Println(string(data))

上面代码片段输出:

1
{"id":16505201442738640000}

原始json string中,id的值是16505201442738640729,经过json.Unmarshal和json.Marshal后,id的值变成了16505201442738640000,看起来出现了精度有丢失。

Read more »

虽然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 »