2023年4月

1.简介

在日常工作测试中,经常要抓包看请求的request,response是不是传的对,返回的字段值对不对,众多的请求中看得眼花缭乱,如何找到自己想要的请求,那么我们就需要过滤请求。Charles有4种过滤方式,用那一种都可以,看个人喜好了。

2.Charles过滤方式

大部分情况下网络请求数量很多,想要在众多请求里找到某个特定请求很难。可以使用如下几种方法过滤。

2.1直接过滤域名(临时性域名过滤)

软件下方的过滤可以进行简单的过滤,在主界面的中部的Filter栏中填入需要过滤出来的关键字。

方法:直接在主界面的中部的 Filter 栏中输入需要过滤出来的关键字,例如我们的服务器的地址是:
www.baidu.com
,那么只需要在 Filter 栏中填入
www.baidu.com
即可。如下图所示:

2.2Focus过滤(过滤焦点域名)

方法:1.选中想要抓取的地址,然后鼠标右键,选中Focus。如下图所示:

2.在目标的网络请求上右键,选中focus(此时,该域名已经被设置为一个焦点标记了;);然后点击fillter后面的focused来筛选你的做的focus标记文件。

3.在菜单栏选择“View”->“Focused Hosts...”,如下图所示:

4.你设置的焦点域名在”focused Hosts”里面可以查看和管理,如下图所示:

5.结构视图,这种模式下的展现更加人性化,当你设置某个域名为焦点域名的时候,会把当前域名单独显示在上面,而其它的非焦点域名,都会在other Hosts里显示。如下图所示:

2.3
修改Include的域名和端口
(适合经常性封包过滤)

方法:1.菜单栏选择 “Proxy”->”Recording Settings”,如下图所示:

2.在弹出的窗口中选择 Include 栏,再点击“Add”,在弹出的窗口中输入需要监控的协议,主机地址,端口号等信息,来添加一个项目。如下图所示:

注意,此方法是在抓取地址之前需要提前设置好。

2.4在会话中查找

Ctrl + F 进行全局过滤(最实用,可以针对域名ip过滤/也可以对单个对路径过滤)

方法:1.按Ctrl+F,出现查找会话框,输入要找的接口,点击find按钮,就可以过滤对应的请求(1输入要搜索的接口,2点击find,3点击筛选到的接口即可)。如下图所示:

2.搜索结果可能过多请根据 Search in 进行控制,如下图所示:

2.5白名单过滤

此方法是设置白名单,在此名单内的地址浏览器才会渲染成功,其他的地址将会被阻塞,导致浏览器无法访问。注意,此方法也需要在抓包之前设置好。

1.菜单栏选择 “Tools”->”White List...”,如下图所示:

2.在弹出的窗口中勾选“Enable White List”,再点击“Add”,在弹出的窗口中输入需要监控的协议,主机地址,端口号等信息,来添加一个项目。如下图所示:

3.小结

通常情况下,我们使用Filter直接过滤做封包过滤,
修改Include的域名和端口
做一些唯一的域名抓包,正常情况下,不推荐这种设置方法;因为这种方法,你的charles只能抓你配置的域名;如果某天早晨,你的charles一切正常,访问也正常,而且在active commections里也看到了某个域名的请求信息,但是在主界面死活看到获取到的信息,不用着急,非常有可能是因为你设置了include的指定域名;而且是设置后你忘记解除了,导致你一脸懵逼;这种方法非常不推荐,太粗暴了,除非你这半个月都只看某个HOST下的信息,否则千万千万别这么搞,很容易在以后使用时候的忘记解除;如果你只是为了更清楚的查看某个域名下的请求和响应信息,推荐使用结构视图模式下的焦点域名设置;那种模式比这种方法更好。

本篇内容基于 Redis v7.0 的阐述;官网: https://redis.io/

本篇计划用 Docker 容器辅助部署,所以需要了解点 Docker 知识;官网: https://www.docker.com

系列目录:

微服务 - 概念 · 应用 · 通讯 · 授权 · 跨域 · 限流

微服务 - 集群化 · 服务注册 · 健康检测 · 服务发现 · 负载均衡

微服务 - Redis缓存 · 数据结构 · 持久化 · 分布式 · 高并发

一、分布式解决 Session 的问题

在单站点中,可以将在线用户信息存储在Session中,随时变更获取信息;在多站点分布式集群如何做到Session共享呢?架设一个Session服务,供多服务使用。

频繁使用的数据存在DB端,频繁的DB连接,频繁的IO;数据存于内存中更能减少性能的消耗,更能提高使用效率。

集群化分布式时,为解决以上现象,建立缓存服务显得尤为重要。

建立缓存服务选择性很多,如:Redis、MongoDB等,以下以 Redis 为例:

作者:[
Sol·wang
] - 博客园,原文出处:
https://www.cnblogs.com/Sol-wang/

二、内存数据库 Redis

Re
mote
Di
ctionary
S
erver;
远程字典服务,Key/Value 存储系统、列存储、文档型存储等,NoSQL开源内存数据库。最多的使用场景是作为数据缓存,存在于应用与DB之间,减少对DB的访问,提高数据操作的性能。

下图展示了缓存服务在整体架构中的位置:
Redis 内存数据库在整体架构中的位置

2.1 Redis 特性

高性能 / 高可用 / 持久化 / 集群化,单实例每秒读写达10万次;

丰富的数据类型 :String / Hash / Set / Zset / 队列 / 订阅 / 发布;

高性能数据结构:SDS / Intset / ziplist / listpack / quicklist / skiplist;

支持 ACL:Access Control List;精细化的权限管理策略;

单线程处理事务:顺序执行,容易上锁;

多线程处理辅助功能:连接请求 / 持久化等;

单线程处理事务的优缺点

优点:顺序执行,不存在脏读脏写幻读等情况,不存在死锁,不存在线程管理的开销。
缺点:单线程的性能瓶颈,多处理器的资源浪费。

2.2 单线程IO多路复用

通常情况下,同时连接 Redis 的客户端有成千上万的,但 Redis 只有一个主线程处理事务,那如何做到多路连接集中到一个线程处理呢?

当多个客户端同时发起连接后,这也是需要一个过程的,也是有连接完成的先后,谁连接完成就会告诉 Redis,这里的告诉用的是回调方式,Redis就会把他的任务放到单一的队列中,队列的另一头连接着主线程。

在这个过程当中,多路的连接汇集到一个有足够处理能力的队列中进行传输;是不是可以理解为:多路连接重复利用了单个管道;我想...这里也是体现了 Redis 的多路复用技术。当然,单单就多路复用来讲,也会是多路集中到一路,然后这一路又分成了多路到各各目标。

多路复用也有不同的算法:select、poll、epoll,Redis用的是epoll算法,所以其中有回调的动作,目前而言,epoll是最高效的;关于每个具体的算法,有兴趣的同学可以继续研究一下。

2.3 启动 Redis

用 Docker 启动 Redis 简单示例:

1、拉取镜像
docker pull redis

2、运行容器
docker run -d --name=some-redis redis

3、连接到 Redis
docker exec -it some-redis redis-cli

Redis Docker版默认是没有配置文件的,官网说:可以再生成镜像方式解决...

既然没有配置文件,那 Redis 启动完全是按所有配置项的默认值运行的;如下传参启动:

# 启动 redis-server,多参数配置
docker run -d --name some-redis -p 6379:6379 redis redis-server \
    --bind 0.0.0.0 \        # 支持任意的连接
    --save 60 1 \           # 每60秒 持久化一次
    --protected-mode no \   # 取消保护模式
    --requirepass 123456    # 登录密码 123456;连接后用 auth {passwd} 方式登录

传参也就是覆盖了配置项的默认值,以下查看覆盖后的配置项效果:
config get *
列出所有配置项

Redis 启动后,都包含 redis-server / redis-client;
所以任意 Redis 都可用 redis-client 连接到其它 redis-server:
docker exec -it some-redis redis-cli -h {目标IP} -p {目标端口}

2.4 重要配置项

通常配置参数于配置文件中;比如:
/etc/redis/redis.conf

配置项 说明
bind 可访问限制,白名单;注释后不限制
port 对外端口
timeout 连接后,没有通信任务的空闲时间,超出此时长后自动断开
daemonize 后台运行(
容器运行时忽略
protected-mode 只能本地访问的保护模式
tcp-backlog 网络连接队列最大连接数(
对应Linux内核参数 net.core.somaxconn,有关命令
sysctl
tcp-keepalive 网路通信检测间隔,网络是否已断开(
当timeout为0才起效吧
pidfile 存放ProcessID编号的文件Pid的存放目录(
后台运行时才会产生pid文件,容器时是否忽略
loglevel 日志级别
logfile 日志文件目录
database 数据库个数
requirepass Client 连接密码
maxclients 客户端同时最大连接数(
对应Linux user openfile limit,
必设
;有关命令
ulimit
maxmemory 内存最大使用量,推荐70%

三、数据类型 / 常用命令

Key/Value 的存储系统,Key相当于区分的变量名称,类型区别在于Value;常用数据类型有:

String
:字符,基础类型,过期时长,递增

  • SET/GET key value
    写入/取值
  • GET key key key
    取多个值
  • INCR/DECR key
    递增1/递减1

List
:队列,先进先出,先进后出

  • LPUSH/RPUSH key value
    头/尾添加
  • LPOP/RPOP key
    头/尾移除
  • LLEN key
    队列长度

Set
:无序集合,可查询 交集、并集、差集等

  • SADD|SREM key member member member
    插入/移除 元素
  • SISMEMBER key member
    是否存在成员
  • SINTER key1 key2
    多集合取交集
  • SCARD key
    元素个数总数

ZSet
:带排序的Set集合;比Set多出一个专用的score值,又可排出名次列

  • ZADD key score member score member
    新增成员及序列数,可覆盖
  • ZRANGE key start stop
    按序列范围取集合
  • ZRANK key member
    取正序排名;从0开始的名次
  • ZREVRANK key member
    取倒序排名;从0开始的名次

Hash
:可同时设置/获取多个属性值

  • HSET key field value field value
    成对设置多个属性与值
  • HMGET key field [field field]
    取多个列的值
  • HINCRBY key field -5
    指定属性的值,递增/递减

四、数据结构

为什么 Redis 要有自己的数据结构

  • 查询要快:体现在单条Key用了连续性的内存空间
  • 占用空间少,节约内存:体现在了集合的数据压缩

Redis 中有这么几种数据结构:sds (动态字符串)、hashtable (字典)、linkedlist (链表)、intset (整数集合)、ziplist (压缩列表)、listpack (紧凑列表)、quicklist (混合列表)、skiplist (跳表);它们分别应用于Redis各数据类型中,使得Redis在资源利用和运行效率上有着明显的效果。

以下列出了数据类型与数据结构的应用关系图:
Redis 数据类型应用数据结构关系图
关于 Listpack;未来作为 Ziplist 的升级替代品,v7.0版本也会有 Ziplist 的存在,后续版本中逐渐被替代。

数据结构的自动切换

每种数据类型也会有多种数据结构,至于何种数据类型在什么情况下用何种数据结构,取决于存储的数据;
比如:List 每元素小于8KB时,自动使用 Ziplist,否则自动切换为 Quicklist;
比如:Set 每元素为数字,元素小于512个时,自动使用 Intset,否则自动切换为 Hashtable;
比如:Hash 元素小于512个,每元素长度小于64字节,自动使用 Ziplist;否则自动切换为 Hashtable。

影响数据结构转换的配置项

# 配置文件中 各数据类型中的数据结构
# 能承载的最大长度/个数/容量
# 超出最大限制后,变更为其它数据结构
hash-max-listpack-value 64
hash-max-listpack-entries 512
list-max-listpack-size -2(8KB)
set-max-intset-entries 512
set-max-listpack-value 64
set-max-listpack-entries 128
zset-max-listpack-value 64
zset-max-listpack-entries 128

以下主要以 sds / ziplist / listpack / skiplist 为例的阐述,基本涵盖了重要的数据结构,一些扩展性的数据结构有兴趣的同学再深入了解下。

4.1 动态字符串 - SDS

很多计算机语言都一样,Redis 也是基于C语言写的;对于String类型,当给一个变量拼接一个字符串时,都是以一个新对象在内存中重新开辟新的更长的连续空间来存储;重新开辟/释放旧空间这样的内存损耗。。。所以为什么开发人员会避免
strname + "xxx"
这样的拼接方式;字符串长度也是底层每次通过遍历得出的结果。。。Redis为了避免这样的损耗,于是就有了 Simple Dynamic Strings 这样的解决方案 SDS。

Redis String 会事先分配好比实际字符串更长的内存空间,并记录实际字符串的长度,也记录存储后剩余的空间长度。

  1. 取字符串长度时,直接返回记录的长度值
  2. 追加字符串时,直接使用空闲的剩余空间

预分配空间有多长?总有用完的时候:

  1. 当实际字符串长度<1M时,预分配实际字符串两倍的连续长度,相当于本次只会占用一半
  2. 当实际字符串长度>1M时,每次预分配多出1M的长度空间。便于下次直接存储
  3. 当字符串减少缩短时,多出的剩余空间保留,便于下次追加,或手动命令清除空闲空间

这种
空间预分配策略

惰性删除策略
就是SDS的性能优势。

4.2 压缩列表 - ziplist

一块连续性的内存空间存放了整个Value,也就是一个ziplist,如何做的呢?一个集合的示例:

ziplist结构图
bytes:记录 ziplist 的总长度
tail:记录最后一个 entry 的偏移量,便于快速定位
len:记录 entry 的总个数
entry:列表元素,数据存放的元素
end:单个 ziplist 的结束符
prevlen:记录上个 entry 的总长度;
这里记录值所占用的空间长度,取决于上个 entry 的长度,所以1-5会自动切换
encoding:记录 data 的实际数据类型 及长度;如:
int时占空间小,可直接存到 encoding,就不用 data 了
data:实际数据;
占用空间小的数据类型,可直接放到 encoding 中,所以这时候没有 data 的存在,省空间

ziplist 的取值

结构图中,有总长/个数/偏移量/结束符/上个元素长度;所以 ziplist 正序倒序是都可以算出每个 entry 具体位置并取出数据。

ziplist 的写入
,插/改/删 统一为覆盖方式:
1、为新元素找到被覆盖元素的位置,位置之前的元素 + 新元素 + 位置之后的元素 = 新的ziplist的完整结构
2、申请新ziplist所需长度的内存连续空间,并存入新空间
3、释放旧空间

看起来挺不错的,相比链表的非连续性存储所带来的性能提升明显。。那为什么还会有新的改进版本 listpack 的出现?

ZIPLIST 的弊端

ziplist 的问题出在 prevlen;
也就是上面蓝色部分的描述,存了相邻 entry 的长度;如果 entry 长度过长,相邻的 prevlen 所占空间长度就会从1变为5;也就是说 entry 数据变更,会影响到相邻的 entry;最严重的情况是很多entry长度恰好都在一个临界值,会导致相邻prevlen长度的变化,连锁反应是之后位置上的entry级联性的连续重复多次变更;
上面提到,变更:就是重新申请新的内存连续空间,释放旧空间;那么级联性的连锁反应呢?一个写操作,引发的恶性灾难事件!!!

4.3 紧凑列表 - listpack

listpack 是 ziplist 的升级版于v7.0中,作为替代品都有什么变化,listpack结构如下图:
listpack 结构图
上图看出,listpack 与 ziplist 的区别在于:
1、header 取消 tail
2、entry 取消 prevlen
3、当前 entry 中增加 encoding + data 的总长度 element-total-len
4、element-total-len 中的首位会标识出左侧是否还有值,主要用于逆序读

element-total-len 编码

长度 1-5 bytes 是可变的,不固定长度如何读出整个 element-total-len?这其中0/1标识了左侧是否有数据;
element-total-len 示意图

listpack 的读
,假设逆序读出每个元素的值,已知end固定长度,跳过 1 bytes 到 element-total-len 中读固定(7)位数,就会知道左侧是否有值,这样会把 element-total-len 整个读下来,也就知道了当前 entry 长度,那么也就知道了相邻 entry 的起始位置;继续这样按序把每个 entry 读出来就完成逆序读数据。

listpack 的写入
,同样是基于 ziplist 的方式,新元素替换旧元素组成新的长度,存储到新申请的内存连续空间,释放旧空间;由于未影响到其它元素,申请一次新空间后完成写入操作。

这样以来,
listpack 任何写入的操作,entry 都是在变更自己,不会牵连到其它 entry,这就是对 ziplist 改善的地方

4.4 跳跃列表 - skiplist

跳表是在链表的基础上追加了更多指针的存储;链表的指针指向了相邻元素的地址,但跳表又追加了指向间隔元素的指针;这使得跳表在查询元素的效率上更快。

下表 Linkedlist 与 Skiplist 的查询区别:
skiplist 结构图
上图:两种查询的路径不同,影响到的元素数量也不同,链表搜了11次才找到指定的元素,而跳表仅搜了5次就找到了指定的元素。
比如:元素1 既存了指向下个元素2 的指针,也存了指向元素4 的指针;
所以跳表多存的指针让其可以跳跃搜索,相对于链表减少了搜索次数,这就体现了相比链表搜索的高效率。

其它数据结构

除以上几种数据结构外,Redis也会有Intset、quicklist、Hashtable等,由于基本原理相识,这里简单描述:
Intset:与ziplist、listpack相似的连续性集合,主要区别在于Intset仅支持数字型;
quicklist:在 ziplist 基础上扩展的数据结构,其中每个成员是一个ziplist,插入元素时:插入到前个元素ziplist中的末尾、后ziplist的开头、或独立的ziplist。

五、持久化

5.1 RDB 模式

Redis Database:持久化以指定的时间间隔执行数据集的时间点的快照整库备份。

触发RDB配置

save 36000 1 600 10 30 100
以上从右往左,成对解释:
当30秒内写入了100次,触发持久化,如果未满足条件,继续下一对;
当600秒内写入了10次,触发持久化,如果未满足条件,继续下一对;
当36000秒内写入了1次,触发持久化。

持久化过程
:内存 -> 临时文件 -> 磁盘。

影响RDB效率的配置项

stop-writes-on-bgsave-error yes
当持久化失败后强制停止写入
rdbcompression yes
快照数据压缩,损耗CPU
rdbchecksum yes
是否检测备份文件,损耗CPU≈10%

关闭RDB持久化模式:
save ""

模式优劣

优势:体积小,占用磁盘少。
劣势:当持久化发生异常时,最后一次的持久化有可能失效,不能确保整体数据的绝对完整性。

5.2 AOF 模式

Append Only File:追加记录服务器接收到的每个写入命令,增量保存;如果写入错误,Redis 也会具有自动修复受损的AOF文件;恢复时,重新按序执行指令,从而重建内存库。

配置开启AOF模式:
appendonly yes

持久化频率策略配置:
appendfysnc always|everysec|no

  • 每次命令追加:每次写命令立刻记录,太频繁,太耗性能
  • 每秒追加一次:每秒集中记录一次,依然有可观的性能表现

文件压缩 Rewrite

主进程 redis-server 创建出一个子进程 bgrewriteaof 对 AOF文件的重新整理,先整理出一个临时文件,再覆盖原AOF文件。

Rewrite 的重写策略:

  • 只针对写入命令的整理
  • 相同数据只记录最后写入命令
  • 过期数据不记录
  • 多命令合并记录

多命令合并示例:如累加命令 Incrby 的累加总和合并成一次累加;如集合命令 rpush 的多次追加合并成一次追加多个。

手动触发执行重写命令:
bgrewriteaof

自动触发重写相关配置:
no-appendfsync-on-rewrite no
重写开关
auto-aof-rewrite-min-size 64mb
重写触发条件,文件超过指定大小
auto-aof-rewrite-percentage 100
重写触发条件,文件超过已使用%

AOF文件64MB是不是显得太小了,
可适当增加容量如3GB,以防止过多的触发压缩重写后影响性能;
也不可过大,影响数据恢复效率。

模式优劣

优势:丢失率低,数据较完整。
劣势:AOF占用磁盘空间大;恢复时重新全部执行一遍命令,恢复速度慢了点;持久化失败时,最后一秒写入命令可能丢失。

Redis 默认开启 RDB,可同时开启 RDB + AOF,恢复数据时以 AOF 为优先。

由于是单线程方式,Redis 会创建子线程负责持久化处理,不管是哪种持久化方式,在创建子进程的瞬间,都会有阻塞的现象。

六、分布式集群

6.1 虚拟插槽

Redis 分布式给出一个 16384 长度的集合,每个元素称为一个 Slot,将所有 Slots 分段平均映射到各个 Master 节点上;数据通过对 Key 的算法映射到各Slot,也就存到了对应的 Master 节点上,所以每个节点实例负责其中一部分的Slot读写。

通过控制节点与槽 Slot 的关系,决定每个 Master 节点所承载的数据量;这在集群节点维护的时候非常有用。

6.2 创建集群

集群必须了解的conf配置项:

# 每个节点必须的配置项
cluster-enabled yes
# 节点失去连接超时时间
cluster-node-timeout 15000
# 节点间传输效率 默认 no(yes:单次多量发送/no:单次少量多次发送)
tcp-nodelay no
# DOCKER/NAT support
# (地址端口可能被转发)静态配置公共地址
cluster-announce-ip <外部访问IP>
cluster-announce-port <外部访问端口>
cluster-announce-bus-port <节点互通外部端口>

按 Redis 要求的最低 Master 节点数量3实例,多主多从的模式,需要创建6个运行实例:
(6371-6376,16371-16376)

# 实例示例1(6371,16371)
docker run -d --name clu-rds-1 \
    -p 6371:6371 -p 16371:16371 \        # 容器分别开放 对外连接端口 和 节点间通信端口
    redis \
    redis-server \                       # 启动 Redis 服务命令
    --bind 0.0.0.0 \                     # 不限连接的客户端来源
    --port 6371 \                        # 实例对外连接端口
    --protected-mode no \                # 非保护模式
    --cluster-enabled yes \              # 开启集群
    --cluster-announce-ip 13.13.1.16 \   # 对外的访问IP
    --cluster-announce-port 6371 \       # 集群对外的连接端口
    --cluster-announce-bus-port 16371    # 集群节点间通讯端口(通常为:10000+Port)

Docker 查看6个容器实例:

Docker 3主3从模式 创建集群:

docker exec -it clu-rds-1 \
    # 连接到任意实例
    redis-cli -p 6371 \
    # 创建集群 并指定 1主几从
    --cluster create --cluster-replicas 1 \
    # 要包含的所有(6个)运行实例<ip:port>
    13.13.1.16:6371 13.13.1.16:6372 13.13.1.16:6373 \
    13.13.1.16:6374 13.13.1.16:6375 13.13.1.16:6376

连接到任意容器节点,查看集群成员:
docker exec -it <container-name> redis-cli -p <container-port> cluster nodes

6.3 节点管理

# 集群信息
redis-cli -p <port> cluster info
# 查看现有节点成员
redis-cli -p <port> cluster nodes
# 加入新成员,从节点
redis-cli --cluster add-node <new-node-ip:port> <cluster-member-ip:port> \
    --cluster-slave --cluster-master-id <to-master-id>
# 删除集群成员节点
redis-cli --cluster del-node <del-ip:port> <del-node-id>
# 重新分配节点与插槽映射
redis-cli --cluster reshard <member-ip:port>
# 查看某节点同步信息
redis-cli -p <port> info replication
# 停止某节点实例运行
redis-cli -p <port> shutdown

分布式带来的影响

事务支持有限;
跨节点的多Key操作有限;如:SET集合不能计算两KEY的交集等

6.4 分布式锁

6.4.1 为什么会需要锁?

逆向生成难以实现飞跃

软件行业长期面临生产率瓶颈。程序员的工作强度已经接近极限,要继续提高软件交付速度似乎难上加难。近年来在开发流程和工具上,如GPT显然提供了一定的提效,但是实质上没有改变软件开发的基本模式————逆向生成。目前软件项目的大部分工作时间都花在理解“现有”系统和代码上,然后基于此进行修改或扩展。仅靠文档很难完全重构一个代码库。程序员不得不深入理解语句级别的代码细节,这显然不是人工智能的最佳应用场景。代码一旦完成,整个系统的内部机理又成为一个黑箱。

GPT仍属逆向生成

GPT虽然可以根据上下文生成代码,但它并不是在进行正向生成————根据需求构建新系统,而是在翻译和扩展现有系统,难免局限于系统原有的设计和架构。这更像一种“逆向生成”————机械化地解读并翻译一段既有的代码,而非根据需求正向构建新系统。GPT需要大量的训练数据来学习通用的代码模式和上下文关联,然后在特定上下文中生成相应的代码。这种生成过程其实是在解读训练数据,并在新的上下文中翻译类似的代码。它难以理解真实需求中的概念和逻辑,真正根据需求生成全新的解决方案。

正向生成是软件工程的未来

真正意义上的强人工智能出现之前,软件行业要进一步提高生产力,还需要一个全新的编码范式————正向生成。这需要系统性地研究从需求到设计与实现的正向生成流程,构建能够根据诉求自动推演出高质量解决方案的智能系统。要实现正向生成,关键是建立“需求->设计->代码”之间的可信映射关系。
相比之下,逆向生成的难度更大,生成的代码质量更难保证。因此,理想的正向生成系统应该可以大幅超越逆向生成系统的生成能力。这需要跨越的技术难度不小,但其潜在的产业价值同样巨大。
总之,就目前而言,软件工程主要依靠逆向生成完成工作,这难以实现质的飞跃,并导致编码质量和效率的天花板。软件行业迫切需要一个完整的正向生成编码模型与之对应,真正实现「说需求,出系统」的软件生产自动化新范式。这将使软件工程从“逆向解码之艺”迈向“正向构建之道”,开启软件工业化的新篇章。

本文将从GB/T 22239《信息安全技术 网络安全等级保护基本要求》规定的安全计算环境中解读、摘要若干安全要求,结合Abp框架,对站点进行安全升级。

【身份鉴别】应对登录的用户进行身份标识和鉴别,身份标识具有唯一性,身份鉴别信息具有复杂度要求并定期更换。

解决方案

  1. 设置密码最小长度

  2. 密码由数字、字母和特殊字符组合而成

  3. 设置密码每隔90天需要更换一次

实施

在AbpSettings表中配置开启PasswordComplexity密码复杂度校验,设置密码最小长度为7位

在这里插入图片描述

配置密码强制过期策略,参考
用Abp实现找回密码和密码强制过期策略

【身份鉴别】应具有登录失败处理功能,应配置并启用结束会话、限制非法登录次数和当登录连接超时自动退出等相关措施。

解决方案

  1. 登录后无操作15分钟,系统自动退出登录状态。

  2. 连续登录失败5次后,锁定账户15分钟

实施

在WebCoreModule中,将Jwt Bearer Token的过期时间从默认的1天改为15分钟

在这里插入图片描述

配置用户登录失败锁定
默认将新增用户的IsLockoutEnabled打开

public User()
{
    this.IsLockoutEnabled = true;
}

在AbpSettings表中配置开启用户登录失败锁定,并配置失败尝试次数和锁定时长

在这里插入图片描述

【身份鉴别】应采用口令、密码技术、生物技术等两种或两种以上组合的鉴别技术对用户进行身份鉴别,且其中一种鉴别技术至少应使用密码技术来实现。

解决方案

采用两种以上组合的鉴别技术对用户进行身份鉴别,采用用户名+手机短信验证码方式登录

实施

对Abp框架改造,加入两步验证功能,参考
用Abp实现两步验证(Two-Factor Authentication,2FA)登录(一):认证模块

【访问控制】应对登录的用户分配账户和权限;并授予管理用户所需的最小权限,实现管理用户的权限分离。

解决方案

  1. 删除或者禁用默认账户,

  2. 禁用超级管理员,

  3. 设置独立的审计管理员、安全管理员等。

实施

更改AbpPermissions,AbpRoles,AbpUserRoles表,合理安排用户角色的权限配置。

【入侵防范】应通过设定终端接入方式或网络地址范围对通过网络进行管理的管理终端进行限制。

解决方案

限制管理员的登录地址范围,仅允许特定IP进行登录管理。

实施

使用IP白名单限制管理员账户登录。

AppSettingNames.cs:

public static class AppSettingNames
{
    ...
    public const string AdminIpAddressWhitelist = "CAH.AdminIpAddressWhitelist";
}

AppSettingProvider.cs:

public class AppSettingProvider : SettingProvider
{
    public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context)
    {
        return new[]
        {
            ...
            new SettingDefinition(AppSettingNames.AdminIpAddressWhitelist, "127.0.0.1,::1,localhost,0.0.0.0", scopes: SettingScopes.Application | SettingScopes.Tenant | SettingScopes.User, isVisibleToClients: true),
        };
    }
}

在身份验证终节点方法Authenticate中,添加对管理员账户IP白名单的校验:


var ipAddress = this._logInManager.ClientInfoProvider.ClientIpAddress;
var adminIpAddressWhitelist = await SettingManager.GetSettingValueForTenantAsync(AppSettingNames.AdminIpAddressWhitelist, loginResult.Tenant.Id);
var IpCheckRequired = false;
var roles = await userManager.GetRolesAsync(loginResult.User);
if (roles.Contains(StaticRoleNames.Tenants.Admin) || roles.Contains(StaticRoleNames.Tenants.Super))
{
    IpCheckRequired = true;
}
if (!string.IsNullOrEmpty(adminIpAddressWhitelist))
{
    if (!adminIpAddressWhitelist.Split(',').Contains(ipAddress) && IpCheckRequired)
    {
        throw new UserFriendlyException("IP不在允许的列表中");
    }
}


【入侵防范】应提供数据有效性检验功能,保证通过人机接口输入或通过通信接口输入的内容符合系统设定要求。

解决方案

  1. 对上传接口进行文件格式限制转义处理。
  2. 对系统配置防范XSS跨站脚本攻击

实施

文件系统中配置仅允许业务相关的文件类型上传

 "FileStorage": {
    ...
    "AllowOnlyConfiguredFileExtensions": true,
    "FileExtensionsConfiguration": ".jpg,.png",
    }

配置上传文本中高危脚本过滤,参考
[Asp.Net Core] 网站中的XSS跨站脚本攻击和防范

【安全审计】应对审计记录进行保护,定期备份,避免受到未预期的删除、修改或覆盖等

解决方案

审计日志存储6个月以上。

实施

对AbpAuditLogs中的数据定期异地备份,保存时长至少6个月以上。

【数据完整性、保密性】应采用校验技术或密码技术保证重要数据在存储过程中的完整性以及保密性,包括但不限于鉴别数据、重要业务数据、重要审计数据、重要配置数据、重要视频数据和重要个人信息等

解决方案

  1. 采用AES加密对身份证号码等重要数据进行加密后再存储
  2. 传输报文中对敏感数据进行脱敏处理
  3. 页面中对敏感数据进行脱敏处理

实施

使用加密转换器对身份证号字段进行加密存储,参考
在EF Core中为数据表按列加密存储

modelBuilder.Entity<User>().Property(c => c.IdentificationNumber).HasConversion<EncryptionConverter<string>>();

修改User到UserDto的字段映射,对手机号,身份证号和邮箱地址进行正则替换。

public UserMapProfile()
{
    CreateMap<UserDto, User>();
    CreateMap<UserDto, User>()
        .ForMember(x => x.Roles, opt => opt.Ignore())
        .ForMember(x => x.CreationTime, opt => opt.Ignore())
        .ForMember(x => x.IdentificationNumber, opt => opt.Ignore())
        .ForMember(x => x.EmailAddress, opt => opt.Ignore())
        .ForMember(x => x.PhoneNumber, opt => opt.Ignore());

    CreateMap<User, UserDto>()
            .ForMember(
                dest => dest.PhoneNumber,
                opt => opt.MapFrom(
                    src => Regex.Replace(src.PhoneNumber, "(\\d{3})\\d{4}(\\d{4})", "$1****$2")))

            .ForMember(
                dest => dest.IdentificationNumber,
                opt => opt.MapFrom(
                    src => Regex.Replace(src.IdentificationNumber, "(?<=\\w{3})\\w(?=\\w{4})", "*")))


            .ForMember(
                dest => dest.EmailAddress,
                opt => opt.MapFrom(
                    src => Regex.Replace(src.EmailAddress, "(^\\w)[^@]*(@.*$)", "$1****$2")));



    CreateMap<CreateUserDto, User>();
    CreateMap<CreateUserDto, User>().ForMember(x => x.Roles, opt => opt.Ignore());
}

页面中修改更新方式

在这里插入图片描述

【数据备份恢复】应提供重要数据的本地数据备份与恢复功能

解决方案

在本地定期备份配置数据、业务数据。根据实际业务需求定期对备份数据进行恢复测试,保存相关的恢复测试记录。

实施

项目按照
SQL server完全备份指南
进行备份作业,

由运维人员定期对业务数据进行异地备份,根据实际业务需求定期对备份数据进行恢复测试,并对恢复测试进行记录

hi,我是熵减,见字如面。

image

在软件开发中,你是否遇到过这种情况:

你正在开发一个新的软件,你已经完成了测试并发布了软件。然而,在用户开始使用软件之后,你开始接到了大量的错误报告。你发现用户遇到的问题并不是你测试过程中遇到的问题,这些问题可能是因为用户使用了不同的操作系统、浏览器或设备等原因。

这个案例说明了墨菲定律在软件开发中的应用,即任何可能出错的地方,最终都会出错。即使你进行了彻底的测试,但由于用户环境的复杂性,仍然可能会出现一些问题。

什么是墨菲定律

墨菲定律(Murphy's Law)是一种广为人知的经验法则,它指出:
“如果有什么事情可能出错,那么它就会出错”。

这个定律源于20世纪中期美国空军的一项研究,研究人员在一次试验中发现,一些随机事件总是发生在最不适当的时候。

墨菲定律已经被广泛应用于不同领域,包括科学、工程、经济、法律、管理等等。

在工程领域,墨菲定律通常用来提醒人们在设计和实施系统时要预见可能出现的问题,并采取相应的措施来防止或减少它们的发生。

墨菲定律的另一个常见表述是:
“如果有两种或多种方法做某事,那么总有一种方法是错误的”。

其源于著名软件工程师,弗雷德里克·布鲁克斯在其经典著作《人月神话》中的一句名言。

墨菲定律有效的3个原因

墨菲定律之所以在许多领域都得到了广泛的应用和认可,是因为它揭示了自然界中普遍存在的一些规律和现象。

在软件开发中,墨菲定律有效的原因主要有以下3点:

  • 复杂性
    :软件开发是一个极其复杂的过程,涉及到许多不同的环节和组成部分。即使是经验丰富的软件开发者也无法完全掌握和预见所有可能的问题和错误。因此,墨菲定律提醒我们要时刻保持警惕和谨慎。

  • 人为因素
    :软件开发中涉及到许多人为因素,如人员变动、沟通不畅、工作压力等等。这些因素都可能影响软件开发的质量和进度,从而导致问题和错误的发生。

  • 不确定性
    :在软件开发过程中,存在许多不确定性因素,如技术的变化、用户需求的变更、市场的变化等等。这些不确定性因素都可能对软件开发的质量和进度产生影响,从而导致问题和错误的发生。

基于以上的原因,墨菲定律在软件开发中得到了广泛的应用和认可,它提醒软件开发者要时刻保持警惕和谨慎,并采取相应的措施来减少问题和错误的发生。

对软件工程的提示

基于对墨菲定律的理解和作用机制,在我们的日常的软件工程中,可以带来以下的5点有效的启发或提示:

  • 认识复杂性
    :软件开发是一个极其复杂的过程,涉及到许多不同的环节和组成部分。因此,软件开发者要时刻保持警惕和谨慎,充分认识到复杂性带来的挑战和风险。

  • 强调质量控制
    :软件质量是软件开发中至关重要的一部分。软件开发者需要采用各种测试和质量控制措施,以确保软件的质量和稳定性,减少问题和错误的发生概率。

  • 倡导团队合作
    :软件开发是一个集体劳动,需要开发者之间的紧密合作和协作。通过开展团队合作和沟通,可以更好地利用各种资源和知识,从而提高软件开发的效率和质量。

  • 强调用户需求
    :软件开发的最终目的是满足用户需求。因此,软件开发者需要充分了解用户需求,并根据用户的反馈和需求进行持续改进和优化。

  • 加强自动化工具
    :软件开发中存在许多重复和繁琐的工作,例如测试和代码审查。通过采用自动化工具,可以大大减少开发者的工作量,提高工作效率和质量。

墨菲定律为软件工程提供了重要的启示和指导,帮助软件开发者更好地应对工程中挑战,提高软件质量和稳定性,最终实现用户的满意。

对墨菲定律的误解

在软件开发中,我们可能会对墨菲定律存在着一些误解,从而为软件工程带来更大或更多的问题。以下是5个比较常见的对墨菲定律的误解:

  • 将墨菲定律视为“不可避免的命运”。
    有些人可能认为墨菲定律是不可避免的,因此不值得花时间和精力去预防或纠正错误。这种想法是错误的,因为通过认真规划和有效措施,可以减少错误的发生概率,提高软件开发的效率和质量。

  • 认为所有问题都是人为造成的。
    尽管人为因素是软件开发中问题的一个重要来源,但是墨菲定律也提醒我们,有些问题可能是不可预测的,例如自然灾害或硬件故障等。因此,软件开发者需要充分认识到这些风险和挑战,并制定应对策略。

  • 忽视小问题。
    有些人可能会忽视一些看似微不足道的小问题,认为它们不会对整个软件系统产生影响。然而,这些小问题可能会逐渐累积,导致软件系统的稳定性和质量下降。

  • 认为技术是解决所有问题的答案。
    技术是软件开发中的一个重要组成部分,但并不是解决所有问题的唯一答案。软件开发还需要注重团队合作、质量控制、用户需求等方面。

  • 遵循“一切按计划进行”的信条。
    有些人可能会认为,只要严格按照计划执行,就可以避免墨菲定律的影响。然而,软件开发是一个复杂的过程,难以完全按照计划进行。软件开发者需要保持灵活性和适应性,及时调整计划,以适应变化和不可预测的情况。

对于软件开发者,或者软件工程团队来说,都需要认真对待和理解墨菲定律,同时尽可能的避免误解和误判。只有通过认真规划、有效措施、团队协作和灵活性,才能最大程度地减少墨菲定律的负面影响,提高软件开发的效率和质量。

写在最后

在软件工程中,墨菲定律的存在是不可完全避免的,但我们可以通过一些措施来避免其对我们造成的负面影响。

譬如,建立备份机制、制定全面的测试计划和质量保障措施、采取安全措施、重视用户反馈和需求、采用简洁可维护的技术方案等。通过这些措施,我们可以降低软件开发中的风险,提高系统的稳定性和质量,从而满足用户的需求和期望。

作为工程师和工程团队,我们应该始终谨记墨菲定律,从软件开发中的规划、测试、质量保障、安全和用户反馈等方面保持足够的谨慎态度,以确保我们的软件系统,能够成功地满足用户的需求和期望。

运营软件系统,
平常要多做准备,提升成功和稳定的概率,降低突发问题的影响范围。


阅读,思考,练习,分享,日日不断之功。

嗯,写完了。

新的一天,加油哦 (ง •̀_•́)ง