系统架构

socket.io多实例集群化实现

月盾

Socket.io作为服务器推送的首要选择,可以很方便的在客户端和Web服务器之间实现双向通信。很多人在选择之初都会有个疑问:socket.io是有状态长链接服务,它能支撑多少个用户同时在线?

虽然目前没有标准统一的答案,但是,在开发实践中已经证明,在单机情况下10万用户是没问题的。

如果您觉得10万还是不够,那么可以通过多实例的方式来支持更多的用户也是可行的。实现方式几乎是零成本升级。

您只需要将默认的适配器更换为redis或其他适配器就可以支持多实例:

一个基于epxress和socket.io的多实例服务配置:

#!/usr/bin/env node

const app = require('../app');
const debug = require('debug')('ws_server:www');
const { createServer } = require('http');
const { Server } = require('socket.io');
const { createAdapter } = require("@socket.io/redis-adapter");
const { Redis } = require("ioredis");

const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

const httpServer = createServer(app);

/**
 * Normalize a port into a number, string, or false.
 */
function normalizePort(val) {
	var port = parseInt(val, 10);
	if (isNaN(port)) {
		// named pipe
		return val;
	}
	if (port >= 0) {
		// port number
		return port;
	}
	return false;
}

/**
 * Event listener for HTTP server "error" event.
 */
function onError(error) {
	if (error.syscall !== 'listen') {
		throw error;
	}

	var bind = typeof port === 'string'
		? 'Pipe ' + port
		: 'Port ' + port;

	// handle specific listen errors with friendly messages
	switch (error.code) {
		case 'EACCES':
			console.error(bind + ' requires elevated privileges');
			process.exit(1);
			break;
		case 'EADDRINUSE':
			console.error(bind + ' is already in use');
			process.exit(1);
			break;
		default:
			throw error;
	}
}

/**
 * Event listener for HTTP server "listening" event.
 */
function onListening() {
	var addr = httpServer.address();
	var bind = typeof addr === 'string'
		? 'pipe ' + addr
		: 'port ' + addr.port;
	console.log('Listening on ' + bind);
}
// 重点部分
const pubClient = new Redis({
	host: process.env.REDIS_HOST,
	port: process.env.REDIS_PORT,
	password: process.env.REDIS_PASSWORD,
});
const subClient = pubClient.duplicate();

const io = new Server(httpServer, {
	adapter: createAdapter(pubClient, subClient),
	cors: {
		origin: ["https://admin.socket.io", "http://127.0.0.1:8080"],
		// allowedHeaders: ["Authorization", "Cookie"],
		credentials: true
	}
});

app.set("IO", io);

httpServer.listen(port);
httpServer.on('error', onError);
httpServer.on('listening', onListening);

经过上面的改动后,所有的socket.io服务器实例可以共享一个redis实例来存储连接的状态信息。

gitlab-ci使用Harbor机器人账户

月盾

gitlab ci/cd 中,需要使用到 docker registry 账号 push 镜像,这个可以设置为机器人,可以避免个人账号泄露

配置Harbor机器人

  • 登录 Harbor 管理界面,进入系统管理→认证页面;
  • 选择机器人 Token页签,点击左上角的新建机器人,创建一个名为gitlab-ci的机器人;
  • 给该机器人一个名称,并给该机器人添加相应的权限;
  • 机器人创建完成以后,点击右侧的查看token来复制该机器人的 token,这个就是以后用 gitlab-ci 配置文件使用的账号密码;
  • 在gitlab 创建 harbor-robot 用户;
  • 为该用户添加项目级别的 开发 权限;

配置gitlab-ci

  • 在项目的设置中添加 docker registry 账号密码,仓库地址是harbor的;
  • 编辑.gitlab-ci.yml文件,把账号密码改为之前复制的 token;

注意点

有些情况下创建的机器人账户会带上$符号,在gitlab中是无法使用的,需要将$改为两个$$来表示$

结束

这样gitlab-ci 就可以顺利跑测试、构建、推送镜像了;

以上内容,只是简单的配了配 harbor,可以有很多其他方法完成该工作,这里只是简单的记录下而已;

如果需要复杂的设置,欢迎移步harbor 文档,那里有更多的内容可以提供参考。

kubernetes部署mysql8

月盾

虽然很多文章说不建议将数据库部署在容器中,因为有性能问题。但我觉得这事还是要看具体使用场景来决定,而不是全盘否定。开发的过程中并不只有性能是最重要的,还有效率,易用性等也很重要。

对于测试环境来说,数据库部署在容器中肯定是可以的。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mysql
  name: mysql
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: mysql
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
      labels:
        app: mysql
    spec:
      containers:
      - env:
        - name: MYSQL_ROOT_PASSWORD
          value: xxx
        image: mysql:8.0.33
        imagePullPolicy: IfNotPresent
        name: mysql
        ports:
        - containerPort: 3306
          name: mysql
          protocol: TCP
        resources:
          limits:
            cpu: "4"
            memory: 8Gi
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: data
        - mountPath: /etc/mysql/conf.d/
          name: mysql-config
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: mysql
      - configMap:
          defaultMode: 420
          name: mysql-config
        name: mysql-config

---

apiVersion: v1
kind: ConfigMap
data:
  my.cnf: |-
    [client]
    default-character-set = utf8mb4
    [mysql]
    default-character-set=utf8mb4
    [mysqld]
    character-set-server=utf8mb4
    collation_server=utf8mb4_unicode_ci
    gtid_mode=ON
    enforce_gtid_consistency=ON
    lower_case_table_names=1
    open_files_limit=65535
    slow_query_log = ON
    long_query_time = 2
    max_heap_table_size = 32M
    tmp_table_size = 2M
    log-bin = mysql-bin
    binlog_cache_size = 32K
    max_binlog_cache_size = 1G
    max_binlog_size = 1G
    binlog-format = ROW
    sync_binlog = 1
    log-slave-updates = 1
    expire_logs_days = 7
    default-time_zone = +8:00
    max_connect_errors = 65535
    max_allowed_packet = 536870912
    max_connections = 5120
    innodb_buffer_pool_instances = 1
    innodb_buffer_pool_size = 6442450944
    innodb_temp_data_file_path = ibtmp1:12M:autoextend:max:15G
    server_id = 100
metadata:
  name: mysql-config

常用SQL审计平台介绍

月盾

数据安全一直是企业安全的重中之重,在企业系统开发过程中,数据库的安全日益增强,所以需要有个平台能够记录和审查数据库变更。

SQL 审计平台的功能特点:

  • 实时监控和记录:SQL 审计平台能够实时监听数据库操作,记录所有的 SQL 查询和事务操作,确保数据变更的可追溯性。
  • 用户行为分析:通过分析用户的查询行为和访问模式,SQL 审计平台可以识别风险操作、异常查询和异常登录行为,帮助提前发现潜在的安全威胁。
  • 合规报告与警报:SQL 审计平台能够生成详尽的合规报告,满足合规要求,并根据预设的规则触发警报,帮助管理员及时采取应对措施。
  • 数据完整性保护:通过审计日志记录的数据变更信息,SQL 审计平台能够帮助恢复受损的数据或进行调查取证,确保数据库的完整性和可靠性。
  • 权限管理与访问控制:SQL 审计平台提供丰富的权限管理功能,可以对用户进行细粒度的访问控制,确保只有经过授权的用户能够访问敏感数据。

常见的sql审计平台:

  • Yearning
  • Archery
  • bytebase

Yearning

官网:https://yearning.io

开发语言:golang

功能:

  • SQL 查询
    • 查询工单
    • 导出
    • 自动补全,智能提示
    • 查询语句审计
    • 查询结果脱敏
  • SQL 审核
    • 流程化工单
    • SQL语句语法检测
    • 根据规则检测SQL语句合规性
    • 自动生成DDL/DML回滚语句
    • 历史审核记录
  • 推送
    • E-mail 工单推送
    • 钉钉 webhook 机器人工单推送
  • 用户权限及管理
    • 角色划分
    • 基于用户的细粒度权限
    • 注册
  • 其他
    • todoList
    • LDAP 登录
    • 动态审核规则配置
    • 自定义审核层级
  • AutoTask 自动执行

部署

支持容器部署。

Archery

官网:https://archerydms.com/

开发语言:python

功能:

查询 审核 执行 备份 数据字典 慢日志 会话管理 账号管理 参数管理 数据归档
MySQL
MsSQL × × × × × × ×
Redis × × × × × × ×
PgSQL × × × × × × ×
Oracle × × × × ×
MongoDB × × × × × ×
Phoenix × × × × × × ×
ODPS × × × × × × × ×
ClickHouse × × × × × × × ×

SQL审核

MySQL实例

基于Inception/goInception实现,集成审核、执行、备份

微信开发基础设施准备

微信开发基础设施准备

月盾

多年前微信开发写过一篇关于内网穿透的文章《微信开发通过公网访问本地服务器》,随着时间的推移,有些方法已经不再适用。

本地服务器地址

前面提到的公网访问本地服务器还是有一些限制,那时候是基于路由器的内网映射实现的,不是所有路由器都具备内网映射功能,就像我现在使用的路由器是移动宽带送的,外面看着挺有内涵,实际功能少的可怜。还是需要通过其他方式实现内网穿透。

不可用的方式——ngrok

最开始想通过ngrok来实现,结果发现怎么都无法通过token验证,原因是ngrok会在首次打开时展现广告页面。

可用的方式——借助花生壳来实现

花生壳域名.png

如果通过客户端无法选择htt和http协议,提示:“不支持web访问方式,如有需要请使用HTTP或HTTPS”,就去web页面配置即可 -> https://console.hsk.oray.com/forward

服务器配置

微信服务器配置.png

此处的服务器地址(URL) 不止用于验证token,如果在文档中看到这样的字样,就是指这个地址,比如它还可以用于接收各种消息。这个地址是生产服务器地址,如果是开发阶段也不用将开发服务器地址配置到这里,因为微信提供了专门用于测试的配置,往下看。

测试号管理

测试号管理点开后就会看到和上面服务器配置一样的配置内容。

微信测试号管理.png

测试账号具备所有API权限,所以你不用为了测试接口而花300元认证,而且认证也不对个人开放。

接口访问白名单

设置路径:设置与开发 -> 安全中心 -> IP白名单

这个IP是通过ping内网穿透得到的IP,不是你电脑的内网IP,也不是你的电脑公网IP。有可能会变如果接口突然访问不同请检测是否IP已变动。

有了以上准备基本就可以正式进入开发阶段了。祝一切顺利!

https证书生成

月盾

生成crt证书

openssl req -new -x509 -key domain.key -out domain.crt -days 365

将CRT转换为CSR:

openssl req -new -key domain.key -out domain.csr

事件驱动编程、消息驱动编程、数据驱动编程

月盾

事件驱动

事件驱动机制就是:
让驴拉磨,它不拉,你用鞭抽一下,它就开始拉了。然后又停了,你再抽一下,它又继续拉了
这叫用“鞭”驱动“驴”拉磨

在程序里,程序停止在那不动,你点击一个按钮,它就有反应了,过一会,又没反应了,你再点一下,它又继续运行。
这叫用“事件”驱动“程序”运行

0. 基本概念

窗口/组件

事件

消息(队列)

事件响应(服务处理程序)

调度算法

进程/线程

非阻塞I/O

程序的执行可以看成对CPU,内存,IO资源一次占用

现代操作系统支持多任务,可以分时复用上述资源.

1. 为什么采用事件驱动模型?

事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点:

首先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方);

当目标发送改变(发布),观察者(订阅者)就可以接收到改变;

观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的),目标无需干涉;所以就松散耦合了它们之间的关系。

2. 代码执行流程

在传统的或“过程化”的应用程序中,应用程序自身控制了执行哪一部分代码和按何种顺序执行代码。从第一行代码执行程序并按应用程序中预定的路径执行,必要时调用过程。 在事件驱动的应用程序中,代码不是按照预定的路径执行-而是在响应不同的事件时执行不同的代码片段。事件可以由用户操作触发、也可以由来自操作系统或其它应用程序调度算法的消息触发、甚至由应用程序本身的消息触发。这些事件的顺序决定了代码执行的顺序,因此应用程序每次运行时所经过的代码的路径都是不同的。

3. 事件驱动模型

在UI编程中,常常要对鼠标点击进行相应,首先如何获得鼠标点击呢?

方式一:创建一个线程,该线程一直循环检测是否有鼠标点击,那么这个方式有以下几个缺点:

CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢?

如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘;

如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;所以,该方式是非常不好的。

方式二:就是事件驱动模型目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:

有一个事件(消息)队列;

鼠标按下时,往这个队列中增加一个点击事件(消息);

有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;

事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;如图:

4. 事件驱动处理库

select

poll

epoll

libev

消息驱动

事件驱动机制跟消息驱动机制相比

消息驱动和事件驱动很类似,都是先有一个事件,然后产生一个相应的消息,再把消息放入消息队列,由需要的项目获取。他们的区别是消息是谁产生的

消息驱动:鼠标管自己点击不需要和系统有过多的交互,消息由系统(第三方)循环检测,来捕获并放入消息队列。消息对于点击事件来说是被动产生的,高内聚。

事件驱动:鼠标点击产生点击事件后要向系统发送消息“我点击了”的消息,消息是主动产生的。再发送到消息队列中。

事件:按下鼠标,按下键盘,按下游戏手柄,将U盘插入USB接口,都将产生事件。比如说按下鼠标左键,将产生鼠标左键被按下的事件。

消息:当鼠标被按下,产生了鼠标按下事件,windows侦测到这一事件的发生,随即发出鼠标被按下的消息到消息队列中,这消息附带了一系列相关的事件信息,比如鼠标哪个键被按了,在哪个窗口被按的,按下点的坐标是多少?如此等等。

  1. 要理解事件驱动和程序,就需要与非事件驱动的程序进行比较。实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的。早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,会不断地检查这个条件,直到条件满足,这是很浪费cpu时间的。而事件驱动的程序,则有机会释放cpu从而进入睡眠态(注意是有机会,当然程序也可自行决定不释放cpu),当事件触发时被操作系统唤醒,这样就能更加有效地使用cpu.
  2. 再说什么是事件驱动的程序。一个典型的事件驱动的程序,就是一个死循环,并以一个线程的形式存在,这个死循环包括两个部分,第一个部分是按照一定的条件接收并选择一个要处理的事件,第二个部分就是事件的处理过程。程序的执行过程就是选择事件和处理事件,而当没有任何事件触发时,程序会因查询事件队列失败而进入睡眠状态,从而释放cpu。
  3. 事件驱动的程序,必定会直接或者间接拥有一个事件队列,用于存储未能及时处理的事件。
  4. 事件驱动的程序的行为,完全受外部输入的事件控制,所以,事件驱动的系统中,存在大量这种程序,并以事件作为主要的通信方式。
  5. 事件驱动的程序,还有一个最大的好处,就是可以按照一定的顺序处理队列中的事件,而这个顺序则是由事件的触发顺序决定的,这一特性往往被用于保证某些过程的原子化。
  6. 目前windows,linux,nucleus,vxworks都是事件驱动的,只有一些单片机可能是非事件驱动的。

事件模式耦合高,同模块内好用;消息模式耦合低,跨模块好用。事件模式集成其它语言比较繁琐,消息模式集成其他语言比较轻松。事件是侵入式设计,霸占你的主循环;消息是非侵入式设计,将主循环该怎样设计的自由留给用户。如果你在设计一个东西举棋不定,那么你可以参考win32的GetMessage,本身就是一个藕合度极低的接口,又足够自由,接口任何语言都很方便,具体应用场景再在其基础上封装成事件并不是难事,接口耦合较低,即便哪天事件框架调整,修改外层即可,不会伤经动骨。而如果直接实现成事件,那就完全反过来了。

什么是数据驱动编程

最近在学习《Unix编程艺术》。以前粗略的翻过,以为是介绍unix工具的。现在认真的看了下,原来是介绍设计原则的。它的核心就是第一章介绍的unix的哲学以及17个设计原则,而后面的内容就是围绕它来展开的。以前说过,要学习适合自己的资料,而判断是否适合的一个方法就是看你是否能够读得下去。我对这本书有一种相见恨晚的感觉。推荐有4~6年工作经验的朋友可以读一下。

正题

作者在介绍Unix设计原则时,其中有一条为“表示原则:把知识叠入数据以求逻辑质朴而健壮”。结合之前自己的一些经验,我对这个原则很有共鸣,所以先学习了数据驱动编程相关的内容,这里和大家分享出来和大家一起讨论。

数据驱动编程的核心

数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长于处理数据。数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。

gitpage Hugo统计每一篇文章浏览量

月盾

接上篇《博客迁移至hugo gitpage》后,因为缺失了每一篇文章的浏览量,而hugo又不具备这样的功能,原因还是gitpage不具备数据存储能力,自然就没办法统计每一篇文章的浏览量了。原本想使用自己服务器提供一个接口来记录,但发现https协议不支持调用http协议的接口,会出现block:mixed-content错误。

错误:https页面去发送http请求报错(浏览器阻止https发送http请求)

问题是明确了,但是我也没办法提供https的接口,免费的证书也用在了www.yuedun.wang上了。

后来想到了leancloud,直接在前端调用api,将数据存储在云端。

<script src="//cdn.jsdelivr.net/npm/leancloud-storage@4.11.1/dist/av-min.js"></script>
<script>
  // https://leancloud.cn/docs/sdk_setup-js.html#hash14962003 
  // https://leancloud.cn/docs/leanstorage_guide-js.html#hash813593086
  const appId = "xxx";
  const appKey = "xxx";
  const serverURL = "xxx";
  AV.init({ appId, appKey, serverURL });
  function updateCollect() {
    const collect = new AV.Query('Collect');
    const url = location.pathname;
    collect.select(['url', 'pv'])
    collect.equalTo('url', url);
    collect.first().then((col) => {
      if (!col) {
        // 声明 class
        const Collect = AV.Object.extend('Collect');
        // 构建对象
        const collect = new Collect();
        // 为属性赋值
        collect.set('url', url);
        collect.set('pv', 1);
        collect.save()
      } else {
        col.increment('pv', 1)
        col.save();

        // 元素不存在的情况
        try {
          const context = document.querySelector('.post__meta.meta');
          const pv = document.createElement('div');
          pv.setAttribute('class', 'meta__item-author meta__item');
          const span = document.createElement('span');
          span.setAttribute('class', 'meta__text');
          span.innerText = "浏览(" + col.get('pv') + ")";
          pv.appendChild(span)
          context.appendChild(pv)
        } catch (error) {
          console.error(error);
        }
      }
    });
  }

  updateCollect()
</script>

因为hugo-dpsg主题没有浏览量字段,所以通过js在对应的位置插入浏览量字段即可。

博客迁移至hugo gitpage

月盾

为什么迁移?

6月初的时候,3年前买的阿里云服务器到期了,又买了其他服务器,但是只有1年期,于是进行了一番数据,应用迁移,这么一顿操作下来还是挺累的,尤其是在linux上安装mysql,mongodb,安装后又是连接不上,用了好几天时间才搞定,挺烦的。
再想到一年后又是一顿操作,不由得一个激灵。

经过一番的思量后,决定将自建博客迁移至gitpage上。

自建和gitpage优劣对比

首先列出自己博客具备的功能:

  • 首页 包含最近10篇文章的标题和部分内容,最近发表的5篇,分类,标签,友链
  • 目录 所有文章时间线
  • 留言 自维护留言系统
  • 微博 新浪微博,最近出问题不显示了
  • 速记 简单记录
  • 关于 自我介绍

整体比较简单,没什么复杂功能。而很多静态博客也具备这些功能,而且做的更好,所以基本不会有主要功能的缺失。所以迁移到gitpage上也不会有什么问题。

劣势

静态博客在一些细节功能上面确实会有缺失,比如:

  • 评论功能,数据原来是存在自己服务器上的,gitpage不具备数据存储能力,所以需要对接第三方评论或留言功能。
  • 目录缺失,这点主要由博客主题决定,有些主题是没有目录功能的。
  • PV/UV统计,同样是由于数据存储的缺失,所以gitpage也没有这样的统计功能,但是可以添加谷歌,百度统计之类的。
  • 自定义功能较弱,只能使用主题提供的页面模式,除非不使用静态博客工具,完全自己开发。
  • 不能在线编辑。

优势

  • 免费,没有服务器费用。
  • 可以自定义域名,只需要一丁点儿的域名费用。
  • seo良好。
  • 规范的书写格式,使文章内容更统一美观。
  • 更强大的编辑器,可以选择自己喜欢的markdown编辑器。
  • 便于本地检索。
  • 数据更保险,不易丢失。
  • 安全,静态博客可以避免一些网络攻击。

整体来说,除了数据存储功能缺失外,其他都是可以实现的。

迁移过程

将mongodb数据中的博客导出为本地markdown文件。

// mongodb数据转markdown
function genMd() {
	return Blog.find({ status: 1 }, null, { sort: { '_id': -1 } })
		.then(data => {
			data.forEach((b) => {
				debug(b)
				let tags = b.tags.split(",")
				let blog = `---
title: "${b.title}"
description: "${b.content.replace(/<[^<>]+>/g, "").replace(/\r\n/g, "").replace(/\n/g, "").substr(0, 80)}"
date: ${b.updatedAt.toISOString()}
draft: false
authorbox: false

categories:
  - "${b.category}"
tags:
  - "${tags[0]}"
  - "${tags[1]}"
  - "${tags[2]}"
  - "${tags[3]}"

---
${b.content}
                    `
				fs.writeFileSync(`f:/workspace/yuedun\.wang/content/blogdetail/${b.id}.md`, blog, { flag: 'a+' })
				// debug(b.createdAt.toISOString())
			})
			return "data";
		});
}

ok!完成。

让web项目不再502

月盾

前后端分离

近几年比较流行的web项目开发架构是前后端分离,前后端分离架构在系统稳定性方面非常有优势,其中一点优势主要体现在用户感知上,即使服务端发生错误也不会展现在视图层,一般情况下用户是可以继续浏览网页,不会很突兀的显示这样的信息: 502 bad gateway

502 bad gateway

在接口发生错误时虽然可能会获取不到一些数据,但是在用户体验上比直接显示502错误要好。

部署也相对安全和方便。

前后端分离架构虽好,但不是”银弹”,不是所有网站都能使用前后端分离架构来做。

服务端渲染

至于分不分离都有诸多的优缺点,可根据实际场景选择,本文要说的是不分离情况下文章开始所提到的问题——难看的502,500错误。

本文我将使用“服务端渲染”来代替不分离的架构。

由于服务端渲染的所有数据处理都在服务端进行,发生任何错误都可能引起500错误,这种错误会直接体现在视图层,用户看到这样的信息很不友好。 500 Internal Server Error

500 Internal Server Error

遇到这种错误,可以使用适当的措施来弥补,带来的效果却非常良好。开发web最多遇到的错误码有:404,500,502。

404错误很多网站都有默认页面,像这样的:

404 not found

但是500,502错误却鲜有默认页面,我们何不也搞一个呢?

500错误

首先说一下500错误的应对措施,500发生在服务端,比如空指针,数组溢出,超时等都会引起web服务500错误,一般的500错误在应用层面是可以捕捉到的,在response返回之前捕获异常跳转到一个”合适的页面”,这样就可以避免出现500 Internal Server Error信息。虽然这个“合适的页面”不是本应该跳转的页面,但起码还是个正常页面。

502错误

再说一下502错误,发生502错误一般是应用直接宕机了,任何页面都是访问不到的,这样是无法在应用层面监听和处理的,但也不是没办法,我们可以在NGINX上处理,当NGINX发生502时跳转到”合适的页面”。这个页面需要是一个静态页,可以是友好的提示信息,可以是网站首页,根据需求自己定制即可。

server{
    error_page  502  /502.html;
        location = /502.html {
                root   /usr/share/nginx/html;
        }
}

以上两种错误处理虽然不能应对所有网站,比如一些个性化网站,即使掩饰的再好,用户也知道出问题了,但是在有些网站却能起到良好的作用,比如有些web站点发布时会中断服务出现502,比起光秃秃的500和502错误,上面的处理是不是就好多了。