开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Linux常用查看日志文件命令

发表于 2021-09-22

查看日志常用命令

tail

tail -200f test.log 实时监控200行日志

tail -n 1000 test.log 查询日志尾部最后1000行的日志

tail -n +1000 test.log 查询1000行之后的所有日志

head

head -n 1000 test.log 查询日志文件中的头1000行日志

head -n -1000 test.log 查询日志文件除了最后1000行的其他所有日志

cat

cat -n test.log |grep "debug"  查询关键字的日志

more

  more命令和cat的功能一样都是查看文件里的内容,但有所不同的是more可以按页来查看文件的内容,还支持直接跳转行等功能。

more test.log 查看日志

  • +n 从笫n行开始显示
  • -n 定义屏幕大小为n行
  • Enter 向下n行,需要定义。默认为1行
  • Ctrl+F 向下滚动一屏
  • 空格键 向下滚动一屏
  • Ctrl+B 返回上一屏

less

  less与more类似,但使用less可以随意浏览文件,而more仅能向前移动,却不能向后移动,而且less在查看之前不会加载整个文件。

less test.log 查看日志

  • b 向后翻一页
  • d 向后翻半页
  • h 显示帮助界面
  • Q 退出less 命令
  • u 向前滚动半页
  • y 向前滚动一行
  • 空格键 滚动一行
  • 回车键 滚动一页

应用场景

场景一:按行号查看—过滤出关键字附近的日志

cat -n test.log |grep "debug" 得到关键日志的行号

cat -n test.log |tail -n +92|head -n 20 选择关键字所在的中间一行. 然后查看这个关键字前10行和后10行的日志:

tail -n +100 表示查询100行之后的日志

head -n 20 则表示在前面的查询结果里再查前20条记录

场景二:根据日期查询日志

grep '2021-09-30 16:20:00' test.log 确定日志中是否有该时间点

sed -n '/2021-09-30 16:00:00/,/2021-09-30 16:20:00/p' test.log 查看时间节点内的日志

注意:日期必须是日志中打印出来的日志,否则无效.\color{red} 注意:日期必须是日志中打印出来的日志,否则无效. 注意:日期必须是日志中打印出来的日志,否则无效.

场景三:日志内容特别多,打印在屏幕上不方便查看

  1. 使用more和less命令

cat -n test.log |grep "debug" |more 分页打印,通过空格键翻页
2. 使用 >xxx.txt 将查询到的日志保存到文件中,可以下载这个文件分析

cat -n test.log |grep "debug" >debug.txt

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

ES 深分页问题解决方案 问题背景 ES 深分页解决方案梳理

发表于 2021-09-22

问题背景

image.png

  • 这是一条线上的方法性能监控报警信息,收到报警第一时间查看报警接口的线上监控;

image.png

  • 看监控发现接口几乎不可用了,继续查看接口内部方法的监控,包括 HBASE、ES 等相关内部调用方法的监控,定位造成接口性能异常的具体方法;
  • 经排查发现除 ES 方法性能异常之外,其他方法的监控性能都正常,继续查看 ES 监控;

image.png

  • ES 监控(QPS、CPU、内存等指数异常升高几倍);
  • 初步定位是 ES 出现问题,ES 相关方法代码主要是在做分页检索,猜测存在深分页问题,继续排查日志;
  • 日志情况(被刷到 1000 多页),通过日志最终定位到异常原因是有人在不停的发起翻页请求,但是接口未做保护限制,导致线上接口性能超时严重。

ES 深分页解决方案梳理

一、根据业务情况限制分页

限制翻页数(page)为 100 页,后续不再提供检索,这是业内最常用的方法,简单有效,之所以这么做,一方面是因为通常认为 100 页以后的检索内容对于检索者的参考意义并不大,另一方面考虑到除了恶意请求外,应该不会有翻到 100 页没有找到检索内容还依然要检索的;

二、通过 ES 自身方案解决

既然第一种方式简单有效就可以解决线上问题,那为什么还要深究 ES 自身的一些技术方案呢?作为一个技术人,对于技术,还是要做到 知其然并知其所以然,对于一些技术方案,你可以不用,但你不能不懂。

关于 ES 查询的三种方式如下:

1、from + size 方式

  • 一个最基本的 ES 查询语句是这样的:
1
2
3
4
5
6
bash复制代码POST /my_index/my_type/_search
{
"query": { "match_all": {}},
"from": 100,
"size": 10
}
  • 上面的查询表示从搜索结果中取第 100 条开始的 10 条数据,那么,这个查询语句在 ES 集群内部是怎么执行的呢;
  • 在 ES 中,搜索一般包括两个阶段,query 和 fetch 阶段,可以简单的理解,query 阶段确定要取哪些 doc(ids),fetch 阶段取出具体的 doc;
  • Query 阶段:
+ ![image.png](https://gitee.com/songjianzaina/juejin_p7/raw/master/img/8f4a71e6d06a7f16ebc4e32a25367ef4b6650e655df2746055067669bbda5a59)
+ 如上图所示,描述了一次搜索请求的 query 阶段;
+ Client 发送一次搜索请求,node1 接收到请求,然后,node1 创建一个大小为 from + size 的优先级队列用来存结果,我们管 node1 叫 **`coordinating node`**;
+ coordinating node 将请求广播到涉及到的 shards,每个 shard 在内部执行搜索请求,然后,将结果存到内部的大小同样为 from + size 的优先级队列里,可以把优先级队列理解为一个包含 top N 结果的列表;
+ 每个 shard 把暂存在自身优先级队列里的数据返回给 coordinating node,coordinating node 拿到各个 shards 返回的结果后对结果进行一次合并,产生一个全局的优先级队列,存到自身的优先级队列里;
+ 在上面的例子中,coordinating node 拿到 (from + size) \* 6 条数据,然后合并并排序后选择前面的 from + size 条数据存到优先级队列,以便 fetch 阶段使用。另外,各个分片返回给 coordinating node 的数据用于选出前 from + size 条数据,所以,只需要返回 **`唯一标记 doc 的 _id`** 以及 **`用于排序的 _score`** 即可,这样也可以保证返回的数据量足够小;
+ coordinating node 计算好自己的优先级队列后,query 阶段结束,进入 fetch 阶段。
  • Fetch 阶段:
+ ![image.png](https://gitee.com/songjianzaina/juejin_p7/raw/master/img/7d74580339072422294b92134fe67472dc8e411570e907cfb3c9bd3b5b9639a4)
+ 上图展示了 fetch 过程;
+ coordinating node 发送 GET 请求到相关 shards;
+ shard 根据 doc 的 \_id 取到数据详情,然后返回给 coordinating node;
+ coordinating node 返回数据给 Client;
+ coordinating node 的优先级队列里有 from + size 个 \_doc \_id,但是,在 fetch 阶段,**`并不需要取回所有数据`**,在上面的例子中,前100条数据是不需要取的,只需要取优先级队列里的第101到110条数据即可;
+ 需要取的数据可能在不同分片,也可能在同一分片,coordinating node 使用 **`multi-get`** 来避免多次去同一分片取数据,从而提高性能。
  • ES 的这种方式提供了分页的功能,同时,也有相应的限制。
+ 举个例子,一个索引,有 1 亿数据,分 10 个 shards,然后,一个搜索请求,from=1,000,000,size=100,这时候,会带来严重的性能问题;
+ 在 query 阶段,每个 shard 需要返回 1,000,100 条数据给 coordinating node,而 coordinating node 需要接收 10 \* 1,000,100 条数据,即使每条数据只有 \_id 和 \_score,这数据量也很大了,而且,这才一个查询请求而已;
+ 在另一方面,这种深分页的请求并不合理,因为我们是很少人为的看很后面的请求的,在很多的业务场景中,都直接限制分页,比如只能看前100页;
+ 不过,这种深度分页确实存在,比如,被爬虫了,这个时候,直接干掉深度分页就好;又或者,业务上有遍历数据的需要,这时候就需要取得所有符合条件的数据,而最容易想到的就是利用 from + size 来实现,不过,这个是不现实的,这时,可以采用 ES 提供的 **`scroll`** 方式来实现遍历。

2、scroll 方式

  • 官网描述:
    • scroll 查询可以用来对 Elasticsearch 有效地执行大批量的文档查询,而又不用付出深度分页那种代价;
    • 游标查询允许我们 先做查询初始化,然后再批量地拉取结果。这有点儿像传统数据库中的 cursor;
    • 游标查询会 取某个时间点的快照数据。查询初始化之后索引上的任何变化会被它忽略。它通过保存旧的数据文件来实现这个特性,结果就像保留初始化时的索引视图一样;
    • 深度分页的代价根源是 结果集全局排序,如果去掉全局排序的特性的话查询结果的成本就会很低。游标查询用字段 _doc 来排序。这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果。
  • 怎么理解官网的描述呢?
    • Scroll 是 为检索大量的结果而设计 的。例如,我们需要查询 1~100 页的数据,每页 100 条数据;
    • 如果使用 from + size(Search)查询:每次都需要在每个分片上查询得分最高的 from + 100 条数据,然后协同节点把收集到的 n × (from + 100) 条数据聚合起来再进行一次排序(Query 阶段);
    • 每次返回 from + 1 开始的 100 条数据(Fetch 阶段),并且要重复执行 100 次 Search 查询过程(Query + Fetch),才能获取全部 100 页数据;
    • Scroll 查询:
      • 如果使用 Scroll 查询,只需要在各个分片上查询 10000 条数据,协同节点聚合 n × 10000 条数据进行合并、排序(Query 阶段),并取出排名前 10000 的结果(Fetch 阶段)快照起来,后续滚动查询时,只需根据设置的 size,直接 在生成的快照里面以游标的形式,取回这个 size 数量的文档即可。这样做的好处是 减少了查询和排序的次数。
      • image.png
      • image.png
      • image.png
      • image.png

3、search_after 方式

image.png

  • search_after 是 ES 5 新引入的一种分页查询机制,其实 原理几乎就是和 scroll 一样,简单总结如下:
+ 必须先要 **`指定排序`**;
+ 必须 **`从第一页开始`**;
+ 从第一页开始以后每次都带上 **`search_after=lastEmittedDocFieldValue`** 从而为无状态实现一个状态,其实就是把每次固定的 from + size 偏移变成一个确定值 lastEmittedDocFieldValue,而查询则从这个偏移量开始获取 size 个 \_doc(每个 shard 获取 size 个,coordinate node 最后汇总 shards \* size 个)。
  • 最后一点非常重要,也就是说,无论去到多少页,coordinate node 向其它 node 发送的请求始终就是请求 size 个 docs,是个 常量,而不再是 from + size 那样,越往后,你要请求的 docs 就越多,而要丢弃的垃圾结果也就越多;
  • 也就是说,如果要做非常多页的查询时,最起码 search_after 是一个 常量查询延迟和开销;
  • 有人就会问,为啥每次提供一个 search_after 值就可以找到确定的那一页的内容呢,Elasticsearch 不是分布式的么,每个 shard 只维护一部分的离散的文档,那 Elasticsearch 是怎么做的呢?
  • search_after 的实现原理:
+ 第一次只能够查第一页,每个 shard 返回了一页数据;
+ 服务层得到 2 页数据,内存排序,取出前 100 条数据,作为最终的第一页数据,这个全局的第一页数据,一般来说 **`每个 shard 都包含一部分数据`**(比如 shard1 包含了 20 条,shard2 包含了 80 条);
+ 这个方案也需要服务器内存排序,岂不是和 scroll 一样么?第一页数据的拉取确实一样,但每一次“下一页”拉取的方案就不一样了;
+ 点击“下一页”时,需要拉取第二页数据,在第一页数据的基础之上,能够 **`找到第一页数据被拉取的最大值`**,这个上一页记录的 max,会作为第二页数据拉取的查询条件;
+ 这样就可以不需要像 scroll 那样返回 2 页数据了,每个 shard 还是返回一页数据,只不过是 **`从 max 开始的一页长度的数据`**;
+ 服务层得到 2 页数据,内存排序,取出前 100 条数据,作为最终的第 2 页数据,这个全局的第 2 页数据,一般来说也是每个 shard 都包含一部分数据;
+ 如此往复,查询第 100 页数据时,不是返回 100 页数据,而是仍返回一页数据,以保证数据的传输量和排序的数据量 **`不会随着不断翻页而导致性能下降`**。
  • search_after 实验结果(随机部分日志截取):
+ ![image.png](https://gitee.com/songjianzaina/juejin_p7/raw/master/img/ec03e0a2b541e7c7ea0fe5cca7d22d149f831099e883e0451f5bce77ac6eeaa9)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Nginx搭建简单文件服务器

发表于 2021-09-22

1. Nginx安装

  • Ubuntu: sudo apt-get install nginx
  • Fedora、Centos: sudo yum install nginx
  • Manjaro: sudo pacman -S nginx

2. 配置文件

  • 查找Nginx配置文件: sudo find / -name nginx.conf
  • 一般存放在 /etc/nginx/nginx.conf

3. 修改配置文件

  • 在 http 节点中添加以下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
nginx复制代码  autoindex on;             #开启索引功能
autoindex_exact_size off; # 关闭计算文件确切大小(单位bytes),只显示大概大小(单位kb、mb、gb)
autoindex_localtime on; # 显示本机时间而非 GMT 时间
charset utf-8; # 避免中文乱码

server {
listen 9999; #监听端口号
server_name localhost;
#root /usr/share/nginx/html;
root /home/evil/share; # 共享的文件目录
error_log /home/evil/share/log/error.log ; # 报错的文件目录

location / {
}

error_page 404 /404.html;
location = /40x.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

保存文件并重启Nginx

4. 效果图

  • Nginx搭建简单文件服务器

5. 坑

  • 403 Forbidden

403 forbidden错误是禁止读取访问。也就是说服务器理解了请求,但是不允许访问

  • 解决方案:

cd 进入文件夹,确认文件夹存在,文件夹存在即为访问权限问题

修改文件夹访问权限 chmod 755 文件夹路径

重启 nginx 后还是 403

查看日志文件内容,显示:

2021/01/11 18:07:11 [error] 98226#98226: *1 "/home/evil/share/index.html" is forbidden (13: Permission denied), client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:9999"

是 nginx 运行用户的问题,ps -aux | grep nginx 查看 nginx worker 用户

Nginx搭建简单文件服务器
运行用户为 http ,修改 nginx 运行用户(如果nginx配置的用户和静态文件的用户不匹配,那怕权限是777也会出现权限问题)

在 nginx.conf 开头加入:

1
2
3
> powershell复制代码  user 用户名 用户组; # 这里的用户名和组就是文件夹的
>
>

Over

本文由博客一文多发平台 OpenWrite 发布!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

搭建一个低代码平台给你打下手! 前言 概念与特征 JEECG

发表于 2021-09-22

中秋节过完马上又是国庆节,假期跟几个同为程序猿的哥们交流技术(chedan),低代码被提及到比较多,主要涉及的低代码平台有jeecg-boot还有前端低代码平台amis,学习过程中也发现一个不错的低代码平台Erupt,感谢这些平台开发大佬。


前言

或许很多程序员工作中用不到低代码平台,但是个人认为玩转一个低代码平台,学习低代码平台的技术栈能够系统的提高自己技术储备。1.低代码平台通常是提供了一套完整的解决方案,涉及到工作流、报表、前后端环境及部署、数据中台、分布式锁、移动支付、短信验证码平台的接入等等,把低代码平台当做一个技术学习与实践应用平台是非常不错的选择;2.学习低代码平台对编程语言的高级应用;3.低代码平台提供的完整的表设计及模板代码生成也能够帮助我们减少重复工作。


概念与特征

低代码开发平台(LCDP)是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台。通过可视化进行应用程序开发的方法,使具有不同经验水平的开发人员可以通过图形化的用户界面,使用拖拽组件和模型驱动的逻辑来创建网页和移动应用程序。低代码开发平台(LCDP)的正式名称直到2014年6月才正式确定,整个低代码开发领域却可以追溯到更早前第四代编程语言和快速应用开发工具。(来自于百度百科)


通过对百度百科介绍的解读,低代码平台需要具备以下几个特征:

  • 0代码或少量代码快速生成应用
  • 提供可视化界面进行程序开发
  • 使用拖拽等操作实现客户端的搭建

根据以上特征,我们可以了解低代码的平台的构成,也就知道了低代码能够做什么。如果抛出平台二字,我们开发中备受欢迎的mybatis-plus可以称为低代码持久化框架,因为框架帮助我们降低了代码量。随着低代码平台的流行,一个框架如果能够帮助我们降低代码量,这也算得上是框架的优势之一了,所以很多低代码平台都引用了类似于mybatis-plus、lombok之类的框架。

JEECG-BOOT踩坑指南

JEECG官网 开发文档

上面的链接中,我附带了jeecg-boot官方提供的开发文档,文档非常详细,包含环境准备、搭建、部署、使用相关内容,但是在实际实践中,还是会遇到小问题,当然这不是jeecg-boot本身的问题,而是我们使用默认环境的问题。

问题

初始化数据库脚本报错

  • 根据官方流程,我们拉下jeecg-boot相关代码,安装数据库(我用的是mysql5.7.20),docker安装

jeecg-boot数据库使用的字符集为uft8mb4,支持存储emoji表情、utf8存储emoji会报错,不了解此方面的同学可以当个拓展知识点

  • 执行数据库初初始化脚本(jeecgboot-mysql-5.7)
    image.png
    使用Navicat执行初始化脚本,在执行1084条后就停止执行了,通常这种情况我们可能会去查看一下sql脚本是否有问题,经过检查报错位子的脚本,拷贝出来单挑执行是OK的,于是经过各种调研(百度),原来是mysql限制了执行脚本大小,默认为2M,这个mysql脚本大小为6M
  • 脚本信息
    image.png

解决办法

  • 将此脚本拆分为多个小于2M的脚本,按顺序依次执行,注意不要拆到语句中间就行了。
  • 修改数据库配置,将脚本大小限制调大一些
    修改方式根据部署的方式不同略有差别,我们需要修改就是max_allowed_packet这个配置项,修改后删除之前执行了错误产生的数据,再次执行脚本即可,下面是docker下mysql修改此配置项的命令
1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码1.进入mysql容器
docker exec -it mysql bash
2.进入配置文件所在文件夹
cd /etc/mysql/mysql.conf.d/
3.修改配置文件
vi mysqld.cnf
4.在文件中[mysqld]下面另起一行 添加配置信息
max_allowed_packet=200M
5.保存
esc :wq
6.退出容器 exit
7.重启mysql服务docker restart mysql

容器中没有vim需要安装vim,建议配置好加速再操作,不然会很慢

启动报错

处理好数据库的初始化问题,按照官方文档修改了配置文件中的数据库地址,配置好redis,然后启动jeecg-boot服务后报错

image.png

解决办法

报错信息的意思就是说QRTZ_LOCKS这个表不存在,我们查看数据库,这个表是存在的,但是表名是小写,问题就定位到了,mysql默认表名称大小写是敏感的,jeecg-boot生成的sql中表名称都是大写,解决此问题就是修改mysql表名称大小写敏感配置项lower_case_table_names=1,具体操作参考上面修改配置项即可

后端服务启动完成

解决掉这两个问题,jeecg-boot的服务端就可以顺利启动了!!!

前端

前端服务只需要按照官网文档修改好相关配置,构建后启动即可

Docker极简部署

为了方便部署一个学习环境,我按照官方推荐的极简部署方案将前端代码放到后端服务jeecg-boot-module-system 项目的 /src/main/resources/static目录下,然后使用JAR部署方案将后端服务达成jar包,然后将jar包上传到docker宿主机环境中,构建docker镜像进行部署,详细操作如下:


  • 修改前端服务配置(参考官方文档-极简部署方案)

image.png

  • 改完前端配置文件,后续按照极简部署方案第三步及后面的步骤进行操作
  • 前后端代码准备完毕,修改后端配置文件中mysql、redis配置,修改为docker容器中对应服务名称即可,如mysql:3306…,注意修改的是prod后缀的yaml文件

image.png

image.png

  • 按照官方文档JAR部署方案第四步及后面步骤进行操作
  • jar包打好了 ,修改名称为jeecg.jar,docker宿主机使用rz命令上传到服务器中
  • 在服务器jar所在目录创建Dockerfile文件内容如下
1
2
3
4
bash复制代码FROM frolvlad/alpine-java
COPY jeecg.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
  • 在当前目录执行命令创建镜像 docker build -t jeecg .
  • 启动服务 docker run -d –name jeecg -p 8080:8080 –link redis –link mysql jeecg
  • 使用–link是因为我创建其它容器时没将容器加入同一个网络,所以需要使用–link保证服务于mysql及redis的通信,docker网络相关知识可以参考掘金文章链接
  • 服务启动完毕

部署小结

因为官方文档的docker部署需要使用docker for windows,我更加习惯使用虚拟机部署ubuntu安装docker部署一些自己开发及学习环境,所以结合官方教程整了一个docker极简部署,适合新手在自己的电脑上搭建开发环境同时学习一下docker,不会docker的同学可以通过学习一下docker单机环境搭建相关,比较简单

使用

使用jeecg-boot低代码平台官方文档有详细的介绍,在使用中由于对整个平台业务了解不够,我在使用代码生成后,将生成的前后端代码添加到服务中,页面没有显示出来,后来发现添加菜单需要配置权限,所以建议在搭建好学习环境后,使用前要充分了解整个平台的业务设计,避免因为不了解而把正常业务当做问题。

Erupt(轻量级低代码平台)

Erupt官网

相对于JEECG-BOOT,Erupt更加轻量级一些,对于纯后端程序猿也更加方便(无需前端相关基础),只需要在springboot2.x的项目中添加相关maven依赖即可,官方的快速开始手册照着做就可以成功完成平台搭建,遇到的问题和解决办法我也在官方文档中找到,参考链接即可。

总结

低代码平台很多,作为程序员,除了编码以外了解一些主流的低代码平台的使用及搭建也是非常有必要,本次主要介绍了jeecg-boot的上手及问题的解决方案,对于Erupt搭建及使用没有遇到什么环境上的问题,上手快,推荐大家都可以部署起来学习一下。

点击了解一下mongo索引优化

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Nginx服务器常见配置清单备忘录 写在前面 Nginx用户

发表于 2021-09-22

写在前面

Nginx是高性能轻量级WEB服务器的优秀代表,由于其提供HTTP代理和反向代理、负载均衡、缓存等一系列重要特性,从而广泛应用于当今的Web后端服务之中,而且各大互联网公司也都在重度使用,所以作为一个开发者,学会Nginx的使用和配置很有必要。

在本文中,我们将会从一份示例配置清单开始,来简单梳理一下Nginx服务器的各种常见配置指令的作用和用法。

话不多说,上菜!

本文在GitHub开源仓库「编程之路」 github.com/rd2coding/R… 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及我的程序员人生,欢迎鉴赏。


Nginx配置文件的整体结构

这里直接画一幅图就一目了然了,几个大的配置模块看得就很清楚。

从图中可以看出主要包含以下几大部分内容:

1. 全局块

该部分配置主要影响Nginx全局,通常包括下面几个部分:

  • 配置运行Nginx服务器用户(组)
  • worker进程数
  • Nginx进程PID存放路径
  • 错误日志的存放路径
  • 配置文件的引入
  • …

2. events块

该部分配置主要影响Nginx服务器与用户的网络连接,主要包括:

  • 设置网络连接的序列化
  • 是否允许同时接收多个网络连接
  • 事件驱动模型的选择
  • 最大连接数的配置
  • …

3. http块

  • 定义MIMI-Type
  • 自定义服务日志
  • 是否允许sendfile方式传输文件
  • 连接超时时间
  • 单连接请求数上限
  • …

4. server块

  • 配置网络端口监听
  • 访问日志和错误页
  • 基于名称的虚拟主机配置
  • 基于IP的虚拟主机配置
  • location块配置
  • …

5. location块

  • location配置
  • 请求根目录配置
  • 更改location的URI
  • 网站默认首页配置
  • …

一份配置清单例析

这里给出了一份简要的Nginx配置清单举例:

配置代码如下:

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
ini复制代码user  nobody  nobody;
worker_processes 3;
error_log logs/error.log;
pid logs/nginx.pid;

events {
use epoll;
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;
keepalive_timeout 65;

server {
listen 8088;
server_name codesheep;
access_log /codesheep/webserver/server1/log/access.log;
error_page 404 /404.html;

location /server1/location1 {
root /codesheep/webserver;
index index.server2-location1.htm;
}

location /server1/location2 {
root /codesheep/webserver;
index index.server2-location2.htm;
}

}

server {
listen 8089;
server_name 192.168.31.177;
access_log /codesheep/webserver/server2/log/access.log;
error_page 404 /404.html;

location /server2/location1 {
root /codesheep/webserver;
index index.server2-location1.htm;
}

location /srv2/loc2 {
alias /codesheep/webserver/server2/location2/;
index index.server2-location2.htm;
}

location = /404.html {
root /codesheep/webserver/;
index 404.html;
}

}

}

接下来就对照这份示例配置清单来详细剖析一下配置文件中几个主要指令的含义及用法。


Nginx用户(组)配置

配置项格式:user user [group];

  • user:指定可以运行Nginx的用户
  • group:指定可以运行Nginx的用户组(可选项)

如果user指令不配置或者配置为 user nobody nobody ,则默认所有用户都可以启动Nginx进程。


worker进程数配置

这是Nginx服务器实现并发处理的关键配置,配置项格式为:

worker_processes number数;

  • number:Nginx进程最多可以产生的worker process数
  • 如果设置为auto,则Nginx将进行自动检测

按照上文中的配置清单的实验,我们给worker_processes配置的数目是:3,启动Nginx服务器后,我们可以后台看一下主机上的Nginx进程情况:

1
perl复制代码ps -aux | grep nginx

很明显,理解 worker_processes 这个指令的含义就很容易了


error日志路径配置

配置项格式:error_log file [可选日志级别];

  • file:指定日志输出到某个文件file
  • 常见的可选日志级别包括:info、debug、warn、error…等

Nginx进程PID存放路径配置

由于Nginx进程是作为系统守护进程在后台运行,所以该选项用于自定义配置PID文件的保存路径。

配置项格式:pid file;

  • file:指定其存放路径+文件名称
  • 如果不指定默认置于路径 logs/nginx.pid

事件驱动模型配置

配置项格式:use model;

  • model模型可选择项包括:select、poll、kqueue、epoll、rtsig等……

最大连接数配置

配置项格式:worker_connections number数;

  • number默认值为512,表示允许每一个worker进程可以同时开启的最大连接数。

配置文件的引入

该配置主要用于引入其他或者第三方的Nginx配置文件到当前的主配置文件中

配置项格式:include conf_file;


网络连接的序列化配置

配置项格式:accept_mutex on;

  • 该配置默认为on状态,表示会对多个Nginx工作进程接收连接进行序列化,防止多个worker进程对连接的争抢。

说到该指令,首先得阐述一下什么是所谓的“惊群问题”。就Nginx的场景来解释的话大致的意思就是:当一个新网络连接来到时,多个worker进程会被同时唤醒,但仅仅只有一个进程可以真正获得连接并处理之。如果每次唤醒的进程数目过多的话,其实是会影响一部分性能的。

所以在这里,如果accept_mutex on,那么多个worker将是以串行方式来处理,其中有一个worker会被唤醒;反之若accept_mutex off,那么所有的worker都会被唤醒,不过只有一个worker能获取新连接,其它的worker会重新进入休眠状态。

这个值的开关与否其实是要和具体场景挂钩的,一定程度上会影响系统的吞吐量。Nginx默认打开了accept_mutex,也算是一种保守的做法。


多网络连接 接收配置

配置项格式:multi_accept off;

  • 该配置默认为off,意指每个worker进程一次只能接收一个新到达的网络连接。如果想让每个Nginx的worker process都能同时接收多个网络连接,则需要开启此配置。

MIME-Type定义

MIME-Type指的是网络资源的媒体类型,也即前端请求的资源类型。

配置项格式:

1
2
3
ini复制代码include mime.types;

default_type type类型;
  • include配置用于将mime.types文件包含进来

可以用cat mime.types 来查看mime.types的文件内容,我们发现其就是一个types结构,里面包含了各种浏览器能够识别的MIME类型以及对应类型的文件后缀名,如下所示:


访问日志配置

配置项格式:

1
ini复制代码access_log path [format];
  • path:自定义访问日志的路径+名称
  • format:自定义服务日志的格式(可选项)。

连接超时配置

配置项格式:keepalive_timeout timeout [header_timeout];

  • timeout 表示server端对连接的保持时间
  • header_timeout表示在应答报文头部的 Keep-Alive 域设置超时时间,可选项。

sendfile配置

配置项格式:

1
csharp复制代码sendfile on;
  • sendfile配置用于开启或关闭使用sendfile()系统调用来传输文件,默认off
  • 注:在很多Web Server中,都引入了 sendfile的机制,来实现高性能文件传输。

网络地址监听配置

配置项格式:

  • 第一种:配置监听的IP地址:listen IP[:PORT];
  • 第二种:配置监听的端口:listen PORT;

实际举例:

1
2
3
perl复制代码listen 192.168.31.177:8080; # 监听特定IP和端口上的连接
listen 192.168.31.177; # 监听特定IP上所有端口的连接
listen 8080; # 监听特定端口上的所有IP的连接

基于名称或IP的虚拟主机配置

配置项格式:server_name name1 name2 ...

  • name可以有多个并列名称,而且此处的name支持正则表达式书写

实际举例:

1
ini复制代码server_name ~^www\.codesheep\d+\.com$;

至于基于IP的虚拟主机配置就更简单了:

配置项格式:server_name IP地址


location配置

配置项格式为:location [ = | ~ | ~* | ^~ ] /uri/ {...}

  • 这里的uri可包含正则表达式的模糊匹配。

uri前面的方括号中的内容是可选项,几种常见的情形如下:

  • “=”:用于标准uri,进行字符串的精确匹配
  • “~”:用于正则uri,表示区分大小写的匹配
  • “~*”:用于正则uri,表示不区分大小写的匹配
  • “^”:用于标准uri,^进行前缀匹配,表示区分大小写

根目录配置

配置项格式:root path;

  • path:表示Nginx接收到请求以后查找资源的根目录路径

当然,也还可以通过alias指令来更改location接收到的URI请求路径,指令为:

1
bash复制代码alias path;  # path为修改后的根路径

默认首页配置

配置项格式:index index_file ......

  • index_file可以包含多个用空格隔开的文件名,首先找到哪个页面,就使用哪个进行响应。

后记

本文在GitHub开源仓库「编程之路」 github.com/rd2coding/R… 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及我的程序员人生,欢迎鉴赏。

下篇见!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

为什么 Go map 和 slice 是非线程安全的?

发表于 2021-09-22

大家好,我是煎鱼。

初入 Go 语言的大门,有不少的小伙伴会快速的 3 天精通 Go,5 天上手项目,14 天上线业务迭代,21 天排查、定位问题,顺带捎个反省报告。

其中最常见的初级错误,Go 面试较最爱问的问题之一:

图片
(来自读者提问)

为什么在 Go 语言里,map 和 slice 不支持并发读写,也就是是非线程安全的,为什么不支持?

见招拆招后,紧接着就会开始讨论如何让他们俩 ”冤家“ 支持并发读写?

今天我们这篇文章就来理一理,了解其前因后果,一起吸鱼学懂 Go 语言。

非线程安全的例子

slice

我们使用多个 goroutine 对类型为 slice 的变量进行操作,看看结果会变的怎么样。

如下:

1
2
3
4
5
6
7
8
9
10
go复制代码func main() {
 var s []string
 for i := 0; i < 9999; i++ {
  go func() {
   s = append(s, "脑子进煎鱼了")
  }()
 }

 fmt.Printf("进了 %d 只煎鱼", len(s))
}

输出结果:

1
2
3
4
5
6
arduino复制代码// 第一次执行
进了 5790 只煎鱼
// 第二次执行
进了 7370 只煎鱼
// 第三次执行
进了 6792 只煎鱼

你会发现无论你执行多少次,每次输出的值大概率都不会一样。也就是追加进 slice 的值,出现了覆盖的情况。

因此在循环中所追加的数量,与最终的值并不相等。且这种情况,是不会报错的,是一个出现率不算高的隐式问题。

这个产生的主要原因是程序逻辑本身就有问题,同时读取到相同索引位,自然也就会产生覆盖的写入了。

map

同样针对 map 也如法炮制一下。重复针对类型为 map 的变量进行写入。

如下:

1
2
3
4
5
6
7
8
9
10
go复制代码func main() {
 s := make(map[string]string)
 for i := 0; i < 99; i++ {
  go func() {
   s["煎鱼"] = "吸鱼"
  }()
 }

 fmt.Printf("进了 %d 只煎鱼", len(s))
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
bash复制代码fatal error: concurrent map writes

goroutine 18 [running]:
runtime.throw(0x10cb861, 0x15)
        /usr/local/Cellar/go/1.16.2/libexec/src/runtime/panic.go:1117 +0x72 fp=0xc00002e738 sp=0xc00002e708 pc=0x1032472
runtime.mapassign_faststr(0x10b3360, 0xc0000a2180, 0x10c91da, 0x6, 0x0)
        /usr/local/Cellar/go/1.16.2/libexec/src/runtime/map_faststr.go:211 +0x3f1 fp=0xc00002e7a0 sp=0xc00002e738 pc=0x1011a71
main.main.func1(0xc0000a2180)
        /Users/eddycjy/go-application/awesomeProject/main.go:9 +0x4c fp=0xc00002e7d8 sp=0xc00002e7a0 pc=0x10a474c
runtime.goexit()
        /usr/local/Cellar/go/1.16.2/libexec/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc00002e7e0 sp=0xc00002e7d8 pc=0x1063fe1
created by main.main
        /Users/eddycjy/go-application/awesomeProject/main.go:8 +0x55

好家伙,程序运行会直接报错。并且是 Go 源码调用 throw 方法所导致的致命错误,也就是说 Go 进程会中断。

不得不说,这个并发写 map 导致的 fatal error: concurrent map writes 错误提示。我有一个朋友,已经看过少说几十次了,不同组,不同人…

是个日经的隐式问题。

如何支持并发读写

对 map 上锁

实际上我们仍然存在并发读写 map 的诉求(程序逻辑决定),因为 Go 语言中的 goroutine 实在是太方便了。

像是一般写爬虫任务时,基本会用到多个 goroutine,获取到数据后再写入到 map 或者 slice 中去。

Go 官方在 Go maps in action 中提供了一种简单又便利的方式来实现:

1
2
3
4
go复制代码var counter = struct{
    sync.RWMutex
    m map[string]int
}{m: make(map[string]int)}

这条语句声明了一个变量,它是一个匿名结构(struct)体,包含一个原生和一个嵌入读写锁 sync.RWMutex。

要想从变量中中读出数据,则调用读锁:

1
2
3
4
scss复制代码counter.RLock()
n := counter.m["煎鱼"]
counter.RUnlock()
fmt.Println("煎鱼:", n)

要往变量中写数据,则调用写锁:

1
2
3
scss复制代码counter.Lock()
counter.m["煎鱼"]++
counter.Unlock()

这就是一个最常见的 Map 支持并发读写的方式了。

sync.Map

前言

虽然有了 Map+Mutex 的极简方案,但是也仍然存在一定问题。那就是在 map 的数据量非常大时,只有一把锁(Mutex)就非常可怕了,一把锁会导致大量的争夺锁,导致各种冲突和性能低下。

常见的解决方案是分片化,将一个大 map 分成多个区间,各区间使用多个锁,这样子锁的粒度就大大降低了。不过该方案实现起来很复杂,很容易出错。因此 Go 团队到比较为止暂无推荐,而是采取了其他方案。

该方案就是在 Go1.9 起支持的 sync.Map,其支持并发读写 map,起到一个补充的作用。

具体介绍

Go 语言的 sync.Map 支持并发读写 map,采取了 “空间换时间” 的机制,冗余了两个数据结构,分别是:read 和 dirty,减少加锁对性能的影响:

1
2
3
4
5
6
go复制代码type Map struct {
 mu Mutex
 read atomic.Value // readOnly
 dirty map[interface{}]*entry
 misses int
}

其是专门为 append-only 场景设计的,也就是适合读多写少的场景。这是他的优点之一。

若出现写多/并发多的场景,会导致 read map 缓存失效,需要加锁,冲突变多,性能急剧下降。这是他的重大缺点。

提供了以下常用方法:

1
2
3
4
5
6
go复制代码func (m *Map) Delete(key interface{})
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
func (m *Map) LoadAndDelete(key interface{}) (value interface{}, loaded bool)
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
func (m *Map) Range(f func(key, value interface{}) bool)
func (m *Map) Store(key, value interface{})
  • Delete:删除某一个键的值。
  • Load:返回存储在 map 中的键的值,如果没有值,则返回 nil。ok 结果表示是否在 map 中找到了值。
  • LoadAndDelete:删除一个键的值,如果有的话返回之前的值。
  • LoadOrStore:如果存在的话,则返回键的现有值。否则,它存储并返回给定的值。如果值被加载,加载的结果为 true,如果被存储,则为 false。
  • Range:递归调用,对 map 中存在的每个键和值依次调用闭包函数 f。如果 f 返回 false 就停止迭代。
  • Store:存储并设置一个键的值。

实际运行例子如下:

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
go复制代码var m sync.Map

func main() {
 //写入
 data := []string{"煎鱼", "咸鱼", "烤鱼", "蒸鱼"}
 for i := 0; i < 4; i++ {
  go func(i int) {
   m.Store(i, data[i])
  }(i)
 }
 time.Sleep(time.Second)

 //读取
 v, ok := m.Load(0)
 fmt.Printf("Load: %v, %v\n", v, ok)

 //删除
 m.Delete(1)

 //读或写
 v, ok = m.LoadOrStore(1, "吸鱼")
 fmt.Printf("LoadOrStore: %v, %v\n", v, ok)

 //遍历
 m.Range(func(key, value interface{}) bool {
  fmt.Printf("Range: %v, %v\n", key, value)
  return true
 })
}

输出结果:

1
2
3
4
5
6
vbnet复制代码Load: 煎鱼, true
LoadOrStore: 吸鱼, false
Range: 0, 煎鱼
Range: 1, 吸鱼
Range: 3, 蒸鱼
Range: 2, 烤鱼

为什么不支持

Go Slice 的话,主要还是索引位覆写问题,这个就不需要纠结了,势必是程序逻辑在编写上有明显缺陷,自行改之就好。

但 Go map 就不大一样了,很多人以为是默认支持的,一个不小心就翻车,这么的常见。那凭什么 Go 官方还不支持,难不成太复杂了,性能太差了,到底是为什么?

原因如下(via @go faq):

  • 典型使用场景:map 的典型使用场景是不需要从多个 goroutine 中进行安全访问。
  • 非典型场景(需要原子操作):map 可能是一些更大的数据结构或已经同步的计算的一部分。
  • 性能场景考虑:若是只是为少数程序增加安全性,导致 map 所有的操作都要处理 mutex,将会降低大多数程序的性能。

汇总来讲,就是 Go 官方在经过了长时间的讨论后,认为 Go map 更应适配典型使用场景,而不是为了小部分情况,导致大部分程序付出代价(性能),决定了不支持。

总结

在今天这篇文章中,我们针对 Go 语言中的 map 和 slice 进行了基本的介绍,也对不支持并发读者的场景进行了模拟展示。

同时也针对业内常见的支持并发读写的方式进行了讲述,最后分析了不支持的原因,让我们对整个前因后果有了一个完整的了解。

不知道你在日常是否有遇到过 Go 语言中非线性安全的问题呢,欢迎你在评论区留言和大家一起交流!

若有任何疑问欢迎评论区反馈和交流,最好的关系是互相成就,各位的点赞就是煎鱼创作的最大动力,感谢支持。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo… 已收录,欢迎 Star 催更。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

ECMAScript 2021(ES12)新特性简介 简介

发表于 2021-09-22

简介

ES12是ECMA协会在2021年6月发行的一个版本,因为是ECMAScript的第十二个版本,所以也称为ES12.

ES12发行到现在已经有一个月了,那么ES12有些什么新特性和不一样的地方呢?一起来看看吧。

基本上ES12引入了replaceAll方法用于对String进行操作,Promise.any用于对Promise进行组合操作,AggregateError用于表示多个错误的集合,新的逻辑操作符??=, &&=, ||=,弱引用WeakRef,FinalizationRegistry用于垃圾回收的注册,一个数字的分隔符1_000,更加精准的数组sort方法Array.prototype.sort。

下面本文将会一一进行讲解。

replaceAll

熟悉java的朋友应该都知道,java中有两个进行字符串替换的方法,分别是replace和replaceAll,他们的区别在于replace是替换字符串,而replaceAll是进行正则表达式匹配。

但是在javascript中两者的涵义有所不同,在JS中replace是替换第一个出现的字符串,而replaceAll就是字面上的意思替换所有的字符串,我们举个例子:

1
2
c复制代码const string="flydean is a good fly"
console.log(string.replace("fly", "butterfly"));

上面的值返回:

1
csharp复制代码butterflydean is a good fly

如果改用replaceAll:

1
2
3
c复制代码const string="flydean is a good fly"
console.log(string.replaceAll("fly", "butterfly"));
butterflydean is a good butterfly

私有方法

自从JS有了类的概念之后,就可以在类中定义方法,并通过实例化之后的类进行调用,如下所示:

1
2
3
4
5
6
7
8
javascript复制代码class Student {
getAge() {
console.log("永远18岁")
}
}

student= new Student();
student.getAge();

上面代码运行结果:

1
arduino复制代码"永远18岁"

但是如果我们不希望getAge()方法直接暴露给外部使用,也就是说希望getAge()是一个私有方法,那么只需要在方法前面加上#即可。

1
2
3
4
5
6
7
8
javascript复制代码class Student {
#getAge() {
console.log("永远18岁")
}
}

student= new Student();
student.getAge();

同样运行,那么会得到下面的错误提示:

1
vbnet复制代码Error: student.getAge is not a function

怎么处理呢?我们知道私有方法是可以在方法内部调用的,那么只需要创建一个公有方法,然后在这个公有方法中调用私有方法即可,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
javascript复制代码class Student {
#getAge() {
console.log("永远18岁")
}

getPublicAge(){
this.#getAge();
}

}

student= new Student();
student.getPublicAge();

我们可以得到同样的结果。

私有属性

上面讲到了私有方法,那么对于私有属性是怎处理的呢?

通常,对于属性,我们可以以get修饰符来进行修饰,然后就可以直接通过属性名来访问了:

1
2
3
4
5
6
7
8
9
arduino复制代码class Student {
get Age() {
return 18;
}

}

student= new Student();
console.log(student.Age);

结果我们会得到18这个输出。

同样,可以在属性名前面加上#,让其变成私有变量,如下所示:

1
2
3
4
5
6
7
8
9
ini复制代码class Student {
get #Age() {
return 18;
}

}

student= new Student();
console.log(student.Age);

上面代码将会输出undefined。

要想访问上述的私有属性,则可以用公有属性去调用私有属性方法:

1
2
3
4
5
6
7
8
9
10
11
csharp复制代码class Student {
get #Age() {
return 18;
}
get publicAge() {
return this.#Age
}
}

student= new Student();
console.log(student.publicAge);

非常好用。

Promise.any() 和 AggregateError

promise.any可以返回任意一个提前resolve的结果,在现实的应用中,这种情况是非常常见的,我们来模拟一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
javascript复制代码const prom1 = new Promise((resolve, reject) => {
setTimeout(
() => resolve("promise one"),
Math.floor(Math.random() * 100)
);
});
const prom2 = new Promise((resolve, reject) => {
setTimeout(
() => resolve("promise two"),
Math.floor(Math.random() * 100)
);
});
const prom3 = new Promise((resolve, reject) => {
setTimeout(
() => resolve("promise three"),
Math.floor(Math.random() * 100)
);
});

(async function() {
const result = await Promise.any([prom1, prom2, prom3]);
console.log(result);
})();

上述代码可以随机输出promise one,promise two,promise three。

如果将上述代码改成所有的都reject,那么会抛出AggregateError:

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
javascript复制代码const prom1 = new Promise((resolve, reject) => {
setTimeout(
() => reject("promise one rejected"),
Math.floor(Math.random() * 100)
);
});
const prom2 = new Promise((resolve, reject) => {
setTimeout(
() => reject("promise two rejected"),
Math.floor(Math.random() * 100)
);
});
const prom3 = new Promise((resolve, reject) => {
setTimeout(
() => reject("promise three rejected"),
Math.floor(Math.random() * 100)
);
});

try{
(async function() {
const result = await Promise.any([prom1, prom2, prom3]);
console.log(result);
})();
} catch(error) {
console.log(error.errors);
}

报的错如下:

1
arduino复制代码Uncaught (in promise) AggregateError: No Promise in Promise.any was resolved

注意,必须是所有的promise都被reject之后才会抛出AggregateError,如果有部分成功,那么将会返回成功的结果。

数字分隔符

这个新特性是为了方便程序员看代码而出现的,如果数字比较大,那么看起来就不是那么一目了然,比如下面的长数字:

1
ini复制代码const number= 123456789;

一眼看不出这个数字的体量到底是多大,所以ES12提供了数字分隔符_。

分隔符不仅可以分割十进制,也可以分割二净值或者十六净值的数据,非常好用。

1
2
3
ini复制代码const number = 1_000_000_000_000;
const binary = 0b1010_0101_1111_1101;
const hex = 0xAF_BF_C3;

上面例子分别代表了十进制,二进制和十六进制的数据,非常直观好用。

新的逻辑操作符

我们知道&& 和 || 是被来进行逻辑操作的运算符。

比如:

1
2
复制代码1 && 2 
1 || 2

等操作,ES12提供了&& 和||的二元操作符,如下:

1
2
3
4
ini复制代码var x = 1;
var y = 2;
x &&= y;
x ||= y;

另外还提供了??的二元操作符,如:

1
2
3
ini复制代码var x;
var y = 2;
x ??= y;

上面代码的意思是,判断x是不是空,如果是空那么将y的值赋给x。

总结

ES12的几个新特性还是挺实用的,大家可以尝试一下。

本文已收录于 www.flydean.com/ecmascript-…

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Navicat 中没有设置默认CURRENT_TIMESTA

发表于 2021-09-22

如图所示

image.png

没有CURRENT_TIMESTAMP下拉选项,这个可以用sql语句弥补

第一步 把数据库导成sql文件

image.png

第二步 用txt打开sql文件

image.png

DEFAULT CURRENT_TIMESTAMP(0)
只要是 create_time TIMESTAMP语句就要加上(有默认CURRENT_TIMESTAMP需求的)

第三步 把修改完的sql文件导入进软件

导入的步骤就不重复了,可以先新建数据库,然后执行sql文件即可

第四步 修改成功

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

强大的mock数据生成工具--apipost 在APIPOS

发表于 2021-09-22

在APIPOST中使用Mock

APIPOST可以让你在没有后端程序的情况下能真实地返回接口数据,你可以用APIPOST实现项目初期纯前端的效果演示,也可以用APIPOST实现开发中的数据模拟从而实现前后端分离。在使用APIPOST之前,你的团队实现数据模拟可能是下面的方案中的一种或者多种:

  • 本地手写数据模拟,在前端代码中产生一大堆的mock代码。
  • 利用mockjs或者canjs的can-fixture实现ajax拦截,本地配置必要的json规则。
  • 后端在Controller层造假数据返回给前端。

上面的方式中,不管哪一种方式,都会要求开发人员写一些跟项目本无任何关联的代码,第一个和第二个方式还会需要前端项目在本地引入不必要的js文件。

使用APIPOST 的Mock 服务

您可以通过APIPOST 提供的Mock 服务实现上述功能。

编写Mock 规则

image.png

在APIPOST中,Mock 规则模板支持类型丰富(5.4版本起)。

基本数据(固定json结构)

1
2
3
4
5
6
7
8
css复制代码{
"code": "0",
"data": {
"name": "张三丰",
"age": 100
},
"desc": "成功"
}

基本数据(Mock随机json结构)

1
2
3
4
5
6
7
8
9
10
11
perl复制代码{
"code": "0",
"data": {
"list|20": [{
"name": "@name",
"age": "@integer(2)"
}],
"url": "https://echo.apipost.cn"
},
"desc": "成功"
}

RESTFUL逻辑数据

某些场景中,我们可能需要根据接口的入参规则,加入适当的逻辑处理后再返回数据。一个简单的场景就是登录场景,需要根据用户名密码,判断是否登录成功。再或者,我们需要根据产品ID动态返回产品信息,等等。

现在,ApiPost 的Mock 服务提供了这种场景的解决方案。

以下示例中,我们用到了 _req.body对象,其含义是:

当 post 请求以 x-www-form-urlencoded 或者application/json 方式提交时,我们可以拿到请求的参数对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
php复制代码{
"code": "0000",
"data": {
"verifySuccess": function() {
let body = _req.body;
return body.username === 'admin' && body.password === '123456';
},
"userInfo": function() {
let body = _req.body;
if (body.username === 'admin' && body.password === '123456') {
return Mock.mock({
username: "admin",
email: "@email",
address: "@address"
});
} else {
return null;
}
},
},
"desc": "成功"
}

填写Mock URL 相对地址

Mock URL相对地址是必填项(如果不填写的话,无法正常得到响应结果)。您可以通过在设置里开启“自动获取Mock URL地址”来自动获取Mock URL。

image.png

此项开启后,APIPOST将根据您输入的接口URL自动截取PATH部分作为Mock URL的相对路径。

利用APIPOST发送Mock URL

完成以上2步后,您可以通过在APIPOST中切换到“Mock 环境”来发送查看mock返回的详细数据。

image.png

将生成的mock URL地址发给前端

image.png

您可以将APIPOST生成的mock URL地址发给前端来代替您的接口地址,这样前端就可以使用您模拟的数据进行先一步的调试开发了。当您的接口完成后,再替换回来即可。

APIPOST的 mock 是基于mock.js 开发的。具体文档可以 参见 mock.js 详细文档。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

边缘使用 K8s 门槛太高?OpenYurt 这个功能帮你快

发表于 2021-09-22

简介: 为了降低 OpenYurt 的使用门槛,帮助更多地开发者快速上手 OpenYurt,社区提供了 OpenYurt 易用性工具 yurtctl。该工具致力于屏蔽 OpenYurt 集群创建的复杂性,帮助开发者在本地快速地搭建 OpenYurt 开发测试集群。

OpenYurt 作为阿里巴巴首个开源的边缘云原生项目,涉及到边缘计算和云原生两个领域。然而,许多边缘计算的开发者并不熟悉云原生相关的知识。为了降低 OpenYurt 的使用门槛,帮助更多地开发者快速上手 OpenYurt,社区提供了 OpenYurt 易用性工具 yurtctl。该工具致力于屏蔽 OpenYurt 集群创建的复杂性,帮助开发者在本地快速地搭建 OpenYurt 开发测试集群。

OpenYurt 采用云管边的架构,在原生 Kubernetes 集群之上,以 Addon 的形式进行功能增强,解决了云管边场景中,云边网络不稳定、云边运维难等关键问题,并实现了工作负载/流量的单元化管理、边缘本地存储、物联网设备管理等核心功能。本文实验的拓扑如图所示:

其中,蓝色部分是原生的 k8s 组件,橙色部分是 OpenYurt 提供的组件。

  • Master 节点位于云端,作为 OpenYurt 集群的管控节点,同时也作为集群的 Cloud Node,上面部署了原生 k8s 的控制面组件 controlplane,以及 OpenYurt 的管控组件 yurt-controller-manager、yurt-app-manager、yurt-tunnel-server
  • Cloud-Node 节点位于云端,作为 OpenYurt 集群的 Cloud Node,可以用于部署 OpenYurt 的管控组件,本文实验中只用于演示了云端节点接入操作,没有实际部署OpenYurt的管控组件。
  • Edge-Node 位于边缘,作为集群的边缘节点,部署了节点自治组件 YurtHub,以及云端通道组件 tunnel-agent。

环境准备

(1)三台 Linux 操作系统的计算机。一个作为控制平面节点(同时也是云端节点)、一个作为云端节点和一个作为边缘节点,系统均为 Ubuntu18.04)。

(2)系统预安装 Docker,安装方式参考。

(3)关闭系统交换分区,不同版本系统的关闭方式存在差异,本文环境执行 swapoff -a 关闭。

(4)下载 OpenYurt 社区代码,构建 yurtctl 工具,并将 yurtctl 拷贝到三台主机上。

1
2
3
bash复制代码git clone https://github.com/openyurtio/openyurt.git
cd openyurt
export GOOS=linux GOARCH=amd64; make build WHAT=cmd/yurtctl

构建的 yurtctl 在目录_output/bin/中,其中本文采用的 yurtctl 版本为:

1
2
css复制代码root@master:~# ./yurtctl --version
yurtctl version: projectinfo.Info{GitVersion:"v0.4.1", GitCommit:"3315ccc", BuildDate:"2021-09-08T02:48:34Z", GoVersion:"go1.13", Compiler:"gc", Platform:"linux/amd64"}

一键拉起控制面节点

在 yurtctl 中,提供了init子命令用于拉起 OpenYurt 的管控节点。该节点中部署了 Kubernetes 集群的管控组件(kube-apiserver/kube-scheduler/kube-controller-manager/etcd)。同时也作为 OpenYurt 云端管控节点部署了 OpenYurt 的管控组件(yurt-controller-manager/yurt-app-manager/yurt-tunnel-server)

在控制面节点上,执行如下命令

1
ruby复制代码root@master:~# ./yurtctl init --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers --kubernetes-version=v1.18.8  --pod-network-cidr=10.244.0.0/16

该命令指定了 Kubernetes 相关组件的镜像仓库为 registry.cn-hangzhou.aliyuncs.com/google_containers,此外指定的 Kubernetes 集群的版本为1.18.8(推荐)。yurtctl init 指令的更多参数可以参考yurtctl init –help.

yurtctl init 命令执行成功之后会同步输出添加云端节点和边缘节点的指令。

1
2
3
4
5
6
7
8
9
10
11
sql复制代码Your OpenYurt cluster control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Then you can join any number of edge-nodes by running the following on each as root:
yurtctl join 111.32.157.130:6443 --token tfdxae.lvmb7orduikbyjqu \
--discovery-token-ca-cert-hash sha256:0e1faf696fe976a7b28c03e0dece429c85d72e6e1e6bc2dd1ac3d30d0416f3f0 --node-type=edge-node
And you can join any number of cloud-nodes by running the following on each as root:
yurtctl join 111.32.157.130:6443 --token tfdxae.lvmb7orduikbyjqu \
--discovery-token-ca-cert-hash sha256:0e1faf696fe976a7b28c03e0dece429c85d72e6e1e6bc2dd1ac3d30d0416f3f0 --node-type=cloud-node

根据提示,执行如下命令,拷贝证书到相应的目录,就可以使用 kubectl 操作集群

1
2
3
bash复制代码mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

在 master 节点上,查看 master 节点的状态

1
2
3
ruby复制代码root@master:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready <none> 50s v1.18.8

查看 master 节点组件是否 Running

1
2
3
4
5
6
7
8
9
10
11
sql复制代码root@master:~# kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system controlplane-master 4/4 Running 0 55s
kube-system coredns-546565776c-88hs6 1/1 Running 0 46s
kube-system coredns-546565776c-v5wxb 1/1 Running 0 46s
kube-system kube-flannel-ds-h6qqc 1/1 Running 0 45s
kube-system kube-proxy-6rnq2 1/1 Running 0 45s
kube-system yurt-app-manager-75b7f76546-6dsw9 1/1 Running 0 45s
kube-system yurt-app-manager-75b7f76546-x6wzm 1/1 Running 0 45s
kube-system yurt-controller-manager-697877d548-kd5xf 1/1 Running 0 46s
kube-system yurt-tunnel-server-bc5cb5bf-xxqgj 1/1 Running 0 46s

其中,各个组件的功能如下:

  • controlplane为 all-in-one 的 Kubernetes 管控组件,为了便于理解 OpenYurt 与 Kubernetes 的关系,yurtctl init将 Kubernetes 的管控组件以黑盒的形式部署在同一个 Pod 中。
  • yurt-app-manager为 OpenYurt 的单元化组件,提供 workload 的单元化部署、运维等能力;
  • yurt-controller-manager为节点生命周期管理组件,与边缘节点上的 yurt-hub 配合实现边缘节点的自治功能;
  • yurt-tunnel-server为云边运维通道的 server 端,与边缘节点上的yurt-tunnel-agent配合实现从云到边的运维能力。

一键接入云端节点

云端节点用来部署 OpenYurt 相关的系统组件。在 yurtctl 中,提供了 join 子命令,用于向 OpenYurt 集群中增加云端节点。此外,在用 yurtctl init 初始化 master 节点时,会将 master 节点也作为一个云端节点使用。如果需要增加新的云端节点,可以使用 init 的输出,拷贝云端节点接入指令到需要添加的云端节点上执行。

1
sql复制代码root@cloud-node:~#./yurtctl join 111.32.157.130:6443 --token vowclg.k7059m0f0qbcebpg --discovery-token-ca-cert-hash sha256:30846295ea024260bc3c4988507c4408e8756ca5440221e109fe8167f636f125 --node-type=cloud-node

接入命令中指定了 master 节点的地址,以及接入认证需要的 token 和要接入的节点类型(cloud-node),执行成功输出如下

1
2
3
4
vbscript复制代码This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

在 master 节点上查看刚接入的云端节点状态是否 Ready

1
2
3
4
sql复制代码root@master:~# kubectl get nodes -l openyurt.io/is-edge-worker=false
NAME STATUS ROLES AGE VERSION
cloud-node Ready <none> 5m4s v1.18.8
master Ready <none> 9m40s v1.18.8

一键接入边缘节点

边缘节点作为 OpenYurt 集群实际部署业务的节点,通常部署在用户的内网环境,与管控组件的网络连接通常不稳定。因此,边缘节点上需要部署节点自治组件 yurt-hub 以及云边运维组件 yurt-tunnel-agent。在 yurtctl 中,提供了 join 子命令,用于向 OpenYurt 集群中添加边缘节点。使用 init 中的输出命令,拷贝边缘节点接入指令到需要添加的边缘节点上执行。

1
sql复制代码root@edge-node:~# ./yurtctl join 111.32.157.130:6443 --token vowclg.k7059m0f0qbcebpg --discovery-token-ca-cert-hash sha256:30846295ea024260bc3c4988507c4408e8756ca5440221e109fe8167f636f125  --node-type=edge-node

接入命令中指定了 master 节点的地址,以及接入认证需要的 token 和要接入的节点类型(edge-node),执行成功输出如下

1
2
3
4
vbscript复制代码This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

在master节点上查看刚接入的边缘节点状态是否Ready

1
2
3
kotlin复制代码root@master:~# kubectl get nodes -l openyurt.io/is-edge-worker=true
NAME STATUS ROLES AGE VERSION
edge-node Ready <none> 26s v1.18.8

查看边缘节点的组件是否 Running

1
2
3
4
5
sql复制代码root@master:~# kubectl get pods -A -o wide | grep edge-node
kube-system kube-flannel-ds-tdqtx 1/1 Running 0 58s 103.15.99.183 edge-node <none> <none>
kube-system kube-proxy-8r76s 1/1 Running 0 58s 103.15.99.183 edge-node <none> <none>
kube-system yurt-hub-edge-node 1/1 Running 0 16s 103.15.99.183 edge-node <none> <none>
kube-system yurt-tunnel-agent-v4jwt 1/1 Running 0 38s 103.15.99.183 edge-node <none> <none>

其中,各个边缘节点上各个组件功能如下:

  • yurt-hub 边缘节点自治组件,边缘节点上的组件通过yurt-hub与kube-apiserver交互。当云边网络良好时,yurt-hub转发节点组件的请求到kube-apiserver,并缓存 Response 内容。当云边断网时,edge-hub从本地缓存中获取数据响应边缘节点组件的请求。
  • yurt-tunnel-agent云边运维通道客户端,与yurt-tunnel-server配合,实现从云到边的运维。

经过以上4个步骤,您就可以在本地拥有一套 OpenYurt 集群。如果需要清理 OpenYurt 集群,可以在集群中的每个节点上执行./yurtctl reset。

OpenYurt 背靠原生的 Kubernetes,同时又面向边缘计算场景。由于 Kubernetes 本身的复杂性,导致很多非原生领域的同学难以上手使用。而 OpenYurt 集群的搭建作为上手的第一步,阻挡了大部分的边缘计算玩家。为了提升 OpenYurt 的易用性,yurtctl 设计了 init、join、reset、convert 等工具,目的在于帮助用户快速地在本地搭建 OpenYurt 集群,跨越使用 OpenYurt 的第一步。虽然目前易用性有了很大的提升,但是仍然有很多不足之处。期待社区的同学积极参与,基于 OpenYurt,一起打造更加易用的边缘云原生基础设施。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…523524525…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%