Javascript类型与类型判断

JS类型与类型判断是JS中的基础,有必要归纳总结整理一下。

对于这样一篇文章实际上是个人资料库里的一个整理文档,一直在想这么一篇烂大街的文章分享出来是不是有水文的嫌疑?但是,转过头一想,如果大家把文章中的外链都翻过一遍,那就会觉得:一个不起眼的小点也有它的价值。

JS类型

JS共有8种类型,如下表所示

7种基本类型

  • Boolean
  • Null
  • Undefined
  • Number
  • BigInt
  • String
  • Symbol

基本类型(基本数值、基本数据类型)是一种既非对象也无方法的数据。
所有基本类型的值都是不可改变的。但需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。

剩余一种

  • Object

除 Object 以外的所有类型都是不可变的(值本身无法被改变)

JavaScript 数据类型和数据结构:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures

类型判断

基本类型判断

  • Boolean

    1
    typeof true // boolean
  • Number

    1
    typeof 1 // number
  • String

    1
    typeof '1' // string
  • Undefined

    1
    2
    typeof undefined // undefined
    undefined === undefined // true
  • Null

    1
    2
    typeof null // object
    null === null // true

    为什么typeof null为Object呢?答案如下:
    在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 “object”。(参考来源)
    曾有一个 ECMAScript 的修复提案(通过选择性加入的方式),但被拒绝了。该提案会导致 typeof null === ‘null’。

    Why is typeof null “object”? : https://stackoverflow.com/questions/18808226/why-is-typeof-null-object
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/typeof#null

  • BigInt
    可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数BigInt()。

    1
    2
    3
    4
    typeof 10n === 'bigint'; // true
    10n == 10 // true
    10n === 10 // false
    typeof BigInt('1') === 'bigint'; // true

    BigInt: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt

  • Symbol

    1
    typeof Symbol() // symbol

由上可以看出,基本类型除了null之外,都可以使用typeof判断出来具体类型.

Why is typeof null “object”? : https://stackoverflow.com/questions/18808226/why-is-typeof-null-object

Object类型判断

  • Array

    1
    Array.isArray([1,2]) // true
  • Function

    1
    typeof (()=>{}) // function

对于像Date、Math等对象,就没有很好的直接判断方式了,但是可以通过Object.prototype.toString.call(obj)来判断

1
2
3
4
5
6
7
8
9
10
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call(''); // [object String]
Object.prototype.toString.call(new Date()); // [object Date]
Object.prototype.toString.call(1); // [object Number]
Object.prototype.toString.call(function () {}); // [object Function]
Object.prototype.toString.call(/test/i); // [object RegExp]
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(); // [object Undefined]

Understanding JavaScript types and reliable type checking.
https://ultimatecourses.com/blog/understanding-javascript-types-and-reliable-type-checking

类型判断工具类

通过上面的总结分析,可以考虑封装一个类型判断的工具类,下面是感觉最简洁高效的一个实现:

1
2
3
4
5
6
7
8
9
10
11
12
var type = (function(global) {
var cache = {};
return function(obj) {
var key;
return obj === null ? 'null' // null
: obj === global ? 'global' // window in browser or global in nodejs
: (key = typeof obj) !== 'object' ? key // basic: string, boolean, number, undefined, function
: obj.nodeType ? 'object' // DOM element
: cache[key = ({}).toString.call(obj)] // cached. date, regexp, error, object, array, math
|| (cache[key] = key.slice(8, -1).toLowerCase()); // get XXXX from [object XXXX], and cache it
};
}(this));

这样使用

1
2
3
4
type(function(){}); // -> "function"
type([1, 2, 3]); // -> "array"
type(new Date()); // -> "date"
type({}); // -> "object"

The most accurate way to check JS object’s type? https://stackoverflow.com/questions/7893776/the-most-accurate-way-to-check-js-objects-type

其它

  • NaN
    注意NaN不是一种数据类型。
    NaN是一个全局对象的属性,它表示不是一个数字(Not-A-Number)。

NaN有如下特性:

1
2
3
4
5
typeof NaN // number
NaN === NaN // false
isNaN(NaN) // true,可以通过此种方式来判断NaN
var num = Number('num') // 此时num为NaN
num === num // flase

NaN: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/NaN
Why does typeof NaN return ‘number’? :https://stackoverflow.com/questions/2801601/why-does-typeof-nan-return-number

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

Elasticsearch索引自动化管理

背景

前端性能监控的日志之前为单一索引,随着日志内容的不断增多,索引文件变得越来越多大(官方建议单个索引文件不要超过20G)。

在此种方案下只能定时通过delete query的方式删除xxx天之前的数据,此种方式删除数据时异常缓慢,而且磁盘空间不会立即释放。

亟需采取新的索引方案解决该问题,比如按天生成索引,定时删除一个月之前的索引文件,直接删除索引文件的效率会高不少。

索引创建

索引模板

索引模板是为了方便按天去生成相同配置的索引文件,样例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 创建索引模板
PUT _template/jz-fe-performance-log-template
{
"index_patterns" : ["jz-fe-performance-log-*"],
"settings" : {
"index" : {
"lifecycle" : {
"name" : "jz-fe-log-15days"
}
}
},
"mappings": {
"dynamic": "strict",
"properties":{
"groupName":{"type":"keyword"},
"projectName":{"type":"keyword"},
"href":{"type":"keyword"},
"clientDate":{"type":"date","format":"yyyy-MM-dd HH:mm:ss"},
"serverDate":{"type":"date","format":"yyyy-MM-dd HH:mm:ss"},
"appId":{"type":"keyword"},
"hmsr":{"type":"keyword"},
"znsr":{"type":"keyword"},
"hmpl":{"type":"keyword"},
"unloadTime":{"type":"integer"},
"redirectTime":{"type":"integer"},
"appCacheTime":{"type":"integer"},
"dnsTime":{"type":"integer"},
"tcpTime":{"type":"integer"},
"requestTime":{"type":"integer"},
"responseTime":{"type":"integer"},
"analysisTime":{"type":"integer"},
"loadEventTime":{"type":"integer"},
"connectTime":{"type":"integer"},
"resourceTime":{"type":"integer"},
"domReadyTime":{"type":"integer"},
"TTFBTime":{"type":"integer"},
"TTSRTime":{"type":"integer"},
"TTDCTime":{"type":"integer"},
"TTFLTime":{"type":"integer"},
"FMPTime":{"type":"integer"},
"uid":{"type":"keyword"},
"phone":{"type":"keyword"},
"platform":{"type":"keyword"},
"os":{"type":"keyword"},
"browser":{"type":"keyword"},
"version":{"type":"keyword"},
"userAgent":{"type":"text"},
"ip":{"type":"keyword"},
"networkType":{"type":"keyword"},
"ISP":{"type":"keyword"},
"region":{"type":"keyword"}
}
}
}

说明:

  • PUT _template/jz-fe-performance-log-template创建名称为jz-fe-performance-log-template的索引模板
  • index_patterns中的jz-fe-performance-log-*代表索引名称为jz-fe-performance-log-的索引都按这个模板的配置去生成
  • settings中的lifecycle项注明以哪一个lifecycle配置来管理该索引,在后续的索引删除部分会使用到
  • mappings就是所有文档的配置了,strict表面为严格模式,索引数据只能为下面声明的字段名称,否则无法保存

数据保存

在数据调用Node服务接口时,做如下处理,即可按天把日志保存到当天的日志文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

const { Client } = require('@elastic/elasticsearch')

// 性能数据索引
const PERFORMANCE_PROD_INDEX = 'jz-fe-performance-log'
const PERFORMANCE_TEST_INDEX = 'jz-fe-performance-log-test'

/**
* ES数据插入操作
* @param index 索引
* @param data 数据
* @returns {Promise<ApiResponse<any>>}
*/
async function base ({ index, data }) {
const res = await client.index({
index: index,
body: data
}).catch(err=>{
console.error('err', JSON.stringify(err))
})
return res
}

/**
* 日期格式化
* 返回 2020-6-5类型数据
* @param {日期} date
*/
function dateFormat (date) {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
return year + '-' + month + '-' + day
}

/**
* 性能数据入库
* @param body
* @returns {Promise<ApiResponse<any>|TResult>}
*/
async function performanceAdd (data) {
const index = tools.isTestENV() ? PERFORMANCE_TEST_INDEX : `${PERFORMANCE_PROD_INDEX}-${dateFormat(new Date())}`
const res = await base({ index: index, data })
return res
}

说明:

  • 索引名称以jz-fe-performance-log打头的索引文件没有时都为以上面的模板新创建
  • 性能数据保存时,指定保存到当天的索引文件中

索引删除

实现了索引文件按天拆分之后,下一步就需要考虑如何把索引文件管理起来。

日志数据一般只保留一个月,这个时候可以考虑可以写一个程序定时去删除一个月之前的索引。

Elasticsearch 6.6开始提供了一个叫Index Lifecycle Management的功能来管理日志。

ILM配置

可以通过kibana可视化的做配置,也可以通过写ES语句的方式创建

点击Create policy即可创建对应配置

可通过如下方式查看刚才创建的具体配置信息

1
2
# 查看Index Lifecycle Manegment配置
GET /_ilm/policy/jz-fe-log-15days

返回结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"jz-fe-log-15days" : {
"version" : 1,
"modified_date" : "2020-06-04T10:02:16.233Z",
"policy" : {
"phases" : {
"hot" : {
"min_age" : "0ms",
"actions" : {
"set_priority" : {
"priority" : 100
}
}
},
"delete" : {
"min_age" : "15d",
"actions" : {
"delete" : { }
}
}
}
}
}
}

说明:

  • 索引要被哪个ILM管理是在索引的settingslifecycle处指定的
  • ES默认10分钟执行一次检查,如果对应索引满足创建时间大于15天,则删除索引

其他操作

下面是测试验证该功能会用到的相关ES语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# =========索引模板相关===========

# 创建索引模板
PUT _template/jz-fe-test-template
{
"index_patterns" : ["jz-fe-test-*"],
"settings" : {
"index" : {
"lifecycle" : {
"name" : "jz-fe-log-30s"
}
}
},
"mappings": {
"dynamic": "strict",
"properties":{
"gitGroup":{"type":"keyword"},
"projectName":{"type":"keyword"}
}
}
}


# 查看指定template信息
GET /_template/jz-fe-test-template
# 查看指定前缀template信息
GET /_template/jz-fe-*

# 删除索引模板
DELETE /_template/jz-fe-test-template

# 增加数据
POST /jz-fe-test-2020-06-03/_doc
{
"gitGroup":"OS X",
"projectName":"iPhone2"
}

# 查询数据
GET jz-fe-test-2020-06-*/_search

# 查看索引详情
GET /_cat/indices/jz-fe-test*?v&s=index

# 删除测试索引
DELETE /jz-fe-test-2020-06-*

# =========Index Lifecyce Management===========

# 设置10秒刷新1次(即定时器间隔),生产环境10分种刷新一次
# 可设置索引的保留时间为30s,每10s判断一次是否满足条件
PUT _cluster/settings
{
"persistent": {
"indices.lifecycle.poll_interval":"10s"
}
}

# 查看设置
GET _cluster/settings

# 查看Index Lifecycle Manegment配置
GET /_ilm/policy/jz-fe-log-15days

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

两个“跑路”级别BUG复盘

自动登录越权

背景

小程序原生跳转到webview的H5页面时,需要打通用户登录;此时需要在跳转的URL上带上临时token,通过临时token换取登录cookie。

实现的方案为通过Node服务的拦截器,自动拦截小程序环境中的H5页面请求,自动设置上cookie。

问题&原因

自动登录相关的逻辑封装在AutoLogin类中,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 自动登录相关
*/
class AutoLogin {
constructor () {
this.ctx = null
}

init (ctx) {
this.ctx = ctx
return this
}

/**
* 触发条件
* @returns {boolean}
*/
trackRule () {

}

/**
* 自动登录逻辑
* @param res
*/
async autoLogin () {

}

/**
* 自动退出登录逻辑
* 备注:当实现小程序跳H5自动登录后,此时小程序退出登录,需同时保证H5页面也退出登录
*/
async autoLogout () {

}
}
}

module.exports = new AutoLogin()

核心原因在于this.ctx = ctx这一行,这行代码意味着在内存中持有了koa的ctx对象,当并发场景,会造成第一个请求链路还未完结时,ctx被替换为第二个请求的上下文对象了,从而造成header中的cookie信息不对,权限错乱。

类型异常

问题&原因

问题代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object.entries(config.proxy).map(([path, target]) => {
let { url, login, intercept, extParams } = this.getProxyParams(target)
this.addRouter({
routerPath: `${routerRoot}${path}`,
target: url,
callback: async ctx => {
// 支持extParams为function类型,传入ctx方便从request中获取数据
extParams = Object.assign({}, typeof (extParams) === 'function' ? extParams(ctx) : extParams)
// 登录校验
if (login) {

}
// 接口透传
await proxy.launch({ url, reqParams }, ctx)
}
})
})

问题在于这一行代码extParams = Object.assign({}, typeof (extParams) === 'function' ? extParams(ctx) : extParams)

第一次请求的时候extParams为function类型,正常运行;第二次进来由于extParams已经变为了Object类型了,所以这段代码当第二个人请求进来时,永远都是运行的false逻辑,也就是第一个人ctx中的内容。

最后

  • 在Node服务中,切忌保存上下文对象,尽量不要持有跟用户相关的非全局信息
  • 缺少TS的场景下,注意JS的类型

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

记一次Node线上服务迁移

原有的Node服务,需要迁移到新的主机上,故需要准备一套新的环境,以及对应的迁移方案。

Node安装

手动安装

下载地址: https://nodejs.org/en/download/
选择:Linux Binaries (x64) 右键复制下载链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 下载到机器上,放在/opt目录
[work@40-14-22 opt]$ wget https://nodejs.org/dist/v12.16.2/node-v12.16.2-linux-x64.tar.xz
# 解压
[work@40-14-22 opt]$ tar -xvf node-v12.16.2-linux-x64.tar.xz
# 重命名
[work@40-14-22 opt]$ mv node-v12.16.2-linux-x64 nodejs

# 添加软链让node、npm命令能够全局访问(需要 ROOT权限)
[root@40-14-22 opt]# ln -s /opt/nodejs/bin/node /usr/local/bin/
[root@40-14-22 opt]# node -v
v12.16.2

[root@40-14-22 opt]# ln -s /opt/nodejs/bin/npm /usr/local/bin/
[root@40-14-22 opt]# npm -v
6.14.4

使用NVM安装

1
2
3
4
# 安装NVM:https://github.com/nvm-sh/nvm
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
# 安装Node
nvm install v12.16.3

使用NVM的好处在于可以管理Node版本,同一机器上可灵活切换不同Node版本,一键安装较方便

NVM常用命令

  • nvm install v12.16.3:安装指定版本的Node
  • nvm use v12.16.2:使用指定版本的Node
  • nvm ls: 查看当前使用的Node版本
  • nvm -h: 查看帮助文档

运行环境准备

安装PM2

1
2
3
4
# 全局安装
[root@40-14-22 opt]# npm install pm2 -g
# 如果不支持全局使用,需配置软链
[root@40-14-22 bin]# ln -s /opt/nodejs/lib/node_modules/pm2/bin/pm2 /usr/local/bin/

日志分割

官方文档:https://www.npmjs.com/package/pm2-logrotate

1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装pm2-logrotate
[work@40-14-22 log]$ pm2 install pm2-logrotate
# 设置超过1G分割
pm2 set pm2-logrotate:max_size 1G
# 设置最多保存200个日志文件
pm2 set pm2-logrotate:retain 200
# 是否通过gzip压缩日志
pm2 set pm2-logrotate:compress false
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
pm2 set pm2-logrotate:workerInterval 30
# 每天晚上23:59:30分割
pm2 set pm2-logrotate:rotateInterval '30 59 23 * * *'
pm2 set pm2-logrotate:rotateModule true

设置完成后可以通过pm2 conf pm2-logrotate查看设置的是否正确

1
2
3
4
5
6
7
8
9
10
11
[work@40-14-22 .pm2]$ pm2 conf pm2-logrotate
Module: pm2-logrotate
$ pm2 set pm2-logrotate:max_size 1G
$ pm2 set pm2-logrotate:retain 200
$ pm2 set pm2-logrotate:compress false
$ pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
$ pm2 set pm2-logrotate:workerInterval 30
$ pm2 set pm2-logrotate:rotateInterval 0 0 * * *
$ pm2 set pm2-logrotate:rotateModule true
Module: module-db-v2
$ pm2 set module-db-v2:pm2-logrotate [object Object]

安装Git

安装git是为了让pm2部署时能够从仓库中拉取代码,并部署

安装

1
[root@40-14-22 ~]# yum install git

配置公钥

拷贝机器的公钥串(当前用户的.ssh目录下的.pub文件内容),粘贴到公共的gitlab下的授权下即可;目的是允许机器拉取gitlab仓库中的私有代码

1
2
# 生成公钥-一直回车即可
[work@40-14-22 .ssh]$ ssh-keygen -t rsa

最好使用公共账号,不要使用个人账号

如不配置ssh,pm2拉代码的时候会提示如下无权限错误

服务器授信

部署机与服务器

目的: 部署机可免密访问目标机

编辑目标机.ssh目录下的authorized_keys文件,粘贴保存部署机上.ssh目录下.pub文件内容即可

vim快捷键:https://www.cnblogs.com/junwen5599/p/9996873.html

服务器与Gitlab服务器

向服务器的known_hosts中新增Gitlab服务器的公钥

缺少配置在部署时会报如下错误

其他

查看Linux内核版本

1
2
[work@37-14-42 log]$ uname -a
Linux 37-14-42 2.6.32-573.22.1.el6.x86_64 #1 SMP Wed Mar 23 03:35:39 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

查看centos版本

1
2
[work@37-14-42 log]$ cat /etc/redhat-release
CentOS release 6.5 (Final)

在centos6上安装最新的Node12会报如下错误

1
2
3
4
5
6
7
8
[work@40-31-60 ~]$ node -v
node: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.14' not found (required by node)
node: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.18' not found (required by node)
node: /usr/lib64/libstdc++.so.6: version `CXXABI_1.3.5' not found (required by node)
node: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by node)
node: /lib64/libc.so.6: version `GLIBC_2.16' not found (required by node)
node: /lib64/libc.so.6: version `GLIBC_2.17' not found (required by node)
node: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by node)

原因为gcc版本过低,要么升级gcc,要么系统升级到centos7

建议不要折腾,在centos6上升级gcc版本,耗力费时,可能还会存在版本匹配问题;最好选择一步到位升级系统到centos7

迁移方案

测试验证

服务器环境准备好后,接下来就是按照线上的部署流程在新服务器上部署Node服务。

此时,由于没有经过验证,不能够直接让外网流量进来;另外,线上服务环境与公司内网环境一般也是隔离的,那么如何才能请求到新的服务验证新服务是否可用呢?

这种情况,可以借助部署机,部署机比较特殊,既可以访问内网的gitlab服务拉取代码,又可以把拉取到的代码部署到线上环境,可以部署机为跳板访问线上新的Node服务,在内网环境验证服务是否可用。

具体可参见这篇文章:通过Nginx解决网络隔离实践记录

切流量

当验证完服务正常后,接下来就是切流量操作了,可以采用如下两种方案

  • 把线上某一台机器的流量切到新服务上
  • 把线上流量的10%切到新的服务上

流量进来后,观察服务器的各项参数是否有异常,观察Node服务的日志监控是否有异常上报。

运行一段时间无异常,再切50%,循序渐进,最终把全部流量切到新主机。

最后,服务迁移繁琐复杂,容器化会是一个更好的选择

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

Centos安装Mysql记录

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 下载yum源
wget 'https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm'

# 安装yum源
sudo rpm -Uvh mysql57-community-release-el7-11.noarch.rpm

# 查看有哪些版本可安装
yum repolist all | grep mysql

# 切换安装版本
# 关闭5.7版本
sudo yum-config-manager --disable mysql57-community
# 打开5.6版本
sudo yum-config-manager --enable mysql56-community

# 安装
sudo yum install mysql-community-server

在centos6下会报如下错误:(centos7正常)

1
2
3
4
5
6
7
8
9
10
Error: Package: mysql-community-server-5.6.48-2.el7.x86_64 (mysql56-community)
Requires: libc.so.6(GLIBC_2.17)(64bit)
Error: Package: mysql-community-server-5.6.48-2.el7.x86_64 (mysql56-community)
Requires: systemd
Error: Package: mysql-community-libs-5.6.48-2.el7.x86_64 (mysql56-community)
Requires: libc.so.6(GLIBC_2.17)(64bit)
Error: Package: mysql-community-server-5.6.48-2.el7.x86_64 (mysql56-community)
Requires: libstdc++.so.6(GLIBCXX_3.4.15)(64bit)
Error: Package: mysql-community-client-5.6.48-2.el7.x86_64 (mysql56-community)
Requires: libc.so.6(GLIBC_2.17)(64bit)

原因分析如下:
https://unix.stackexchange.com/questions/280385/can-not-install-mysql-server-on-centos-6-7-32bit-error-need-rpm

在网上找了一圈,按照下面一顿操作,发现能够正常安装成功

1
2
3
4
5
# cd /etc/yum.repos.d/  找到mysql-56-community,将enable置为0 enable=0
sudo vi mysql-community.repo

# 重新安装mysql
sudo yum install mysql-server

在centos6上安装完后,发现由于网络安全原因,机器无法开3306端口;索性换了一台centos7的机器,直接一气呵成安装MySQL8.0

基本操作

1
2
3
4
5
6
7
8
# 查看mysql运行状态
sudo service mysqld status
# 查看端口情况
sudo lsof -i tcp:3306
# 启动mysql,需要加sudo,否则会报FAILED错误
sudo service mysqld start
# 结束服务
sudo service mysqld stop

修改密码

首次安装后查看默认密码

1
2
[work@40-31-60 soft]$ sudo grep 'temporary password' /var/log/mysqld.log
2020-05-21T09:56:30.576083Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: UlCbRjdoE4?a

默认情况下Mysql是不运行远程连接的,故需要新增远程连接账户

1
2
# 连接数据库(输入上面查询出来的密码)
mysql -u root -p

修改密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 8.0下报错
mysql> set password for root@localhost = password('root');
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'password('root')' at line 1

# 修改密码(数字、大小写、特殊字符)
alter user 'root'@'localhost' identified by '58daojiaDJ!!';
# 密码复杂度过低会报如下错误
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

# 查看密码强度规则
mysql> SHOW VARIABLES LIKE 'validate_password%';
+--------------------------------------+--------+
| Variable_name | Value |
+--------------------------------------+--------+
| validate_password.check_user_name | ON |
| validate_password.dictionary_file | |
| validate_password.length | 8 |
| validate_password.mixed_case_count | 1 |
| validate_password.number_count | 1 |
| validate_password.policy | MEDIUM |
| validate_password.special_char_count | 1 |
+--------------------------------------+--------+
7 rows in set (0.01 sec)

添加账户

查看现有用户

1
2
3
4
5
6
7
8
9
10
11
12
13
# 选择数据库
mysql> use mysql;
# 用户查询
mysql> select host,user from user;
+-----------+------------------+
| host | user |
+-----------+------------------+
| localhost | mysql.infoschema |
| localhost | mysql.session |
| localhost | mysql.sys |
| localhost | root |
+-----------+------------------+
4 rows in set (0.00 sec)

新增允许远程连接的账户

1
2
3
4
# 新增用户;[admin'@'%]中的%号代表允许任意远程客户端连接
mysql> CREATE USER 'admin'@'%' IDENTIFIED BY '58admin!!AAA';
# 添加权限
mysql> GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%';

其他

为work账户添加sudo权限

root 账户键入visudo即可进入sudo配置
找到root ALL=(ALL) ALL
在这一行下面增加work ALL=(ALL) NOPASSWD:ALL即可

Node连接异常处理

使用mysql包( https://www.npmjs.com/package/mysql )连接服务时报如下错误

1
Error: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client

解决方案如下:

1
2
3
4
5
6
7
# 选择mysql数据库
use mysql;
# 修改admin的密码,关键在于mysql_native_password关键字指定密码类型
ALTER user 'admin'@'%' IDENTIFIED WITH mysql_native_password by '58admin!!AAA';
# 更新
FLUSH PRIVILEGES;

执行完,node端就可以连接上mysql8.0了。

可以到mysql数据库中的user表中查看密码,其它的都为caching_sha2_password类型,修改完的这个为mysql_native_password

资料:https://stackoverflow.com/questions/50093144/mysql-8-0-client-does-not-support-authentication-protocol-requested-by-server

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

VR:虚拟与现实

1

周末体验了一把VR眼镜带来的不同乐趣。

给我的直观感受可以用两个字来形容:”颠覆“。

其中的游戏体验可以用2D到3D上升一个维度,3D再到VR再次上升一个维度来形容,完全的沉浸体验,给人身临其境的感觉。

戴上VR眼镜,犹如进入了另外的一个世界,而且还是一个分外逼真的世界,你的一举一动都得在这个世界有所反馈,由此引出一个问题:”现实世界中,我们看到的一切都是真实的吗?

我相信,通过目前的VR技术,完全有能力把现实里的部分场景原封不动的搬进VR眼镜里,在这种情况下,我们该如何区分虚拟与现实?

会不会出现所谓的精分(精神分裂)?会不会出现《盗梦空间》里的场景,通过一个虚拟现实中的场景,来影响到我们对现实世界的认知?

从我第一次接触体验的感受来讲,我觉得不久的将来极有可能变为现实。

2

除了这些引人深思的问题,还让让我想到了另外一个词:”降维打击“。

通过模拟出一个电影院的场景,戴上VR眼镜,你看到的周围环境,犹如真正置身于电影院里(此时你唯一缺少的就是让你能够舒舒服服坐下的沙发),通过软件设置,你还可以切换你喜欢的私人影院风格。

此时,你不再需要去电影院,在家里就能获得去电影院里观看电影的体验,甚至体验还更好。

因为,我们的感官完全来自于我们眼睛看到的内容,通过VR技术,完全有条件让眼睛信以为真。

前年新房装修,专门考虑把电视墙做平,方便后续买投影放幕布,提升观影体验,在VR里体验了一把看电影的体验后,现在想想有点可笑。

当时的思维认知,决定了当时的思考与行动。

就像网上的段子说:”农民的认知里皇帝是用金锄头锄地的,实际上皇帝根本就用不着锄地!“

3

前几天在知乎看到这样一个问题:”前端会有未来吗?

当时的回答是这样的:

如果你把前端定位为写html、css、js那么可以预见的是没有未来的

如果把前端定位为用户接受信息的一种渠道,那么前端必定还有更为广阔的未来

随着技术的进步,人和信息的交互与互动会更加的多元化与场景化,这就对前端提出了更高的要求

所以,各位不要再抱怨学不动了,后面还有得学…

也正是因为这样,前端的未来才充满无数的挑战和机会,作为一个技术人,值得为此一搏!

随着VR的不断普及,我相信跟用户相关的”前端“还会更进一步的爆炸,还会有更多的机会与挑战。

目前VR的普及主要受制于硬件的制约,想想功能机到现在人手一台的智能机,短短十年间就完成了更新换代与颠覆。

相信VR的春天一定不会太遥远。

作为一个技术人,想想都激动呀!~~~

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

一文弄清传统软件开发与互联网软件开发的异同

在传统软件开发领域8年,从一线开发,到项目技术经理,再到产品研发技术负责人,一路走来,感受着互联网行业的软件越来越贴近日常生活,一直有一个疑问,那就是互联网行业中的软件开发是怎么样的?两年前,决定转行到互联网行业实际体验看一看,通过两年的观察总结以及实际亲身感受,我觉得我已经找到了答案,解除了心中的疑问,此文写给那些跟我有相同疑问的朋友。

相信很多在传统软件开发领域的朋友或多或少对互联网公司的软件开发有如下疑问:开发流程是怎么样的?人员组成是怎么样的?系统架构是怎么样的?成长路线是怎么样的?是不是加班厉害?薪资福利是不是传言的那么有诱惑力?

而在互联网行业高压力的同学,也会问:传统软件开发公司工作会不会轻松点?会不会稳定一点?是不是在传统软件开发领域年龄没那么敏感?

这篇文章会一一解答上述疑问

介绍

首先,先来介绍一下何谓传统软件行业,何谓互联网软件行业,有如下几个明显差异点。

传统行业

传统软件开发行业,也可以叫做企业软件开发行业,他的明显特征如下

  • 软件: 比如财务系统、OA系统、CRM、ERP等业务系统,医疗、电力、智慧城市、税务等行业软件,目的为帮助公司或政府与事业单位更高效运转的业务支撑系统
  • 目标群体: 客户 - 软件的使用群体一般为公司内部员工、政府、事业单位职员
  • 公司: 金蝶、用友、中软、东软等

互联网行业

  • 软件: 比如微信、QQ、baidu.com、今日头条、抖音、优酷等
  • 目标群体: 用户 - 广大互联网用户
  • 公司: 阿里、腾讯、百度、头条等

项目流程

作为码农,不论在传统行业还是互联网行业,都是做软件开发,软件开发一般都是以项目为单位。首先,就先以项目流程的角度聊一下传统软件行业与互联网软件行业的区别。

传统行业

结合上图,从如下几点说说传统软件项目的项目流程

需求来源

需求来源一般为甲方客户,当一家公司慢慢成长起来时,往往需要软件来提升公司的管理与运行效率。比如所有公司无一例外都需要人事管理系统、财务管理系统、OA系统、考勤系统;一些制造行业还需要生产管理的ERP系统,医院需要病例管理系统、学校需要学生管理系统、政府需要公文管理系统等等。这些甲方客户一般都没有专门的软件开发团队,大部分只有一个叫技术中心的部门负责网络与各种系统的管理。所以,要落地这些软件,就需要专业的软件公司来实施。

需求收集

有了实际的需求,要么客户主动找到软件公司,要么软件公司挖掘客户;首先入场的一般都是销售入场(主管商务事项,比如报价、签约,客户关系维护,需求挖掘,以便签约项目二期、三期…)。
有时还会有售前顾问支持(主管技术,比如给客户给出技术方案、初步分析出项目实施范围,方便合理报价);需要说明的是售前顾问通常都是技术出身,项目经验丰富,不仅是技术专家,还是业务专家,对某一行业有深刻理解,能够告诉客户,怎么做才是行业最好的方案,利用行业经验帮助客户提升效率,而不是简单把客户的下线流程搬到线上。

需求实施

项目签约下来后,紧接着就是项目实施团队入场。
项目团队包含这些人:项目经理、需求分析师、技术经理、技术开发、UI、QA
项目实施有时为驻场开发,有时是在公司开发好后到甲方公司去部署上线

需求交付

需求通过QA测试部署后,通常会有试运行阶段,在试运行阶段,客户可提出问题点与优化点。所有功能没问题后,即完成交付,技术团队撤出,投入到下一个项目,销售收尾款。
偶尔客户还会购买系统运维服务,即支持系统运行期间的问题处理以及小需求迭代。

互联网行业

结合上图,从如下几点说说互联网软件项目的项目流程

需求来源

需求来源主要在于这三个方面

  1. 产品需求:PM发起的需求,比如要做个视频面试的新产品、要优化下单流程之类的
  2. 营销需求:此类需求一般为公司运营和营销人员发起的需求,PM把需求梳理为产品文档后,交由技术团队落地
  3. 内部需求:此类需求可理解为内部系统建设需求;前面两种一般称之为To C,这种叫To B

最后,还有一种活儿的来源,那就是技术自己发起的需求,比如系统重构,技术基础设施的建设,比如系统性能、异常监控系统、持续集成系统等

需求收集

在互联网公司,所有的需求都会收集到产品经理(PM)处,技术团队原则上只从产品经理处承接需求

PM会把各种需求整理成需求文档,还会附带上需求原型。通常会先期找技术初评,确认哪些功能无法实现然后调整需求文档。

需求实施

需求没问题后,C端需求最先入场的是UE、UI,即交互设计与视觉设计,完事后前端FE拿到设计稿后进行开发,这期间后端开发可并行,最后接口联调、测试

需求交付

  • 测试完毕后,预发布、预发布没问题由QA上线

贴一个以前端视角的大致项目流程:

最明显的感受就是,传统行业的软件开发是给甲方干,在互联网行业的软件开发是给自己干!

项目相关人员

上面介绍了项目的整体流程,接着针对于项目团队的组成,即实际参与项目落地的人员以及人员分工聊一聊

传统软件项目

  • 项目经理

    项目的总负责人,负责与客户对接,大部分情况参与需求的调研,把客户的线下需求转变为需求文档,给到技术人员去落地

  • 技术经理

    项目的技术负责人,负责整个项目的关键技术把控,业务抽象、系统架构、模块划分,以及对开发工程师的工作分配与管理

  • 需求分析师

    协助项目经理梳理需求,比较大的项目一般是项目经理整体把控,带领多个需求分析师,没人负责一块业务需求,把需求文档化。目的在于与客户对齐需求,保证开发出来的东西是客户想要的。还有一个比较重要的目的是,开发之前都需要客户确认需求,方便控制需求变更以及需求范围,避免无限制的增加需求。

  • UI设计师

    一般PC端系统首页,以及移动端的一些页面需要UI单独设计,后台系统一般不需要。UI属于公共资源,需项目组申请,一般不常驻项目。

  • 开发工程师

    在传统软件开发中,很少会有专职的前端人员,大部分情况都是项目经理划分好功能模块,开发人员从数据库设计、后端业务逻辑编写,再到前端UI实现,都是同一个人。好处很明显,就是效率高;坏处就是相对于互联网行业的分工细化,质量会相对差一些。

  • 测试工程师

    公共资源,到测试阶段入场。一般负责压力测试、性能测试、功能逻辑测试。

  • 系统安全

    关于系统安全这块,政府或者国家级项目,甲方一般会找第三方安全厂商来做渗透测试,或者直接找国家网络安全中心来做系统的安全评估,最终给出系统的安全报告,有哪些系统漏洞会一一列出来。在经历过的项目中,私企这一块基本没有,政府与事业单位软件项目居多。

互联网软件项目

下面贴一张曾经以项目经理角色负责过的项目,因为该项目较大,牵涉到的人较多,可以很清晰的看到一个项目的完整人员分工与构成。

  • PM

    产品经理,负责需求文档编写,以及需求上线后产品效果的跟踪

  • UE

    交互设计,跟进PM的需求,制定页面的交互逻辑

  • UI

    根据需求与交互稿设计UI图,然后注明UI图的标注,直接给到前端或者上传到蓝湖

  • 视觉

    视觉与UI实际上都属于设计部门,视觉设计的主要分工为活动海报、运营活动页面,插画等的设计

  • RD

    后端开发,提供数据接口给到端上(FE、APP)

  • FE

    前端开发,负责所有H5页面、各种小程序、NodeJS层的开发。

  • APP

    一般分为两块,IOS与Android

  • QA

    测试人员,FE与RD并行开发,都开发完毕后进行接口联调,联调完成后QA介入测试

  • BI

    数据分析人员,数据一般来源于前端的埋点以及业务数据,负责根据相关数据给出数据分析报表

  • 安全

    安全团队负责把控系统上线前是否有安全漏洞

  • 法务

    保障新上线的项目需求无法律风险。比如一些文案的提示,活动的规则、用户协议等。

  • 运营

    提出需求,营销活动规则的指定

  • 财务

    营销活动类需求活动资金的控制

传统软件项目开发一般都会基于公司产品来做二次开发,提升开发效率。互联网软件大部分情况都是对现有线上业务的迭代,为了提升开发效率后端也有中台组、架构组支撑,前端与UI也会抽取业务组件方便开发。

传统软件开发项目,由项目经理负责制,从项目最开始跟到系统上线验收;互联网公司中的项目组织相对零散,需求详设评审进入开发后基本上就没PM的事情了,这个时候一般会在FE、RD、QA中推举一位项目负责人推进项目的落地,把控项目进度。敏捷项目一般由Master来负责。

项目技术架构

传统行业

核心诉求: 在满足功能需求的情况下,怎么好维护,怎么开发成本低怎么来。机器都是甲方出,所以能通过堆机器解决的问题都不是问题 (不过也需要为客户考虑项目整体成本)

常见的架构是这样的:

传统行业的企业内部系统技术架构80%都是只做到读写分离、按应用拆分、分布式缓存、单独的查询服务就不再往下走了,因为再往下走,开发成本会成指数级上升。少数会做到大表拆分、负载会上LVS或F5。

对于这样的技术架构,只要机器足够,性能够强,足以支撑一家上万人的公司日常正常运转了。

对于那种项目金额上千万的项目,更多的也是采取多地分开部署,数据集中上报汇总的方式,避免架构复杂化带来的开发成本提升。

互联网行业

核心诉求: 支持快速迭代、稳定、高并发。另外,机器都是自己出,多一台都是成本….

为了达成上述诉求,基础配置大部分都是这样的,上不封顶

可以看到,对于传统行业软件技术架构,相对于互联网软件架构,最明显的区别标志就是微服务

这里是一篇很不错的讲述架构演进的文章:https://mp.weixin.qq.com/s/yZlQUZQS0Rkn_7vY7hjvHQ

成长路线

大部分上了规模的互联网公司都有清晰明确的职级体系;传统行业软件公司大部分职级体系较模糊。

传统行业

一般分为技术路线与业务路线两种

技术路线

职级从初级开发、中级开发、高级开发、资深开发、一路到系统架构师;

实际工作中,做到在项目中负责整个项目的技术负责人,或者公司的产品研发负责人,技术路线基本就到头了

项目技术负责人更多的要求综合能力;产品研发负责人给更多要求技术深度与从项目业务中提炼成产品的能力

业务路线

大多数都会先做一两年技术,然后做项目的需求分析人员,再然后到项目经理,成为业务方面的专家;例如财务领域专家、生产制造领域业务专家、金融领域业务专家等。

这条线是业务经验越丰富越值钱,需要靠一个个实际项目历练出来,无捷径可走。

互联网行业

上规模的互联网公司,大部分都有成体系的晋升路线图

下面从网络上找到几张图,比较清晰

以阿里为例,分为技术线和管理线

技术线职级对应的要求如下所示

能力要求

  • 传统软件开发

    在传统软件开发中,更多的是要求技术的广度,以及综合能力,希望技术人员是多面手,要求以最快的速度,最低的成本搞定需求。开发时,更多的也是按功能模块拆分,希望开发人员能够从前到后一条龙搞定。对时间、代码质量不敏感,对项目的资源投入与收益敏感。

  • 互联网软件开发

    在互联网公司中,岗位拆分的很细,会更多的要求单一方向的技术深度,专门的岗位干专业的事。之所以会把岗位拆这么细,是因为这样方便模块拆分,实现更多的并行开发,尽力做到增加人员,就能加快项目开发进度,实现快速抢占市场的目的。

行业关注点

  • 传统行业

    更多的关心一个项目的投入与产出比,所以会在产品上多下功夫,尽量的把通用功能产品化,以更多的复用来减少开发成本。同时,更注重业务解决方案的抽取,提升核心竞争力。

  • 互联网行业

    更多的关心需求的上线速度,更快的速度占领市场就会有更多的优势;所以会更多的关注模块化,实现通过不断的增加开发人员,就能明显提升开发速度,所以岗位、系统才会拆得比较细粒度。为了解决复用问题,衍生出中台概念。

关于加班

传统软件行业

大部分情况是早九晚六,中午可午休,基本不加班。

由于传统软件每个项目的开发周期较长,大部分都是按月计,所以紧急情况下,有足够的消化空间,很少有加班特别狠的情况。

特殊情况,从业8年,甲方为日企,唯一一次连续996一个月。

其他: 有出差需求,因为有时需要到客户所在地驻场开发。

互联网软件行业

公认的加班狠,什么996(早9点,晚9点,一周6天)、大小周(隔周双休)的开创者全部来源于这个行业;

也有极少数公司能做到早10晚7,不过碰到上线,基本都得加班(有时上线还挺频繁的,一周至少有一半时间有需求上线)

其他: 基本无出差需求

总的来说,传统软件行业加班时间是少数,有更多的非工作时间;互联网软件开发行业,加班是常态,不加班或少加班的公司简直是一股清流存在。

关于薪资福利

传统软件行业

从实际待过的两家A股上市公司,以及所了解的其他头部传统行业软件公司来看,涨薪基本上靠你的直属主管觉得你应该加薪才会获得薪资的提升

虽然从系统里能够查出来你的职位是助理开发还是资深架构师,但是公司没有一个相对明确的每个职位层级的薪资范围,也没有正式的述职与职位晋升一说,我的感受就是你的薪资越高,代表着你的职级越高。整体来看,同职级岗位薪资低于互联网行业一个层级,月薪30k是一个比较难达到的坎。

股票、期权激励较难见到。

互联网软件行业

互联网公司的涨薪基本上靠如下几个方面

  • 年度普调(发展好的公司)
  • 每年绩效不错,核心骨干(针对性涨薪)
  • 职位晋升

薪资基本与职级挂钩,每个职级对应一个薪资范围,达到薪资范围的上限,就只有靠职位晋升来提升了。薪资范围可参见成长路线部分的贴图。

股票、期权激励较常见。

关于稳定性

传统软件行业

传统行业相对稳定,原因有如下几点

  • 以项目为单位,每个项目都是独立的业务逻辑,要想摸清摸透一个方向的业务逻辑,需要花费大量的时间;即使一个业务方向熟悉了,还有下一个业务方向等着你。有效避免过早进入舒适区,导致人员的不稳定因素增加。即使不想做项目往业务方向扎根,希望多做技术,还可以转产品研发,摸透整个产品架构与细节至少又是两年过去了。
  • 行业内的公司就那么些家,可选择面不大
  • 行业内跳槽薪资涨幅有限

传统软件领域,很多软件系统属于用户的核心业务系统,比如ERP、财务系统等,属于刚需。所以,这一块只要有稳定的客户来源,即使是运维需求也会有一口饭吃。因为稳定性,收入也很难像互联网行业公司那样快速增长。

互联网软件行业

业务不赚钱,即使你再努力、个人能力再强也只能走人

见过上一天还在努力上班,第二天就被n+1裁掉的场景

在互联网行业能够真切的感受到个人的渺小,选择大于努力。

还有那句,只要在风口上,即使是猪也能飞起来的生动诠释。

在互联网软件开发领域里,两三年一跳槽是常态,人员流动性较大。

关于年龄

传统软件行业

由于加班没那么狠,很少有拼体力的情况,所以在传统行业软件开发领域年龄没那么敏感。

曾经的同事,好多都是在这个行业干了20+year的老码农,照样干得风生水起。

因为传统软件开发领域的特殊性,需要更多的与甲方客户沟通交流,外加对行业业务需求的深刻理解。年龄大,代表着更丰富的与客户打交道经验,以及更丰富的业务行业经验,更具竞争力。

之前还碰到有客户指明项目实施团队必须要有10+year的带队,或者不能低于多少比例,直接写进合同那种。

互联网软件行业

对于这个行业,崇尚一个字“

要求业务发展快、个人成长快

经常可以在网络或工作中听到说XXX多年轻就晋升xxx职级了

网络上甚至还流传xxx大厂到了35岁还没晋升到xxx职级,就极有可能被优化掉的说法

这样的氛围,对于那些想把更多时间放到生活上的人极不友好

难道就不能保持低职级,拿该拿的薪资,保持work balance?

no,no,no;随着年龄的增大,这种安于工作现状的人会显得跟整个团队格格不入,极易绩效背锅

xxx公司对于这类员工,还发明了一个叫做“老白兔”的标签

由此可见,互联网行业对于年龄的友好程度!

所以,建议想再奋斗一下,再挑战一下自己的到互联网行业去,那里有更大的机遇与空间。
建议对技术追求没那么高,希望工作生活相对平衡,可以考虑一下传统软件行业,那里只要你做事靠谱,年龄不是问题。

关于转行

首先要注意的是,得看在什么团队,什么岗位,做什么事情。

传统软件转互联网

可能在传统行业公司,做的事情偏互联网公司的玩法。例如:做针对于互联网用户的系统。

互联网公司做事、沟通相对open,竞争激烈,优胜劣汰;业务发展不好,能力再强努力再多也得面对裁员;所以跳槽到互联网公司一定要选对行业、选对部门;去冷门行业、边缘部门要多考虑

优势: 技术宽度、软实力、综合能力

互联网转传统软件

也可能在互联网公司,实际上做的事情跟在传统行业的软件公司差不多。例如:做公司内部的各种系统。

传统软件公司相对比较稳定,企业业务系统是刚需,旱涝保收;正因如此,公司业务也很难有指数级的增长,薪资也同理;可考虑走业务专家路线。

优势: 技术深度、良好的自驱力、技术创新能力

最后

如果用一句话来总结传统软件开发与互联网软件开发,我觉得可以用一个更“”,一个更“”来概括。

行业的业务形态决定了诉求点不同,由此带来工作方式、能力要求等方方面面的不同。

如果要问到底从事传统软件开发好还是互联网软件开发好?

我要说的是:“这个问题对于不同的人有不同的答案,没有好与不好,只有适合与不适合”。

如有其它相关问题,在公众号回复问题或加微信咨询,尽量知无不言言无不尽。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

通过Nginx解决网络隔离实践记录

需求

最近需要迁移Node线上服务,于是新申请了两台线上服务器;

部署服务器后,需要验证服务是否正常,办公环境与线上环境网络是隔离的,无法直接访问;但是,线上服务器可通过部署服务器访问,而办公网络是可以访问部署机的;

所以,可通过在部署机上配置代理的方式,办公环境请求部署机,然后把请求代理到线上服务的方式验证服务是否正常。

整个网络结构如下图所示:

Nginx安装

下载

下载页面: http://nginx.org/en/download.html 选择版本鼠标右键拷贝链接地址

1
2
3
4
# 下载
[work@40-10-14 opt]$ wget http://nginx.org/download/nginx-1.18.0.tar.gz
# 解压文件
[work@40-10-14 opt]$ tar -xvf nginx-1.18.0.tar.gz

安装

1
2
3
4
5
6
# 1. 默认安装:root权限进入解压后的目录,执行如下命令安装
[root@40-10-14 nginx-1.18.0]# ./configure && make && make install

# 2.指定目录:安装到指定的/opt/nginx目录
[work@40-10-14 opt]$ mkdir /opt/nginx
[work@40-10-14 nginx-1.18.0]$ ./configure --prefix=/opt/nginx && make && mae install

默认安装,非root权限会报如下错误

1
2
3
4
mkdir: cannot create directory `/usr/local/nginx': Permission denied
make[1]: *** [install] Error 1
make[1]: Leaving directory `/opt/nginx-1.18.0'
make: *** [install] Error 2

默认安装后,查看nginx的安装目录,可以看到安装在/usr/local/nginx目录下

1
2
[root@40-10-14 opt]# whereis nginx
nginx: /usr/local/nginx

1.建议使用指定目录方式安装。如果切换为root权限去安装,后续修改config文件也需要root权限
2.或者root安装后,修改权限为普通用户可操作也行

添加软链

添加软链,使得nginx命令全局能访问,每次运行就不用切换到安装目录中了

1
2
3
4
5
# 添加软链
[root@40-10-14 sbin]# ln -s /opt/nginx/sbin/nginx /usr/local/bin/
# 查看版本
[root@40-10-14 sbin]# nginx -v
nginx version: nginx/1.18.0

常用命令

  • 启动:nginx
  • 停止:nginx -s stop
  • 重启:nginx -s reload
  • 帮助命令: nginx -h

强制停止:

1
2
3
4
5
6
7
8
9
# 查看linux进程id
[root@40-10-14 ~]# ps -ef | grep nginx
nobody 45198 1 0 16:12 ? 00:00:00 nginx: worker process
root 51261 50692 0 17:00 pts/0 00:00:00 grep nginx
# 关闭进程
[root@40-10-14 ~]# kill 45198
# 之前的进程已被关闭
[root@40-10-14 ~]# ps -ef | grep nginx
root 51277 50692 0 17:00 pts/0 00:00:00 grep nginx

配置代理

配置两台机器的请求转发,编辑nginx安装目录下的nginx/conf/nginx.conf文件即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#user  nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

# 请求需要转发到如下两台机器上,流量平分;指定IP和端口
upstream zpserver {
server xx.xx.xx.22:10001;
server xx.xx.xx.23:10001;
}

server {
# nginx服务端口为80
listen 80;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

# /user根路径的请求才转发
location /user {
root html;
index index.html index.htm;
proxy_pass http://zpserver;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}


# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}

}

注意:修改完nginx的配置文件后,需要运行nginx -s reload才能生效

验证

由于线上服务很多都是需要登录的,所以访问时需要使用域名访问,而不能使用IP访问,因为cookie都是跟域名绑定的

解决这个问题很简单,配置本机host即可

1
2
# IP为Nginx服务器IP
xx.xx.xx.14 edu.daojia.com

通过上述配置,在本机浏览器上请求edu.daojia.com即可间接通过部署机上的Nginx访问到线上服务,以此在内网测试服务是否正确;待服务无异常后,把线上流量切过来即可。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

实际案例聊聊系统建模与面向对象设计

什么样的设计才是最好的设计?
别问,问就是自己去体会……

背景

PM同学组织了一场关于下单流程业务系统拆分的需求评审,整体功能为可线上售卖视频课,用户买完课之后可在线学习、不同的课程还包含线下实地培训等、学习完之后在线考试,颁发合格证书。

实际上,该需求可分为两个部分来看

  • 售卖:通过各种渠道把各种各样的商品卖出去
  • 履约:完成不同商品与之对应的履约流程

已有的实现

从上图可以清晰的看到,左侧为售卖的各种商品,右侧为商品与之对应的履约流程。

同时,每个商品对应一条独立的履约流程,商品之间可能存在相同的履约环节。

有什么问题?

有什么问题得看从什么角度看

如果从业务的角度看当然没啥问题,因为它能满足业务需求。

但是,从系统设计角度看,存在如下几个问题

  • 要上架售卖新品类的商品,需要定制实现对应的履约流程
  • 已有商品履约流程调整,系统也需要做对应调整
  • 履约流程中的履约环节缺少复用性

业务抽象建模

从上图中,可以看出如下几个改动点

  • 抽象了一个履约规则层,不同的商品对应不同的履约规则
  • 履约规则串联起商品下单订单履约,实现下单与履约的解耦
  • 订单履约模块可随意组合,以此完成履约流程

相对与之前的设计,核心变更如下所示:

有哪些好处?

通过这样的调整,好处很明显,完美的解决掉之前的问题

  • 上架新品类商品,无需对应开发与之的履约流程,新增履约配置即可
  • 已有商品履约流程调整,系统无需对应调整,只需修改履约配置即可
  • 因为实现了解耦,下单与履约相对独立,互不影响,履约部分可提供各种各样的履约单元组合实现履约流程

聊聊面向对象设计

面向对象的四大特性

  • 封装:独立模块只暴露对外接口,封装内部实现
  • 抽象:例如上面抽象出来业务需求表象之下的履约规则层
  • 继承:实现通用逻辑抽取,所有子类具有父类已实现的功能
  • 多态:代码易扩展的利器,基于接口而不基于实现编程

面向对象的六大设计原则

  • SRP 单一职责原则:每一个类、模块尽量做到职责单一
  • OCP 开闭原则:对扩展开放,对修改关闭
  • LSP 里式替换原则:即子类能够替换父类对象出现的任何地方,并且保证原来程序的逻辑行为不变或者说正确性不被破坏
  • DIP 依赖倒置原则:高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象
  • ISP 接口隔离原则:意思即一个类不应该被强迫依赖它不需要的接口,即接口设计的时候不应该大而全,可做好分类,多使用接口组合
  • LOD 迪米特法则:核心在于降低类的耦合;不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。

其他还有

  • DRY原则(Don’t Repeat Yourself)
  • KISS原则(Keep It Simple and Stupid)
  • YAGNI原则(You Ain’t Gonna Need It)

最后一个,我个人喜欢叫做不要加戏原则。

设计模式

设计模式更多的在于解决具体问题,我的体会是不要去套模式,多去思考怎么样去实现代码才会是最优解,当你这么做了之后,设计模式自然而然就会从你的代码里涌现出来。

几年前翻了一遍《Head first 设计模式》一书,翻完的第一感受就是原来很多设计模式在实际代码中都用过,原来每个套路还有对应的名字…

曾经整理了一个设计模式的系列文章,分别用Java与Javascript实现,在这里:

设计模式系列文章:
http://muchstudy.com/2016/12/28/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E6%80%BB%E7%BB%93/

最后

工作得越久,越会不自觉的思考,十年一线老码农相对与工作四五年的正走上轨道且精力旺盛的生力军的核,心竞争力在哪里?

可能就在于系统建模能力与面向对象设计能力上?

在于哪些东西技术上能行,而实际上坑很深,敢于有所为有所不为上?

不在于实现一个功能的快慢,而在于后续的持续迭代与维护成本上?

软件开发说简单也简单,说复杂也复杂,复杂到无法量化精确评估它的好坏!

什么样的设计才是最好的设计?

别问,问就是自己去体会……

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

剩者为王?

在疫情寒冬下,公司开启新的一拨裁员,蓦然发现,在某一个钉钉群里由群众变为了群主!~~

看着同事的离开,要说心理上没有波动,那是不可能的。

但是,那又能怎么样呢,这就是社会。作为一个上了年纪的人,不论内心多波动也必须表现得无比的镇静,不然就会被认为幼稚愚蠢,这也是社会的一部分……

不缩减成本,可能整个公司都会死掉,到时就是所有人被裁,没有资金,拿不出N+1,就别提好聚好散了。站在公司的角度,表示理解。

但是,站在个人的角度呢?

始终感觉被裁员是一件特别被动的事情,作为还没轮到的,也会想着,万一哪天裁到自己了,该怎么办?

答案只有一个:早做准备!

我的理解,裁员一般分为两种,一种为末尾淘汰;一种为不管你表现得有多好,业务线亏损严重看不到希望,导致整个业务线被砍掉。

对于末位淘汰的情况,我觉得首先需要从自身上面找原因,多去想想为什么淘汰的是自己而不是别人?是产出低?贡献少?技术弱?不顶事?挖坑多?甚至是跟领导关系不好?跟其他人沟通不顺畅?如果不想清楚这个问题,然后做针对性的改善,相信换一个工作环境还是会遇到相同的问题。

对于整个业务线砍掉这种,相信在这之前的很长一段时间就早有迹象,这种情况就需要提早准备,多做打算,考虑到最坏的情况,不要被裁得措手不及!从个人方面,时刻保持自己的竞争力,不要被温水煮青蛙,避免长期处于舒适区。

最后,真心的希望离开的同学都能开启更好的下一段征程。

剩者为王?凛冽的寒冬,幸存者也需要拼尽全力!

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。