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

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


  • 首页

  • 归档

  • 搜索

每天一个 Linux 命令(16)—— netstat 命令

发表于 2021-11-21

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

命令简介

netstat命令用于显示网络连接、内核路由表和网络接口等各种网络统计数据与状态信息等,一般用于检验本机各端口的网络连接情况。netstat 是在内核中访问网络及相关信息的程序,它能提供 TCP 连接,TCP 和 UDP 监听,进程内存管理的相关报告。

注意,netstat 命令将会隐退,替换的命令是 ss。取代 netstat -r 的是 iproute,取代 netstat -i 的是 ip -s link,取代 netstat -g 的是 ip maddr。

命令格式

1
css复制代码netstat [-acCeFghilMnNoprstuvVwx][-A<网络类型>][--ip]

命令参数

参数 解释
-g,--groups 显示 IPv4 与 IPv6 的广播组成员信息。
-i,-I=iface,--interfaces=iface 显示指定或所有网络接口的统计数据及状态信息等。
-r,--route 显示内核路由表。
-s,--statistics 按照协议分类显示每个协议的汇总统计数据。
-a,--all 显示所有套接字(包括正在监听和未监听的套接字)的状态信息。
-c,--continuous 每秒一次,连续显示指定的信息。
-e,--extend 显示附加的信息。连续指定 -e 选项两次将会显示更多的内容。
-l,--listening 仅显示监听的套接字。
-n,--numeric 只需显示 IP 地址或端口号即可,不必把IP地址解析成相应的主机名或端口名等。
-o,--timers 显示时增加网络时钟方面的信息。
-p,--program 表示显示每个套接字所属程序的名字与进程 ID。
-v,--verbose 显示命令的处理过程与动作。
-t,--tcp 仅显示 TCP 套接字的状态信息。
-u,--udp 仅显示 UDP 套接字的状态信息。
-w,--raw 仅显示原始套接字的状态信息。
-x,--unix 仅显示 UNIX 域套接字的状态信息。
--inet,--ip 显示 TCP、UDP 及原始套接字的状态信息。

输出字段

Active Internet connections (TCP, UDP, raw)

字段 解释
Proto 套接字采用的协议,如 tcp、udp 或 raw。
Recv-Q 本地主机当前套接字接收队列中网络应用程序尚未读取的数据字节计数。
Send-Q 本地主机当前套接字发送队列中尚未确认远程主机是否已读取的数据字节计数。
Local Address 活动套接字的本地主机地址与端口号,其表示形式为“主机:端口号”,“主机”是主机名或 IP 地址。“端口号”是一个网络服务,可以是 /etc/services 文件定义的端口号,也可以是相应的服务名(如 telnet)。除非指定了 -n 选项,本地主机地址字段通常是主机名(或规范域名)与服务名的组合。
Foreign Address 活动套接字的远程主机地址与端口号。表示方法同上。
State 表示网络连接(套接字)的状态。下面是部分常见的状态(注意,raw 模式与 UDP 协议不提供网络连接状态信息,故其状态列通常为空)。

Active UNIX domain Sockets

字段 解释
Proto 套接字采用的协议(通常为 unix)。
RefCnt 引用计数,表示加接到相应套接字的进程数量。
Flags 标志字段。其中 ACC(SO_ACCEPTON)、 W(SO_WAITDATA)或 N(SO_NOSPACE)是可能出现的标志之一。ACC 标志表示一个套接字尚未连接,其相应的进程正在等待网络连接请求。
Type 套接字访问类型。
PID/Program name 打开套接字的进程的 PID 与进程名字。注意,仅当指定了 -p 选项后才输出此项,而且只有超级用户才能看到完整的信息。
Path 套接字的文件路径名。

Kernel Interface table(netstat -i命令)

字段 解释
Iface 网络接口的名字。
MTU 网络接口当前支持的最大传输单位。MTU 是 IP 模块的设备驱动程序一次收发时能够处理的最大字节数量。对于常规的以太网络接口、回环网络接口(loopback)和 IEEE 802.3 网络接口,MTU 的默认值分别为 1500、8232 和 1492。
Met 网络接口当前支持的路由度量值。
RX-OK 正确无误地接收分组数据的数量。
RX-ERR 接收的分组数据本身有误的分组数据数量。
RX-DRP 接收时由于各种原因而丢弃的分组数据数量。
RX-OVR 由于接收错误或处理不及时等原因遗失的分组数据数量。
TX-OK 正确无误发送的分组数据数量。
TX-ERR 发送有误的分组数据数量。
TX-DRP 发送时丢弃的分组数据数量。
TX-OVR 由于发送错误而遗失的分组数据数量。
Flg 网络接口标志。

Kernel IP routing table( netstat -r 命令)

字段 解释
Destination 表示路由的目的主机或网络。
Gateway 表示把分组数据转发到目的主机或网络需要用到的网关。如果这个字段为星号 *,则意味着未使用网关。
Genmask 确定路由时使用的子网掩码。当给定一个 IP 地址,期望找到一个适当的路由时,系统内核将会利用这个子网掩码对IP地址进行逻辑与运算,然后依次检索每一个路由表项,从中找出匹配的路由。
Flags 表示路由当前状态的标志。
C 表示缓存的路由。
MSS MSS(Maximum Segment Size)是最大数据段的字节数量,也是系统内核通过相应路由能够传输的最大数据报大小。
Window 本地系统能够接收远程主机一次传输的最大数据量。
irtt “initial round trip time”的缩写,意即初始的往返传输时间。TCP 协议能够确保在通信的主机之间可靠地传输数据。如果数据传输有误,TCP 将会重传丢失的数据报。TCP 协议采用一个计数器,记录数据报传输到远程主机花费的时间,以及收到一个确认需要多长时间,因而知道在重传数据报之前需要等待多长时间。这个时间称作数据报传输与确认的往返时间。在开始建立网络连接时,TCP协议将会采用一个初始的往返时间作为默认值。对于大多数网络而言,TCP 协议采用的默认往返时间值是适当的,但对于一些速度较慢的网络,如果这个时间太短,将会引起不必要的数据报重传。必要的话,可以利用 route 命令设置 irtt 值。如果这个字段值为 0,意味着采用默认值。
Iface 表示路由经由的网络接口。

参考文档

  • netstat命令
  • 《Linux 常用命令简明手册》—— 邢国庆编著

本文转载自: 掘金

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

“愚公移山”的方法解atoi,自以为巧妙!

发表于 2021-11-21

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

一、写在前面

LeetCode 第一题两数之和传输门:听说你还在写双层for循环解两数之和?

LeetCode 第二题两数之和传输门:两个排序数组的中位数,“最”有技术含量的解法!

LeetCode 第三题最长回文子串传输门:马拉车算法解最长回文子串!Manacher

今天给大家分享的是LeetCode 数组与字符串 第四题:字符串转整数 (atoi),为面试而生,期待你的加入。

二、今日题目

实现 atoi,将字符串转为整数。

该函数首先根据需要丢弃任意多的空格字符,直到找到第一个非空格字符为止。如果第一个非空字符是正号或负号,选取该符号,并将其与后面尽可能多的连续的数字组合起来,这部分字符即为整数的值。如果第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

字符串可以在形成整数的字符后面包括多余的字符,这些字符可以被忽略,它们对于函数没有影响。

当字符串中的第一个非空字符序列不是个有效的整数;或字符串为空;或字符串仅包含空白字符时,则不进行转换。

若函数不能执行有效的转换,返回 0。

说明:

假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。如果数值超过可表示的范围,则返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例:

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
python复制代码示例 1:

输入: "42"
输出: 42

示例 2:

输入: " -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3:

输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。

示例 4:

输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
因此无法执行有效的转换。

示例 5:

输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。

三、 分析

这个题目呢,题目比较长,示例也比较多,可能大家看着比较不耐烦或者“害怕”,我以过来人的身份告诉大家,这个题目,很简单,看我下面对题目的解析吧,如标题所说,我愿将此方法称之为“愚公移山”,直接分情况讨论,然后合并成一般(通用)情况和特色(个例)情况,逐个判断击破即可,欢迎大家评论区分享更多解法。

Process Analysis
可能流程看着还有些复杂,但仔细读,都是比较简单的实现,接下来就一起敲键盘吧~

四、解题

  • 我的方法:
    思路比较简单:就是根据上面的分析,把每个关键点一一击破,那些时候return 0是首要的,然后再看符号怎么处理,数据范围怎么处理…

代码调试过程异常艰辛,运行过程中遇到的各种bug…

1
2
3
4
5
6
7
8
9
10
11
12
python复制代码输入:"-5-"
输出:0
预期:-5

输入:"+-2"
输出:2
预期:0

输入:"5+"
输出:报错,'5+'不能被转换成float
预期:5
这里只列举了一部分典型错误。。。
  • 代码
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
python复制代码'''
题目:
实现 atoi,将字符串转为整数。
ascii to integer
'''

# 我的方法
class Solution(object):
def myAtoi(self, str):
"""
:type str: str
:rtype: int
"""
# 字符串不为空/第一个字符不为数字/-/+
str = str.strip() # 去除两端空格
if str == '': #为空
return 0
if str[0] != '-' and str[0] != '+' and not str[0].isdigit():
return 0 # 第一个字符不为数字/-/+
import re
# 确保字符串内包含数字/+/-
pattern = re.compile("[-+0-9]+")
judge = pattern.findall(str)
# 例如:" ","-","+"
if not judge or judge[0] == '+' or judge[0] == '-':
return 0
# 例如:"++","--","-+/+-","-2-","2-","--2"
if len(judge[0]) >= 2:
# 确保字符串内包含数字
pattern0 = re.compile("[0-9]+")
test01 = pattern0.findall(judge[0])
if not test01:
return 0 # 只有+/-
if not judge[0][1:2].isdigit():
return 0 #去除 ++/-- 属于第一个字符不为数字情况
if judge[0][0] == '-' or judge[0][0] == '+':
judge[0] = judge[0][0] + test01[0] # 第一个字符为 +/- 结果为符号+数字
else: # 只有数字,无符号位
judge[0] = test01[0]
interim_target = float(judge[0]) # 转换成float判断范围
if interim_target < -2147483648:
return -2147483648
if interim_target > 2147483647:
return 2147483647
target = int(judge[0]) # 范围内,转换成int 返回
return target
  • 提交结果

提交结果

测试数据:1079组
运行时间:48ms
击败人百分比:73.24%

好在提交结果还不错,不然,花费这么长时间,真的要吐血啊。。。

五、结语

坚持 and 努力 : 终有所获。

思想很复杂,

实现很有趣,

只要不放弃,

终有成名日。

—《老表打油诗》

下期见,我是爱猫爱技术的老表,如果觉得本文对你学习有所帮助,欢迎点赞、评论、关注我!

本文转载自: 掘金

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

组织架构数据同步效率优化,百倍提速

发表于 2021-11-21

「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」

首先介绍下这个功能的背景:

这是一个多租户系统,即一个用户可以同时有多个租户,系统中有user表(记录登录账号的信息),employee表(记录租户下员工的信息),而用户所拥有的租户就是通过这两个表关联的(即user表一对多employee表)。

正常需要使用系统,需要新建用户,员工,两者之间的关联关系以及用户和员工相关的额外信息,涉及的表结构较多,而且流程比较复杂。

为了方便客户能够更快的接入,我们提供了API给第三方客户调用,来同步他们的员工数据,第三方通过系统提供的API同步员工数据(用户),但是部分客户的反馈并不是很好,具体问题看下文。

1 优化前的流程

处理的方式:一次接口请求接收到一个数据集合,之后一条条消化处理落库,最后返回处理的情况给调用者。

对于五千内量级的用户来说其实影响不大,也可以接受。但是当企业的用户量在一万以上,第一次同步的时候是全量数据就花费很长的时间,在一小时以上,随着数据量的增加时间只会更长。

图片

流程如下:

  • 调用API,list集合传输批量数据;
  • for循环处理list集合,获取单条数据;
  • 校验数据的有效性:手机号/邮箱/工号格式,使用google的libphonenumber校验手机号是否合法,性别/员工状态是否合法,岗位/部门/额外字段(员工扩展信息)是否存在,生成姓名生成拼音等;
  • 校验不符合条件的数据标注其状态,符合条件的继续后面的流程;
  • 员工入库:新增或更新员工表信息;
  • 员工岗位关联入库:新增或更新员工和部门岗位的关联关系表;
  • 额外字段入库:新增或更新员工额外信息表;
  • 用户入库:根据姓名生成头像,新增或更新员工对应的用户表;
  • 用户角色入库:新增或更新用户对应的用户角色关联表;
  • 用户员工关联入库:新增或更新用户和关联的关联关系表;
  • 刷新es:把员工和用户数据更新到elasticsearch中,方便移动端搜索查询;
  • 一条数据完成后继续下一条数据,循环处理每一条数据,有问题的数据标注错误状态,然后给出具体错误的message;
  • 全部更新完成之后返回;

缺点: 速度慢,虽然是批量接收但是为了保持数据的正确性和完整性,校验和入库的逻辑处理都是单条顺序处理的。由于涉及的表较多而且逻辑复杂,存在锁的使用。

优点: 各项数据的完整性比较好,入库的数据规范控制比较好。

由于API的同步速度实在不太好, 在功能都已经开发完之后重新整理的下相关的逻辑,准备重新开发一个API,提高其同步效率。系统中同步数据的时候不仅仅有员工和用户,还包括数据正确性的校验,如手机号,邮箱,工号,性别,部门,额外字段,还有根据姓名生成头像,姓名生成拼音等操作,一长串逻辑在事务中处理并最终所有结果返回给调用端。

假如按照原来的设计执行,如果有一万条数据 那么最终调用数据库处理的次数会超过10万次(啊,这么多)。如果要缩短时间提高效率,上图提到的业务是不能缩减的,不然就会出现数据不完整进而影响用户正常使用,只能在调用次数上优化。

2 优化后的API流程

最终设计的的方案:实现真正的批量处理,新增一张中间表,用来存储API请求的所有数据集合(不管数据是否符合条件全部存入中间表),后续所有的正式表通过和这个中间表关联一次请求n条数据批量处理。数据校验,入库等都在数据库中批量完成。速度还是很快的,1万数据在1分钟内完成。

流程如下:

图片

处理流程:

  • 调用API,list集合传输批量数据;
  • 按照每次请求的批次 插入到中间表;
  • 数据校验:直接用sql校验中间表的邮箱/手机号/工号格式是否符合;用两表关联的sql校验工号/邮箱/手机号/岗位/部门等是否与正式表中的重复;
  • 员工相关表:用中间表与需要操作的员工相关的表关联,新增/更新员工表,员工岗位表,额外字段表等;
  • 用户相关表:用中间表与需要操作的用户相关的表关联,新增/更新用户表,用户info表,用户员工关联表等;
  • 状态更改:每次数据校验和更新相关表的时候,不符合条件的中间表数据都会被更改状态。
  • 流程执行完之后 把这一批次的所有数据再次返回给接口(包含每条数据的处理状态)。
  • 用消息队列更新es数据和姓名拼音,而头像直接使用默认的图片,不根据姓名生成。

当然,为了数据的正确性还做了事务的控制以及API的请求限制。

优点: 相比之前的API的速度要提升了百倍,再也不用在同步的时候苦苦等待了,可以在短时间内把主要数据同步成功。

缺点: 要求数据的格式控制不那么精确了。例如 放弃手机有效性检查(只检查是不是数字),同步es,更新拼音等不再是实时的了,头像不使用姓名生成,而是使用默认的图片。

如下是使用优化后的API同步数据的测试结果:

单次API数据量(条) 花费时间(ms)
2000 4965
5000 7556
10000 16950
10000 20202 |

从测试结果可以看出 一万条数据可以在半分钟内结束,大大的减少了同步的时间。

3 总结

本次优化是使用批量操作数据库,减少数据库的调用次数,以及在业务层去除单条数据处理的方式。就像用户头像,elasticsearch数据等非主数据可以放入队列异步处理。还有手机号使用正则表达式只校验是数字即可,至于是不是有效手机号可以先忽略,后续可以使用其他方式校验(例如后续系统自检查然后推给管理员修改)。


我是纪先生,用输出倒逼输入而持续学习,持续分享技术系列文章,以及全网值得收藏好文,欢迎关注或者关注公众号,做一个持续成长的技术人。

实际问题系列的历史文章(也可以在掘金专栏中看其他相关文章)

1. 好用工具:慢SQL分析pt-query-digest;

2. 好用工具: pt-online-schame-change;

3. 怎么防止短信被白嫖;

4. 巧用双索引避免es出现索引不存在的问题;

5. 本地复现不了问题,那就线上debug;

本文转载自: 掘金

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

Midwayjs 使用RabbitMQ的使用过程 Midw

发表于 2021-11-21

Midway.js 使用RabbitMQ的使用过程

前言

这段时间在做公司的一个项目,这个项目是基于midway.js为架构的一个项目。 主要是基于typescript的一个nodejs的web框架。 因为要用到rabbitmq,所以,在midway官网上调试了rabbitmq相关的demo,发现demo会出现生成者能够正常生产消息,但是消费者订阅后并没有收到生产者发出的消息的bug,所以经过我的的思考和查阅了相当大的一部分资料,顺利的吧把demo跑通了。以下就是我解决的一下方法和思路。

  1. 安装

官方参考链接

前提是已经部署好了rabbitmq服务和midway
1.1 安装依赖

1
2
ruby复制代码$ cnpm i @midwayjs/rabbitmq amqplib --save
$ cnpm i @types/amqplib --save-dev

1.2 创建服务要使用的文件

image.png

以上 server/index.js 消费者 用来监听rabbitmq中的队列, 直接用node运行

src/consumer/userConsumer.ts 不过没有用到这个文件

src/service/rabbitmq.ts rabbitmq服务

src/controller/home.ts 本身文件就有, 用这个文件来发起请求
src/config 配置文件,配置rabbitmq的链接信息

  1. 使用

server/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
javascript复制代码const amqp = require("amqplib/callback_api")

// 创建连接
amqp.connect('amqp://localhost', (err, conn) => {
// 创建通道
conn.createChannel((err, ch) => {
// 定义队列的名称
const q = 'task_queue'
// Declare the queue 声明队列
ch.assertQueue(q, { durable: true })

// Wait for Queue Messages 监听消息
console.log(` [*] Waiting for messages in ${q}. To exit press CTRL+C`)

ch.consume( q, msg => {
console.log(` [x] Received ${msg.content.toString()}`)
}, { noAck: true }
)
})
})

src/consumer/userConsumer.ts

1
复制代码没啥用,现在这里埋个坑

src/service/rabbitmq.ts

这里我没有根据官方的demo去写,自己重新定义了一个类

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
kotlin复制代码import {Provide, Scope, ScopeEnum, Init, Config} from "@midwayjs/decorator";
import { Connection, Channel, Message, Options } from 'amqplib';
import * as rabbitmq from 'amqplib';

interface IRabbitConf {
hostname: string;
prot: number;
username: string;
password: string;
queueName: string[];
}

@Scope(ScopeEnum.Singleton) // singleon 单例模式 全局唯一 ,进程级别
@Provide('rabbitmqService')
export class RabbitmqService {

public config: Options.Connect;
public connection: Connection;
public channel: Channel;
private isAsert: Record<string, boolean>;
private queueNames: string[];
private isCheck: boolean;

// 注意这个地方, 使用的是config文件中的配置,所以记得要在config中配置
@Config('rabbit')
rabbitConf: IRabbitConf;


@Init()
async connect() {
console.log("正在连接rabbitmq")

this.config = Object.assign(
{
protocol: 'amqp',
hostname: 'localhost',
port: 5672,
username: 'fgc1101',
password: 'fgc19941030',
frameMax: 0,
heartbeat: 0,
vhost: '/',
},
this.rabbitConf
);

console.log(this.rabbitConf)
this.connection = await rabbitmq.connect(this.config);
await this.creatChannel();
this.queueNames = this.rabbitConf.queueName || [];
this.isAsert = {};
this.isCheck = false;
}

async checkQueue() {
if (!this.channel) {
await this.creatChannel();
}
for (const queueName of this.queueNames) {
const checkQueue = await this.channel.checkQueue(queueName);
if (checkQueue?.queue === queueName) this.isAsert[queueName] = true;
}
this.isCheck = true;
}

close() {
this.connection.close();
}

async creatChannel() {
this.channel = await this.connection.createChannel();
}

async push(
queueName: string,
msg: string,
option?: { priority?: number; durable?: boolean; siId?: string }
) {
const options = {
priority: 0,
durable: true,
headers: { time: 0, siId: option?.siId },
...option,
};
if (!this.queueNames.includes(queueName)){
console.log(`you did not define default queueName ${queueName}`)
return new Error(`you did not define default queueName ${queueName}`);
}

if (!this.isCheck) {
await this.checkQueue();
}
if (!this.isAsert[queueName]) {
this.channel.assertQueue(queueName, { durable: options.durable });
this.isAsert[queueName] = true;
}
this.channel.sendToQueue(queueName, Buffer.from(msg), options);
}

async pull(queueName: string): Promise<false | Message> {
if (!this.queueNames.includes(queueName))
new Error(`you did not default queueName ${queueName}`);

if (!this.isCheck) {
await this.checkQueue();
}
if (!this.isAsert[queueName]) {
await this.channel.assertQueue(queueName, { durable: true });
await this.channel.prefetch(1);
this.isAsert[queueName] = true;
}
return this.channel.get(queueName, { noAck: false });
}

async repush(msg: Message) {
const { fields, properties, content } = msg;
if (!this.isCheck) {
await this.checkQueue();
}
if (!this.isAsert[fields.routingKey]) {
this.channel.assertQueue(fields.routingKey, { durable: true });
this.channel.prefetch(1);
this.isAsert[fields.routingKey] = true;
}
const { headers } = properties;

this.channel.ack(msg);
headers.time++;
this.channel.sendToQueue(fields.routingKey, content, properties);
}

ack(msg: Message) {
this.channel.ack(msg);
}

nack(msg: Message) {
this.channel.nack(msg);
}

}

src/controller/home.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kotlin复制代码
import {Controller, Get, Inject, Provide} from '@midwayjs/decorator';
import {RabbitmqService} from "../service/rabbitmq";

@Provide()
@Controller('/')
export class HomeController {

@Inject()
rabbitmqService : RabbitmqService

@Get('/')
async home() {

console.log("测试连接rabbitmq服务")
await this.rabbitmqService.push('task_queue', 'world typeScript');

return {success: true, message: 'OK', data: {}};
}

}

src/config/config.default.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
arduino复制代码export default (appInfo: EggAppInfo) => {
// 数据库配置
config.sequelize = {
...
}

config.rabbit = {
hostname: '127.0.0.1',
prot: 5672,
username: 'fgc1101',
password: 'fgc19941030',
queueName: ['task_queue']
}

return config;
}
  1. 注意点

3.1 配置文件不要忘记写

3.2 监听队列的文件可以直接用js写

  1. 测试

4.1 启动服务

1
arduino复制代码npm run dev

image.png

4.2 根据控制器访问路径

image.png

4.3 进入的Server目录下执行消费者服务的代码

1
复制代码 node index.js

image.png

image.png

本文转载自: 掘金

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

Spring Cloud Gateway的断路器(Circu

发表于 2021-11-21

欢迎访问我的GitHub

github.com/zq2599/blog…

内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

本篇概览

  • 一起深入了解Spring Cloud Gateway的断路器(CircuitBreaker)功能:
  1. 先聊聊理论
  2. 再结合官方和大神的信息确定技术栈
  3. 再动手开发,先实现再验证
  4. 再趁热打铁,看看它的源码
  5. 最后,回顾一下有哪些不足(下一篇文章解决这些不足)

关于断路器(CircuitBreaker)

  • 下图来自resilience4j官方文档,介绍了什么是断路器:

在这里插入图片描述

  1. CLOSED状态时,请求正常放行
  2. 请求失败率达到设定阈值时,变为OPEN状态,此时请求全部不放行
  3. OPEN状态持续设定时间后,进入半开状态(HALE_OPEN),放过部分请求
  4. 半开状态下,失败率低于设定阈值,就进入CLOSE状态,即全部放行
  5. 半开状态下,失败率高于设定阈值,就进入OPEN状态,即全部不放行

确认概念

  • 有个概念先确认一下,即Spring Cloud断路器与Spring Cloud Gateway断路器功能不是同一个概念,Spring Cloud Gateway断路器功能还涉及过滤器,即在过滤器的规则下使用断路器:

在这里插入图片描述

  • 本篇的重点是Spring Cloud Gateway如何配置和使用断路器(CircuitBreaker),因此不会讨论Resilience4J的细节,如果您想深入了解Resilience4J,推荐资料是Spring Cloud Circuit Breaker

关于Spring Cloud断路器

  • 先看Spring Cloud断路器,如下图,Hystrix、Sentinel这些都是熟悉的概念:

在这里插入图片描述

关于Spring Cloud Gateway的断路器功能

  • 来看Spring Cloud Gateway的官方文档,如下图,有几个关键点稍后介绍:

在这里插入图片描述

  • 上图透露了几个关键信息:
  1. Spring Cloud Gateway内置了断路器filter,
  2. 具体做法是使用Spring Cloud断路器的API,将gateway的路由逻辑封装到断路器中
  3. 有多个断路器的库都可以用在Spring Cloud Gateway(遗憾的是没有列举是哪些)
  4. Resilience4J对Spring Cloud 来说是开箱即用的
  • 简单来说Spring Cloud Gateway的断路器功能是通过内置filter实现的,这个filter使用了Spring Cloud断路器;
  • 官方说多个断路器的库都可以用在Spring Cloud Gateway,但是并没有说具体是哪些,这就郁闷了,此时咱们去了解一位牛人的观点:Piotr Mińkowski,就是下面这本书的作者:

在这里插入图片描述

  • Piotr Mińkowski的博客对Spring Cloud Gateway的断路器功能做了详细介绍,如下图,有几个重要信息稍后会提到:

在这里插入图片描述

  • 上图可以get到三个关键信息:
  1. 从2.2.1版本起,Spring Cloud Gateway集成了Resilience4J的断路器实现
  2. Netflix的Hystrix进入了维护阶段(能理解为即将退休吗?)
  3. Netflix的Hystrix依然可用,但是已废弃(deprecated),而且Spring Cloud将来的版本可能会不支持
  • 再关联到官方文档也以resilience4为例(如下图),胆小的我似乎没有别的选择了,就Resilience4J吧:

在这里插入图片描述

  • 理论分析就到此吧,接下来开始实战,具体的步骤如下:
  1. 准备工作:服务提供者新增一个web接口/account/{id},根据入参的不同,该接口可以立即返回或者延时500毫秒返回
  2. 新增名为circuitbreaker-gateway的子工程,这是个带有断路器功能的Spring Cloud Gateway应用
  3. 在circuitbreaker-gateway里面编写单元测试代码,用来验证断路器是否正常
  4. 运行单元测试代码,观察断路器是否生效
  5. 给断路器添加fallback并验证是否生效
  6. 做一次简单的源码分析,一为想深入了解断路器的同学捋清楚源码路径,二为检验自己以前了解的springboot知识在阅读源码时有么有帮助

源码下载

  • 本篇实战中的完整源码可在GitHub下载到,地址和链接信息如下表所示(github.com/zq2599/blog…%EF%BC%9A)
名称 链接 备注
项目主页 github.com/zq2599/blog… 该项目在GitHub上的主页
git仓库地址(https) github.com/zq2599/blog… 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在spring-cloud-tutorials文件夹下,如下图红框所示:

在这里插入图片描述

  • spring-cloud-tutorials文件夹下有多个子工程,本篇的代码是circuitbreaker-gateway,如下图红框所示:

在这里插入图片描述

准备工作

  • 咱们要准备一个可控的web接口,通过参数控制它成功或者失败,这样才能触发断路器
  • 本篇的实战中,服务提供者依旧是provider-hello,为了满足本次实战的需求,咱们在Hello.java文件中增加一个web接口,对应的源码如下:
1
2
3
4
5
6
7
8
java复制代码    @RequestMapping(value = "/account/{id}", method = RequestMethod.GET)
public String account(@PathVariable("id") int id) throws InterruptedException {
if(1==id) {
Thread.sleep(500);
}

return Constants.ACCOUNT_PREFIX + dateStr();
}
  • 上述代码很简单:就是接收id参数,如果等于1就延时五百毫秒,不等于1就立即返回
  • 如果把断路器设置为超过两百毫秒就算失败,那么通过控制id参数的值,咱们就能模拟请求成功或者失败了,这是验证断路器功能的关键
  • 准备完成,开始写代码

实战

  • 在父工程spring-cloud-tutorials下面新增子工程circuitbreaker-gateway
  • 增加以下依赖
1
2
3
4
xml复制代码<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
  • 配置文件application.yml如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
yml复制代码server:
#服务端口
port: 8081
spring:
application:
name: circuitbreaker-gateway
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Path=/hello/**
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
  • 启动类:
1
2
3
4
5
6
7
8
9
10
11
java复制代码package com.bolingcavalry.circuitbreakergateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CircuitbreakerApplication {
public static void main(String[] args) {
SpringApplication.run(CircuitbreakerApplication.class,args);
}
}
  • 配置类如下,这是断路器相关的参数配置:
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
java复制代码package com.bolingcavalry.circuitbreakergateway.config;

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;

@Configuration
public class CustomizeCircuitBreakerConfig {

@Bean
public ReactiveResilience4JCircuitBreakerFactory defaultCustomizer() {

CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() //
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED) // 滑动窗口的类型为时间窗口
.slidingWindowSize(10) // 时间窗口的大小为60秒
.minimumNumberOfCalls(5) // 在单位时间窗口内最少需要5次调用才能开始进行统计计算
.failureRateThreshold(50) // 在单位时间窗口内调用失败率达到50%后会启动断路器
.enableAutomaticTransitionFromOpenToHalfOpen() // 允许断路器自动由打开状态转换为半开状态
.permittedNumberOfCallsInHalfOpenState(5) // 在半开状态下允许进行正常调用的次数
.waitDurationInOpenState(Duration.ofSeconds(5)) // 断路器打开状态转换为半开状态需要等待60秒
.recordExceptions(Throwable.class) // 所有异常都当作失败来处理
.build();

ReactiveResilience4JCircuitBreakerFactory factory = new ReactiveResilience4JCircuitBreakerFactory();
factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(200)).build())
.circuitBreakerConfig(circuitBreakerConfig).build());

return factory;
}
}
  • 上述代码有一次需要注意:timeLimiterConfig方法设置了超时时间,服务提供者如果超过200毫秒没有响应,Spring Cloud Gateway就会向调用者返回失败
  • 开发完成了,接下来要考虑的是如何验证

单元测试类

  • 为了验证Spring Cloud Gateway的断路器功能,咱们可以用Junit单元测试来精确控制请求参数和请求次数,测试类如下,可见测试类会连续发一百次请求,在前五十次中,请求参数始终在0和1之间切换,参数等于1的时候,接口会有500毫秒延时,超过了Spring Cloud Gateway的200毫秒超时限制,这时候就会返回失败,等失败多了,就会触发断路器的断开:
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
java复制代码package com.bolingcavalry.circuitbreakergateway;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureWebTestClient
public class CircuitbreakerTest {

// 测试的总次数
private static int i=0;

@Autowired
private WebTestClient webClient;

@Test
@RepeatedTest(100)
void testHelloPredicates() throws InterruptedException {
// 低于50次时,gen在0和1之间切换,也就是一次正常一次超时,
// 超过50次时,gen固定为0,此时每个请求都不会超时
int gen = (i<50) ? (i % 2) : 0;

// 次数加一
i++;

final String tag = "[" + i + "]";

// 发起web请求
webClient.get()
.uri("/hello/account/" + gen)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectBody(String.class).consumeWith(result -> System.out.println(tag + result.getRawStatusCode() + " - " + result.getResponseBody()));

Thread.sleep(1000);
}
}

验证

  • 启动nacos(服务提供者依赖的)
  • 启动子工程provider-hello
  • 运行咱们刚才开发的单元测试类,控制台输入的内容截取部分如下,稍后会有分析:
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
shell复制代码[2]504 - {"timestamp":"2021-08-28T02:55:42.920+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"594efed1"}
[3]200 - Account2021-08-28 10:55:43
[4]504 - {"timestamp":"2021-08-28T02:55:45.177+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"427720b"}
[5]200 - Account2021-08-28 10:55:46
[6]503 - {"timestamp":"2021-08-28T02:55:47.227+00:00","path":"/hello/account/1","status":503,"error":"Service Unavailable","message":"","requestId":"6595d7f4"}
[7]503 - {"timestamp":"2021-08-28T02:55:48.250+00:00","path":"/hello/account/0","status":503,"error":"Service Unavailable","message":"","requestId":"169ae1c"}
[8]503 - {"timestamp":"2021-08-28T02:55:49.259+00:00","path":"/hello/account/1","status":503,"error":"Service Unavailable","message":"","requestId":"53b695a1"}
[9]503 - {"timestamp":"2021-08-28T02:55:50.269+00:00","path":"/hello/account/0","status":503,"error":"Service Unavailable","message":"","requestId":"4a072f52"}
[10]504 - {"timestamp":"2021-08-28T02:55:51.499+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"4bdd96c4"}
[11]200 - Account2021-08-28 10:55:52
[12]504 - {"timestamp":"2021-08-28T02:55:53.745+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"4e0e7eab"}
[13]200 - Account2021-08-28 10:55:54
[14]504 - {"timestamp":"2021-08-28T02:55:56.013+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"27685405"}
[15]503 - {"timestamp":"2021-08-28T02:55:57.035+00:00","path":"/hello/account/0","status":503,"error":"Service Unavailable","message":"","requestId":"3e40c5db"}
[16]503 - {"timestamp":"2021-08-28T02:55:58.053+00:00","path":"/hello/account/1","status":503,"error":"Service Unavailable","message":"","requestId":"2bf2698b"}
[17]503 - {"timestamp":"2021-08-28T02:55:59.075+00:00","path":"/hello/account/0","status":503,"error":"Service Unavailable","message":"","requestId":"38cb1840"}
[18]503 - {"timestamp":"2021-08-28T02:56:00.091+00:00","path":"/hello/account/1","status":503,"error":"Service Unavailable","message":"","requestId":"21586fa"}
[19]200 - Account2021-08-28 10:56:01
[20]504 - {"timestamp":"2021-08-28T02:56:02.325+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"4014d6d4"}
[21]200 - Account2021-08-28 10:56:03
[22]504 - {"timestamp":"2021-08-28T02:56:04.557+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"173a3b9d"}
[23]200 - Account2021-08-28 10:56:05
[24]504 - {"timestamp":"2021-08-28T02:56:06.811+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"aa8761f"}
[25]200 - Account2021-08-28 10:56:07
[26]504 - {"timestamp":"2021-08-28T02:56:09.057+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"769bfefc"}
[27]200 - Account2021-08-28 10:56:10
[28]504 - {"timestamp":"2021-08-28T02:56:11.314+00:00","path":"/hello/account/1","status":504,"error":"Gateway Timeout","message":"","requestId":"2fbcb6c0"}
[29]503 - {"timestamp":"2021-08-28T02:56:12.332+00:00","path":"/hello/account/0","status":503,"error":"Service Unavailable","message":"","requestId":"58e4e70f"}
[30]503 - {"timestamp":"2021-08-28T02:56:13.342+00:00","path":"/hello/account/1","status":503,"error":"Service Unavailable","message":"","requestId":"367651c5"}
  • 分析上述输出的返回码:
  1. 504是超时返回的错误,200是服务提供者的正常返回
  2. 504和200两种返回码都表示请求到达了服务提供者,所以此时断路器是关闭状态
  3. 多次504错误后,达到了配置的门限,触发断路器开启
  4. 连续出现的503就是断路器开启后的返回码,此时请求是无法到达服务提供者的
  5. 连续的503之后,504和200再次交替出现,证明此时进入半开状态,然后504再次达到门限触发断路器从半开转为开启,五十次之后,由于不在发送超时请求,断路器进入关闭状态

fallback

  • 通过上述测试可见,Spring Cloud Gateway通过返回码来告知调用者错误信息,这种方式不够友好,我们可以自定义fallback,在返回错误时由它来构建返回信息
  • 再开发一个web接口,没错,就是在circuitbreaker-gateway工程中添加一个web接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码package com.bolingcavalry.circuitbreakergateway.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;

@RestController
public class Fallback {

private String dateStr(){
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
}

/**
* 返回字符串类型
* @return
*/
@GetMapping("/myfallback")
public String helloStr() {
return "myfallback, " + dateStr();
}
}
  • application.yml配置如下,可见是给filter增加了fallbackUri属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
yml复制代码server:
#服务端口
port: 8081
spring:
application:
name: circuitbreaker-gateway
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Path=/hello/**
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/myfallback
  • 再运行单元测试,可见返回码全部是200,原来的错误现在全部变成了刚才新增的接口的返回内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
shell复制代码[2]200 - myfallback, 2021-08-28 11:15:02
[3]200 - Account2021-08-28 11:15:03
[4]200 - myfallback, 2021-08-28 11:15:04
[5]200 - Account2021-08-28 11:15:05
[6]200 - myfallback, 2021-08-28 11:15:06
[7]200 - myfallback, 2021-08-28 11:15:08
[8]200 - myfallback, 2021-08-28 11:15:09
[9]200 - myfallback, 2021-08-28 11:15:10
[10]200 - myfallback, 2021-08-28 11:15:11
[11]200 - Account2021-08-28 11:15:12
[12]200 - myfallback, 2021-08-28 11:15:13
[13]200 - Account2021-08-28 11:15:14
[14]200 - myfallback, 2021-08-28 11:15:15
  • 至此,咱们已完成了Spring Cloud Gateway的断路器功能的开发和测试,如果聪明好学的您并不满足这寥寥几行配置和代码,想要深入了解断路器的内部,那么请您接往下看,咱们聊聊它的源码;

源码分析

  • RouteDefinitionRouteLocator的构造方法(bean注入)中有如下代码,将name和实例绑定:
1
java复制代码gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
  • 然后会在loadGatewayFilters方法中使用这个map,找到上面put的bean;
  • 最终的效果:路由配置中指定了name等于CircuitBreaker,即可对应SpringCloudCircuitBreakerFilterFactory类型的bean,因为它的name方法返回了”CircuitBreaker”,如下图:

在这里插入图片描述

  • 现在的问题:SpringCloudCircuitBreakerFilterFactory类型的bean是什么?如下图红框,SpringCloudCircuitBreakerResilience4JFilterFactory是SpringCloudCircuitBreakerFilterFactory唯一的子类:

在这里插入图片描述

  • 从上图来看,CircuitBreaker类型的filter应该是SpringCloudCircuitBreakerResilience4JFilterFactory,不过那只是从继承关系推断出来的,还差一个关键证据:在spring中,到底存不存在SpringCloudCircuitBreakerResilience4JFilterFactory类型的bean?
  • 最终发现了GatewayResilience4JCircuitBreakerAutoConfiguration中的配置,可以证明SpringCloudCircuitBreakerResilience4JFilterFactory会被实例化并注册到spring:
1
2
3
4
5
6
7
8
java复制代码@Bean
@ConditionalOnBean(ReactiveResilience4JCircuitBreakerFactory.class)
@ConditionalOnEnabledFilter
public SpringCloudCircuitBreakerResilience4JFilterFactory springCloudCircuitBreakerResilience4JFilterFactory(
ReactiveResilience4JCircuitBreakerFactory reactiveCircuitBreakerFactory,
ObjectProvider<DispatcherHandler> dispatcherHandler) {
return new SpringCloudCircuitBreakerResilience4JFilterFactory(reactiveCircuitBreakerFactory, dispatcherHandler);
}
  • 综上所述,当您配置了CircuitBreaker过滤器时,实际上是SpringCloudCircuitBreakerResilience4JFilterFactory类在为您服务,而关键代码都集中在其父类SpringCloudCircuitBreakerFilterFactory中;
  • 所以,要想深入了解Spring Cloud Gateway的断路器功能,请阅读SpringCloudCircuitBreakerFilterFactory.apply方法

一点遗憾

  • 还记得刚才分析控制台输出的那段内容吗?就是下图红框中的那段,当时咱们用返回码来推测断路器处于什么状态:

在这里插入图片描述

  • 相信您在看这段纯文字时,对欣宸的分析还是存在疑惑的,根据返回码就把断路器的状态确定了?例如504的时候到底是关闭还是半开呢?都有可能吧,所以,这种推测只能证明断路器正在工作,但是无法确定某个时刻具体的状态
  • 所以,咱们需要一种更准确的方式知道每个时刻断路器的状态,这样才算对断路器有了深刻了解
  • 接下来的文章中,咱们在今天的成果上更进一步,在请求中把断路器状态打印出来,那就…敬请期待吧,欣宸原创,从未让您失望;

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界…
github.com/zq2599/blog…

本文转载自: 掘金

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

Java实现图片转字符输出示例demo

发表于 2021-11-21

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」。

前面几篇博文介绍了使用jdk来对图片做一些有意思的转换,接下来我们再介绍一个有意思的玩法,直接根据图片,输出一个二维字符数组,实现用字符来实现绘画的场景

各位小伙伴可能都有看到过一些有趣的注释,比如大佛,美女之类的,通关本文,相信你也很可以很简单的实现类似的场景

关键实现,在前面的文章中其实也说到了,下面是超链

  • Java实现图片灰度化
  • Java实现图片转字符图片示例demo
  • Java实现Gif图转字符动图

接下来我们需要做的就是将之前转成字符图片输出的地方稍微改一下,根据当前色颜色,来选择合适的替换字符保存下来

所以关键的实现在于,如何根据颜色来选择字符

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码// 这个字符来自于github搜索结果,下面将最后一个从原来的点号改成了空格,即白色时,不输出字符
private static final String DEFAULT_CHAR_SET = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\\\"^`' ";

/**
* 基于颜色的灰度值,获取对应的字符
* @param g
* @return
*/
public static char toChar(Color g) {
double gray = 0.299 * g.getRed() + 0.578 * g.getGreen() + 0.114 * g.getBlue();
return DEFAULT_CHAR_SET.charAt((int) (gray / 255 * DEFAULT_CHAR_SET.length()));
}

接下来我们针对之前的方法,稍微改造一下

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
java复制代码Color getAverage(BufferedImage image, int x, int y, int w, int h) {
int red = 0;
int green = 0;
int blue = 0;

int size = 0;
for (int i = y; (i < h + y) && (i < image.getHeight()); i++) {
for (int j = x; (j < w + x) && (j < image.getWidth()); j++) {
int color = image.getRGB(j, i);
red += ((color & 0xff0000) >> 16);
green += ((color & 0xff00) >> 8);
blue += (color & 0x0000ff);
++size;
}
}

red = Math.round(red / (float) size);
green = Math.round(green / (float) size);
blue = Math.round(blue / (float) size);
return new Color(red, green, blue);
}

private void parseChars(BufferedImage img) {
int w = img.getWidth(), h = img.getHeight();
// 这个size可用来控制精度,越小则越像原图
int size = 4;
List<List<String>> list = new ArrayList<>();
for (int y = 0; y < h; y += size) {
List<String> line = new ArrayList<>();
for (int x = 0; x < w; x += size) {
Color avgColor = getAverage(img, x, y, size, size);
line.add(String.valueOf(toChar(avgColor)));
}
list.add(line);
}

System.out.println("---------------------- 开始 ------------------------");
for (List<String> line: list) {
for (String s: line) {
System.out.print(s + " ");
}
System.out.println();
}
System.out.println("---------------------- 结束 ------------------------");
}

注意上面的实现,需要重点注意的是原图的遍历方式,一层一层的遍历,即外部是y轴,内部循环是x轴

接下来看一下测试case

1
2
3
4
5
6
7
8
9
java复制代码@Test
public void testChars() throws Exception{
String file = "http://pic.dphydh.com/pic/newspic/2017-12-13/505831-1.png";
BufferedImage img = ImageLoadUtil.getImageByPath(file);
// 缩放一下图片为300x300,方便对输出字符截图
img = GraphicUtil.scaleImg(300,300, img);
parseChars(img);
System.out.println("---over------");
}

实际输出如下(实际输出结果与皮神还是很像的)

00.jpg

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
text复制代码---------------------- 开始 ------------------------
l m
' b $ I
f $ $ [
\ 8 $ $ f
i ~ , x $ $ $ u
_ $ $ a X } ^ ' W $ $ $ c
c $ $ $ $ B L ] ' } q $ $ $ z
` d $ $ $ $ $ 0 r ( " t < U $ $ c
, * $ $ $ $ z < + j | ` \ t < < O $ n
l W $ $ $ U < < < ~ t [ { + < < _ W f
> & $ $ 0 < < < < < - j ~ \ < < < < n (
! # $ k < < < < < < < ( t ` j < < < < ] ?
: k B 1 + < < < < < < + n ! > } < < < < \ i
^ C z ( [ ~ < < < < < < f ] 1 < < < < < u `
1 v ( ) ? < < < < < < | { ( < < < < < u
I v / ( 1 + < < < < < 1 } ' l > i " \ < < < < ~ x
1 v ( ( [ ~ < < < < z r z t | \ n z f ( + ' t < < < < ? /
" / v | ) ? < < < < < < < < < < < < < ] f ) ^ " \ < < < < | ?
" ) n v / ~ < < < < < < < < < < < < < ~ j [ l 1 < < < + z ^
- | < < < < < < < < < < < < < < < < ] j ~ { < < < [ u
' f < < < < < < < < < < < < < < < < < < ~ z | < < ~ ( /
] + < < < < < < < < < < < < < < < < < < < < n - < ? n i
' | < < < < < < < < < < < < < < < < < < < < < < { ~ ) v
) + < ] 0 w f < < < < < < < < < < < < < < < < < < ? / 1
x < ~ * @ " | [ < < < < < < < < < < < < < < < < < } c '
i ( < } $ $ x w \ < < < < < < < < < < < < < < < < < / -
/ < < + % $ $ 8 _ < < < < < < < < < < < < < < < < < { >
_ q f < < ( q m } < < < < < < < < < < < < { \ ~ < < < } <
" O U Z < < < < < < < < < < n f < < < < < n r [ h + < < \ "
j U U 0 } < < < < _ < < < < ~ ~ < < < < < M u ( $ r < < t
U U U Q ( < < < < { Y v Y 0 Z } < < < < < * $ $ $ x < < |
J U U O [ < < < < < # * # # o a t < < < < j $ $ # _ < < )
Y U U O < < < < < < W b q q k # # O r \ < < [ / + < < } <
x U L r < < < < < < d U c c C w * o L < < < < < < < < f '
} Q n < < < < < < < J x x x x z w W [ < < < < < < < < f : + [ { ,
^ f < < < < < < < < L x x x x x J Y < < < < < < _ Y O Y ] \ j \ [ _ } -
/ < < < < < < < < L x x x x x C _ < < < < < - Z U U Z j ? < < < < < \ v >
l | < < < < < < < Y x x x x X | < < < < < < J U U U z < < < < < < < + ) (
i ) t / L | + < < < < < c x x x c x < < < < < < ) L U U C t < < < < < < < < f !
? x ( < < < < ? f x j ~ < < 1 J x Y x < < < < < < < u U U U 0 < < < < < < < < + r
1 { < < < < < < < < < _ x \ < < t v 1 < < < < < < < < n U U Z [ < < < < < < < v x _
< ) < < < < < < < < < < < < { u ] < < < < < < < < < < < - 0 m { < < < < < < < } n | / n r ( / \ 1 } i '
/ < < < < < < < < < < < < < < ~ U < < < < < < < < < < < | c _ < < < < < < < - n < < < < < < < < < < { f / ( ] ^
| < < < < < < < < < < < < < < < ) n ] _ ~ < < < < < / n 1 < < < < < < < ~ [ L + < < < < < < < < < < < < < < _ t / 1 l
t < < < < < < < < < < < < < < < v j ( ( ) - < < ~ } + < < < < < < < < _ 1 Q j < < < < < < < < < < < < < < < < < < < ) f ( :
) + < < < < < < < < < < < < < < c [ ] ? ~ < < < < < < < < < < < < ~ } ( c t [ < < < < < < < < < < < < < < < < < < < < < ~ / t <
^ x { } ] ] - _ ~ < < < ~ _ ~ r c < < < < < < < < < < < < < < < - ) ( u < \ < < 1 - < < < < < < < < < < < < < < < < < < < < < \ )
< c ( ( ( ( ( ( ( ) ) / Y n n < < < < < < < < < < < < < < ~ { ( ( v i f < _ ( ( 1 _ < < < < < < < < < < < < < < < < < < + j '
! v n ( ( ( ( r X c f ~ < < < < < < < < < < < < < < < ~ 1 ( ( c ; r < ] ( ( ( ( } + < < < < < < < < < < < < < < < + f '
] X x u u | + < < < < < < < < < < < < < < < < < < { ( t n ^ \ f < { ( ( ( ( ( ( [ ~ < < < < < < < < < < < < + / '
t ~ < < < < < < < < < < < < < < < < < < < < < < - ( v { ? ] < ) ( ( ( ( ( ( ( ) ? < < < < < < < < < < + \
u < < < < < < < < < < < < < < < < < < < < < < < { w : \ < + ( ( ( | ( ( ( ( ( ( { ~ < < < < < < < ~ (
i \ < < < < < < < < < < < < < < < < < < < < < < < ~ n j < - ( ( / x t c n ( ( ( ( ) - < < < < < ~ (
t < < < < < < < < < < < < < < < < < < < < < < < < < n + ] : x < [ ( ( v ' ! | n X \ ( ( [ < < < ~ /
u < < < < < < < < < < < < < < < < < < < < < < < < < x ( 1 | r t ( < { ( x + " { j z r { ~ ~ f '
~ 1 < < < < < < < < < < < < < < < < < < < < < < < < < t ] t 1 + < < < ( ( x ' ~ / n u ^
t < < < < < < < < < < < < < < < < < < < < < < < < < < \ i n ( ( ? < + ( v :
' r < < < < < < < < < < < < < < < < < < < < < < < < < < / " z ( ( ( } ] | \
_ [ < < < < < < < < < < < < < < < < < < < < < < < < < < t L t \ Y u z z `
/ < < < < < < < < < < < < < < < < < < < < < < < < < < < / ; Z Q Q \ , I
' f < < < < < < < < < < < < < < < < < < < < < < < < < < < + # 0 d d d }
] ? < < < < < < < < < < < < < < < < < < < < < < < < < < < < Z d d d b ?
\ < < < < < < < < < < < < < < < < < < < < < < < < < < < < < u o b d k ~
j < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ] | ? u d :
j < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < j
r < ~ < < < < < < < < < < < < < < < < < < < < < < < < < < < < /
/ + ( ( { ? + < < < < < < < < < _ ? ] ] ] ] ] - + < < < < < < f
i f ( ( ( ( ( 1 { } [ [ [ } 1 ( ( ( ( ( ( ( ( ( ( ) } _ < < < /
] X ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( } < < |
: ( f ) v u ( ( \ j u c c u x J d m C J | ( ( ( ( ( ( ( ( ( [ r \
` \ J t ] ~ { ( ( n X r , ' ` < ( j c C z r ( ( ( ( ( | n z }
u X z r } / u c f \ 1 l ] j z J Y Y n ) } } v _
+ r ( ) ( - I I n c \ + < < ] L Z u ^
' i ] } | ( - x O w
; 1 \ z J
---------------------- 结束 ------------------------

虽说上面这个是输出了字符图,从结果上看也比价像,但是需要注意的是,若图片的背景非白色,主角不是那么突出的场景,通过上面的方式输出的结果可能就不太友好了,解决办法当然就是识别背景,识别主体,针对主体元素进行转换(这个过程后面有机会再介绍)

接下来我们借助开源项目 github.com/liuyueyi/qu… 来迅速的实现字符图输出

以一个冰雪女王的转换图来验证下效果

1
2
java复制代码String file = "http://5b0988e595225.cdn.sohucs.com/images/20200410/76499041d3b144b58d6ed83f307df8a3.jpeg";
BufferedImage res = ImgPixelWrapper.build().setSourceImg(file).setBlockSize(4).setPixelType(PixelStyleEnum.CHAR_BLACK).build().asBufferedImg();

01.jpg

一灰灰的联系方式

尽信书则不如无书,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

  • 个人站点:blog.hhui.top
  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840
  • 微信公众号:一灰灰blog

本文转载自: 掘金

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

MySQL主从复制和读写分离 简述 主从复制 读写分离

发表于 2021-11-21

简述

在实际的生产环境中,如果对MySQL数据库的读和写都在一台数据库服务器中操作,无论是在安全性、高可用性,还是高并发等各个方面都是不能满足实际需求的。因此,一般通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力。

image.png

主从复制

MySQL的主从复制和MySQL的读写分离两者有紧密的联系,首先要部署主从复制,只有主从复制完成了,才能再此基础上进行数据的读写分离。

一、MySQL支持的复制类型

  1. 基于语句的复制:在主服务器上执行的sql语句,在从服务器上会执行同样的语句。MySQL默认采用基于语句的复制,效率比较高,但是有时不能实现精准复制。
  2. 基于行的复制:把改变的内容复制过去,而不是把命令在从服务器上执行一遍。
  3. 混合类型的复制:默认采用基于语句的复制,一旦发现基于语句的复制不能精准复制时,就会采用基于行的复制。

image.png

二、主从复制过程

  1. 在每个事物更新数据完成之前,master在二进制日志记录这些改变,写入二进制日志完成后,master通知存储引擎提交事务。
  2. slave将master的binary log复制到其中的中继日志。首先从MySQL服务器开始一个工作线程I/O线程,I/O线程在master上打开一个普通的连接,然后开始binlog dump process。Binlog dump process从master的二进制日志中读取事件,如果已经跟上master。他会睡眠并等待master产生新的事件。I/O线程将这些事件写入中继日志。
  3. sql从线程处理该过程的最后一步。Sql线程从中继日志中读取事件,并重放其中的事件而更新slave的数据,使其与master的数据一致。

读写分离

简单的来说,读写分离就是只在MySQL主服务器上写,只在MySQL从服务器上读。基本原理是让主数据库处理事务性查询,而从数据库处理select查询。数据库复制被用来把事务性查询导致的变更同步到集群中的数据库。

目前较为常见的MySQL读写分离有两种:

1. 基于程序代码的内部实现

在代码中根据select、insert进行路由分类,这类方法也是目前生产环境中较为常用的,优点是性能较好,因为在程序代码中实现,不需要增加额外的设备作为硬件开支;缺点是需要研发人员来实现,运维人员无从下手。

2. 基于中间代理层实现

代理一般位于客户端和服务器之间,代理服务器接收到客户端请求后通过判断后转发到后端数据库。如下有两个常用代理:

Mysql-proxy:其为MySQL的开源项目,通过其自带的lua脚本进行sql判断,虽然是MySQL官方产品,但是mysql官方并不建议其使用到生产环境中。

Amoeba: 由陈思儒开发,该程序由Java语言进行开发。这个软件致力于MySQL的分布式数据库前端代理层,它主要为应用层访问MySQL的时候充当sql路由功能。Amoeba能够完成多数据源的高可用、负载均衡、数据切片等功能。

  • 转载自
    www.cnblogs.com/skfa/articl…

本文转载自: 掘金

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

1、SpringMVC简单介绍与使用 1、回顾MVC架构 2

发表于 2021-11-21

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」

1、回顾MVC架构

1.1 MVC三层是什么?

MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。就是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC 是一种架构模式。当然不同的MVC存在差异。

image.png

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。

最典型的MVC就是JSP + servlet + javabean的模式。

2、SpringMVC

2.1、什么是SpringMVC?

springMVC是Spring框架的一部分,是基于java实现的一个轻量级web框架。SpringMVC框架最核心的就是DispatcherServlet的设计。

2.2 SpringMVC的实现原理

springmvc的mvc模式:
image.png
DispatcherServlet(前端控制器):
Spring的web框架围绕DispatcherServlet设计。 DispatcherServlet的作用是将请求分发到不同的处理器。Spring MVC框架像许多其他MVC框架一样, 以请求为驱动 , 围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet是一个实际的Servlet (它继承自HttpServlet基类)。

2.3 SpringMVC的具体执行流程:

当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据 库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返 回给中心控制器,再将结果返回给请求者。

image.png
1、DispatcherServlet表示前端控制器,是整个SpringMVC的控制中心。用户发出请求, DispatcherServlet接收请求并拦截请求。

2、HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请 求url查找Handler。

3、返回处理器执行链,根据url查找控制器,并且将解析后的信息传递给DispatcherServlet

4、HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。

5、执行handler找到具体的处理器

6、Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。

7、HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。

8、DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。

9、视图解析器将解析的逻辑视图名传给DispatcherServlet。

10、DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图,进行试图渲染

11、将响应数据返回给客户端

DispatcherServlet:前端调度器

负责将请求拦截下来分发到各控制器方法中。是整个SpringMVC的控制中心。用户发出请求, DispatcherServlet接收请求并拦截请求。

HandlerMapping:处理器映射器

负责根据请求的URL和配置@RequestMapping映射去匹配, 匹配到会返回Handler(具体控制器的方法)。
DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。返回处理器执行链,根据url查找控制器,并且将解析后的信息传递给DispatcherServlet

HandlerAdaper: 处理器适配器

负责调用Handler——》具体的方法——》返回视图的名字。Handler将它封装到 ModelAndView(封装视图名,request域的数据)。

ViewReslover: 视图解析器

根据ModelAndView里面的视图名地址去找到具体的jsp封装在View对象中

View:视图

进行视图渲染(将jsp转换成html内容)最终response到的客户端。

3、基于注解的SpringMVC-Hellow

3.1 添加pom依赖

1
2
3
4
5
6
7
8
9
10
11
xml复制代码<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>

3.2 配置web.xml文件

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
xml复制代码<!--配置前端控制器  接收所有除了.jsp的请求 都交给springmvc去处理 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--初始化参数 contextConfigLocation 配置springmvc的xml配置文件, 指定路径
也可以不配置: 会自动去WEB-INF去找一个名字叫做 springmvc-servlet.xml 的文件
启动的时候,加载 Bean等操作
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--启动时加载servlet : 当web服务器 启动时就会创建servlet(会自动调用servlet的构造函数及init()方法) -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--
配置DispatcherServlet映射
通常会为springmvc映射的路径为:
/ 除了.jsp的请求都会被匹配
/* 所有的请求都会匹配
*.do 、*.action url结尾以.do或者.action的请求会匹配
/request/* 要进行约定 将jsp放在/views/ 所有的servlet请求都用/request/
-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

3.3 配置springmvc需要的spring配置文件,spring-mvc.xml

1
ini复制代码<context:component-scan base-package="com.zhl.controller"></context:component-scan>

3.4 HellowController.java

1
2
3
4
5
6
7
8
kotlin复制代码@Controller
public class HellowController {
@RequestMapping("/sayHellow")
public String sayHellow(){
System.out.println("欢迎使用Hellow!");
return "/index.jsp";
}
}

上述流程如下:

1、客户端发送请求http://localhost:8080/springmMVC/sayHellow

2、由tomcat接受到对应的请求

3、SpringMVC的前端控制器DispatcherServlet接收到所有的请求

4、查看请求地址和@RequestMapping注解的哪个匹配,来找到具体的类的处理方法

5、前端控制器找到目标处理类和方法之后,执行目标方法

6、方法执行完成之后会有一个返回值,SpringMVC会将这个返回值 用视图解析器进行解析拼接成完整的页面地址

7、DispatcherServlet拿到页面地址之后,转发到具体的页面

本文转载自: 掘金

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

【中间件】了解消息队列 消息队列 消息队列保证消息的可靠性

发表于 2021-11-21

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」

消息队列

Q:消息队列的应用场景

通过MQ进行 数据分发 ,提高系统处理性能

  • 订阅模式-关注一件商品的价格,价格变动通知点击关注了的用户;
  • 相当于分布式事务的事务补偿,当订单确认出错,回滚库存和订单状态;

Q:消息队列的模型

三者都注册到类似于注册中心的 nameserver

消息生产者 –> broker –> 消息消费者

broker相当于邮局,所有的消息都要经过这个组件!!

  • broker结构
    • topic主题,用来区分消息种类吧
      • message queue,是 topic的分区,他可以保证消息的有序

消息队列保证消息的可靠性

Q:消息丢失场景和解决?

消息的可靠性 取决于 三方的正确响应顺序;(消息提供者、broker、消息消费者)

疑问

  1. 消息提供者将消息发送给 broker;
    1. 没有响应或者响应失败,开启重试
  1. broker先进行刷盘,存储消息,之后响应消息提供者;
  2. broker将消息发送给消息消费者;
  1. 消息消费者消费完消息响应给 broker;
  2. broker判断消息消费成功。

Q:消息存储?

首先 RocketMQ本身就支持消息的硬盘存储的,

不像 RabbitMQ默认不支持消息的硬盘存储,当设置消息硬盘存储后,RabbitMQ的并发量会从 1万,降到 2000左右;

RocketMQ不会出现这样的原因是,它的硬盘存储使用了 MMap的算法,

原来从服务器到服务器的硬盘经过的4次拷贝,减少成了3次,大大提高了数据拷贝的速度。

然后 服务提供者提供消息给 broker后,broker会开启存储消息阶段,即服务刷盘;

之后才发送给生产者响应发送消息成功。

如果 broker是集群的情况,不仅仅写入当前 broker,它的副本也要写入,保证存储的可靠性。

Q:处理重复消息?

首先因为要保证消息的可靠性,会出现重试的机制,无法避免的会产生重复数据;

然后要 RocketMQ进行消息查重会极大的降低效率,所有需要服务消费者来解决消息重复的问题;

解决的方案是保证服务消费的幂等性,保证多次消费不会对原来预期的消费结果产生不同;

比如要更新一条数据的时候添加判断条件,保证修改的数据是通第一次修改的结果一致;或者是判断数据的状态是已经消费过了,不进行处理;

根据实际的业务来保证重复消费不会对预期的消费结果产生影响即可。

服务消费的问题

Q:保证消息的有序性?

比如一个需求是 注销一个用户;

先把用户在除用户模块以外的服务,比如订单的状态、购物车里的状态等先进行处理;

之后才在用户表修改用户的状态;

会用到 broker里 topic主题里的 message queue;

他是队列的形式,可以保证消息的顺序;在代码里就是指定主题的 tag;

Q:解决消息的堆积?

生产者的生产速度与消费者的消费速度不匹配;

可能是生产者反复重试生产消息或消费者消费速度太慢;

首先定位消费满的原因,该改的 bug改掉,改优化的 sql优化;

如果优化后还是慢,就考虑增加消费者了;

而一个 topic里的 message queue对应一个服务消费者;

本文转载自: 掘金

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

Go语言入门之基础语法 写在前面👀 写在后面

发表于 2021-11-21

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」

写在前面👀

Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言,它用批判吸收的眼光,融合C语言、Java等众家之长,将简洁、高效演绎得淋漓尽致。🌍

一、命名规范⭕

Go 语言中,任何标识符(变量,常量,函数,自定义类型等)都应该满足以下规律:

  1. 只能以字母或下划线_开头
  2. 不能使用关键字作为标识符
  3. go语言区分大小写
    Go 的源文件以 .go 为后缀名存储在计算机中,go文件名推荐用纯英文命令,中间可以用下划线_分割,文件名不包含空格或其他特殊字符
  • go语言中25个关键字或保留字👇
    break | default | func | interface | select |
    | ——– | ———– | —— | ——— | —— |
    | case | defer | go | map | struct |
    | chan | else | goto | package | switch |
    | const | fallthrough | if | range | type |
    | continue | for | import | return | var
  • Go 语言还有 36 个预定义标识符,其中包含了基本类型的名称和一些基本的内置函数👇
    append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
    | —— | ——- | ——- | ——- | —— | ——- | ——— | ———- | ——- |
    | copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
    | int32 | int64 | iota | len | make | new | nil | panic | uint64 |
    | print | println | real | recover | string | true | uint | uint8 | uintptr |

二、Go程序的基本结构🚗

  • Hello World !
  • 建立一个hello.go的文件,用go run命令运行下面的代码,用go build命令可以编译将代码编译成一个可执行文件,通过./hello命令就可以执行它了
1
2
3
4
5
6
7
go复制代码package main

import "fmt"

func main(){
fmt.Println("Hello World!")
}

image.png

1.包💼

  • 每个Go程序都是由包构成的,可以使用自身的包或者从其它包中导入内容
  • 每个 Go 文件都属于且仅属于一个包
  • 包名必须是小写
  • 必须在源文件中的第一行指明这个文件属于哪个包,如:package main,package main表示一个可独立执行的程序,每个 Go 的可执行程序都包含一个名为 main 的包
  • 当然你也可以编译包名不为 main 的源文件,如:pack1,编译后产生的是pack1.a文件而不是可执行程序
  • go run是执行命令,必须用main调用,而main方法只在package main里面

2.导入包🎆

  • import(导入)多个包经常用小括号( ),包名用双引号包住" "
  • 在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的,如fmt.Prinln,math.Pi
  • 在导入一个包时,你只能引用其中已导出的名字。任何未导出的名字在该包外均无法访问
1
2
3
4
5
6
7
8
9
10
go复制代码package main

import (
"fmt"
"math"
)

func main() {
fmt.Println(math.Pi) //math.pi会报错
}

image.png

3.main函数🥚

  • main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init( ) 函数则会先执行该函数)
  • main 函数既没有参数,也没有返回类型
  • main方法只能放在package main中,go run 是执行命令,必须要一个main用来调用

4.数据类型🥩

  • 变量(或常量)包含数据,这些数据可以有不同的数据类型,简称类型
  • 类型可以是基本类型,如:int、float、bool、string;结构化的(复合的),如:struct、array、slice、map、channel;只描述类型的行为的,如:interface
  • 函数也可以是一个确定的类型,就是以函数作为返回类型

5.注释🍚

  1. //单行注释
  2. /* */多行注释

三、GO的语法特色🛴

  1. 每个变量声明之后必须要在后面使用,不允许存在变量声明没有使用的情况,这就是go语言的特色:没有无用的代码
  2. 每行程序结束后不需要撰写分号 ; 因为
  3. 花括号 { 不能够换行放置
  4. if判断和for循环不需要以小括号( )包覆起来
  5. go语言的字符串可以通过加号+连接

写在后面

感谢观看❤

如有错误,欢迎指正✨

本文转载自: 掘金

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

1…245246247…956

开发者博客

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