-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfeed.json
239 lines (239 loc) · 222 KB
/
feed.json
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
{
"version": "https://jsonfeed.org/version/1",
"title": "艾瑞可erik • All posts by \"undefined\" categories",
"description": "一只PHP开发的程序猿,偶尔做做运维、Goland、Python、Java、摄影、画画、写作、顺便睡觉,反正整站都搞过。",
"home_page_url": "https://erik.xyz",
"items": [
{
"id": "https://erik.xyz/2025/02/05/baimasi/",
"url": "https://erik.xyz/2025/02/05/baimasi/",
"title": "白马寺",
"date_published": "2025-02-05T05:16:00.000Z",
"content_html": "<pre><code> 白马寺\n\n千年风霜斑驳目,新出寰宇客来慕。\n佛丘已非今朝时,独得枯木逢春时。\n</code></pre>",
"tags": [
"随笔"
]
},
{
"id": "https://erik.xyz/2025/01/12/composer-intranet-deployment/",
"url": "https://erik.xyz/2025/01/12/composer-intranet-deployment/",
"title": "composer内网部署",
"date_published": "2025-01-12T14:01:00.000Z",
"content_html": "<p>在内网环境中部署 Composer(PHP的依赖管理工具),通常是为了避免因外网访问限制而导致的依赖下载问题。内网部署 Composer 主要涉及配置一个 私有 Composer 仓库,或者使用 本地代理镜像 来加速和管理依赖。</p>\n<h3 id=\"1-使用-composer-json-设置依赖\"><a href=\"#1-使用-composer-json-设置依赖\" class=\"headerlink\" title=\"1. 使用 composer.json 设置依赖\"></a><b>1. 使用 composer.json 设置依赖</b></h3><p>首先,确保你的 composer.json 文件包含所有项目依赖,并正确配置了包的版本和来源。<br><span id=\"more\"></span><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "name": "vendor/project",</span><br><span class=\"line\"> "require": {</span><br><span class=\"line\"> "monolog/monolog": "^2.0"</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p>\n<h3 id=\"2-使用代理镜像\"><a href=\"#2-使用代理镜像\" class=\"headerlink\" title=\"2. 使用代理镜像\"></a><b>2. 使用代理镜像</b></h3><p>由于内网无法直接访问 Composer 官方的包仓库,常见的做法是使用国内镜像或搭建代理服务器,常见的方案包括使用 阿里云 Composer 镜像 或 私有镜像仓库。</p>\n<p> <b>2.1 使用国内镜像源</b></p>\n<p>你可以通过配置 Composer 使用国内镜像源来加速依赖包的下载。在 Composer 中,你可以使用下面的命令设置国内镜像:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">composer config repo.packagist composer https://mirrors.aliyun.com/composer/</span><br></pre></td></tr></table></figure><br>这个命令将 packagist.org 源更换为阿里云的镜像源。</p>\n<p>你还可以通过在 composer.json 文件中进行设置,强制使用镜像源:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "repositories": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "type": "composer",</span><br><span class=\"line\"> "url": "https://mirrors.aliyun.com/composer/"</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>这样每次执行 composer install 时,都会从阿里云的镜像源下载依赖。</p>\n<p><b>2.2 配置自建 Composer 镜像代理</b></p>\n<p>如果你想在企业内部完全控制包管理,可以搭建自己的 Composer 镜像代理。常见的选择包括:</p>\n<ul>\n<li>Satis:一个官方提供的工具,用来搭建私有的 Composer 仓库。</li>\n<li>Private Packagist:一个商业解决方案,专门用于在私有环境中管理 Composer 包。</li>\n<li>Sinopia (npm 仓库代理工具):可以作为私有的 Composer 仓库代理使用。</li>\n</ul>\n<p><b>2.2.1 使用 Satis 搭建私有 Composer 仓库</b></p>\n<p>Satis 是 Composer 官方提供的一个轻量级工具,专门用于创建私有的 Composer 仓库。通过使用 Satis,你可以将内网环境下常用的依赖缓存下来,并提供给项目中使用。</p>\n<p>步骤:</p>\n<ol>\n<li>安装 Satis:</li>\n</ol>\n<p>使用 Composer 安装 Satis:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">composer require composer/satis</span><br></pre></td></tr></table></figure>\n<ul>\n<li>配置 Satis:</li>\n</ul>\n<p>在你的服务器上创建一个 satis.json 配置文件,指定你希望托管的包源。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "name": "my-private-repo",</span><br><span class=\"line\"> "homepage": "https://example.com/packages",</span><br><span class=\"line\"> "repositories": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "type": "vcs",</span><br><span class=\"line\"> "url": "https://github.com/some/package"</span><br><span class=\"line\"> },</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "type": "composer",</span><br><span class=\"line\"> "url": "https://packagist.org"</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ],</span><br><span class=\"line\"> "output-dir": "/path/to/output"</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<ul>\n<li>生成静态资源:</li>\n</ul>\n<p>运行以下命令生成静态的 Composer 包:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">php bin/satis build satis.json /path/to/output</span><br></pre></td></tr></table></figure>\n<ul>\n<li>提供访问:</li>\n</ul>\n<p>你可以通过 Web 服务器(如 Nginx 或 Apache)提供访问静态资源。这样,其他项目就能通过你的内部 Satis 仓库获取依赖了。</p>\n<p><b>2.2.2 配置 Composer 使用私有仓库</b></p>\n<p>在 composer.json 中配置私有仓库(例如,你的公司内部搭建的 Satis 仓库)。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">{</span><br><span class=\"line\"> "repositories": [</span><br><span class=\"line\"> {</span><br><span class=\"line\"> "type": "composer",</span><br><span class=\"line\"> "url": "http://your-internal-repository.com"</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ]</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p>这样,Composer 会从你配置的私有仓库中拉取依赖。</p>\n<h3 id=\"3-离线安装-Composer-依赖\"><a href=\"#3-离线安装-Composer-依赖\" class=\"headerlink\" title=\"3. 离线安装 Composer 依赖\"></a><b>3. 离线安装 Composer 依赖</b></h3><p>如果内网无法访问外部网络,也可以考虑在有外网访问权限的机器上下载依赖包,然后将它们导入到内网机器中进行安装。</p>\n<p><b>3.1 在有外网的机器上下载依赖</b></p>\n<p>在能够访问外网的机器上执行 composer install。<br>下载完成后,将 vendor 目录及 composer.lock 文件拷贝到内网服务器相同位置。</p>\n<p><b>3.2 使用 —prefer-dist 参数</b></p>\n<p>你还可以使用 composer install —prefer-dist 来下载 .tar.gz 或 .zip 格式的包,这样可以方便地进行离线安装。</p>\n<p><b>3.3 配置 COMPOSER_HOME</b></p>\n<p>如果你希望在内网机器上使用本地缓存的包,可以设置 COMPOSER_HOME 环境变量来指定 Composer 缓存目录。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">export COMPOSER_HOME=/path/to/composer/cache</span><br></pre></td></tr></table></figure>\n<p>然后,将缓存目录复制到内网机器,确保 Composer 能够使用这些缓存。</p>\n<h3 id=\"4-其他考虑\"><a href=\"#4-其他考虑\" class=\"headerlink\" title=\"4. 其他考虑\"></a><b>4. 其他考虑</b></h3><ul>\n<li>代理配置:如果内网可以通过 HTTP 代理访问外网,确保 Composer 配置了代理。在 composer.json 中配置代理,或者使用环境变量来指定代理:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">export HTTP_PROXY=http://proxy.example.com:8080</span><br><span class=\"line\">export HTTPS_PROXY=http://proxy.example.com:8080</span><br></pre></td></tr></table></figure>\n<ul>\n<li>私有包的权限:如果你使用了私有 Composer 仓库或 GitHub 仓库,记得配置好认证方式(如使用 auth.json 文件存储 GitHub Token)。</li>\n</ul>\n",
"tags": [
"composer",
"composer部署",
"内网部署composer"
]
},
{
"id": "https://erik.xyz/2025/01/10/agricultural-detection-system/",
"url": "https://erik.xyz/2025/01/10/agricultural-detection-system/",
"title": "农业检测系统设计",
"date_published": "2025-01-10T14:34:00.000Z",
"content_html": "<p><b>农业检测系统设计</b></p>\n<p>根据你的需求,我们将设计一个智能农业系统,通过多种技术手段实现温湿度、光照度监测、设备控制和远程管理。系统的核心功能包括:智能节点监控环境数据、手动和自动控制模式、通过 LoRa 和 4G 通信传输数据、以及通过手机或电脑端进行远程控制。<br><span id=\"more\"></span></p>\n<h4 id=\"1-系统模块概述\"><a href=\"#1-系统模块概述\" class=\"headerlink\" title=\"1. 系统模块概述\"></a><b>1. 系统模块概述</b></h4><p>该农业检测系统包括以下几个主要模块:</p>\n<ol>\n<li><p>智能节点(传感器与控制单元):</p>\n<ul>\n<li>获取温湿度、光照度传感器数据。</li>\n<li>控制风扇的启停,显示当前风扇状态。</li>\n<li>支持手动和自动模式切换。</li>\n<li>显示温湿度、光照度、风扇状态和模式状态。</li>\n<li>通过 LoRa 发送数据到 4G 节点。</li>\n</ul>\n</li>\n<li><p>4G 网关节点:</p>\n<ul>\n<li>接收来自智能节点的传感器数据。</li>\n<li>将数据通过 4G 网络上传至 MOTT 服务器。</li>\n<li>在显示屏上显示各项传感器数据。</li>\n</ul>\n</li>\n</ol>\n<ul>\n<li><p>MOTT 服务器:</p>\n<ul>\n<li>存储从 4G 节点发送来的数据。</li>\n<li>提供远程访问接口。</li>\n</ul>\n</li>\n<li><p>应用软件(电脑端或手机端):</p>\n<ul>\n<li>显示温湿度、光照度、风扇状态、手动/自动模式等数据。</li>\n<li>提供登录、手动控制、自动控制等功能。</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"2-系统功能设计\"><a href=\"#2-系统功能设计\" class=\"headerlink\" title=\"2.系统功能设计\"></a><b>2.系统功能设计</b></h4><p>2.1 智能节点功能</p>\n<ol>\n<li><p>传感器数据采集:</p>\n<ul>\n<li>温湿度传感器:实时获取温度和湿度数据。</li>\n<li>光照度传感器:实时获取光照强度数据。</li>\n</ul>\n</li>\n</ol>\n<ul>\n<li><p>模式切换:</p>\n<ul>\n<li>手动模式:允许用户通过按键手动启动或停止风扇。</li>\n<li>自动模式:当温度和光照度超过设定阈值时,自动启停风扇。</li>\n</ul>\n</li>\n<li><p>风扇控制:</p>\n<ul>\n<li>在手动模式下,用户通过按键控制风扇开关,并通过绿色 LED 指示灯显示风扇状态。</li>\n<li>在自动模式下,系统会根据设定的温度和光照度阈值自动控制风扇,并使用绿灯和红灯进行状态指示。</li>\n</ul>\n</li>\n<li><p>数据传输:</p>\n<ul>\n<li>使用 LoRa 无线传输模块将传感器数据和风扇状态上传至 4G 网关节点。</li>\n</ul>\n</li>\n<li><p>显示屏:</p>\n<ul>\n<li>显示当前温湿度、光照度、模式状态和风扇状态。</li>\n</ul>\n</li>\n</ul>\n<p><b>2.2 4G 网关节点功能</b></p>\n<ol>\n<li><p>数据接收:</p>\n<ul>\n<li>通过 LoRa 接收智能节点上传的传感器数据(温度、湿度、光照度、风扇状态、手动/自动模式状态)。</li>\n</ul>\n</li>\n</ol>\n<ul>\n<li><p>4G 网络传输:</p>\n<ul>\n<li>将智能节点的数据通过 4G 网络上传至 MOTT 服务器。</li>\n</ul>\n</li>\n<li><p>显示屏显示:</p>\n<ul>\n<li>在 4G 网关节点的显示屏上展示温湿度、光照度、模式状态和风扇状态。</li>\n</ul>\n</li>\n</ul>\n<p><b>2.3 MOTT 服务器功能</b></p>\n<ol>\n<li><p>数据存储:</p>\n<ul>\n<li>存储来自 4G 网关节点的温湿度、光照度、风扇状态等数据。</li>\n</ul>\n</li>\n</ol>\n<ul>\n<li><p>远程管理接口:</p>\n<ul>\n<li>提供 RESTful API 或 WebSocket 接口,供客户端应用访问。</li>\n</ul>\n</li>\n</ul>\n<p><b>2.4 应用软件功能</b></p>\n<ol>\n<li>用户登录:<ul>\n<li>提供登录界面,用户通过输入用户名和密码登录(用户名:admin,密码:admin123)。</li>\n</ul>\n</li>\n</ol>\n<ul>\n<li><p>手动控制:</p>\n<ul>\n<li>登录成功后,用户可通过界面手动启停风扇,操作时触发红灯和蜂鸣器的提示。</li>\n</ul>\n</li>\n<li><p>自动控制:</p>\n<ul>\n<li>用户可以设定温度和光照度的阈值,智能节点进入自动模式后,系统根据传感器数据自动控制风扇开关,并通过 LED 指示灯显示状态。</li>\n</ul>\n</li>\n<li><p>显示功能:</p>\n<ul>\n<li>实时显示温度、湿度、光照度、风扇状态、手动/自动模式状态。</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"3-原型图设计\"><a href=\"#3-原型图设计\" class=\"headerlink\" title=\"3. 原型图设计\"></a><b>3. 原型图设计</b></h4><p>3.1 智能节点原型图<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">+-------------------------+</span><br><span class=\"line\">| 温湿度传感器 |</span><br><span class=\"line\">| |</span><br><span class=\"line\">| 光照度传感器 |</span><br><span class=\"line\">| |</span><br><span class=\"line\">| 按键面板 (5个按键) |</span><br><span class=\"line\">| |</span><br><span class=\"line\">| 显示屏 (LCD) |</span><br><span class=\"line\">| |</span><br><span class=\"line\">| 风扇控制 (LED/蜂鸣器) |</span><br><span class=\"line\">+-------------------------+</span><br></pre></td></tr></table></figure></p>\n<ul>\n<li>显示屏:显示温度、湿度、光照度、当前模式(手动/自动)和风扇状态。</li>\n<li>按键面板:5个按键,其中包括:<ul>\n<li>切换模式按键(手动/自动)</li>\n<li>启动风扇的控制按键</li>\n<li>停止风扇的控制按键</li>\n</ul>\n</li>\n<li>LED 灯:指示风扇是否正在运行(绿色为运行,红色为停止)。</li>\n<li>蜂鸣器:在风扇运行时以 1Hz 的频率发出声音。</li>\n</ul>\n<p><b>3.2 4G 网关节点原型图</b></p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">+-------------------------+</span><br><span class=\"line\">| LoRa 接收模块 |</span><br><span class=\"line\">| (接收数据) |</span><br><span class=\"line\">| |</span><br><span class=\"line\">| 显示屏 (LCD) |</span><br><span class=\"line\">| |</span><br><span class=\"line\">| 4G 网络模块 |</span><br><span class=\"line\">| (上传数据) |</span><br><span class=\"line\">+-------------------------+</span><br></pre></td></tr></table></figure>\n<ul>\n<li>显示屏:显示温湿度、光照度、模式状态和风扇状态。</li>\n<li>LoRa 接收模块:接收智能节点发送的传感器数据。</li>\n<li>4G 网络模块:将接收到的数据上传到 MOTT 服务器。</li>\n</ul>\n<p><b>3.3 应用软件原型图(电脑端或手机端)</b></p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">+-------------------------------------------------------+</span><br><span class=\"line\">| 登录界面 |</span><br><span class=\"line\">| - 用户名输入框 |</span><br><span class=\"line\">| - 密码输入框 |</span><br><span class=\"line\">| - 登录按钮 |</span><br><span class=\"line\">+-------------------------------------------------------+</span><br><span class=\"line\">| 主界面 |</span><br><span class=\"line\">| - 实时显示温湿度、光照度、风扇状态、手动/自动模式 |</span><br><span class=\"line\">| - 手动控制按钮 (启动/停止风扇) |</span><br><span class=\"line\">| - 自动模式阈值设置 (温度、光照度) |</span><br><span class=\"line\">| - 状态显示:当前模式(手动/自动)、风扇状态(开/关)|</span><br><span class=\"line\">+-------------------------------------------------------+</span><br></pre></td></tr></table></figure>\n<h4 id=\"4-系统设计步骤\"><a href=\"#4-系统设计步骤\" class=\"headerlink\" title=\"4. 系统设计步骤\"></a><b>4. 系统设计步骤</b></h4><p><b>4.1 硬件设计与开发</b></p>\n<ul>\n<li>选择适当的传感器:温湿度传感器(如 DHT22)、光照度传感器、风扇控制模块、LED 指示灯、蜂鸣器。</li>\n<li>智能节点电路设计:设计电源管理电路,确保所有传感器和控制组件能正常工作。</li>\n<li>LoRa 模块集成:将 LoRa 模块集成到智能节点中,实现数据的无线传输。</li>\n</ul>\n<p><b>4.2 软件开发</b></p>\n<ol>\n<li><p>智能节点程序:</p>\n<ul>\n<li>编写代码获取传感器数据。</li>\n<li>控制风扇及 LED 指示灯。</li>\n<li>实现手动与自动模式的切换。</li>\n<li>实现 LoRa 数据发送功能。</li>\n</ul>\n</li>\n</ol>\n<ul>\n<li>4G 网关节点程序:<ul>\n<li>接收 LoRa 数据并通过 4G 网络上传至 MOTT 服务器。</li>\n</ul>\n</li>\n<li>MOTT 服务器开发:<ul>\n<li>提供数据存储和远程管理接口(API)。</li>\n</ul>\n</li>\n<li>应用软件开发:<ul>\n<li>实现用户登录、手动控制、自动控制、数据展示等功能。</li>\n</ul>\n</li>\n</ul>\n<p><b>4.3 系统集成与测试</b></p>\n<ul>\n<li>硬件集成:将传感器、LoRa 模块、显示屏等硬件连接至智能节点和 4G 网关节点。</li>\n<li>功能测试:测试手动模式、自动模式、数据传输功能等。</li>\n<li>性能优化:确保系统在实时数据传输和控制中的稳定性。</li>\n</ul>\n<p><b>4.4 部署与维护</b></p>\n<ul>\n<li>部署智能节点和 4G 网关:将系统部署到实际农业环境中,进行现场测试和调试。</li>\n<li>维护与更新:定期检查系统,进行必要的功能更新和优化。</li>\n</ul>\n",
"tags": [
"农业系统",
"系统设计"
]
},
{
"id": "https://erik.xyz/2025/01/03/php-serial-port-development/",
"url": "https://erik.xyz/2025/01/03/php-serial-port-development/",
"title": "php串口开发",
"date_published": "2025-01-03T07:30:00.000Z",
"content_html": "<p>使用外置设备,通过串口发送和接收数据。那么,就要在php端有个串口的操作代码。<br>PHP 的 dio 扩展(Direct I/O)提供了对底层 I/O 操作的访问,包括串口通信。通过 dio 扩展,你可以直接操作串口设备文件(如 /dev/ttyUSB0 或 COM1)来实现串口通信。<br> <span id=\"more\"></span></p>\n<h3 id=\"1-安装-dio-扩展\"><a href=\"#1-安装-dio-扩展\" class=\"headerlink\" title=\"1. 安装 dio 扩展\"></a>1. 安装 dio 扩展</h3><p>dio 扩展是 PHP 的一个 PECL 扩展。你可以通过以下步骤安装:</p>\n<p>在 Linux 上安装:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sudo apt-get install php-dev # 安装 PHP 开发工具</span><br><span class=\"line\">sudo pecl install dio # 安装 dio 扩展</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></p>\n<p>安装完成后,在 php.ini 文件中启用扩展:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">extension=dio.so</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<h3 id=\"2-使用-dio-实现串口通信\"><a href=\"#2-使用-dio-实现串口通信\" class=\"headerlink\" title=\"2. 使用 dio 实现串口通信\"></a>2. 使用 dio 实现串口通信</h3><p>以下是一个使用 dio 扩展实现串口通信的示例代码:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><?php</span><br><span class=\"line\">// 串口设备路径</span><br><span class=\"line\">$device = '/dev/pts/4'; // Linux</span><br><span class=\"line\">// $device = 'COM1'; // Windows</span><br><span class=\"line\"></span><br><span class=\"line\">// 打开串口设备</span><br><span class=\"line\">$fd = dio_open($device, O_RDWR | O_NOCTTY | O_NONBLOCK);</span><br><span class=\"line\"></span><br><span class=\"line\">if (!$fd) {</span><br><span class=\"line\"> die("无法打开串口设备: $device\\n");</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">// 配置串口参数</span><br><span class=\"line\">dio_tcsetattr($fd, [</span><br><span class=\"line\"> 'baud' => 9600, // 波特率</span><br><span class=\"line\"> 'bits' => 8, // 数据位</span><br><span class=\"line\"> 'stop' => 1, // 停止位</span><br><span class=\"line\"> 'parity' => 0, // 校验位 (0: none, 1: odd, 2: even)</span><br><span class=\"line\"> 'flow_control' => 0, // 流控制 (0: none, 1: hardware)</span><br><span class=\"line\">]);</span><br><span class=\"line\"></span><br><span class=\"line\">// 发送数据到串口</span><br><span class=\"line\">$message = "你好我在https://erik.xyz上出生了!";</span><br><span class=\"line\">dio_write($fd, $message);</span><br><span class=\"line\">echo "已发送: $message";</span><br><span class=\"line\"></span><br><span class=\"line\">// 从串口读取数据</span><br><span class=\"line\">$data = dio_read($fd, 1024); // 读取最多 1024 字节</span><br><span class=\"line\">echo "已接收: $data\\n";</span><br><span class=\"line\"></span><br><span class=\"line\">// 关闭串口</span><br><span class=\"line\">dio_close($fd);</span><br><span class=\"line\">?></span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></p>\n<h3 id=\"3-代码说明\"><a href=\"#3-代码说明\" class=\"headerlink\" title=\"3. 代码说明\"></a>3. 代码说明</h3><ul>\n<li><p>dio_open: 打开串口设备文件。O_RDWR 表示以读写模式打开,O_NOCTTY 表示不将设备作为控制终端,O_NONBLOCK 表示非阻塞模式。</p>\n</li>\n<li><p>dio_tcsetattr: 配置串口参数,包括波特率、数据位、停止位、校验位和流控制。</p>\n</li>\n<li><p>dio_write: 向串口写入数据。</p>\n</li>\n<li><p>dio_read: 从串口读取数据。</p>\n</li>\n<li><p>dio_close: 关闭串口设备。</p>\n</li>\n</ul>\n<h3 id=\"4-串口参数配置\"><a href=\"#4-串口参数配置\" class=\"headerlink\" title=\"4. 串口参数配置\"></a>4. 串口参数配置</h3><ul>\n<li><p>dio_tcsetattr 的配置选项:</p>\n</li>\n<li><p>baud: 波特率(如 9600、19200、38400、57600、115200)。</p>\n</li>\n<li><p>bits: 数据位(通常为 8)。</p>\n</li>\n<li><p>stop: 停止位(1 或 2)。</p>\n</li>\n<li><p>parity: 校验位(0: 无校验,1: 奇校验,2: 偶校验)。</p>\n</li>\n<li><p>flow_control: 流控制(0: 无流控制,1: 硬件流控制)。</p>\n</li>\n</ul>\n<h2 id=\"那么这时候需要测试一下代码。那总不能真的找个串口设备吧,然而虚拟串口真香。\"><a href=\"#那么这时候需要测试一下代码。那总不能真的找个串口设备吧,然而虚拟串口真香。\" class=\"headerlink\" title=\"那么这时候需要测试一下代码。那总不能真的找个串口设备吧,然而虚拟串口真香。\"></a>那么这时候需要测试一下代码。那总不能真的找个串口设备吧,然而虚拟串口真香。</h2><h3 id=\"5-在Linux中使用-socat-模拟虚拟串口\"><a href=\"#5-在Linux中使用-socat-模拟虚拟串口\" class=\"headerlink\" title=\"5.在Linux中使用 socat 模拟虚拟串口\"></a>5.在Linux中使用 socat 模拟虚拟串口</h3><p>socat 是一个强大的工具,可以创建虚拟串口对。</p>\n<p>安装 socat:<br>在Debian/Ubuntu系统上:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sudo apt update</span><br><span class=\"line\">sudo apt install socat</span><br></pre></td></tr></table></figure>\n<p>创建虚拟串口对:<br>运行以下命令创建一对虚拟串口:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">socat -d -d pty,raw,echo=0 pty,raw,echo=0</span><br></pre></td></tr></table></figure>\n<p>运行后如图:<br><img src=\"/img/2024/20250103151316.png\" alt=\"https://erik.xyz\"><br>这样可以看到出现两个虚拟串口。<br>把上面的php代码放到文件中运行一下:<br><img src=\"/img/2024/20250103151620.png\" alt=\"https://erik.xyz\"></p>\n<p>同时新开个窗口执行:<code>cat /dev/pts/5</code>来读取串口数据。<br>如下图:<br><img src=\"/img/2024/20250103151927.png\" alt=\"https://erik.xyz\"></p>\n<p>这里是发送数据,那接收数据怎么看呢。<br>那就在代码上改造一下加个for:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">// 从串口读取数据</span><br><span class=\"line\">$data = dio_read($fd, 1024); // 读取最多 1024 字节</span><br><span class=\"line\">echo "已接收: $data\\n";</span><br></pre></td></tr></table></figure><br>这里改造主要是测试用,实际上不需要。<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">//测试接收</span><br><span class=\"line\">for($i=0;$i<20;$i++){</span><br><span class=\"line\"> sleep(3);</span><br><span class=\"line\">// 从串口读取数据</span><br><span class=\"line\">$data = dio_read($fd, 1024); // 读取最多 1024 字节</span><br><span class=\"line\">echo "已接收: $data\\n";</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure><br>那么,再次运行php代码,然后在新窗口运行<code>echo "欢迎你出生在https://erik.xyz" > /dev/pts/5</code>来发送信息,你会看到如图的接收:<br><img src=\"/img/2024/20250103152421.png\" alt=\"https://erik.xyz\"></p>\n<p>到这里,发送和接收串口已经好了。</p>\n",
"tags": [
"php",
"php串口开发"
]
},
{
"id": "https://erik.xyz/2024/12/18/maven-intranet-library/",
"url": "https://erik.xyz/2024/12/18/maven-intranet-library/",
"title": "maven内网库",
"date_published": "2024-12-18T12:55:00.000Z",
"content_html": "<h4 id=\"1-搭建-Maven-私有仓库\"><a href=\"#1-搭建-Maven-私有仓库\" class=\"headerlink\" title=\"1. 搭建 Maven 私有仓库\"></a>1. 搭建 Maven 私有仓库</h4><p>首先,需要在内网环境中搭建一个 Maven 仓库,常用的私有 Maven 仓库工具有:</p>\n<ul>\n<li>Nexus Repository:Sonatype Nexus 是最流行的私有 Maven 仓库管理工具。</li>\n<li>Artifactory:JFrog Artifactory 是另一种流行的构建管理工具,提供了私有仓库的支持。</li>\n<li>Apache Archiva:Apache Archiva 也是一个支持 Maven 的仓库管理工具。</li>\n</ul>\n<p>以下是搭建 Nexus Repository 的简单步骤:<br><span id=\"more\"></span></p>\n<p>1.1 安装 Nexus Repository</p>\n<ul>\n<li><p>下载 Nexus: 访问 Nexus Repository 下载页面 下载 Nexus OSS 版本。</p>\n</li>\n<li><p>解压并启动: 解压下载的压缩包并启动 Nexus。</p>\n</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">cd /opt/nexus/bin</span><br><span class=\"line\">./nexus start</span><br></pre></td></tr></table></figure>\n<ul>\n<li>访问 Nexus UI: 打开浏览器,访问 Nexus 的默认地址:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">http://localhost:8081</span><br></pre></td></tr></table></figure>\n<p>默认用户名为 admin,密码为 admin123,可以在首次登录后修改密码。</p>\n<p>1.2 配置 Maven 仓库</p>\n<p>在 Nexus UI 中,你可以创建一个新的 Maven 仓库。创建仓库后,你可以上传公司的内部依赖、插件和构建工件。</p>\n<h4 id=\"2-配置-Maven-使用内网仓库\"><a href=\"#2-配置-Maven-使用内网仓库\" class=\"headerlink\" title=\"2. 配置 Maven 使用内网仓库\"></a>2. 配置 Maven 使用内网仓库</h4><p>配置 Maven 使用内网仓库需要修改 settings.xml 文件。</p>\n<p>2.1 修改 settings.xml</p>\n<p>在 Maven 的 settings.xml 文件中,配置私有仓库的地址和认证信息。settings.xml 文件通常位于 ~/.m2/ 目录下(用户级别配置)或者 ${MAVEN_HOME}/conf/ 目录下(全局配置)。</p>\n<p>以下是配置内网 Maven 仓库的示例:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"</span><br><span class=\"line\"> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"</span><br><span class=\"line\"> xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"></span><br><span class=\"line\"></span><br><span class=\"line\"> <mirrors></span><br><span class=\"line\"> <!-- 配置私有仓库镜像 --></span><br><span class=\"line\"> <mirror></span><br><span class=\"line\"> <id>nexus</id></span><br><span class=\"line\"> <mirrorOf>external:http://central</mirrorOf></span><br><span class=\"line\"> <url>http://your-nexus-server:8081/repository/maven-public/</url></span><br><span class=\"line\"> <blocked>false</blocked></span><br><span class=\"line\"> </mirror></span><br><span class=\"line\"> </mirrors></span><br><span class=\"line\"></span><br><span class=\"line\"> <repositories></span><br><span class=\"line\"> <repository></span><br><span class=\"line\"> <id>internal-repo</id></span><br><span class=\"line\"> <url>http://your-nexus-server:8081/repository/maven-releases/</url></span><br><span class=\"line\"> <snapshots></span><br><span class=\"line\"> <enabled>false</enabled></span><br><span class=\"line\"> </snapshots></span><br><span class=\"line\"> </repository></span><br><span class=\"line\"> </repositories></span><br><span class=\"line\"></span><br><span class=\"line\"> <pluginRepositories></span><br><span class=\"line\"> <pluginRepository></span><br><span class=\"line\"> <id>internal-plugins</id></span><br><span class=\"line\"> <url>http://your-nexus-server:8081/repository/maven-plugins/</url></span><br><span class=\"line\"> </pluginRepository></span><br><span class=\"line\"> </pluginRepositories></span><br><span class=\"line\"></span><br><span class=\"line\"> <servers></span><br><span class=\"line\"> <!-- 配置 Maven 仓库认证 --></span><br><span class=\"line\"> <server></span><br><span class=\"line\"> <id>nexus</id></span><br><span class=\"line\"> <username>your-nexus-username</username></span><br><span class=\"line\"> <password>your-nexus-password</password></span><br><span class=\"line\"> </server></span><br><span class=\"line\"> </servers></span><br><span class=\"line\"></settings></span><br></pre></td></tr></table></figure>\n<p>2.2 配置镜像和仓库</p>\n<ul>\n<li><p>镜像(Mirror):在 <mirrors> 标签中配置私有仓库的 URL,将 Maven 的中央仓库或其他公共仓库的请求代理到私有仓库中。通过 mirrorOf 配置来选择代理哪些仓库(external:<a href=\"http://central\">http://central</a> 表示代理所有外部仓库)。</p>\n</li>\n<li><p>仓库(Repository):在 <repositories> 和 <pluginRepositories> 标签中配置你的内网仓库的 URL。</p>\n</li>\n<li><p>认证(Server Authentication):在 <servers> 标签中配置内网仓库的认证信息(如果仓库需要认证)。</p>\n</li>\n</ul>\n<h4 id=\"3-配置项目使用内网仓库\"><a href=\"#3-配置项目使用内网仓库\" class=\"headerlink\" title=\"3. 配置项目使用内网仓库\"></a>3. 配置项目使用内网仓库</h4><p>在项目的 pom.xml 文件中,通常不需要额外配置仓库,因为 Maven 会使用 settings.xml 中配置的内网仓库。但是如果需要强制指定某个仓库,可以在 pom.xml 中配置 <repositories> 标签:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><repositories></span><br><span class=\"line\"> <repository></span><br><span class=\"line\"> <id>nexus-repo</id></span><br><span class=\"line\"> <url>http://your-nexus-server:8081/repository/maven-releases/</url></span><br><span class=\"line\"> </repository></span><br><span class=\"line\"></repositories></span><br></pre></td></tr></table></figure>\n<h4 id=\"4-上传和下载依赖\"><a href=\"#4-上传和下载依赖\" class=\"headerlink\" title=\"4. 上传和下载依赖\"></a>4. 上传和下载依赖</h4><p>4.1 上传依赖到内网仓库<br>你可以通过 Maven 命令将本地构建的 JAR 文件上传到内网仓库。例如,将某个 JAR 上传到 Nexus:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">mvn deploy:deploy-file \\</span><br><span class=\"line\"> -DgroupId=com.example \\</span><br><span class=\"line\"> -DartifactId=my-artifact \\</span><br><span class=\"line\"> -Dversion=1.0.0 \\</span><br><span class=\"line\"> -Dpackaging=jar \\</span><br><span class=\"line\"> -Dfile=path/to/your-artifact.jar \\</span><br><span class=\"line\"> -DrepositoryId=nexus \\</span><br><span class=\"line\"> -Durl=http://your-nexus-server:8081/repository/maven-releases/</span><br></pre></td></tr></table></figure>\n<p>4.2 从内网仓库下载依赖</p>\n<p>配置好内网仓库后,Maven 会自动从内网仓库下载依赖。如果仓库中没有该依赖,Maven 会尝试从其他配置的仓库下载。</p>\n<h4 id=\"5-使用私有仓库中的依赖\"><a href=\"#5-使用私有仓库中的依赖\" class=\"headerlink\" title=\"5. 使用私有仓库中的依赖\"></a>5. 使用私有仓库中的依赖</h4><p>一旦仓库配置好,Maven 将会从配置的内网仓库下载依赖。你可以在项目的 pom.xml 中正常引用依赖,Maven 会自动从私有仓库中拉取。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><dependencies></span><br><span class=\"line\"> <dependency></span><br><span class=\"line\"> <groupId>com.example</groupId></span><br><span class=\"line\"> <artifactId>my-artifact</artifactId></span><br><span class=\"line\"> <version>1.0.0</version></span><br><span class=\"line\"> </dependency></span><br><span class=\"line\"></dependencies></span><br></pre></td></tr></table></figure>\n<h4 id=\"6-配置镜像以提高构建速度(可选)\"><a href=\"#6-配置镜像以提高构建速度(可选)\" class=\"headerlink\" title=\"6. 配置镜像以提高构建速度(可选)\"></a>6. 配置镜像以提高构建速度(可选)</h4><p>为了提高构建速度,你可以配置 settings.xml 来使用私有仓库作为 Maven 的默认镜像,确保所有的构建依赖都从私有仓库中拉取,避免每次访问外部仓库,降低构建时间。</p>\n",
"tags": [
"maven搭建库",
"maven",
"maven内网库"
]
},
{
"id": "https://erik.xyz/2024/12/12/intranet-spring-boot-install/",
"url": "https://erik.xyz/2024/12/12/intranet-spring-boot-install/",
"title": "spring boot内网部署",
"date_published": "2024-12-12T10:22:18.000Z",
"content_html": "<h4 id=\"1-准备环境\"><a href=\"#1-准备环境\" class=\"headerlink\" title=\"1. 准备环境\"></a>1. 准备环境</h4><p>确保内网中的服务器或机器具备运行 Spring Boot 应用的基本环境:</p>\n<ul>\n<li>JDK:确保服务器安装了合适版本的 JDK(通常建议使用 Java 8 及以上版本)。</li>\n<li>Maven/Gradle:根据项目使用的构建工具安装 Maven 或 Gradle。</li>\n<li>数据库:如果应用需要连接数据库,确保数据库在内网中可访问,且连接配置正确。</li>\n</ul>\n<span id=\"more\"></span>\n<h4 id=\"2-编译-Spring-Boot-应用\"><a href=\"#2-编译-Spring-Boot-应用\" class=\"headerlink\" title=\"2. 编译 Spring Boot 应用\"></a>2. 编译 Spring Boot 应用</h4><p>首先,你需要编译你的 Spring Boot 应用,生成可执行的 JAR 文件。可以通过以下命令在项目目录下执行:</p>\n<ul>\n<li>Maven 构建命令:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">mvn clean package</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<ul>\n<li>Gradle 构建命令:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">./gradlew build</span><br></pre></td></tr></table></figure>\n<p>这会在 target/ 或 build/libs/ 目录下生成一个可执行的 JAR 文件,通常名为 your-application-name.jar。</p>\n<h4 id=\"3-传输-JAR-到内网服务器\"><a href=\"#3-传输-JAR-到内网服务器\" class=\"headerlink\" title=\"3. 传输 JAR 到内网服务器\"></a>3. 传输 JAR 到内网服务器</h4><p>将生成的 JAR 文件上传到内网的目标服务器。可以使用各种文件传输工具,比如:</p>\n<ul>\n<li>SCP(如果服务器支持 SSH):</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">scp your-application-name.jar user@server-ip:/path/to/deploy/</span><br></pre></td></tr></table></figure>\n<ul>\n<li>FTP 或 SFTP(如果有配置 FTP 服务)。</li>\n</ul>\n<h4 id=\"4-配置-Spring-Boot-应用\"><a href=\"#4-配置-Spring-Boot-应用\" class=\"headerlink\" title=\"4. 配置 Spring Boot 应用\"></a>4. 配置 Spring Boot 应用</h4><p>在内网部署时,你可能需要根据环境修改配置文件,比如 application.properties 或 application.yml。常见的配置项包括:</p>\n<ul>\n<li>数据库连接信息(spring.datasource.url、spring.datasource.username 等)。</li>\n<li>日志配置。</li>\n<li>服务端口(server.port)。</li>\n<li>安全设置(如禁用外部访问,或者设置访问白名单等)。</li>\n</ul>\n<p>例如,修改 application.properties 中的数据库配置:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">spring.datasource.url=jdbc:mysql://localhost:3306/your_db</span><br><span class=\"line\">spring.datasource.username=db_user</span><br><span class=\"line\">spring.datasource.password=db_password</span><br></pre></td></tr></table></figure></p>\n<h4 id=\"5-启动-Spring-Boot-应用\"><a href=\"#5-启动-Spring-Boot-应用\" class=\"headerlink\" title=\"5. 启动 Spring Boot 应用\"></a>5. 启动 Spring Boot 应用</h4><p>在内网服务器上,使用以下命令启动 Spring Boot 应用:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">java -jar your-application-name.jar</span><br></pre></td></tr></table></figure>\n<p>如果你希望应用在后台运行,可以使用 nohup 或者类似的工具:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">nohup java -jar your-application-name.jar > output.log 2>&1 &</span><br></pre></td></tr></table></figure>\n<p>这会将输出重定向到 output.log 文件,并让应用在后台运行。</p>\n<h4 id=\"6-配置防火墙和网络\"><a href=\"#6-配置防火墙和网络\" class=\"headerlink\" title=\"6. 配置防火墙和网络\"></a>6. 配置防火墙和网络</h4><ul>\n<li>确保服务器的防火墙允许访问应用所绑定的端口(默认是 8080)。如果使用其他端口,可以在防火墙中配置允许访问该端口。</li>\n<li>如果 Spring Boot 应用需要通过内网的特定 IP 地址或域名访问,确保 DNS 或 hosts 配置正确。</li>\n</ul>\n<h4 id=\"7-监控和日志\"><a href=\"#7-监控和日志\" class=\"headerlink\" title=\"7. 监控和日志\"></a>7. 监控和日志</h4><ul>\n<li>日志:Spring Boot 应用会将日志输出到控制台,你可以将日志配置为输出到文件中进行持久化存储。常见做法是在 application.properties 或 application.yml 中设置日志路径:</li>\n</ul>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">logging.file.name=/path/to/logs/application.log</span><br></pre></td></tr></table></figure>\n<ul>\n<li>监控:可以使用 Spring Boot 的 Actuator 或其他监控工具(如 Prometheus 和 Grafana)来监控应用的运行状态。</li>\n</ul>\n<h4 id=\"8-设置开机启动(可选)\"><a href=\"#8-设置开机启动(可选)\" class=\"headerlink\" title=\"8. 设置开机启动(可选)\"></a>8. 设置开机启动(可选)</h4><p>如果希望应用在服务器重启时自动启动,可以使用 systemd(Linux 系统)或配置为 Windows 服务。</p>\n<p>Linux 系统 (Systemd)</p>\n<p>创建一个 systemd 服务文件,如 /etc/systemd/system/yourapp.service:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">[Unit]</span><br><span class=\"line\">Description=Spring Boot Application</span><br><span class=\"line\">After=network.target</span><br><span class=\"line\"></span><br><span class=\"line\">[Service]</span><br><span class=\"line\">User=your_user</span><br><span class=\"line\">ExecStart=/usr/bin/java -jar /path/to/your-application-name.jar</span><br><span class=\"line\">SuccessExitStatus=143</span><br><span class=\"line\">Restart=always</span><br><span class=\"line\"></span><br><span class=\"line\">[Install]</span><br><span class=\"line\">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure>\n<p>然后启用并启动服务:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sudo systemctl enable yourapp</span><br><span class=\"line\">sudo systemctl start yourapp</span><br></pre></td></tr></table></figure>",
"tags": [
"springboot",
"springboot内网部署"
]
},
{
"id": "https://erik.xyz/2024/11/15/deepin-not-login/",
"url": "https://erik.xyz/2024/11/15/deepin-not-login/",
"title": "deepin无法登录报错emergency mode",
"date_published": "2024-11-15T02:56:00.000Z",
"content_html": "<p>由于系统重启后,新安装的微信一直弹出框,卡在那难受。立马强制重启,开机后就一直显示You are in emergency mode……一堆东西,意思是说让进入root用户,查看报错并修复。</p>\n<p> 有点头大了。</p>\n<p> 系统命令可以显示,图形界面不显示。<br> <span id=\"more\"></span><br> 果断拿出安装系统的u盘,使用u盘启动进入安装界面,按ctrl+alt+f4进入u盘命令界面。输入startx进入u盘图形界面。这下可以看到电脑的挂着盘。根据挂着的盘一个个找根目录盘。找到后使用u盘系统的命令窗口:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sudo vi /etc/fstab</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></p>\n<p> 注释掉home目录,重启系统。</p>\n<p> 重启后会显示个登录图形界面。这个是假的,没用。按ctrl+alt+f2进入命令界面。<br> 使用账号密码登录命令界面。</p>\n<p> 打开/etc/fstab文件取消home注释。(这里要看一下home挂载路径,比如我的/dev/sda6)</p>\n<p> 使用以下命令修复:<br> <figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sudo e2fsck /dev/sda6</span><br></pre></td></tr></table></figure></p>\n<p> 你会看到一堆修复,一个个确认后,等待修复完成就重启系统。</p>\n",
"tags": [
"deepin",
"emergency",
"emergency mode",
"deepin黑屏"
]
},
{
"id": "https://erik.xyz/2024/11/11/mysql-redis-consistency/",
"url": "https://erik.xyz/2024/11/11/mysql-redis-consistency/",
"title": "如何下保证MySQL数据库与Redis缓存数据一致性?",
"date_published": "2024-11-11T01:58:00.000Z",
"content_html": "<p>有时候感觉MySQL我们懂了,Redis我们懂了,但是面试的时候一直答不好,经常被难住,问题在哪呢?</p>\n<p>答案是:面试官考的不是专项能力,而是多项技术结合应用能力。</p>\n<p>就拿<strong>并发场景下如何保证MySQL与Redis缓存一致性?</strong>这个面试官常见的拷打考点举例。</p>\n<p>对于读多写少并且要求高性能的业务逻辑,我们通常在应用服务器访问MySQL数据库的中间加上一层<strong>Redis缓存层</strong>,以提高数据的查询效率,减轻MySQL数据库的压力,避免在MySQL出现性能瓶颈。<br><span id=\"more\"></span><br><img src=\"/img/2024/2024111101.png\" alt=\"https://erik.xyz\"></p>\n<p>该问题,如果在数据存储后,只读场景下是不会出现MySQL与Redis缓存的一致性问题的,所以真正需要考虑的是<strong>并发读写场景</strong>下的数据一致性问题。</p>\n<p>如果我们不加分析,单独利用MySQL和Redis的知识进行回答并发场景下如何保证MySQL与Redis缓存一致性?很难把这个问题回答好,因为看起来很简单的方案实际上是漏洞百出的。</p>\n<h4 id=\"简单方案下的漏洞百出\"><a href=\"#简单方案下的漏洞百出\" class=\"headerlink\" title=\"简单方案下的漏洞百出\"></a>简单方案下的漏洞百出</h4><p>我们先看下简单的更新数据库、删除缓存和更新缓存方案下,会出现什么问题?</p>\n<p><img src=\"/img/2024/2024111102.png\" alt=\"https://erik.xyz\"></p>\n<h4 id=\"更新缓存,再更新数据库\"><a href=\"#更新缓存,再更新数据库\" class=\"headerlink\" title=\"更新缓存,再更新数据库\"></a>更新缓存,再更新数据库</h4><p>先说结论:不考虑。</p>\n<p>原因是更新缓存成功后,数据库可能更新失败,出现数据库为旧值,缓存为新值。导致后续的所有的读请求,在缓存未过期或缓存未重新正确更新的情况下,会一直保持了数据的完全不一致!并且当前数据库中的值为旧值,而业务数据的正确性应该以数据库的为准。</p>\n<p>那么如果更新缓存成功后,数据库可能更新失败,我们<strong>重新更新缓存</strong>是不是可以了?</p>\n<p><img src=\"/img/2024/2024111103.png\" alt=\"https://erik.xyz\"></p>\n<p>抛开需要重新更新缓存时,要单表或多表重新查询数据,再更新数据带来的性能问题,还可能期间有数据变更再次陷入脏数据的情况。实际上仍然还是会出现并发一致性问题。</p>\n<p>只要缓存进行了更新,后续的读请求<strong>在更新数据库前、更新数据库失败并准备更新缓存前</strong>,基本上都能命中缓存情况,而这时返回的数据都是未落库的脏数据。</p>\n<p><img src=\"/img/2024/2024111104.png\" alt=\"https://erik.xyz\"></p>\n<h4 id=\"更新数据库,再更新缓存\"><a href=\"#更新数据库,再更新缓存\" class=\"headerlink\" title=\"更新数据库,再更新缓存\"></a>更新数据库,再更新缓存</h4><p>不考虑。</p>\n<p>原因是当数据库更新成功后,缓存更新失败,出现数据库为最新值,缓存为旧值。导致后续的所有的读请求,在缓存未过期或缓存未重新正确更新的情况下,会一直保持了数据的完全不一致!</p>\n<p><img src=\"/img/2024/2024111105.png\" alt=\"https://erik.xyz\"></p>\n<p>该方案就算在更新数据库、更新缓存都成功的情况下,还是会存在并发引发的一致性问题,如下图所示(点击图片查看大图):<br><img src=\"/img/2024/2024111106.png\" alt=\"https://erik.xyz\"></p>\n<p>可以看到在并发多写多读的场景下数据存在的不一致性问题。</p>\n<h4 id=\"先删除缓存,再更新数据库\"><a href=\"#先删除缓存,再更新数据库\" class=\"headerlink\" title=\"先删除缓存,再更新数据库\"></a>先删除缓存,再更新数据库</h4><p>不考虑,但是通过使用<strong>延时双删策略</strong>后可以考虑。</p>\n<p>采用“<strong>先删除缓存,再更新数据库</strong>”的方案是一种常见的方法来尝试解决这个问题的策略。</p>\n<p>这种方法逻辑较为简单,易于理解和实现,理论上删除旧缓存后,下次读取时将从数据库获取最新数据。</p>\n<p>但在并发的极端情况下,删除缓存成功后,如果再有大量的并发请求进来,那么便会直接请求到数据库中,对数据库造成巨大的压力。而且此方案还是可能会发生数据不一致性问题。</p>\n<p><img src=\"/img/2024/2024111107.png\" alt=\"https://erik.xyz\"></p>\n<p>通过上图发现在删除缓存后,如果有并发读请求1.1进来,那么查询缓存肯定是不存在,则去读取数据库,但因为此时更新数据库x=10的操作2.更新数据库还未完成,所以读取到的仍然是旧值x=5并设置缓存后,在2.更新数据库完成后,数据是新值10,而缓存是旧值,造成了数据不一致的问题。</p>\n<p>对此我们可以先进行一波的小优化,那就是<strong>延时双删策略</strong>。即在更新数据库之后,先延迟等待一下(等待时间参考该读请求的响应时间+几十毫秒),再继续删除缓存。这样做的目的是确保读请求结束(已经在1.2读库中读取到了旧数据,后续会在该请求中更新缓存),写请求可以删除读请求造成的缓存脏数据,保证再删除缓存之后的所有读请求都能读到最新值。</p>\n<p><img src=\"/img/2024/2024111108.png\" alt=\"https://erik.xyz\"></p>\n<p>可以看出此优化方案关键点在于等待多长时间后,再次删除缓存尤为重要,但是这个时间都是根据历史查询请求的响应时间判断的,实际情况会有浮动。这也导致如果等待的延时时间过短,则仍然会出现数据不一致的情况;等待延迟时间过长,则导致延迟期间出现数据不一致的时间变长。</p>\n<p>另外<strong>延时双删策略</strong>还需要考虑如果再次删除缓存失败的情况如何处理?</p>\n<p>因为删除失败将导致后续的所有的读请求,在缓存未过期或缓存未重新正确更新的情况下,会一直保持了数据的完全不一致!这个在下文的技术优化方案继续讨论。</p>\n<h4 id=\"先更新数据库,再删除缓存\"><a href=\"#先更新数据库,再删除缓存\" class=\"headerlink\" title=\"先更新数据库,再删除缓存\"></a>先更新数据库,再删除缓存</h4><p>比较推荐。</p>\n<p>采用的“先更新数据库,再删除缓存”策略,跟“先删除缓存,再更新数据库”中我们进行<strong>延时双删策略</strong>的小优化基本一样,仍然需要考虑删除缓存失败的情况如何处理。</p>\n<p>单纯从“先更新数据库,再删除缓存”和“先删除缓存,再更新数据库”对比起来。在大多数情况下,“先更新数据库,再删除缓存”被认为是一个更好的选择,原因如下:</p>\n<p>1.<strong>数据的一致性</strong>:这种方法更倾向于保持数据的最终一致性,即使缓存删除失败,也能保证数据的一致性不会长期受损。</p>\n<p>2.<strong>用户体验</strong>:在“先删除缓存,再更新数据库”的情况下,如果数据库更新失败,用户可能会一直看到旧数据,直到缓存过期。相比之下,“先更新数据库,再删除缓存”可以在某种程度上避免这种情况。</p>\n<p>但该方案同样也会出现数据不一致性问题,如下图所示。</p>\n<p><img src=\"/img/2024/2024111109.png\" alt=\"https://erik.xyz\"></p>\n<p>当数据库的数据被更新后,缓存也被删除。接下来的出现读请求3.1和写请求3.2同时进来。</p>\n<p>读请求先读了缓存发现缓存无命中,则查询数据库并在准备更新缓存时,3.2写请求已经完成了数据的更新和删除缓存的动作,之后3.1读请求才更新了缓存。最后导致了数据库中的值未新值,缓存中的值为旧值。</p>\n<h4 id=\"优化后方案\"><a href=\"#优化后方案\" class=\"headerlink\" title=\"优化后方案\"></a>优化后方案</h4><p>从上面的简单方案方案中,似乎没有一种方案真正能解决并发场景下MySQL数据与Redis缓存数据一致性的问题。</p>\n<p>这里有个说明下,如果业务要求必须要满足<strong>强一致性</strong>,那么不管如何优化缓存策略,都无法满足,而最好的办法是不用缓存。</p>\n<p>强一致性:它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。</p>\n<p>解决方案是读写串行化,而此方案会大大增加系统的处理效率,吞吐量也会大大降低。</p>\n<p>另外在大型分布式系统中,其实分布式事务大多数情况都不会使用,因为维护成本太高了、复杂度也高。所以在分布式系统,我们一般都会推崇最终一致性,即这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。</p>\n<p>现在我们接着继续优化..</p>\n<h4 id=\"延迟双删策略-重试机制\"><a href=\"#延迟双删策略-重试机制\" class=\"headerlink\" title=\"延迟双删策略+重试机制\"></a>延迟双删策略+重试机制</h4><p>从上面简单方案下的漏洞百出下的先删除缓存,再更新数据库中,我们可以看出来其实<strong>延迟双删策略</strong>,算是融合“先删除缓存,再更新数据库”和“先更新数据库,再删除缓存”的策略,可以解决大部分的数据一致性的业务逻辑处理问题。</p>\n<p>但我们前面还遗留了一个待解决的问题:如果再次<strong>删除缓存失败的情况如何处理</strong>?</p>\n<p>——-当然是补救去继续删除这个缓存Key了,而补救方法则是<strong>重试</strong>。</p>\n<p><strong>重试机制</strong>可以在当前中启动新协程(Golang中属于用户态的轻量级线程)中进行重试;也可以放到消息队列中进行重试;还可以是先启动新协程重试3次,重试失败后继续放到消息队列中重试,如下图展示的是放到消息队列中进行重试。</p>\n<p>新协程中进行重试需要注意的是使用的新上下文context.Background(),而不是当前请求的上下文。</p>\n<p>一般消息队列会支持高可靠性的队列,例如 RabbitMQ、Kafka 等。这些消息队列提供了非常强的消息传递、异步处理和持久化功能,可以有效地解决数据同步的问题。</p>\n<p><img src=\"/img/2024/2024111110.png\" alt=\"https://erik.xyz\"></p>\n<p>此方案仍然存在一些需要,如:选择合适的延迟等待时间进行删除缓存;协程中重试删除缓存次数、间隔时间;消息队列中删除失败缓存失败后是否需要重试等。</p>\n<h4 id=\"读取binlog异步删除缓存\"><a href=\"#读取binlog异步删除缓存\" class=\"headerlink\" title=\"读取binlog异步删除缓存\"></a>读取binlog异步删除缓存</h4><p>重试删除缓存机制还可以吧,就是会造成好多业务代码入侵。</p>\n<p>其实,还可以这样优化:</p>\n<p>1.通过Canal将binlog日志采集发送到MQ队列来异步淘汰key。</p>\n<p>2.删除缓存的应用程序通过ACK手动机制确认处理这条更新消息,删除缓存,保证数据缓存一致性。</p>\n<p><img src=\"/img/2024/2024111111.png\" alt=\"https://erik.xyz\"></p>\n<p>异步淘汰key相比于等新对比缓存数据并更新会简单一些,因为可能一份缓存数据涉及多张表的数据查询、聚合、排序等。</p>\n<p>尽管该方案看起来也不错了,但是因为引入额外的组件(如Canal、消息队列)复杂性增加了也不少,需要维护和监控这些组件的运行状态,保证组件运行正常。</p>\n<h4 id=\"定时任务\"><a href=\"#定时任务\" class=\"headerlink\" title=\"定时任务\"></a>定时任务</h4><p>在某些业务场景的需求下,也可以通过定时任务的方式进行 Redis 和 MySQL 的数据同步。</p>\n<p>具体做法是通过定时任务从 Redis 中读取数据,然后跟 MySQL 中的数据进行比对,如果 Redis 中数据有变化,则进行同步。</p>\n<p><img src=\"/img/2024/2024111112.png\" alt=\"https://erik.xyz\"></p>\n<p>这种方式虽然实现起来比较简单,但需要注意同步的时效性,如果时间间隔设置不当,可能会导致同步的数据丢失或者不准确。</p>\n<h4 id=\"双写一致性\"><a href=\"#双写一致性\" class=\"headerlink\" title=\"双写一致性\"></a>双写一致性</h4><p>在更新数据库的同时也更新缓存/删除缓存,即所谓的“<strong>双写</strong>”。</p>\n<p>这样可以确保在数据库更新后,缓存中的数据也是最新的,从而减少数据不一致的时间窗口。</p>\n<p><img src=\"/img/2024/2024111113.png\" alt=\"https://erik.xyz\"></p>\n<p><strong>并发控制</strong>:在高并发场景下,多个请求同时对同一个数据进行更新时,如果没有妥善处理并发控制,可能会导致数据不一致的问题。所以这里引入了分布式锁和事务操作:</p>\n<p><strong>使用分布式锁</strong>:在执行双写操作之前,获取一个分布式锁(如Zookeeper、Redis的SETNX命令等),确保同一时刻只有一个线程/进程能够执行双写操作。</p>\n<p><strong>事务处理</strong>:对于支持事务的缓存系统(如Redis的MULTI/EXEC命令)和MySQL事务,可以将Redis缓存和MySQL更新操作放入事务中,确保要么全部成功,要么全部失败。</p>\n<p>当然在“双写”的策略中,除了并发控制外,可以结合上面提到的重试、定时策略进行组合,以应对极端情况下的数据不一致性问题。</p>\n<p>另外也可以处理失败的逻辑上加入告警机制,及时通知开发和运维人员。</p>\n<p>转载自:<a href=\"https://mp.weixin.qq.com/s/sG7xDtLKLtlnu9ntpc5hdw\">皇子谈技术</a></p>\n",
"tags": [
"mysql",
"redis",
"数据一致性"
]
},
{
"id": "https://erik.xyz/2024/10/23/rohibit-video-playback/",
"url": "https://erik.xyz/2024/10/23/rohibit-video-playback/",
"title": "在手机浏览器中禁止播放视频、暂停、快进等操作",
"date_published": "2024-10-23T12:27:00.000Z",
"content_html": "<p><b>1. 禁止暂停、快进等操作</b></p>\n<p>可以通过将 controls 属性从 <code><video></code>元素中移除,防止用户通过控制条进行操作。然后通过 JavaScript 强制控制视频播放的状态,不允许用户暂停或快进。<br><span id=\"more\"></span><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><video id="myVideo" width="100%" autoplay loop></span><br><span class=\"line\"> <source src="your-video.mp4" type="video/mp4"></span><br><span class=\"line\"> Your browser does not support the video tag.</span><br><span class=\"line\"></video></span><br><span class=\"line\"></span><br><span class=\"line\"><script></span><br><span class=\"line\"> var video = document.getElementById("myVideo");</span><br><span class=\"line\"> </span><br><span class=\"line\"> // 禁止暂停、快进等操作</span><br><span class=\"line\"> video.controls = false; // 禁用控件</span><br><span class=\"line\"> video.play(); // 强制播放</span><br><span class=\"line\"> </span><br><span class=\"line\"> // 监听时间更新事件,确保视频不被暂停或快进</span><br><span class=\"line\"> video.addEventListener('play', function() {</span><br><span class=\"line\"> video.currentTime = 0; // 重置视频时间,避免用户快进</span><br><span class=\"line\"> });</span><br><span class=\"line\"></span><br><span class=\"line\"> video.addEventListener('seeked', function() {</span><br><span class=\"line\"> video.currentTime = 0; // 重置视频时间,避免用户跳跃</span><br><span class=\"line\"> });</span><br><span class=\"line\"></span><br><span class=\"line\"> // 监听点击暂停等操作</span><br><span class=\"line\"> video.addEventListener('pause', function() {</span><br><span class=\"line\"> video.play(); // 禁止暂停</span><br><span class=\"line\"> });</span><br><span class=\"line\"></script></span><br></pre></td></tr></table></figure><br><b>2. 使用 pointer-events 禁止点击操作</b></p>\n<p>你还可以通过 CSS 来禁止用户与视频控件进行交互。通过 pointer-events: none; 可以使用户无法点击视频,进而禁止播放、暂停等交互操作。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><video id="myVideo" width="100%" autoplay loop></span><br><span class=\"line\"> <source src="your-video.mp4" type="video/mp4"></span><br><span class=\"line\"> Your browser does not support the video tag.</span><br><span class=\"line\"></video></span><br><span class=\"line\"></span><br><span class=\"line\"><style></span><br><span class=\"line\"> #myVideo {</span><br><span class=\"line\"> pointer-events: none; /* 禁止用户交互 */</span><br><span class=\"line\"> }</span><br><span class=\"line\"></style></span><br></pre></td></tr></table></figure>\n<p><b>3. 禁止视频控制条显示</b></p>\n<p>一些手机浏览器会自动显示视频控制条。为了禁止这种行为,可以通过 CSS 控制:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><video id="myVideo" width="100%" autoplay loop></span><br><span class=\"line\"> <source src="your-video.mp4" type="video/mp4"></span><br><span class=\"line\"> Your browser does not support the video tag.</span><br><span class=\"line\"></video></span><br><span class=\"line\"></span><br><span class=\"line\"><style></span><br><span class=\"line\"> #myVideo::-webkit-media-controls {</span><br><span class=\"line\"> display: none !important; /* Safari/iOS */</span><br><span class=\"line\"> }</span><br><span class=\"line\"> #myVideo::-moz-media-controls {</span><br><span class=\"line\"> display: none !important; /* Firefox */</span><br><span class=\"line\"> }</span><br><span class=\"line\"> #myVideo::-ms-media-controls {</span><br><span class=\"line\"> display: none !important; /* IE/Edge */</span><br><span class=\"line\"> }</span><br><span class=\"line\"></style></span><br></pre></td></tr></table></figure>\n<p><b>4. 完全阻止用户与视频交互</b></p>\n<p>通过将视频元素设置为全屏,或者完全覆盖在视频上的透明层也可以实现禁止用户与视频交互的效果。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><video id="myVideo" width="100%" autoplay loop></span><br><span class=\"line\"> <source src="your-video.mp4" type="video/mp4"></span><br><span class=\"line\"> Your browser does not support the video tag.</span><br><span class=\"line\"></video></span><br><span class=\"line\"></span><br><span class=\"line\"><div id="overlay" style="position:absolute; top:0; left:0; width:100%; height:100%; background: rgba(255, 255, 255, 0); pointer-events: all;"></div></span><br></pre></td></tr></table></figure>",
"tags": [
"浏览器禁止操作视频",
"禁止视频"
]
},
{
"id": "https://erik.xyz/2024/09/22/internet-practice/",
"url": "https://erik.xyz/2024/09/22/internet-practice/",
"title": "史上最详细的互联网项目管理实战图解",
"date_published": "2024-09-22T00:46:00.000Z",
"content_html": "<p><img src=\"/img/2024/640.jpeg\" alt=\"erik.xyz\"></p>\n<p>如何系统的掌握互联网项目管理知识和经验,我搜罗世面上基本没有介绍这方面的图书,为此咱们去年年初,组织咱们前沿社区的众多大咖联合电子工业出版社出版了这本新书《互联网项目管理实战指南》希望可以助大家一臂之力!今天把文章的思维导图干货一起分享给大家!<br><span id=\"more\"></span><br><img src=\"/img/2024/640.png\" alt=\"erik.xyz\"></p>\n<p><img src=\"/img/2024/641.png\" alt=\"erik.xyz\"></p>\n<p><img src=\"/img/2024/642.png\" alt=\"erik.xyz\"></p>\n<p><img src=\"/img/2024/643.png\" alt=\"erik.xyz\"></p>\n<p><img src=\"/img/2024/644.png\" alt=\"erik.xyz\"></p>\n<p>互联网项目管理的17条经验沟通是王道 </p>\n<ul>\n<li>不论技术多么先进,没有良好的沟通机制,项目都会遇到瓶颈。确保团队成员之间有开放的沟通渠道,并且每个人都清楚自己的职责和项目的整体目标。优先级高于一切 </li>\n<li>学会区分哪些功能是必须的,哪些可以稍后添加。优先处理那些对用户来说最重要的功能。短周期发布 </li>\n<li>通过缩短发布周期,可以更快地得到用户反馈,并及时调整方向,减少资源浪费。用户反馈是金 - 始终保持与用户的联系,收集他们的反馈,并快速做出响应。这有助于产品不断改进。简化就是美 </li>\n<li>尽量简化产品的设计和功能,避免不必要的复杂性。简单易用的产品往往更受欢迎。质量控制不可忽视 </li>\n<li>在项目的每个阶段都要进行质量检查,防止小问题积累成大问题。风险管理要提前 - 对潜在的风险进行评估,并制定相应的应对策略,这样可以在问题发生前就解决它们。灵活调整计划 </li>\n<li>计划永远赶不上变化,学会在项目过程中根据实际情况调整计划是非常重要的。团队建设很重要 </li>\n<li>投资于团队建设活动,增强团队凝聚力,提高工作效率。技术选型要谨慎 </li>\n<li>在选择技术栈时要考虑长远发展,而不是仅仅因为某项技术当前流行。数据驱动决策 </li>\n<li>使用数据分析来支持产品决策,而不是仅凭直觉。重视用户体验 </li>\n<li>用户体验应该贯穿整个产品生命周期,从设计到开发再到测试,都要考虑到用户体验。持续学习与适应 </li>\n<li>技术和市场都在不断变化,持续学习新的知识和技术是必要的。文档化重要信息 </li>\n<li>确保所有的关键决策和信息都有记录,这对于新加入的团队成员来说尤其重要。健康的工作生活平衡 - 鼓励团队成员保持良好的工作生活平衡,过度劳累只会降低生产力和创新能力。持续集成/持续部署(CI/CD) </li>\n<li>实施CI/CD流程,不仅提高了代码的质量,也加速了软件的发布周期。透明度提升信任 </li>\n<li>保持项目进展的透明,定期向所有相关方汇报进度,这样可以建立信任并减少误解。</li>\n</ul>\n<p>转载自:<a href=\"https://mp.weixin.qq.com/s/kv0BAmE6ASll9eZjvlZImQ\">PMO前沿</a></p>\n",
"tags": [
"互联",
"项目实战"
]
},
{
"id": "https://erik.xyz/2024/08/23/disk-cleanup/",
"url": "https://erik.xyz/2024/08/23/disk-cleanup/",
"title": "清理c盘",
"date_published": "2024-08-23T14:37:00.000Z",
"content_html": "<p><b>1. 使用磁盘清理工具</b></p>\n<p>Windows 10自带了一个磁盘清理工具,可以用来删除临时文件、系统文件和其他不必要的文件。</p>\n<ol>\n<li>打开磁盘清理工具:<ul>\n<li>点击开始菜单,输入“磁盘清理”,并选择搜索结果中的“磁盘清理”。</li>\n</ul>\n</li>\n</ol>\n<ul>\n<li>选择要清理的驱动器:<ul>\n<li>在弹出的窗口中,选择C盘(通常为系统盘),然后点击“确定”。</li>\n</ul>\n</li>\n<li>选择要删除的文件类型:<ul>\n<li>在磁盘清理窗口中,系统会列出可以删除的文件类型(如临时文件、系统缓存、回收站等)。你可以选择你想清理的文件类型。<br>点击“清理系统文件”按钮,允许清理更多系统文件(如Windows更新文件)。</li>\n</ul>\n</li>\n<li>确认删除:<ul>\n<li>点击“确定”后,系统将开始清理这些文件,清理完成后C盘的空间将得到释放。</li>\n</ul>\n</li>\n</ul>\n<p><b>2. 删除临时文件</b><br><span id=\"more\"></span><br>Windows 10允许你直接删除临时文件,这些文件可能积累过多,占用大量空间。</p>\n<ol>\n<li>打开“设置”应用,选择系统 > 存储。</li>\n</ol>\n<ul>\n<li>在“本地磁盘 (C:)”下,点击临时文件。</li>\n<li>选择要删除的文件类型,例如缓存、下载的文件等。</li>\n<li>点击“删除文件”按钮。</li>\n</ul>\n<p><b>3. 卸载不需要的软件</b><br>有时一些不常用的程序占据了大量的磁盘空间。</p>\n<ol>\n<li>打开“设置”应用,选择应用 > 应用和功能。</li>\n</ol>\n<ul>\n<li>查找不常用的程序,点击它,然后选择卸载。</li>\n</ul>\n<p><b>4. 清理浏览器缓存</b></p>\n<p>浏览器缓存也会占用不少空间,定期清理这些缓存有助于释放磁盘空间。</p>\n<ul>\n<li>Google Chrome:进入设置 > 高级 > 隐私与安全 > 清除浏览数据。</li>\n<li>Microsoft Edge:设置 > 隐私、搜索和服务 > 清除浏览数据。</li>\n</ul>\n<p><b>5. 关闭休眠功能</b><br>如果你不需要休眠功能,可以关闭它,因为休眠文件(hiberfil.sys)占用的空间有时会很大。</p>\n<ol>\n<li>打开命令提示符(管理员),输入以下命令:</li>\n</ol>\n<p><code>powercfg -h off</code></p>\n<ul>\n<li>按回车键,这将禁用休眠功能并删除hiberfil.sys文件。</li>\n</ul>\n<p><b>6. 移动文件到其他驱动器</b></p>\n<p>如果C盘空间仍然紧张,你可以将大文件(如视频、图片和文档)移动到其他磁盘分区或外部硬盘。</p>\n<p><b>7. 使用第三方工具</b></p>\n<p>一些第三方工具也可以帮助你清理C盘并优化磁盘空间,例如:</p>\n<ul>\n<li>CCleaner:清理系统垃圾文件和注册表。</li>\n<li><a href=\"https://windirstat.net/download.html\">WinDirStat</a>:分析磁盘空间占用情况,找出大文件。</li>\n</ul>\n<p><b>8. 删除系统还原点</b></p>\n<p>如果你启用了系统还原,可能会存有很多还原点占用磁盘空间。</p>\n<ol>\n<li>右键点击此电脑,选择属性。</li>\n</ol>\n<ul>\n<li>点击系统保护,选择C盘,然后点击配置。</li>\n<li>在弹出的窗口中,点击“删除”来删除旧的还原点。</li>\n</ul>\n<p><b>9. 清理Windows更新文件</b></p>\n<p>Windows更新可能会留下不必要的安装文件。</p>\n<p>在磁盘清理工具中,点击“清理系统文件”,选择Windows更新清理,然后清理过时的更新文件。</p>\n<p>通过以上步骤,你可以释放C盘的空间,使你的Windows 10系统运行得更加顺畅。</p>\n",
"tags": [
"清理系统",
"清理c盘"
]
},
{
"id": "https://erik.xyz/2024/07/22/sql-performance-optimization/",
"url": "https://erik.xyz/2024/07/22/sql-performance-optimization/",
"title": "SQL性能优化的47个小技巧,果断收藏!",
"date_published": "2024-07-22T04:44:34.000Z",
"content_html": "<p><strong>1、先了解MySQL的执行过程</strong></p>\n<p>了解了MySQL的执行过程,我们才知道如何进行sql优化。</p>\n<p>1.客户端发送一条查询语句到服务器;</p>\n<p>2.服务器先查询缓存,如果命中缓存,则立即返回存储在缓存中的数据;</p>\n<p>3.未命中缓存后,MySQL通过关键字将SQL语句进行解析,并生成一颗对应的解析树,MySQL解析器将使用MySQL语法进行验证和解析。例如,验证是否使用了错误的关键字,或者关键字的使用是否正确;</p>\n<p>4.预处理是根据一些MySQL规则检查解析树是否合理,比如检查表和列是否存在,还会解析名字和别名,然后预处理器会验证权限;</p>\n<p>5.根据执行计划查询执行引擎,调用API接口调用存储引擎来查询数据;</p>\n<p>6.将结果返回客户端,并进行缓存;<br><span id=\"more\"></span></p>\n<p><img src=\"/img/2024/202405201.png\" alt=\"erik.xyz\"></p>\n<p><strong>2、数据库常见规范</strong></p>\n<p>1.所有数据库对象名称必须使用小写字母并用下划线分割;</p>\n<p>2.所有数据库对象名称禁止使用mysql保留关键字;</p>\n<p>3.数据库对象的命名要能做到见名识意,并且最后不要超过32个字符;</p>\n<p>4.临时库表必须以tmp<em>为前缀并以日期为后缀,备份表必须以bak</em>为前缀并以日期(时间戳)为后缀;</p>\n<p>5.所有存储相同数据的列名和列类型必须一致;</p>\n<p><strong>3、所有表必须使用Innodb存储引擎</strong></p>\n<p>没有特殊要求(即Innodb无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用Innodb存储引擎(mysql5.5之前默认使用Myisam,5.6以后默认的为Innodb)。</p>\n<p>Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。</p>\n<p><strong>4、每个Innodb表必须有个主键</strong></p>\n<p>Innodb是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引,但是表的存储顺序只能有一种。</p>\n<p>Innodb是按照主键索引的顺序来组织表的</p>\n<p>1.不要使用更新频繁的列作为主键,不适用多列主键;</p>\n<p>2.不要使用UUID、MD5、HASH、字符串列作为主键(无法保证数据的顺序增长);</p>\n<p>3.主键建议使用自增ID值;</p>\n<p><strong>5、数据库和表的字符集统一使用UTF8</strong></p>\n<p>兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储emoji表情的需要,字符集需要采用utf8mb4字符集。</p>\n<p><strong>6、查询SQL尽量不要使用select *,而是具体字段</strong></p>\n<p>select *的弊端:</p>\n<p>1.增加很多不必要的消耗,比如CPU、IO、内存、网络带宽;</p>\n<p>2.增加了使用覆盖索引的可能性;</p>\n<p>3.增加了回表的可能性;</p>\n<p>4.当表结构发生变化时,前端也需要更改;</p>\n<p>5.查询效率低;</p>\n<p><strong>7、避免在where子句中使用 or 来连接条件</strong></p>\n<p>1.使用or可能会使索引失效,从而全表扫描;</p>\n<p>2.对于or没有索引的salary这种情况,假设它走了id的索引,但是走到salary查询条件时,它还得全表扫描;</p>\n<p>3.也就是说整个过程需要三步:全表扫描+索引扫描+合并。如果它一开始就走全表扫描,直接一遍扫描就搞定;</p>\n<p>4.虽然mysql是有优化器的,处于效率与成本考虑,遇到or条件,索引还是可能失效的;</p>\n<p><strong>8、尽量使用数值替代字符串类型</strong></p>\n<p>1.因为引擎在处理查询和连接时会逐个比较字符串中每一个字符;</p>\n<p>2.而对于数字型而言只需要比较一次就够了;</p>\n<p>3.字符会降低查询和连接的性能,并会增加存储开销;</p>\n<p><strong>9、使用varchar代替char</strong></p>\n<p>1.varchar变长字段按数据内容实际长度存储,存储空间小,可以节省存储空间;</p>\n<p>2.char按声明大小存储,不足补空格;</p>\n<p>3.其次对于查询来说,在一个相对较小的字段内搜索,效率更高;</p>\n<p><strong>10、财务、银行相关的金额字段必须使用decimal类型</strong></p>\n<ul>\n<li><p>非精准浮点:float,double</p>\n</li>\n<li><p>精准浮点:decimal</p>\n</li>\n</ul>\n<p>1.Decimal类型为精准浮点数,在计算时不会丢失精度;</p>\n<p>2.占用空间由定义的宽度决定,每4个字节可以存储9位数字,并且小数点要占用一个字节;</p>\n<p>3.可用于存储比bigint更大的整型数据;</p>\n<p><strong>11、避免使用ENUM类型</strong></p>\n<ul>\n<li><p>修改ENUM值需要使用ALTER语句;</p>\n</li>\n<li><p>ENUM类型的ORDER BY操作效率低,需要额外操作;</p>\n</li>\n<li><p>禁止使用数值作为ENUM的枚举值;</p>\n</li>\n</ul>\n<p><strong>12、去重distinct过滤字段要少</strong></p>\n<p>1.带distinct的语句占用cpu时间高于不带distinct的语句</p>\n<p>2.当查询很多字段时,如果使用distinct,数据库引擎就会对数据进行比较,过滤掉重复数据</p>\n<p>3.然而这个比较、过滤的过程会占用系统资源,如cpu时间</p>\n<p><strong>13、where中使用默认值代替null</strong></p>\n<p>1.并不是说使用了is null或者 is not null就会不走索引了,这个跟mysql版本以及查询成本都有关;</p>\n<p>2.如果mysql优化器发现,走索引比不走索引成本还要高,就会放弃索引,这些条件 !=,<>,is null,is not null经常被认为让索引失效;</p>\n<p>3.其实是因为一般情况下,查询的成本高,优化器自动放弃索引的;</p>\n<p>4.如果把null值,换成默认值,很多时候让走索引成为可能,同时,表达意思也相对清晰一点;</p>\n<p><strong>14、避免在where子句中使用!=或<>操作符</strong></p>\n<p>1.使用!=和<>很可能会让索引失效</p>\n<p>2.应尽量避免在where子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描实</p>\n<p>3.现业务优先,实在没办法,就只能使用,并不是不能使用</p>\n<p><strong>15、inner join 、left join、right join,优先使用inner join</strong></p>\n<p>三种连接如果结果相同,优先使用inner join,如果使用left join左边表尽量小。</p>\n<ul>\n<li><p>inner join 内连接,只保留两张表中完全匹配的结果集;</p>\n</li>\n<li><p>left join会返回左表所有的行,即使在右表中没有匹配的记录;</p>\n</li>\n<li><p>right join会返回右表所有的行,即使在左表中没有匹配的记录;</p>\n</li>\n</ul>\n<p>为什么?</p>\n<ul>\n<li>如果inner join是等值连接,返回的行数比较少,所以性能相对会好一点;</li>\n<li>使用了左连接,左边表数据结果尽量小,条件尽量放到左边处理,意味着返回的行数可能比较少;</li>\n<li>这是mysql优化原则,就是小表驱动大表,小的数据集驱动大的数据集,从而让性能更优;</li>\n</ul>\n<p><strong>16、提高group by语句的效率</strong></p>\n<p>1、反例</p>\n<p>先分组,再过滤<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">select job, avg(salary) from employee group by jobhaving job ='develop' or job = 'test';</span><br></pre></td></tr></table></figure></p>\n<p>2、正例</p>\n<p>先过滤,后分组<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">select job,avg(salary) from employee where job ='develop' or job = 'test' group by job;</span><br></pre></td></tr></table></figure></p>\n<p>3、理由</p>\n<p>可以在执行到该语句前,把不需要的记录过滤掉</p>\n<p><strong>17、清空表时优先使用truncate</strong></p>\n<p>truncate table在功能上与不带 where子句的 delete语句相同:二者均删除表中的全部行。但 truncate table比 delete速度快,且使用的系统和事务日志资源少。</p>\n<p>delete语句每次删除一行,并在事务日志中为所删除的每行记录一项。truncate table通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。</p>\n<p>truncate table删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 drop table语句。</p>\n<p>对于由 foreign key约束引用的表,不能使用 truncate table,而应使用不带 where子句的 DELETE 语句。由于 truncate table不记录在日志中,所以它不能激活触发器。</p>\n<p>truncate table不能用于参与了索引视图的表。</p>\n<p><strong>18、操作delete或者update语句,加个limit或者循环分批次删除</strong></p>\n<p>(1)降低写错SQL的代价</p>\n<p>清空表数据可不是小事情,一个手抖全没了,删库跑路?如果加limit,删错也只是丢失部分数据,可以通过binlog日志快速恢复的。</p>\n<p>(2)SQL效率很可能更高</p>\n<p>SQL中加了limit 1,如果第一条就命中目标return, 没有limit的话,还会继续执行扫描表。</p>\n<p>(3)避免长事务</p>\n<p>delete执行时,如果age加了索引,MySQL会将所有相关的行加写锁和间隙锁,所有执行相关行会被锁住,如果删除数量大,会直接影响相关业务无法使用。</p>\n<p>(4)数据量大的话,容易把CPU打满</p>\n<p>如果你删除数据量很大时,不加 limit限制一下记录数,容易把cpu打满,导致越删越慢。</p>\n<p>(5)锁表</p>\n<p>一次性删除太多数据,可能造成锁表,会有lock wait timeout exceed的错误,所以建议分批操作。</p>\n<p><strong>19、UNION操作符</strong></p>\n<p>UNION在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果。实际大部分应用中是不会产生重复的记录,最常见的是过程表与历史表UNION。如:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">select username,tel from userunionselect departmentname from department</span><br></pre></td></tr></table></figure><br>这个SQL在运行时先取出两个表的结果,再用排序空间进行排序删除重复的记录,最后返回结果集,如果表数据量大的话可能会导致用磁盘进行排序。推荐方案:采用UNION ALL操作符替代UNION,因为UNION ALL操作只是简单的将两个结果合并后就返回。</p>\n<p><strong>20、SQL语句中IN包含的字段不宜过多</strong></p>\n<p>MySQL的IN中的常量全部存储在一个数组中,这个数组是排序的。如果值过多,产生的消耗也是比较大的。如果是连续的数字,可以使用between代替,或者使用连接查询替换。</p>\n<p><strong>21、批量插入性能提升</strong></p>\n<p>(1)多条提交</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">INSERT INTO user (id,username) VALUES(1,'哪吒编程');INSERT INTO user (id,username) VALUES(2,'妲己');</span><br></pre></td></tr></table></figure>\n<p>(2)批量提交<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">INSERT INTO user (id,username) VALUES(1,'哪吒编程'),(2,'妲己');</span><br></pre></td></tr></table></figure></p>\n<p>默认新增SQL有事务控制,导致每条都需要事务开启和事务提交,而批量处理是一次事务开启和提交,效率提升明显,达到一定量级,效果显著,平时看不出来。</p>\n<p><strong>22、表连接不宜太多,索引不宜太多,一般5个以内</strong></p>\n<p>(1)表连接不宜太多,一般5个以内</p>\n<p>1.关联的表个数越多,编译的时间和开销也就越大</p>\n<p>2.每次关联内存中都生成一个临时表</p>\n<p>3.应该把连接表拆开成较小的几个执行,可读性更高</p>\n<p>4.如果一定需要连接很多表才能得到数据,那么意味着这是个糟糕的设计了</p>\n<p>5.阿里规范中,建议多表联查三张表以下</p>\n<p>(2)索引不宜太多,一般5个以内</p>\n<p>1.索引并不是越多越好,虽其提高了查询的效率,但却会降低插入和更新的效率;</p>\n<p>2.索引可以理解为一个就是一张表,其可以存储数据,其数据就要占空间;</p>\n<p>3.索引表的数据是排序的,排序也是要花时间的;</p>\n<p>4.insert或update时有可能会重建索引,如果数据量巨大,重建将进行记录的重新排序,所以建索引需要慎重考虑,视具体情况来定;</p>\n<p>5.一个表的索引数最好不要超过5个,若太多需要考虑一些索引是否有存在的必要;</p>\n<p><strong>23、禁止给表中的每一列都建立单独的索引</strong></p>\n<p>真有这么干的,我也是醉了。</p>\n<p>2万字带你精通MySQL索引</p>\n<p><strong>24、如何选择索引列的顺序</strong></p>\n<p>建立索引的目的是:希望通过索引进行数据查找,减少随机IO,增加查询性能 ,索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。</p>\n<p>区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数)。</p>\n<p>尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO性能也就越好)。</p>\n<p>使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)。</p>\n<p><strong>25、对于频繁的查询优先考虑使用覆盖索引</strong></p>\n<p>覆盖索引:就是包含了所有查询字段(where,select,ordery by,group by包含的字段)的索引。</p>\n<p>覆盖索引的好处:</p>\n<p>(1)避免Innodb表进行索引的二次查询</p>\n<p>Innodb是以聚集索引的顺序来存储的,对于Innodb来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。</p>\n<p>而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询 ,减少了IO操作,提升了查询效率。</p>\n<p>(2)可以把随机IO变成顺序IO加快查询效率</p>\n<p>由于覆盖索引是按键值的顺序存储的,对于IO密集型的范围查找来说,对比随机从磁盘读取每一行的数据IO要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的IO转变成索引查找的顺序IO。</p>\n<p><strong>26、建议使用预编译语句进行数据库操作</strong></p>\n<p>预编译语句可以重复使用这些计划,减少SQL编译所需要的时间,还可以解决动态SQL所带来的SQL注入的问题。</p>\n<p>只传参数,比传递SQL语句更高效。</p>\n<p>相同语句可以一次解析,多次使用,提高处理效率。</p>\n<p><strong>27、避免产生大事务操作</strong></p>\n<p>大批量修改数据,一定是在一个事务中进行的,这就会造成表中大批量数据进行锁定,从而导致大量的阻塞,阻塞会对MySQL的性能产生非常大的影响。</p>\n<p>特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批。</p>\n<p><strong>28、避免在索引列上使用内置函数</strong></p>\n<p>使用索引列上内置函数,索引失效。</p>\n<p><strong>29、组合索引</strong></p>\n<p>排序时应按照组合索引中各列的顺序进行排序,即使索引中只有一个列是要排序的,否则排序性能会比较差。<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">create index IDX_USERNAME_TEL on user(deptid,position,createtime);select username,tel from user where deptid= 1 and position = 'java开发' order by deptid,position,createtime desc; </span><br></pre></td></tr></table></figure></p>\n<p>实际上只是查询出符合 deptid= 1 and position = ‘java开发’条件的记录并按createtime降序排序,但写成order by createtime desc性能较差。</p>\n<p><strong>30、复合索引最左特性</strong></p>\n<p>(1)创建复合索引<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">ALTER TABLE employee ADD INDEX idx_name_salary (name,salary)</span><br></pre></td></tr></table></figure><br>(2)满足复合索引的最左特性,哪怕只是部分,复合索引生效<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">SELECT * FROM employee WHERE NAME='哪吒编程'</span><br></pre></td></tr></table></figure><br>(3)没有出现左边的字段,则不满足最左特性,索引失效<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">SELECT * FROM employee WHERE salary=5000</span><br></pre></td></tr></table></figure><br>(4)复合索引全使用,按左侧顺序出现 name,salary,索引生效<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">SELECT * FROM employee WHERE NAME='哪吒编程' AND salary=5000</span><br></pre></td></tr></table></figure><br>(5)虽然违背了最左特性,但MySQL执行SQL时会进行优化,底层进行颠倒优化<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">SELECT * FROM employee WHERE salary=5000 AND NAME='哪吒编程'</span><br></pre></td></tr></table></figure><br>(6)理由<br>复合索引也称为联合索引,当我们创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。</p>\n<p>联合索引不满足最左原则,索引一般会失效。</p>\n<p><strong>31、必要时可以使用force index来强制查询走某个索引</strong></p>\n<p>有的时候MySQL优化器采取它认为合适的索引来检索SQL语句,但是可能它所采用的索引并不是我们想要的。这时就可以采用forceindex来强制优化器使用我们制定的索引。</p>\n<p><strong>32、优化like语句</strong></p>\n<p>模糊查询,程序员最喜欢的就是使用like,但是like很可能让你的索引失效。</p>\n<ul>\n<li><p>首先尽量避免模糊查询,如果必须使用,不采用全模糊查询,也应尽量采用右模糊查询, 即like ‘…%’,是会使用索引的;</p>\n</li>\n<li><p>左模糊like ‘%…’无法直接使用索引,但可以利用reverse + function index的形式,变化成 like ‘…%’;</p>\n</li>\n<li><p>全模糊查询是无法优化的,一定要使用的话建议使用搜索引擎。</p>\n</li>\n</ul>\n<p><strong>33、统一SQL语句的写法</strong></p>\n<p>对于以下两句SQL语句, 程序员认为是相同的,数据库查询优化器认为是不同的。<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">select * from user;select * From USER;</span><br></pre></td></tr></table></figure></p>\n<p>这都是很常见的写法,也很少有人会注意,就是表名大小写不一样而已。然而,查询解析器认为这是两个不同的SQL语句,要解析两次,生成两个不同的执行计划,作为一名严谨的Java开发工程师,应该保证两个一样的SQL语句,不管在任何地方都是一样的。</p>\n<p><strong>34、不要把SQL语句写得太复杂</strong></p>\n<p>经常听到有人吹牛逼,我写了一个800行的SQL语句,逻辑感超强,我们还开会进行了SQL讲解,大家都投来了崇拜的目光。。。</p>\n<p>一般来说,嵌套子查询、或者是3张表关联查询还是比较常见的,但是,如果超过3层嵌套的话,查询优化器很容易给出错误的执行计划,影响SQL效率。SQL执行计划是可以被重用的,SQL越简单,被重用的概率越大,生成执行计划也是很耗时的。</p>\n<p><strong>35、将大的DELETE,UPDATE、INSERT 查询变成多个小查询</strong></p>\n<p>能写一个几十行、几百行的SQL语句是不是显得逼格很高?然而,为了达到更好的性能以及更好的数据控制,你可以将他们变成多个小查询。</p>\n<p><strong>36、关于临时表</strong></p>\n<p>1.避免频繁创建和删除临时表,以减少系统表资源的消耗;</p>\n<p>2.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log;</p>\n<p>3.如果数据量不大,为了缓和系统表的资源,应先create table,然后insert;</p>\n<p>4.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除。先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。</p>\n<p><strong>37、使用explain分析你SQL执行计划</strong></p>\n<p>(1)type</p>\n<p>1.system:表仅有一行,基本用不到;</p>\n<p>2.const:表最多一行数据配合,主键查询时触发较多;</p>\n<p>3.eq_ref:对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型;</p>\n<p>4.ref:对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取;</p>\n<p>5.range:只检索给定范围的行,使用一个索引来选择行。当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用range;</p>\n<p>6.index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小;</p>\n<p>7.all:全表扫描;</p>\n<p>8.性能排名:system > const > eq_ref > ref > range > index > all。</p>\n<p>9.实际sql优化中,最后达到ref或range级别。</p>\n<p>(2)Extra常用关键字</p>\n<ul>\n<li><p>Using index:只从索引树中获取信息,而不需要回表查询;</p>\n</li>\n<li><p>Using where:WHERE子句用于限制哪一个行匹配下一个表或发送到客户。除非你专门从表中索取或检查所有行,如果Extra值不为Using where并且表联接类型为ALL或index,查询可能会有一些错误。需要回表查询。</p>\n</li>\n<li><p>Using temporary:mysql常建一个临时表来容纳结果,典型情况如查询包含可以按不同情况列出列的GROUP BY和ORDER BY子句时;</p>\n</li>\n</ul>\n<p><strong>38、读写分离与分库分表</strong></p>\n<p>当数据量达到一定的数量之后,限制数据库存储性能的就不再是数据库层面的优化就能够解决的;这个时候往往采用的是读写分离与分库分表同时也会结合缓存一起使用,而这个时候数据库层面的优化只是基础。</p>\n<p>读写分离适用于较小一些的数据量;分表适用于中等数据量;而分库与分表一般是结合着用,这就适用于大数据量的存储了,这也是现在大型互联网公司解决数据存储的方法之一。</p>\n<p><strong>39、使用合理的分页方式以提高分页的效率</strong><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">select id,name from user limit 100000, 20</span><br></pre></td></tr></table></figure></p>\n<p>使用上述SQL语句做分页的时候,随着表数据量的增加,直接使用limit语句会越来越慢。<br>此时,可以通过取前一页的最大ID,以此为起点,再进行limit操作,效率提升显著。<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">select id,name from user where id> 100000 limit 20</span><br></pre></td></tr></table></figure></p>\n<p><strong>40、尽量控制单表数据量的大小,建议控制在500万以内。</strong></p>\n<p>500万并不是MySQL数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。<br>可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小。</p>\n<p><strong>41、谨慎使用Mysql分区</strong></p>\n<ul>\n<li><p>表分区表在物理上表现为多个文件,在逻辑上表现为一个表;</p>\n</li>\n<li><p>谨慎选择分区键,跨分区查询效率可能更低;</p>\n</li>\n<li><p>建议采用物理分表的方式管理大数据。</p>\n</li>\n</ul>\n<p><strong>42、尽量做到冷热数据分离,减小表的宽度</strong></p>\n<p>Mysql限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节。</p>\n<p>减少磁盘IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO);</p>\n<p>更有效的利用缓存,避免读入无用的冷数据;</p>\n<p>经常一起使用的列放到一个表中(避免更多的关联操作)。</p>\n<p><strong>43、禁止在表中建立预留字段</strong></p>\n<p>1.预留字段的命名很难做到见名识义;</p>\n<p>2.预留字段无法确认存储的数据类型,所以无法选择合适的类型;</p>\n<p>3.对预留字段类型的修改,会对表进行锁定;</p>\n<p><strong>44、禁止在数据库中存储图片,文件等大的二进制数据</strong></p>\n<p>通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机IO操作,文件很大时,IO操作很耗时。</p>\n<p>通常存储于文件服务器,数据库只存储文件地址信息。</p>\n<p><strong>45、建议把BLOB或是TEXT列分离到单独的扩展表中</strong></p>\n<p>Mysql内存临时表不支持TEXT、BLOB这样的大数据类型,如果查询中包含这样的数据,在排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。而且对于这种数据,Mysql还是要进行二次查询,会使sql性能变得很差,但是不是说一定不能使用这样的数据类型。</p>\n<p>如果一定要使用,建议把BLOB或是TEXT列分离到单独的扩展表中,查询时一定不要使用select * 而只需要取出必要的列,不需要TEXT列的数据时不要对该列进行查询。</p>\n<p><strong>46、TEXT或BLOB类型只能使用前缀索引</strong></p>\n<p>因为MySQL对索引字段长度是有限制的,所以TEXT类型只能使用前缀索引,并且TEXT列上是不能有默认值的。</p>\n<p><strong>47、一些其它优化方式</strong></p>\n<p>(1)当只需要一条数据的时候,使用limit 1:<br>limit 1可以避免全表扫描,找到对应结果就不会再继续扫描了。</p>\n<p>(2)如果排序字段没有用到索引,就尽量少排序</p>\n<p>(3)所有表和字段都需要添加注释使用comment从句添加表和列的备注,从一开始就进行数据字典的维护。</p>\n<p>(4)SQL书写格式,关键字大小保持一致,使用缩进。</p>\n<p>(5)修改或删除重要数据前,要先备份。</p>\n<p>(6)很多时候用 exists 代替 in 是一个好的选择</p>\n<p>(7)where后面的字段,留意其数据类型的隐式转换。</p>\n<p>(8)尽量把所有列定义为NOT NULL:<br>NOT NULL列更节省空间,NULL列需要一个额外字节作为判断是否为 NULL的标志位。NULL列需要注意空指针问题,NULL列在计算和比较的时候,需要注意空指针问题。</p>\n<p>(9)伪删除设计</p>\n<p>(10)索引不适合建在有大量重复数据的字段上,比如性别,排序字段应创建索引</p>\n<p>(11)尽量避免使用游标:<br>因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。</p>\n<p>转载自:<a href=\"https://mp.weixin.qq.com/s/zGneQEY8_P3nL0nGI8tCFg\">哪吒编程</a></p>\n",
"tags": [
"mysql",
"sql",
"sql优化"
]
},
{
"id": "https://erik.xyz/2024/06/12/loading-zero-copy/",
"url": "https://erik.xyz/2024/06/12/loading-zero-copy/",
"title": "使用懒加载 + 零拷贝后,程序的秒开率提升至99.99%",
"date_published": "2024-06-12T02:00:56.000Z",
"content_html": "<h4 id=\"一、5秒钟加载一个页面的真相\"><a href=\"#一、5秒钟加载一个页面的真相\" class=\"headerlink\" title=\"一、5秒钟加载一个页面的真相\"></a>一、5秒钟加载一个页面的真相</h4><p>今天在修改前端页面的时候,发现程序中有一个页面的加载速度很慢,差不多需要5秒,这其实是难以接受的,我也不知道为什么上线这么长时间了,没人提过这个事儿。</p>\n<p>我记得有一个词儿,叫秒开率。<br><span id=\"more\"></span><br>秒开率是指能够在1秒内完成页面的加载。<br><img src=\"/img/2024/20240501.webp\" alt=\"erik.xyz\"></p>\n<p>查询的时候,会访问后台数据库,查询前20条数据,按道理来说,这应该很快才对。追踪代码,看看啥问题,最后发现问题有三:</p>\n<p>1.表中有一个BLOB大字段,存储着一个PDF模板,也就是上图中的运费模板;</p>\n<p>2.查询后会将这个PDF模板存储到本地磁盘点</p>\n<p>3.击线上显示,会读取本地的PDF模板,通过socket传到服务器。</p>\n<h5 id=\"大字段批量查询、批量文件落地、读取大文件并进行网络传输,不慢才怪,这一顿骚操作,5秒能加载完毕,已经烧高香了。\"><a href=\"#大字段批量查询、批量文件落地、读取大文件并进行网络传输,不慢才怪,这一顿骚操作,5秒能加载完毕,已经烧高香了。\" class=\"headerlink\" title=\"大字段批量查询、批量文件落地、读取大文件并进行网络传输,不慢才怪,这一顿骚操作,5秒能加载完毕,已经烧高香了。\"></a>大字段批量查询、批量文件落地、读取大文件并进行网络传输,不慢才怪,这一顿骚操作,5秒能加载完毕,已经烧高香了。</h5><p><img src=\"/img/2024/20240502.webp\" alt=\"erik.xyz\"></p>\n<h4 id=\"二、优化四步走\"><a href=\"#二、优化四步走\" class=\"headerlink\" title=\"二、优化四步走\"></a>二、优化四步走</h4><h5 id=\"1、“懒加载”\"><a href=\"#1、“懒加载”\" class=\"headerlink\" title=\"1、“懒加载”\"></a>1、“懒加载”</h5><p>经过调查发现,这个PDF模板只有在点击运费模板按钮时才会使用。</p>\n<ul>\n<li>优化1: 在点查询按钮时,不查询PDF模板;</li>\n<li>优化2: 点击运费模板时,根据uuid去查询,这样既能触发索引,也不用按时间排序,只是查询单条,速度快了很多很多,我愿称你为“懒加载”。</li>\n<li>优化3: 通过异步,将文件保存到磁盘中。</li>\n</ul>\n<p><img src=\"/img/2024/20240503.webp\" alt=\"erik.xyz\"></p>\n<p><strong>2、线上显示 = 就读取一个文件,为什么会慢呢?</strong></p>\n<p>打开代码一看,居然是通过FileReader读取的,我了个乖乖~这有什么问题吗?</p>\n<p>都是从百度拷贝过来的,百度还会有错吗?而且也测试了,没问题啊。</p>\n<p>嗯,对,是没问题,是可以实现需求,可是,为什么用这个?不知道。更别说效率问题了~</p>\n<p>优化4:通过缓冲流读取文件</p>\n<p><img src=\"/img/2024/20240504.webp\" alt=\"erik.xyz\"></p>\n<h4 id=\"三、先从上帝视角,了解一下啥子是IO流\"><a href=\"#三、先从上帝视角,了解一下啥子是IO流\" class=\"headerlink\" title=\"三、先从上帝视角,了解一下啥子是IO流\"></a>三、先从上帝视角,了解一下啥子是IO流</h4><p>Java I/O (Input/Output) 是对传统 I/O 操作的封装,它是以流的形式来操作数据的。</p>\n<p>1.InputStream代表一个输入流,它是一个抽象类,不能被实例化。InputStream定义了一些通用方法,如read()和skip()等,用于从输入流中读取数据;</p>\n<p>2.OutputStream代表一个输出流,它也是一个抽象类,不能被实例化。OutputStream定义了一些通用方法,如write()和flush()等,用于向输出流中写入数据;</p>\n<p>3.除了字节流,Java还提供字符流,字符流类似于字节流,不同之处在于字符流是按字符读写数据,而不是按字节。Java中最基本的字符流是Reader和Writer,它们是基于InputStream和OutputStream的转换类,用于完成字节流与字符流之间的转换。</p>\n<p>4.BufferedInputStream和BufferedOutputStream是I/O包中提供的缓冲输入输出流。它们可以提高I/O操作的效率,具有较好的缓存机制,能够减少磁盘操作,缩短文件传输时间。使用BufferedInputStream和 BufferedOutputStream进行读取和写入时,Java会自动调整缓冲区的大小,使其能够适应不同的数据传输速度。</p>\n<p>5.可以读取或写入 Java对象的流,比较典型的对象流包括ObjectInputStream 和ObjectOutputStream,将Java对象转换为字节流进行传输或存储;<br><img src=\"/img/2024/20240505.webp\" alt=\"erik.xyz\"></p>\n<p>在上一篇<a href=\"2024/05/06/index-asynchrony-landing/\">《增加索引+异步+不落地后,从12h优化到15min》</a>中,提到了4种优化方式,数据库优化、复用优化、并行优化、算法优化。</p>\n<p><strong>其中Buffered缓冲流就属于复用优化的一种,这个页面的查询完全可以通过复用优化优化一下。</strong></p>\n<h4 id=\"四、写个栗子,测试一下\"><a href=\"#四、写个栗子,测试一下\" class=\"headerlink\" title=\"四、写个栗子,测试一下\"></a>四、写个栗子,测试一下</h4><p><strong>1、通过字符输入流FileReader读取</strong></p>\n<p>FileReader连readLine()方法都没有,我也是醉了~</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">private static int readFileByReader(String filePath) {</span><br><span class=\"line\"> int result = 0;</span><br><span class=\"line\"> try (Reader reader = new FileReader(filePath)) {</span><br><span class=\"line\"> int value;</span><br><span class=\"line\"> while ((value = reader.read()) != -1) {</span><br><span class=\"line\"> result += value;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } catch (Exception e) {</span><br><span class=\"line\"> System.out.println("readFileByReader异常:" + e);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return result;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p><strong>2、通过缓冲流BufferedReader读取</strong><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">private static String readFileByBuffer(String filePath) {</span><br><span class=\"line\"> StringBuilder builder = new StringBuilder();</span><br><span class=\"line\"> try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {</span><br><span class=\"line\"> String data = null;</span><br><span class=\"line\"> while ((data = reader.readLine())!= null){</span><br><span class=\"line\"> builder.append(data);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }catch (Exception e) {</span><br><span class=\"line\"> System.out.println("readFileByReader异常:" + e);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return builder+"";</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure><br>通过循环模拟了150000个文件进行测试,FileReader耗时8136毫秒,BufferedReader耗时6718毫秒,差不多相差1秒半的时间,差距还是相当大的,俗话说得好,水滴石穿。</p>\n<p>同样是read方法,只不过是包了一层,有啥不同呢?</p>\n<p>BufferedReader 是一个缓冲字符输入流,可以对 FileRead 进行包装,提供了一个缓存数组,将数据按照一定规则读取到缓存区中,输入流每次读取文件数据时都需要将数据进行字符编码,而 BufferedReader 的出现,降低了输入流访问数据源的次数,将一定大小的数据一次读取到缓存区并进行字符编码,从而提高 IO 的效率。</p>\n<p>如果没有缓冲,每次调用 read() 或 readLine() 都可能导致从文件中读取字节,转换为字符,然后返回,这可能非常低效。</p>\n<p><strong>就像取快递一样,在取快递的时候,肯定是想一次性的取完,避免再来一趟。</strong></p>\n<ul>\n<li><p>FileReader就相当于一件一件的取,乐此不疲;</p>\n</li>\n<li><p>BufferedReader就相当于,你尽可能多的拿你的快递,可是这也有个极限,比如你一次只能拿5件快递,这个 5 就相当于缓冲区,效率上,提升数倍。</p>\n</li>\n</ul>\n<p>对 FileRead 进行包装变成了BufferedReader缓冲字符输入流,其实,Java IO流就是最典型的装饰器模式,装饰器模式通过组合替代继承的方式在不改变原始类的情况下添加增强功能,主要解决继承关系过于复杂的问题,之前整理过一篇装饰器模式,这里就不论述了。</p>\n<p><strong>3、再点进源码瞧瞧。</strong></p>\n<p><strong>(1)FileReader.read()源码很简单,就是直接读取</strong><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">public int read(char cbuf[], int off, int len) throws IOException {</span><br><span class=\"line\"> return in.read(cbuf, off, len);</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p>\n<p><strong>(2)BufferedReader.read()的源码就较为复杂了,看一下它的核心方法</strong><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">fill()private void fill() throws IOException {</span><br><span class=\"line\"> int dst;</span><br><span class=\"line\"> if (markedChar <= UNMARKED) {</span><br><span class=\"line\"> /* No mark */</span><br><span class=\"line\"> dst = 0;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> /* Marked */</span><br><span class=\"line\"> int delta = nextChar - markedChar;</span><br><span class=\"line\"> if (delta >= readAheadLimit) {</span><br><span class=\"line\"> /* Gone past read-ahead limit: Invalidate mark */</span><br><span class=\"line\"> markedChar = INVALIDATED;</span><br><span class=\"line\"> readAheadLimit = 0;</span><br><span class=\"line\"> dst = 0;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> if (readAheadLimit <= cb.length) {</span><br><span class=\"line\"> /* Shuffle in the current buffer */</span><br><span class=\"line\"> System.arraycopy(cb, markedChar, cb, 0, delta);</span><br><span class=\"line\"> markedChar = 0;</span><br><span class=\"line\"> dst = delta;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> /* Reallocate buffer to accommodate read-ahead limit */</span><br><span class=\"line\"> char ncb[] = new char[readAheadLimit];</span><br><span class=\"line\"> System.arraycopy(cb, markedChar, ncb, 0, delta);</span><br><span class=\"line\"> cb = ncb;</span><br><span class=\"line\"> markedChar = 0;</span><br><span class=\"line\"> dst = delta;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> nextChar = nChars = delta;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> int n;</span><br><span class=\"line\"> do {</span><br><span class=\"line\"> n = in.read(cb, dst, cb.length - dst);</span><br><span class=\"line\"> } while (n == 0);</span><br><span class=\"line\"> if (n > 0) {</span><br><span class=\"line\"> nChars = dst + n;</span><br><span class=\"line\"> nextChar = dst;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure><br><strong>核心方法fill():</strong></p>\n<p>1.字符缓冲输入流,底层有一个8192个元素的缓冲字符数组,当缓冲区的内容读完时,将使用 fill() 方法从硬盘中读取数据填充缓冲数组;</p>\n<p>2.字符缓冲输出流,底层有一个8192个元素的缓冲字符数组,使用flush方法将缓冲数组中的内容写入到硬盘当中;</p>\n<p>3.使用缓冲数组之后,程序在运行的大部分时间内都是内存和内存直接的数据交互过程。内存直接的操作效率是比较高的。并且降低了CPU通过内存操作硬盘的次数;</p>\n<p>4.关闭字符缓冲流,都会首先释放对应的缓冲数组空间,并且关闭创建对应的字符输入流和字符输出流。</p>\n<p>既然缓冲这么好用,为啥jdk将缓冲字符数组设置的这么小,才8192个字节?这是一个比较折中的方案,如果缓冲区太大的话,就会增加单次读写的时间,同样内存的大小也是有限制的,不可能都让你来干这个一件事。</p>\n<p>很多小伙伴也肯定用过它的read(char[] cbuf),它内部维护了一个char数组,每次写/读数据时,操作的是数组,这样可以减少IO次数。<br><img src=\"/img/2024/20240506.webp\" alt=\"erik.xyz\"></p>\n<p><strong>(3)buffer四大属性</strong></p>\n<p>1.mark:标记</p>\n<p>2.position:位置,下一个要被读或写的元素的索引, 每次读写缓冲区数据时都会改变改值, 为下次读写作准备</p>\n<p>3.limit:表示缓冲区的当前终点,不能对缓冲区 超过极限的位置进行读写操作。且极限 是可以修改的</p>\n<p>4.capacity:容量,即可以容纳的最大数据量;在缓 冲区创建时被设定并且不能改变。</p>\n<p><strong>4、缓冲流:4次上下文切换+4次拷贝</strong></p>\n<p>传统IO执行的话需要4次上下文切换(用户态->内核态->用户态->内核态->用户态)和4次拷贝。</p>\n<p>1.磁盘文件DMA拷贝到内核缓冲区</p>\n<p>2.内核缓冲区CPU拷贝到用户缓冲区</p>\n<p>3.用户缓冲区CPU拷贝到Socket缓冲区</p>\n<p>4.Socket缓冲区DMA拷贝到协议引擎。<br><img src=\"/img/2024/20240508.webp\" alt=\"erik.xyz\"></p>\n<h4 id=\"五、NIO之FileChannel\"><a href=\"#五、NIO之FileChannel\" class=\"headerlink\" title=\"五、NIO之FileChannel\"></a>五、NIO之FileChannel</h4><p>NIO中比较常用的是FileChannel,主要用来对本地文件进行 IO 操作。</p>\n<p><strong>1、FileChannel 常见的方法有</strong></p>\n<p>1.read,从通道读取数据并放到缓冲区中;</p>\n<p>2.write,把缓冲区的数据写到通道中;</p>\n<p>3.transferFrom,从目标通道 中复制数据到当前通道;</p>\n<p>4,transferTo,把数据从当 前通道复制给目标通道。</p>\n<p><strong>2、关于Buffer 和 Channel的注意事项和细节</strong></p>\n<p>1.ByteBuffer支持类型化的put和get, put放入的是什么数据类型,get就应该使用 相应的数据类型来取出,否则可能有 BufferUnderflowException 异常;</p>\n<p>2.可以将一个普通Buffer 转成只读Buffer;</p>\n<p>3.NIO 还提供了 MappedByteBuffer, 可以让文件直接在内存(堆外的内存)中进 行修改, 而如何同步到文件由NIO 来完成;</p>\n<p>4.NIO 还支持 通过多个 Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering。</p>\n<p><strong>3、Selector(选择器)</strong></p>\n<p>1.Java的NIO,用非阻塞的IO方式。可以用一个线程,处理多个的客户端连 接,就会使用到Selector(选择器);</p>\n<p>2.Selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然 后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个 通道,也就是管理多个连接和请求。</p>\n<p>3.只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。</p>\n<p>4,避免了多线程之间的上下文切换导致的开销。</p>\n<p><strong>4、selector的相关方法</strong></p>\n<p>1.open();//得到一个选择器对象</p>\n<p>2.select(long timeout);//监控所有注册的通道,当其中有IO操作可以进行时,将 对应的SelectionKey加入到内部集合中并返回,参数用来设置超时时间</p>\n<p>3.selectedKeys();//从内部集合中得到所有的SelectionKey。</p>\n<h4 id=\"六、内存映射技术mmap\"><a href=\"#六、内存映射技术mmap\" class=\"headerlink\" title=\"六、内存映射技术mmap\"></a>六、内存映射技术mmap</h4><p><strong>1、文件映射</strong></p>\n<p>传统的文件I/O操作可能会变得很慢,这时候mmap就闪亮登场了。</p>\n<p><strong>mmap(Memory-mapped files)是一种在内存中创建映射文件的机制,它可以使我们像访问内存一样访问文件,从而避免频繁的文件I/O操作。</strong></p>\n<p>使用mmap的方式是在内存中创建一个虚拟地址,然后将文件映射到这个虚拟地址上,这个映射的过程是由操作系统完成的。</p>\n<p>实现映射后,进程就可以采用指针的方式读写操作这一段内存,系统会自动回写到对应的文件磁盘上,这样就完成了对文件的读取操作,而不用调用 read、write 等系统函数。</p>\n<p>内核空间对这段区域的修改也会直接反映用户空间,从而可以实现不同进程间的文件共享。<br><img src=\"/img/2024/20240509.webp\" alt=\"erik.xyz\"></p>\n<p><strong>2、Java中使用mmap</strong></p>\n<p>在Java中,mmap技术主要使用了JavaNIO(New IO)库中的FileChannel 类,它提供了一种将文件映射到内存的方法,称为MappedByteBuffer。MappedByteBuffe是ByteBuffer的一个子类,它扩展了ByteBuffer的功能,可以直接将文件映射到内存中。</p>\n<p>根据文件地址创建了一层缓存当作索引,放在虚拟内存中,使用时会根据的地址,直接找到磁盘中文件的位置,把数据分段load到系统内存(pagecache)中。<br><img src=\"/img/2024/20240510.webp\" alt=\"erik.xyz\"></p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">public static String readFileByMmap(String filePath) {</span><br><span class=\"line\"> File file = new File(filePath);</span><br><span class=\"line\"> String ret = "";</span><br><span class=\"line\"> StringBuilder builder = new StringBuilder();</span><br><span class=\"line\"> try (FileChannel channel = new RandomAccessFile(file, "r").getChannel()) {</span><br><span class=\"line\"> long size = channel.size();</span><br><span class=\"line\"></span><br><span class=\"line\"> // 创建一个与文件大小相同的字节数组</span><br><span class=\"line\"> ByteBuffer buffer = ByteBuffer.allocate((int) size);</span><br><span class=\"line\"></span><br><span class=\"line\"> // 将通道上的所有数据都读入到buffer中</span><br><span class=\"line\"> while (channel.read(buffer) != -1) {}</span><br><span class=\"line\"></span><br><span class=\"line\"> // 切换为只读模式</span><br><span class=\"line\"> buffer.flip();</span><br><span class=\"line\"></span><br><span class=\"line\"> // 从buffer中获取数据并处理</span><br><span class=\"line\"> byte[] data = new byte[buffer.remaining()];</span><br><span class=\"line\"> buffer.get(data);</span><br><span class=\"line\"></span><br><span class=\"line\"> ret = new String(data);</span><br><span class=\"line\"> } catch (IOException e) {</span><br><span class=\"line\"> System.out.println("readFileByMmap异常:" + e);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return ret;</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<p><strong>3、内存映射技术mmap:4次上下文切换+3次拷贝</strong></p>\n<p><strong>mmap是一种内存映射技术,mmap相比于传统的缓冲流来说,其实就是少了1次CPU 拷贝,变成了数据共享。</strong></p>\n<p>虽然减少了一次拷贝,但是上下文切换的次数还是没变。</p>\n<p>因为存在一次CPU拷贝,因此mmap并不是严格意义上的零拷贝。</p>\n<p>RocketMQ 中就是使用的 mmap 来提升磁盘文件的读写性能。<br><img src=\"/img/2024/20240511.webp\" alt=\"erik.xyz\"></p>\n<h4 id=\"七、sendFile零拷贝\"><a href=\"#七、sendFile零拷贝\" class=\"headerlink\" title=\"七、sendFile零拷贝\"></a>七、sendFile零拷贝</h4><p>零拷贝将上下文切换和拷贝的次数压缩到了极致。</p>\n<p><strong>1、传统IO流</strong></p>\n<p>1.将磁盘中的文件拷贝到内核空间内存;</p>\n<p>2.将内核空间的内容拷贝到用户空间内存;</p>\n<p>3.用户空间将内容写入到内核空间内存;</p>\n<p>4.socket读取内核空间内存,将内容发送给第三方服务器。<br><img src=\"/img/2024/20240512.webp\" alt=\"erik.xyz\"></p>\n<p><strong>2、sendFile零拷贝</strong></p>\n<p>在内核的支持下,零拷贝少了一个步骤,那就是内核缓存向用户空间的拷贝,这样既节省了内存,也节省了 CPU 的调度时间,让效率更高。<br><img src=\"/img/2024/20240513.webp\" alt=\"erik.xyz\"></p>\n<p><strong>3、sendFile零拷贝:2 次上下文切换 + 2次拷贝</strong></p>\n<p><strong>直接将用户缓冲区干掉,而且没有CPU拷贝,故得名零拷贝。</strong><br><img src=\"/img/2024/20240514.webp\" alt=\"erik.xyz\"></p>\n<p><strong>重置优化4:通过零拷贝读取文件</strong><br><img src=\"/img/2024/20240515.webp\" alt=\"erik.xyz\"></p>\n<h4 id=\"八、总结经过\"><a href=\"#八、总结经过\" class=\"headerlink\" title=\"八、总结经过\"></a>八、总结经过</h4><p><strong>4次优化,将页面的加载时间控制在了1秒以内,实打实的提升了程序的秒开率。</strong></p>\n<p>1.批量查询时,不查询BLOB大字段;</p>\n<p>2.点击运费查询时,单独查询+触发索引,实现“懒加载”;</p>\n<p>3.异步存储文件</p>\n<p>4.通过缓冲流->内存映射技术mmap-> sendFile零拷贝读取本地文件;</p>\n<p><strong>通过一次页面优化,收获颇丰:</strong></p>\n<p>1.通过业务优化,将BLOB大字段进行“懒加载”;</p>\n<p>2.异步存储文件;</p>\n<p>3.系统的学习了Java IO流,输入输出流、字符流、字符流、转换流;</p>\n<p>4.通过NIO的FileChannel读取文件时,较于缓冲流性能上显著提升;</p>\n<p>5.内存映射技术mmap 相比于传统的 缓冲流 来说,其实就是少了1次内核缓冲区到用户缓冲区的CPU拷贝,将其变成了数据共享;</p>\n<p>6.sendFile零拷贝,舍弃了用户空间内存,舍弃了CUP拷贝,完美的零拷贝方案。</p>\n<p>7.通过代码实例,横向对比了FileReader、BufferedReader、NIO之FileChannel、内存映射技术mmap、sendFile零拷贝之间的性能差距;</p>\n<p>转载自:<a href=\"https://mp.weixin.qq.com/s/kaogMK5qz5vkfs9-BYu0Mg\">哪吒编程</a></p>\n",
"tags": [
"web",
"web前端",
"web优化"
]
},
{
"id": "https://erik.xyz/2024/05/06/index-asynchrony-landing/",
"url": "https://erik.xyz/2024/05/06/index-asynchrony-landing/",
"title": "增加索引 + 异步 + 不落地后,从 12h 优化到 15 min",
"date_published": "2024-05-06T03:44:00.000Z",
"content_html": "<p>在开发中,我们经常会遇到这样的需求,将数据库中的图片导出到本地,再传给别人。</p>\n<h4 id=\"一、一般我会这样做:\"><a href=\"#一、一般我会这样做:\" class=\"headerlink\" title=\"一、一般我会这样做:\"></a>一、一般我会这样做:</h4><p>1.通过接口或者定时任务的形式</p>\n<p>2.读取Oracle或者MySQL数据库</p>\n<p>3.通过FileOutputStream将Base64解密后的byte[]存储到本地</p>\n<p>4.遍历本地文件夹,将图片通过FTP上传到第三方服务器<br><span id=\"more\"></span><br><img src=\"/img/2024/202405100.webp\" alt=\"erik.xyz\"></p>\n<p>现场炸锅了!</p>\n<p>实际的数据量非常大,据统计差不多有400G的图片需要导出。</p>\n<p><strong>现场人员的反馈是,已经跑了12个小时了,还在继续,不知道啥时候能导完。</strong></p>\n<p>停下来呢?之前的白导了,不停呢?不知道要等到啥时候才能导完。</p>\n<p>这不行啊,速度太慢了,一个简单的任务,不能被这东西耗死吧?<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">@Value("${months}")</span><br><span class=\"line\">private String months;</span><br><span class=\"line\"></span><br><span class=\"line\">@Value("${imgDir}")</span><br><span class=\"line\">private String imgDir;</span><br><span class=\"line\"></span><br><span class=\"line\">@Resource</span><br><span class=\"line\">private UserDao userDao;</span><br><span class=\"line\"></span><br><span class=\"line\">@Override</span><br><span class=\"line\">public void getUserInfoImg() {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> // 获取需要导出的月表</span><br><span class=\"line\"> String[] monthArr = months.split(",");</span><br><span class=\"line\"> for (int i = 0; i < monthArr.length; i++) {</span><br><span class=\"line\"> // 获取月表中的图片</span><br><span class=\"line\"> Map<String, Object> map = new HashMap<String, Object>();</span><br><span class=\"line\"> String tableName = "USER_INFO_" + monthArr[i];</span><br><span class=\"line\"> map.put("tableName", tableName);</span><br><span class=\"line\"> map.put("status", 1);</span><br><span class=\"line\"> </span><br><span class=\"line\"> List<UserInfo> userInfoList = userDao.getUserInfoImg(map);</span><br><span class=\"line\"> if (userInfoList == null || userInfoList.size() == 0) {</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> for (int j = 0; j < userInfoList.size(); j++) {</span><br><span class=\"line\"> UserInfo user = userInfoList.get(j);</span><br><span class=\"line\"> String userId = user.getUserId();</span><br><span class=\"line\"> String userName = user.getUserName();</span><br><span class=\"line\"> byte[] content = user.getImgContent;</span><br><span class=\"line\"></span><br><span class=\"line\"> // 下载图片到本地</span><br><span class=\"line\"> FileUtil.dowmloadImage(imgDir + userId+"-"+userName+".png", content);</span><br><span class=\"line\"> </span><br><span class=\"line\"> // 将下载好的图片,通过FTP上传给第三方</span><br><span class=\"line\"> FileUtil.uploadByFtp(imgDir);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } catch (Exception e) {</span><br><span class=\"line\"> serviceLogger.error("获取图片异常:", e);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></p>\n<h4 id=\"二、谁写的?赶紧加班优化,会追责吗?\"><a href=\"#二、谁写的?赶紧加班优化,会追责吗?\" class=\"headerlink\" title=\"二、谁写的?赶紧加班优化,会追责吗?\"></a>二、谁写的?赶紧加班优化,会追责吗?</h4><p>经过1小时的深思熟虑,慢的原因可能有以下几点:</p>\n<p>1.查询数据库</p>\n<p>2.程序串行</p>\n<p>3.base64解密</p>\n<p>4.图片落地</p>\n<p>5.FTP上传到服务器</p>\n<p>优化1:数据库中添加对应的索引,提高查询速度</p>\n<p>优化2:采用增加索引+异步+多线程的方式进行导出</p>\n<p><img src=\"/img/2024/202405101.webp\" alt=\"erik.xyz\"></p>\n<p>优化3:不解密+图片不落地,直接通过FTP传给第三方</p>\n<p><img src=\"/img/2024/202405102.webp\" alt=\"erik.xyz\"></p>\n<p><strong>使用索引+异步+不解密+不落地 后,40G图片的导出上传,从12+小时 优化到15 分钟,你敢信?</strong></p>\n<p>差不多的代码,效率差距竟如此之大。</p>\n<p>下面贴出导出图片不落地的关键代码。<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">@Resource</span><br><span class=\"line\">private UserAsyncService userAsyncService;</span><br><span class=\"line\"></span><br><span class=\"line\">@Override</span><br><span class=\"line\">public void getUserInfoImg() {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> // 获取需要导出的月表</span><br><span class=\"line\"> String[] monthArr = months.split(",");</span><br><span class=\"line\"> for (int i = 0; i < monthArr.length; i++) {</span><br><span class=\"line\"> userAsyncService.getUserInfoImgAsync(monthArr[i]);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } catch (Exception e) {</span><br><span class=\"line\"> serviceLogger.error("获取图片异常:", e);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">@Value("${months}")</span><br><span class=\"line\">private String months;</span><br><span class=\"line\"></span><br><span class=\"line\">@Resource</span><br><span class=\"line\">private UserDao userDao;</span><br><span class=\"line\"></span><br><span class=\"line\">@Async("async-executor")</span><br><span class=\"line\">@Override</span><br><span class=\"line\">public void getUserInfoImgAsync(String month) {</span><br><span class=\"line\"> try {</span><br><span class=\"line\"> // 获取月表中的图片</span><br><span class=\"line\"> Map<String, Object> map = new HashMap<String, Object>();</span><br><span class=\"line\"> String tableName = "USER_INFO_" + month;</span><br><span class=\"line\"> map.put("tableName", tableName);</span><br><span class=\"line\"> map.put("status", 1);</span><br><span class=\"line\"> </span><br><span class=\"line\"> List<UserInfo> userInfoList = userDao.getUserInfoImg(map);</span><br><span class=\"line\"> if (userInfoList == null || userInfoList.size() == 0) {</span><br><span class=\"line\"> return;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> for (int i = 0; i < userInfoList.size(); i++) {</span><br><span class=\"line\"> UserInfo user = userInfoList.get(i);</span><br><span class=\"line\"> String userId = user.getUserId();</span><br><span class=\"line\"> String userName = user.getUserName();</span><br><span class=\"line\"> byte[] content = user.getImgContent;</span><br><span class=\"line\"> </span><br><span class=\"line\"> // 不落地,直接通过FTP上传给第三方</span><br><span class=\"line\"> FileUtil.uploadByFtp(content);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> } catch (Exception e) {</span><br><span class=\"line\"> serviceLogger.error("获取图片异常:", e);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>4、异步线程池工具类</strong></p>\n<p><strong>@Async的作用就是异步处理任务。</strong></p>\n<p>1.在方法上添加@Async,表示此方法是异步方法;</p>\n<p>2.在类上添加@Async,表示类中的所有方法都是异步方法;</p>\n<p>3.使用此注解的类,必须是Spring管理的类;</p>\n<p>4.需要在启动类或配置类中加入@EnableAsync注解,@Async才会生效;</p>\n<p>在使用@Async时,如果不指定线程池的名称,也就是不自定义线程池,@Async是有默认线程池的,使用的是Spring默认的线程池SimpleAsyncTaskExecutor。</p>\n<p>默认线程池的默认配置如下:</p>\n<p>1.默认核心线程数:8;</p>\n<p>2.最大线程数:Integet.MAX_VALUE;</p>\n<p>3.队列使用LinkedBlockingQueue;</p>\n<p>4.容量是:Integet.MAX_VALUE;</p>\n<p>5.空闲线程保留时间:60s;</p>\n<p>6.线程池拒绝策略:AbortPolicy;</p>\n<p>从最大线程数可以看出,在并发情况下,会无限制的创建线程,我勒个吗啊。</p>\n<p><strong>也可以通过yml重新配置:</strong><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">spring:</span><br><span class=\"line\"> task:</span><br><span class=\"line\"> execution:</span><br><span class=\"line\"> pool:</span><br><span class=\"line\"> max-size: 10</span><br><span class=\"line\"> core-size: 5</span><br><span class=\"line\"> keep-alive: 3s</span><br><span class=\"line\"> queue-capacity: 1000</span><br><span class=\"line\"> thread-name-prefix: my-executor</span><br></pre></td></tr></table></figure></p>\n<p>也可以自定义线程池,下面通过简单的代码来实现以下@Async自定义线程池。</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br><span class=\"line\">45</span><br><span class=\"line\">46</span><br><span class=\"line\">47</span><br><span class=\"line\">48</span><br><span class=\"line\">49</span><br><span class=\"line\">50</span><br><span class=\"line\">51</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">@EnableAsync// 支持异步操作</span><br><span class=\"line\">@Configuration</span><br><span class=\"line\">public class AsyncTaskConfig {</span><br><span class=\"line\"></span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * com.google.guava中的线程池</span><br><span class=\"line\"> * @return</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @Bean("my-executor")</span><br><span class=\"line\"> public Executor firstExecutor() {</span><br><span class=\"line\"> ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("my-executor").build();</span><br><span class=\"line\"> // 获取CPU的处理器数量</span><br><span class=\"line\"> int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;</span><br><span class=\"line\"> ThreadPoolExecutor threadPool = new ThreadPoolExecutor(curSystemThreads, 100,</span><br><span class=\"line\"> 200, TimeUnit.SECONDS,</span><br><span class=\"line\"> new LinkedBlockingQueue<>(), threadFactory);</span><br><span class=\"line\"> threadPool.allowsCoreThreadTimeOut();</span><br><span class=\"line\"> return threadPool;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * Spring线程池</span><br><span class=\"line\"> * @return</span><br><span class=\"line\"> */</span><br><span class=\"line\"> @Bean("async-executor")</span><br><span class=\"line\"> public Executor asyncExecutor() {</span><br><span class=\"line\"> ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();</span><br><span class=\"line\"> // 核心线程数</span><br><span class=\"line\"> taskExecutor.setCorePoolSize(24);</span><br><span class=\"line\"> // 线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程</span><br><span class=\"line\"> taskExecutor.setMaxPoolSize(200);</span><br><span class=\"line\"> // 缓存队列</span><br><span class=\"line\"> taskExecutor.setQueueCapacity(50);</span><br><span class=\"line\"> // 空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁</span><br><span class=\"line\"> taskExecutor.setKeepAliveSeconds(200);</span><br><span class=\"line\"> // 异步方法内部线程名称</span><br><span class=\"line\"> taskExecutor.setThreadNamePrefix("async-executor-");</span><br><span class=\"line\"></span><br><span class=\"line\"> /**</span><br><span class=\"line\"> * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略</span><br><span class=\"line\"> * 通常有以下四种策略:</span><br><span class=\"line\"> * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。</span><br><span class=\"line\"> * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。</span><br><span class=\"line\"> * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)</span><br><span class=\"line\"> * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功</span><br><span class=\"line\"> */</span><br><span class=\"line\"> taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());</span><br><span class=\"line\"> taskExecutor.initialize();</span><br><span class=\"line\"> return taskExecutor;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure>\n<h4 id=\"三、告别劣质代码,优化从何入手?\"><a href=\"#三、告别劣质代码,优化从何入手?\" class=\"headerlink\" title=\"三、告别劣质代码,优化从何入手?\"></a>三、告别劣质代码,优化从何入手?</h4><p>我觉得优化有两个大方向:</p>\n<p>1.业务优化</p>\n<p>2.代码优化</p>\n<p><strong>1、业务优化业</strong></p>\n<p>务优化的影响力非常大,但它一般属于产品和项目经理的范畴,CRUD程序员很少能接触到。</p>\n<p>比如上面说的图片导出上传需求,经过产品经理和项目经理的不懈努力,这个需求不做了,这优化力度,史无前例啊。</p>\n<p><strong>2、代码优化</strong></p>\n<p>1.数据库优化</p>\n<p>2.复用优化</p>\n<p>3.并行优化</p>\n<p>4.算法优化<br><img src=\"/img/2024/202405103.webp\" alt=\"erik.xyz\"></p>\n<h4 id=\"四、数据库优化\"><a href=\"#四、数据库优化\" class=\"headerlink\" title=\"四、数据库优化\"></a>四、数据库优化</h4><p>1.inner join 、left join、right join,优先使用inner join</p>\n<p>2.表连接不宜太多,索引不宜太多,一般5个以内</p>\n<p>3.复合索引最左特性</p>\n<p>4.操作delete或者update语句,加个limit或者循环分批次删除</p>\n<p>5.使用explain分析你SQL执行计划</p>\n<p>SQL性能优化的47个小技巧,果断收藏!</p>\n<h4 id=\"五、复用优化\"><a href=\"#五、复用优化\" class=\"headerlink\" title=\"五、复用优化\"></a>五、复用优化</h4><p>写代码的时候,大家一般都会将重复性的代码提取出来,写成工具方法,在下次用的时候,就不用重新编码,直接调用就可以了。</p>\n<p>这个就是复用。</p>\n<p>数据库连接池、线程池、长连接也都是复用手段,这些对象的创建和销毁成本过高,复用之后,效率提升显著。</p>\n<p><strong>1、连接池</strong></p>\n<p>连接池是一种常见的优化网络连接复用性的方法。连接池管理着一定数量的网络连接,并且在需要时将这些连接分配给客户端,客户端使用完后将连接归还给连接池。这样可以避免每次通信都建立新的连接,减少了连接的建立和销毁过程,提高了系统的性能和效率。</p>\n<p>在Java开发中,常用的连接池技术有Apache Commons Pool、Druid等。使用连接池时,需要合理设置连接池的大小,并根据实际情况进行调优。连接池的大小过小会导致连接不够用,而过大则会占用过多的系统资源。</p>\n<p><strong>2、长连接</strong></p>\n<p>长连接是另一种优化网络连接复用性的方法。长连接指的是在一次通信后,保持网络连接不关闭,以便后续的通信继续复用该连接。与短连接相比,长连接在一定程度上减少了连接的建立和销毁过程,提高了网络连接的复用性和效率。</p>\n<p>在Java开发中,可以通过使用Socket编程实现长连接。客户端在建立连接后,通过设置Socket的Keep-Alive选项,使得连接保持活跃状态。这样可以避免频繁地建立新的连接,提高网络连接的复用性和效率。</p>\n<p><strong>3、缓存</strong></p>\n<p>缓存也是比较常用的复用,属于数据复用。</p>\n<p>缓存一般是将数据库中的数据缓存到内存或者Redis中,也就是缓存到相对高速的区域,下次查询时,直接访问缓存,就不用查询数据库了,缓存主要针对的是读操作。</p>\n<p><strong>4、缓冲</strong></p>\n<p>缓冲常见于对数据的暂存,然后批量传输或者写入。多使用顺序方式,用来缓解不同设备之间频繁地、缓慢地随机写,缓冲主要针对的是写操作。</p>\n<h4 id=\"六、并行优化\"><a href=\"#六、并行优化\" class=\"headerlink\" title=\"六、并行优化\"></a>六、并行优化</h4><p><strong>1、异步编程</strong></p>\n<p>上面的优化方式就是异步优化,充分利用多核处理器的性能,将串行的程序改为并行,大大提高了程序的执行效率。</p>\n<p>异步编程是一种编程模型,其中任务的执行不会阻塞当前线程的执行。通过将任务提交给其他线程或线程池来处理,当前线程可以继续执行其他操作,而不必等待任务完成。</p>\n<p><strong>2、异步编程的特点</strong></p>\n<p>1.非阻塞:异步任务的执行不会导致调用线程的阻塞,允许线程继续执行其他任务;</p>\n<p>2.回调机制:异步任务通常会注册回调函数,当任务完成时,会调用相应的回调函数进行后续处理;</p>\n<p>3.提高响应性:异步编程能够提高程序的响应性,尤其适用于处理IO密集型任务,如网络请求、数据库查询等;</p>\n<p>Java 8引入了CompletableFuture类,可以方便地进行异步编程。</p>\n<p><strong>3、并行编程</strong></p>\n<p>并行编程是一种利用多个线程或处理器同时执行多个任务的编程模型。它将大任务划分为多个子任务,并发地执行这些子任务,从而加速整体任务的完成时间。</p>\n<p><strong>4、并行编程的特点</strong></p>\n<p>1.分布式任务:并行编程将大任务划分为多个独立的子任务,每个子任务在不同的线程中并行执行;</p>\n<p>2..数据共享:并行编程需要考虑多个线程之间的数据共享和同步问题,以避免出现竞态条件和数据不一致的情况;</p>\n<p>3.提高性能:并行编程能够充分利用多核处理器的计算能力,加速程序的执行速度。</p>\n<p><strong>5、并行编程如何实现?</strong></p>\n<p>1.多线程:Java提供了Thread类和Runnable接口,用于创建和管理多个线程。通过创建多个线程并发执行任务,可以实现并行编程。</p>\n<p>2.线程池:Java的Executor框架提供了线程池的支持,可以方便地管理和调度多个线程。通过线程池,可以复用线程对象,减少线程创建和销毁的开销;</p>\n<p>3.并发集合:Java提供了一系列的并发集合类,如ConcurrentHashMap、ConcurrentLinkedQueue等,用于在并行编程中实现线程安全的数据共享。</p>\n<p>异步编程和并行编程是Java中处理任务并提高程序性能的两种重要方法。</p>\n<p>异步编程通过非阻塞的方式处理任务,提高程序的响应性,并适用于IO密集型任务。</p>\n<p>而并行编程则是通过多个线程或处理器并发执行任务,充分利用计算资源,加速程序的执行速度。</p>\n<p>在Java中,可以使用CompletableFuture和回调接口实现异步编程,使用多线程、线程池和并发集合实现并行编程。通过合理地运用异步和并行编程,我们可以在Java中高效地处理任务和提升程序的性能。</p>\n<p><strong>6、代码示例</strong><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">public static void main(String[] args) {</span><br><span class=\"line\"> // 创建线程池</span><br><span class=\"line\"> ExecutorService executor = Executors.newFixedThreadPool(10);</span><br><span class=\"line\"> </span><br><span class=\"line\"> // 使用线程池创建CompletableFuture对象</span><br><span class=\"line\"> CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {</span><br><span class=\"line\"> // 一些不为人知的操作</span><br><span class=\"line\"> return "result"; // 返回结果</span><br><span class=\"line\"> }, executor);</span><br><span class=\"line\"> </span><br><span class=\"line\"> // 使用CompletableFuture对象执行任务</span><br><span class=\"line\"> CompletableFuture<String> result = future.thenApply(result -> {</span><br><span class=\"line\"> // 一些不为人知的操作</span><br><span class=\"line\"> return "result"; // 返回结果</span><br><span class=\"line\"> });</span><br><span class=\"line\"> </span><br><span class=\"line\"> // 处理任务结果</span><br><span class=\"line\"> String finalResult = result.join();</span><br><span class=\"line\"> </span><br><span class=\"line\"> // 关闭线程池</span><br><span class=\"line\"> executor.shutdown();</span><br><span class=\"line\">}</span><br></pre></td></tr></table></figure></p>\n<p><strong>7、Java 8 parallel</strong></p>\n<p><strong>(1)parallel()是什么</strong></p>\n<p>Stream.parallel() 方法是 Java 8 中 Stream API 提供的一种并行处理方式。在处理大量数据或者耗时操作时,使用 Stream.parallel() 方法可以充分利用多核 CPU 的优势,提高程序的性能。</p>\n<p>Stream.parallel() 方法是将串行流转化为并行流的方法。通过该方法可以将大量数据划分为多个子任务交由多个线程并行处理,最终将各个子任务的计算结果合并得到最终结果。使用 Stream.parallel() 可以简化多线程编程,减少开发难度。</p>\n<p>需要注意的是,并行处理可能会引入线程安全等问题,需要根据具体情况进行选择。</p>\n<p><strong>(2)举一个简单的demo</strong></p>\n<p>定义一个list,然后通过parallel() 方法将集合转化为并行流,对每个元素进行i++,最后通过 collect(Collectors.toList()) 方法将结果转化为 List 集合。</p>\n<p>使用并行处理可以充分利用多核 CPU 的优势,加快处理速度。<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">public class StreamTest {</span><br><span class=\"line\"> public static void main(String[] args) {</span><br><span class=\"line\"> List<Integer> list = new ArrayList<>();</span><br><span class=\"line\"> for (int i = 0; i < 10; i++) {</span><br><span class=\"line\"> list.add(i);</span><br><span class=\"line\"> }</span><br><span class=\"line\"> System.out.println(list);</span><br><span class=\"line\"> List<Integer> result = list.stream().parallel().map(i -> i++).collect(Collectors.toList());</span><br><span class=\"line\"> System.out.println(result);</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></p>\n<p>我勒个去,什么情况?<br><img src=\"/img/2024/202405104.webp\" alt=\"erik.xyz\"></p>\n<p><strong>(3)parallel()的优缺点</strong></p>\n<p><strong>①优点:</strong></p>\n<p>1.充分利用多核 CPU 的优势,提高程序的性能;</p>\n<p>2.可以简化多线程编程,减少开发难度。</p>\n<p><strong>②缺点:</strong></p>\n<p>1.并行处理可能会引入线程安全等问题,需要根据具体情况进行选择;</p>\n<p>2.并行处理需要付出额外的开销,例如线程池的创建和销毁、线程切换等,对于小数据量和简单计算而言,串行处理可能更快。</p>\n<p><strong>(4)何时使用parallel()?</strong></p>\n<p>在实际开发中,应该根据数据量、计算复杂度、硬件等因素综合考虑。</p>\n<p>比如:</p>\n<p>1.数据量较大,有1万个元素;</p>\n<p>2.计算复杂度过大,需要对每个元素进行复杂的计算;</p>\n<p>3.硬件够硬,比如多核CPU。</p>\n<h4 id=\"七、算法优化\"><a href=\"#七、算法优化\" class=\"headerlink\" title=\"七、算法优化\"></a>七、算法优化</h4><p>在上面的例子中,避免base64解密,就应该归类于算法优化。</p>\n<p>程序就是由数据结构和算法组成,一个优质的算法可以显著提高程序的执行效率,从而减少运行时间和资源消耗。相比之下,一个低效的算法就可能导致运行非常缓慢,并占用大量系统资源。</p>\n<p>很多问题都可以通过算法优化来解决,比如:</p>\n<p><strong>1、循环和递归</strong></p>\n<p>循环和递归是Java编程中常见的操作,然而,过于复杂的业务逻辑往往会带来多层循环套用,不必要的重复循环会大大降低程序的执行效率。</p>\n<p>递归是一种函数自我调用的技术,类似于循环,虽然递归可以解决很多问题,但是,递归的效率有待提高。</p>\n<p><strong>2、内存管理</strong></p>\n<p>Java自带垃圾收集器,开发人员不用手动释放内存。</p>\n<p>但是,不合理的内存使用可能导致内存泄漏和性能下降,确保及时释放不再使用的对象,避免创建过多的临时对象。</p>\n<p><strong>3、字符串</strong></p>\n<p>我觉得字符串是Java编程中使用频率最高的技术,很多程序员恨不得把所有的变量都定义成字符串。</p>\n<p>然而,由于字符串是不可变的,每次执行字符串拼接、替换时,都会创建一个新的字符串。这会占用大量的内存和处理时间。</p>\n<p>使用StringBuilder来处理字符串的拼接可以显著的提高性能。</p>\n<p><strong>4、IO操作</strong></p>\n<p>IO操作通常是最耗费性能和资源的操作。在处理大量数据IO操作时,务必注意优化IO代码,提高程序性能,比如上面提高的图片不落地就是彻底解决IO问题。</p>\n<p><strong>5、数据结构的选择</strong></p>\n<p>选择适当的数据结构对程序的性能至关重要。</p>\n<p>比如Java世界中用的第二多的Map,比较常用的有HashMap、HashTable、ConcurrentHashMap。</p>\n<p>HashMap,底层数组+链表实现,可以存储null键和null值,线程不安全;</p>\n<p>HashTable,底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化;</p>\n<p>ConcurrentHashMap,底层采用分段的数组+链表实现,线程安全,通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。</p>\n<p>Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。</p>\n<p>转载自:<a href=\"https://mp.weixin.qq.com/s/V2p_p-KoDowlZxLN2by2Sg\">哪吒编程</a></p>\n",
"tags": [
"web",
"优化"
]
},
{
"id": "https://erik.xyz/2024/04/06/five-jenkis/",
"url": "https://erik.xyz/2024/04/06/five-jenkis/",
"title": "5分钟搞定Jenkis",
"date_published": "2024-04-06T01:04:00.000Z",
"content_html": "<h4 id=\"什么是持续集成\"><a href=\"#什么是持续集成\" class=\"headerlink\" title=\"什么是持续集成\"></a>什么是持续集成</h4><p>持续集成 Continuous integration ,简称CI随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题。尤其是近些年来,敏捷(Agile) 在软件工程领域越来越红火,如何能在不断变化的需求中快速适应和保证软件的质量也显得尤其的重要。持续集成正是针对这一类问题的一种软件开发实践。它倡导团队开发成员必须经常集成他们的工作,甚至每天都可能发生多次集成。而每次的集成都是通过自动化的构建来验证,包括自动编译、发布和测试,从而尽快地发现集成错误,让团队能够更快的开发内聚的软件。<br><span id=\"more\"></span><br>持续集成具有的特点:</p>\n<ul>\n<li><p>它是一个自动化的周期性的集成测试过程,从检出代码、编译构建、运行测试、结果记录、测试统计等都是自动完成的,无需人工干预;</p>\n</li>\n<li><p>需要有专门的集成服务器来执行集成构建;</p>\n</li>\n<li>需要有代码托管工具支持,例如Git以及可视化界面Gogs的使用</li>\n</ul>\n<p>持续集成的作用:</p>\n<ul>\n<li>保证团队开发人员提交代码的质量,减轻了软件发布时的压力;</li>\n<li>持续集成中的任何一个环节都是自动完成的,无需太多的人工干预,有利于减少重复过程以节省时间、费用和工作量;</li>\n</ul>\n<h4 id=\"Jenkins简介\"><a href=\"#Jenkins简介\" class=\"headerlink\" title=\"Jenkins简介\"></a>Jenkins简介</h4><p>Jenkins,原名Hudson,2011年改为现在的名字,它是一个开源的实现持续集成的软件工具。官方网站:<a href=\"http://jenkins-ci.org/。\">http://jenkins-ci.org/。</a></p>\n<p>Jenkins 能实施监控集成中存在的错误,提供详细的日志文件和提醒功能,还能用图表的形式形象地展示项目构建的趋势和稳定性。</p>\n<p>特点:</p>\n<ul>\n<li>易安装:仅仅两个docker命令即可从官网下载直接运行,无需额外的安装,更无需安装数据库;</li>\n<li>易配置:提供友好的GUI配置界面;</li>\n<li>变更支持:Jenkins能从代码仓库(Subversion/CVS)中获取并产生代码更新列表并输出到编译输出信息中;</li>\n<li>支持永久链接:用户是通过web来访问Jenkins的,而这些web页面的链接地址都是永久链接地址,因此,你可以在各种文档中直接使用该链接;</li>\n<li>集成E-Mail/RSS/IM:当完成一次集成时,可通过这些工具实时告诉你集成结果(据我所知,构建一次集成需要花费一定时间,有了这个功能,你就可以在等待结果过程中,干别的事情);</li>\n<li>JUnit/TestNG测试报告:也就是用以图表等形式提供详细的测试报表功能;</li>\n<li>支持分布式构建:Jenkins可以把集成构建等工作分发到多台计算机中完成;</li>\n<li>文件指纹信息:Jenkins会保存哪次集成构建产生了哪些jars文件,哪一次集成构建使用了哪个版本的jars文件等构建记录;</li>\n<li>支持第三方插件:使得 Jenkins 变得越来越强大Jenkins安装与启动(1)执行安装命令,下载jenkins</li>\n</ul>\n<h4 id=\"Jenkins安装与启动\"><a href=\"#Jenkins安装与启动\" class=\"headerlink\" title=\"Jenkins安装与启动\"></a>Jenkins安装与启动</h4><p>1)执行安装命令,下载jenkins<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">docker pull jenkins/jenkins</span><br></pre></td></tr></table></figure><br>(2)启动服务<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">docker run -p 8080:8080 -p 50000:50000 -v /mnt/data/jenkins:/var/jenkins_home --name "jenkins" jenkins/jenkins</span><br></pre></td></tr></table></figure><br><img src=\"/img/2024/20240411.jpeg\" alt=\"erik.xyz\"></p>\n<p>若报错如下:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’: Permission denied</span><br><span class=\"line\">Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?</span><br></pre></td></tr></table></figure></p>\n<p>需要修改下目录权限, 因为当映射本地数据卷时,/mnt/data/jenkins目录的拥有者为root用户,而容器中jenkins user的uid为1000<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">sudo chown -R 1000:1000 /mnt/data/jenkins</span><br></pre></td></tr></table></figure><br>(3)访问链接 <a href=\"http://10.20.29.151:8080\">http://10.20.29.151:8080</a></p>\n<p>若密码忘记,可进入容器,执行<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">cat /var/lib/jenkins/secrets/initialAdminPassword</span><br></pre></td></tr></table></figure><br>获取初始密码串。</p>\n<p>若目录不存在,可使用<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">find / -name "initialAdminPassword" -depth -print</span><br></pre></td></tr></table></figure><br>命令查找。<br><img src=\"/img/2024/20240412.png\" alt=\"erik.xyz\"></p>\n<p>(4)安装插件<br><img src=\"/img/2024/20240413.png\" alt=\"erik.xyz\"><br><img src=\"/img/2024/20240414.png\" alt=\"erik.xyz\"></p>\n<p>(5)新建用户<br><img src=\"/img/2024/20240415.png\" alt=\"erik.xyz\"></p>\n<p>完成安装进入主界面<br><img src=\"/img/2024/20240416.png\" alt=\"erik.xyz\"></p>\n<h4 id=\"Jenkins插件安装\"><a href=\"#Jenkins插件安装\" class=\"headerlink\" title=\"Jenkins插件安装\"></a>Jenkins插件安装</h4><p>我们以安装maven插件为例,演示插件的安装</p>\n<p>(1)点击左侧的“系统管理”菜单 ,然后点击<br><img src=\"/img/2024/20240417.jpeg\" alt=\"erik.xyz\"></p>\n<p>(2)选择“可选插件”选项卡,搜索maven,在列表中选择Maven Integration ,点击“直接安装”按钮<br><img src=\"/img/2024/20240418.jpeg\" alt=\"erik.xyz\"></p>\n<p>看到如下图时,表示已经完成<br><img src=\"/img/2024/20240419.png\" alt=\"erik.xyz\"></p>\n<h5 id=\"全局工具配置\"><a href=\"#全局工具配置\" class=\"headerlink\" title=\"全局工具配置\"></a>全局工具配置</h5><p>选择系统管理,全局工具配置<br><img src=\"/img/2024/20240420.png\" alt=\"erik.xyz\"></p>\n<p><strong>自动安装</strong></p>\n<p>Jenkins提供了两种工具配置的方式,我们还是以maven为例<br><img src=\"/img/2024/20240421.png\" alt=\"erik.xyz\"></p>\n<p>第一种如上图,只需要选择自动安装和版本号就可以,同时Jenkins在右上角给出了一个解疑按键,可以通过该键看到说明和示例。</p>\n<p><strong>本地安装</strong><br>相较于第一种方式,第二种方式相对麻烦一些,但好处是可以在以后打包的时候不必重新下载,缩短打包的时间。</p>\n<p>下面就来教大家如何安装Maven与本地仓库:</p>\n<p>(1)将Maven压缩包上传至服务器(虚拟机)</p>\n<p>(2)解压<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">tar zxvf apache-maven-3.5.4-bin.tar.gz</span><br></pre></td></tr></table></figure><br>(3)移动目录<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">mv apache-maven-3.5.4 /usr/local/maven</span><br></pre></td></tr></table></figure><br>(4)编辑setting.xml配置文件vi /usr/local/maven/conf/settings.xml,配置本地仓库目录,内容如下<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><localRepository>/usr/local/repository</localRepository></span><br></pre></td></tr></table></figure><br>(5)将开发环境的本地仓库上传至服务器(虚拟机)并移动到/usr/local/repository 。<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">mv reponsitory_boot /usr/local/repository</span><br></pre></td></tr></table></figure></p>\n<p>其他示例:</p>\n<p>(1)JDK配置<br><img src=\"/img/2024/20240422.png\" alt=\"erik.xyz\"></p>\n<p>设置javahome为 /usr/java/jdk1.8.0_171-amd64</p>\n<p>(2)Git配置 (本地已经安装了Git软件)<br><img src=\"/img/2024/20240423.png\" alt=\"erik.xyz\"></p>\n<p>(3)Maven配置<br><img src=\"/img/2024/20240424.png\" alt=\"erik.xyz\"></p>\n<p><strong>代码上传至Git服务器</strong></p>\n<p>步骤:</p>\n<p>(1)在本地安装git(Windows版本)</p>\n<p>(2)在IDEA中选择菜单 : File — settings , 在窗口中选择Version Control — Git<br><img src=\"/img/2024/20240425.png\" alt=\"erik.xyz\"></p>\n<p>(3)选择菜单VCS —> Enable Version Control Integration…<br><img src=\"/img/2024/20240426.png\" alt=\"erik.xyz\"></p>\n<p>选择Git</p>\n<p>(4)设置远程地址: 右键点击工程选择菜单 Git —> Repository —>Remotes…<br><img src=\"/img/2024/20240427.png\" alt=\"erik.xyz\"><br><img src=\"/img/2024/20240428.png\" alt=\"erik.xyz\"></p>\n<p>(5)右键点击工程选择菜单 Git —> Add</p>\n<p>(6)右键点击工程选择菜单 Git —> Commit Directory…</p>\n<p>(7)右键点击工程选择菜单 Git —> Repository —> Push …</p>\n<p><strong>任务的创建与执行</strong></p>\n<p>我们以最火的Java项目和Go项目为例,给大家分别演示如何构建项目和执行</p>\n<p><strong>Go项目</strong></p>\n<p>(1)回到首页,点击新建按钮 .如下图,输入名称,选择创建一个自由风格的项目,点击OK<br><img src=\"/img/2024/20240429.png\" alt=\"erik.xyz\"><br>(2)General管理,可以添加项目描述和GitHub项目路径,以及一些配置<br><img src=\"/img/2024/20240430.png\" alt=\"erik.xyz\"><br>(3)源码管理,选择GitHub<br><img src=\"/img/2024/20240431.png\" alt=\"erik.xyz\"><br><img src=\"/img/2024/20240432.png\" alt=\"erik.xyz\"><br><img src=\"/img/2024/20240433.png\" alt=\"erik.xyz\"></p>\n<p>(4)构建触发器,配置触发规则,这里以定时和轮询为示例,配别设置为<br><img src=\"/img/2024/20240434.png\" alt=\"erik.xyz\"></p>\n<p>定时构建:定时构建1次任务</p>\n<p>轮询SCM:定时查看源码管理的代码是否更新,有更新则构建,否则不会构建</p>\n<p>如图所示,定时构建为每间隔10分钟定时构建一次,轮询SCM为每5分钟轮询检测一次。</p>\n<p><span style=\"color: #808080;\">时间*号规则为: 分 时 日 月 周</span></p>\n<p>(5)构建环境,配置控制台输出时间戳和指定Go语言版本<br><img src=\"/img/2024/20240435.png\" alt=\"erik.xyz\"><br>(6)构建,使用Shell脚本测试代码上传后的项目是否有效<br><img src=\"/img/2024/20240436.jpeg\" alt=\"erik.xyz\"></p>\n<p>Shell如下:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">export GOPATH=$WORKSPACE/../ # 指定GOPATH路径,Go语言执行必须有GOPATH路径</span><br><span class=\"line\">export GOWORK=$GOPATH/src/github.com/Jenkins # 创建项目执行时的目录依赖结构</span><br><span class=\"line\">cp -rf $WORKSPACE/* $GOWORK/ # 使测试运行数据和源数据隔离</span><br><span class=\"line\">cd $GOWORK && go build # 进入项目目录并执行</span><br></pre></td></tr></table></figure></p>\n<p>命令解说:<br><span style=\"color: #808080;\"><br>$GOPATH Go 运行需要指定 GOPATH 也即项目运行路径<br>$WORKSPACE /var/jenkins_home/workspace/Jenkins<br>GOWORK 创建符合代码依赖的执行目录<br>注:下载Go的插件在构建时,会自动为我们设置GOROOT,但不会指定GOPATH,因此需要指定\n</span><br>最后点击“保存”按钮</p>\n<p>(7)执行构建,控制台查看输出<br><img src=\"/img/2024/20240437.jpeg\" alt=\"erik.xyz\"><br><img src=\"/img/2024/20240438.jpeg\" alt=\"erik.xyz\"></p>\n<p>构建成功,也输出了WORKSPACE、GOPATH、GOROOT目录,说明配置生效。进入docker容器或是挂载目录查看是否有可执行文件:<br><img src=\"/img/2024/20240439.png\" alt=\"erik.xyz\"><br>除了上述方法,也可以通过shell配置docker等方式构建、部署、运行项目,还可以将项目配置到当前/其他服务器运行,更多配置方式就不一一陈述了,请自行挖掘。</p>\n<p><strong>JAVA项目</strong></p>\n<p>(1)回到首页,点击新建按钮 .如下图,输入名称,选择创建一个Maven项目,点击OK<br><img src=\"/img/2024/20240440.jpeg\" alt=\"erik.xyz\"></p>\n<p>(2)源码管理,选择Git<br><img src=\"/img/2024/20240441.png\" alt=\"erik.xyz\"><br>(3)Build<br><img src=\"/img/2024/20240442.png\" alt=\"erik.xyz\"></p>\n<p>命令:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">clean package docker:build -DpushImage</span><br></pre></td></tr></table></figure><br>用于清除、打包,构建docker镜像</p>\n<p>最后点击“保存”按钮</p>\n<p>(4)执行任务</p>\n<p>后续请参照Go项目的执行步骤。</p>\n<p>转载自:<a href=\"https://mp.weixin.qq.com/s/PEupc5YIKuctZO1Tivy_ug\">架构师社区</a></p>\n",
"tags": [
"转载",
"jenkis",
"jenkis教程"
]
},
{
"id": "https://erik.xyz/2024/03/19/randomization/",
"url": "https://erik.xyz/2024/03/19/randomization/",
"title": "数据随机化算法",
"date_published": "2024-03-19T15:06:00.000Z",
"content_html": "<p>开发卡牌游戏或者匹配游戏等等,需要重新洗牌或重新更换位置,这就需要清洗数据。那么就以最常见的扑克牌为例,来一个数据随机化算法。<br><span id=\"more\"></span><br>直接上代码:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"> function new_rand($arr){</span><br><span class=\"line\"> $len=count($arr);</span><br><span class=\"line\"> $new_arr=[];</span><br><span class=\"line\"> for($i=0;$i<$len;$i++){</span><br><span class=\"line\"> $new_arr[mt_rand()]=$arr[$i];</span><br><span class=\"line\"> }</span><br><span class=\"line\"> ksort($new_arr);</span><br><span class=\"line\"> return array_values($new_arr);</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">$arr=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,</span><br><span class=\"line\">17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,</span><br><span class=\"line\">33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,</span><br><span class=\"line\">49,50,51,52,53,54];</span><br><span class=\"line\">echo "原始结果".PHP_EOL;</span><br><span class=\"line\">print_r(json_encode($arr));</span><br><span class=\"line\">$get_arr=new_rand($arr);</span><br><span class=\"line\">echo PHP_EOL."最终结果".PHP_EOL;</span><br><span class=\"line\">print_r(json_encode($get_arr));</span><br></pre></td></tr></table></figure>\n<p>最终结果如图:<br><img src=\"/img/2024/20240319223317.png\" alt=\"erik.xyz\"></p>\n",
"tags": [
"算法",
"数据随机化"
]
},
{
"id": "https://erik.xyz/2023/11/12/xhprof-plugs/",
"url": "https://erik.xyz/2023/11/12/xhprof-plugs/",
"title": "xhprof插件开发",
"date_published": "2023-11-12T13:51:00.000Z",
"content_html": "<p>最近开发后总要测试接口性能,来定位接口问题,突然想到有xhprof扩展可以用。翻了翻,发现多数要不代码不更新,要不就是太繁琐,干脆自己开发以下。</p>\n<p> 参考phacility/xhprof、phpxxb/xhprof的项目,自己下载了一份代码。于是就先做了webman的插件<a href=\"https://github.com/erikwang2013/xhprof-webman\">xhprof-webman</a>,磨蹭了一段时间就又弄了laravel版的插件<a href=\"https://github.com/erikwang2013/xhprof-laravel\">xhprof-laravel</a>。最新版已更新,大家有用到的去composer安装吧。</p>\n",
"tags": [
"composer",
"xhprof",
"webman",
"laravel",
"composer插件"
]
},
{
"id": "https://erik.xyz/2023/08/21/datahub-install/",
"url": "https://erik.xyz/2023/08/21/datahub-install/",
"title": "datahub安装教程",
"date_published": "2023-08-21T13:30:00.000Z",
"content_html": "<p>偶然发现datahub很不错,单机部署来了。</p>\n<ol>\n<li><p>本地安装docker,进入到opt目录或home目录,执行以下命令(国内锁了):</p>\n<pre><code> git clone https://github.com/linkedin/datahub.git\n\n cd /opt/datahub/docker\n\n datahub docker quickstart -f ./quickstart/docker-compose-without-neo4j.quickstart.yml\n\n source ./quickstart.sh\n</code></pre></li>\n</ol>\n<span id=\"more\"></span>\n<ol>\n<li><p>执行以下命令</p>\n<pre><code> python3 -m pip install --upgrade pip wheel setuptools\n python3 -m pip uninstall datahub acryl-datahub || true \n python3 -m pip install --upgrade acryl-datahub\n datahub version\n datahub docker quickstart\n</code></pre><ul>\n<li><p>发现没有pip配置,果断下载<a href=\"https://bootstrap.pypa.io/get-pip.py -o get-pip.py\">get-pip.py</a>,然后执行以下命令安装pip</p>\n<p> sudo python3 get-pip.py </p>\n</li>\n<li><p>安装成功后,依次执行以上命令。结果如图</p>\n</li>\n</ul>\n</li>\n</ol>\n<p><img src=\"/img/202308/20230821213432.png\" alt=\"https://erik.xyz\"></p>\n<p>3.使用datahub导入数据</p>\n<pre><code> pip install pymysql\n cd /opt/datahub/docker/ingestion\n</code></pre><p><img src=\"/img/202308/20230821213951.png\" alt=\"https://erik.xyz\"></p>\n<ul>\n<li><p>复制一份sample_recipe.yml修改配置文件:</p>\n<pre><code> vi recipe.yml\n</code></pre><p><img src=\"/img/202308/20230821214242.png\" alt=\"https://erik.xyz\"></p>\n</li>\n<li><p>编辑配置<br><img src=\"/img/202308/20230821214729.png\" alt=\"\"></p>\n<ul>\n<li>具体如下:</li>\n</ul>\n</li>\n</ul>\n<pre><code>source:\n type: "mysql"\n config:\n username: "root"\n password: "root"\n database: "games_paly"\n host_port: "127.0.0.1:3306"\nsink:\n type: "datahub-rest"\n config:\n server: 'http://localhost:8080'\n</code></pre><p>4.导入数据</p>\n<pre><code> datahub ingest -c recipe.yml\n</code></pre>",
"tags": [
"datahub",
"datahub安装"
]
},
{
"id": "https://erik.xyz/2023/07/01/new-year-read-moeny/",
"url": "https://erik.xyz/2023/07/01/new-year-read-moeny/",
"title": "红包算法",
"date_published": "2023-07-01T14:27:00.000Z",
"content_html": "<p>红包算法<br>给定具体人数和金额,所有人都有,所有人的金额都是随机。那么,在分配中就要限定最大额度不能超过平局值,最小额度1分,算法如下:</p>\n<span id=\"more\"></span>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">function moneyBag($number, $money, $min = 1)</span><br><span class=\"line\">{</span><br><span class=\"line\"> $max = intval($money / $number);</span><br><span class=\"line\"> for ($i = 0; $i < $number; $i++) {</span><br><span class=\"line\"> if ($number - 1 == $i) {</span><br><span class=\"line\"> $end_arr = $money;</span><br><span class=\"line\"> } else {</span><br><span class=\"line\"> $one_money =mt_rand($min, $max);</span><br><span class=\"line\"> $end_arr = $one_money;</span><br><span class=\"line\"> $money -= $end_arr;</span><br><span class=\"line\"> $max = intval($money / ($number-($i+1)));</span><br><span class=\"line\"> }</span><br><span class=\"line\"> yield $end_arr;</span><br><span class=\"line\"> }</span><br><span class=\"line\">}</span><br><span class=\"line\">$last_arr = moneyBag(1000, 10 * 100);</span><br><span class=\"line\">$arr = [];</span><br><span class=\"line\">foreach ($last_arr as $end) {</span><br><span class=\"line\"> var_dump(bcdiv($end, 100, 2));</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>最终结果如下:<br><img src=\"/img/202306/20230701223119.png\" alt=\"https://erik.xyz\"></p>\n",
"tags": [
"算法",
"红包算法"
]
},
{
"id": "https://erik.xyz/2023/06/06/matching-amount-algorithm/",
"url": "https://erik.xyz/2023/06/06/matching-amount-algorithm/",
"title": "php撮合算法",
"date_published": "2023-06-06T12:37:00.000Z",
"content_html": "<p>在同一平台中买卖交易,需要进行订单撮合。相近的金额从大到小递减组合。期初想用mq分别匹配整数的金额倍数处理,总觉的不太完美。最近刚好有空就找到了猴子选大王算法:<br><span id=\"more\"></span><br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">function King($monkeys_arr,$m)</span><br><span class=\"line\">{</span><br><span class=\"line\"> $i = 0;</span><br><span class=\"line\"> $finsh=[];</span><br><span class=\"line\"> while(count($monkeys_arr)>1)</span><br><span class=\"line\"> {</span><br><span class=\"line\"> $i++;</span><br><span class=\"line\"> if($i>count($monkeys_arr)) break;</span><br><span class=\"line\"> $head = array_shift($monkeys_arr);</span><br><span class=\"line\"> if ($head%$m != 0) {</span><br><span class=\"line\"> array_push($monkeys_arr, $head);</span><br><span class=\"line\"> continue;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> $finsh[]=$head;</span><br><span class=\"line\"> }</span><br><span class=\"line\"></span><br><span class=\"line\"> return $finsh;</span><br><span class=\"line\">}</span><br><span class=\"line\">$a=king([1,2,6,3,4,8,7],7);</span><br><span class=\"line\">var_dump($a);</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br></pre></td></tr></table></figure><br>根据以上算法改造一下。<br>先确定匹配金额顺序,即做降序排列。<br>然后每次匹配到小于指定金额后,减去此金额。把匹配金额赋值为差结果。<br>依次类推。<br>在这过程中记录匹配到的金额、最后差值的余额。<br>撮合代码如下:</p>\n<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">function marriedDeal($arr, $change_number)</span><br><span class=\"line\">{</span><br><span class=\"line\"> $i = 0;</span><br><span class=\"line\"> $finsh = []; //匹配到的金额</span><br><span class=\"line\"> $default_total = count($arr);</span><br><span class=\"line\"> if($default_total==0) return [];</span><br><span class=\"line\"> arsort($arr);</span><br><span class=\"line\"> $blance = 0; //最后余额</span><br><span class=\"line\"> while ($default_total >= 1) {</span><br><span class=\"line\"> $i += 1;</span><br><span class=\"line\"> if ($i > $default_total || $change_number <= 0) break;</span><br><span class=\"line\"> $head = array_shift($arr);</span><br><span class=\"line\"> if ($head > $change_number) {</span><br><span class=\"line\"> array_push($arr, $head);</span><br><span class=\"line\"> continue;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> $finsh[] = $head;</span><br><span class=\"line\"> $change_number -= $head;</span><br><span class=\"line\"> $blance = $change_number;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> return [$finsh, $blance, $arr];</span><br><span class=\"line\">}</span><br><span class=\"line\"> $get_number = marriedDeal([120, 200,100,300,11,221],20);</span><br><span class=\"line\">print_r($get_number);</span><br></pre></td></tr></table></figure>\n<p>最近闲来无事,做了测试。如下图做测试数据<br><img src=\"/img/202306/20230701220606.png\" alt=\"https://erik.xyz\"><br>测试后生成结果<br><img src=\"/img/202306/20230701221810.png\" alt=\"https://erik.xyz\"><br>发现这样撮合数据多的时候会耗时太久,就尝试找优化方法。<br>如果使用生成器呢,生成结果如下<br><img src=\"/img/202306/20230701220832.png\" alt=\"https://erik.xyz\"></p>\n<p>完整优化代码:<br><figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br><span class=\"line\">45</span><br><span class=\"line\">46</span><br><span class=\"line\">47</span><br><span class=\"line\">48</span><br><span class=\"line\">49</span><br><span class=\"line\">50</span><br><span class=\"line\">51</span><br><span class=\"line\">52</span><br><span class=\"line\">53</span><br><span class=\"line\">54</span><br><span class=\"line\">55</span><br><span class=\"line\">56</span><br><span class=\"line\">57</span><br><span class=\"line\">58</span><br><span class=\"line\">59</span><br><span class=\"line\">60</span><br><span class=\"line\">61</span><br><span class=\"line\">62</span><br><span class=\"line\">63</span><br><span class=\"line\">64</span><br><span class=\"line\">65</span><br><span class=\"line\">66</span><br><span class=\"line\">67</span><br><span class=\"line\">68</span><br><span class=\"line\">69</span><br><span class=\"line\">70</span><br><span class=\"line\">71</span><br><span class=\"line\">72</span><br><span class=\"line\">73</span><br><span class=\"line\">74</span><br><span class=\"line\">75</span><br><span class=\"line\">76</span><br><span class=\"line\">77</span><br><span class=\"line\">78</span><br><span class=\"line\">79</span><br><span class=\"line\">80</span><br><span class=\"line\">81</span><br><span class=\"line\">82</span><br><span class=\"line\">83</span><br><span class=\"line\">84</span><br><span class=\"line\">85</span><br><span class=\"line\">86</span><br><span class=\"line\">87</span><br><span class=\"line\">88</span><br><span class=\"line\">89</span><br><span class=\"line\">90</span><br><span class=\"line\">91</span><br><span class=\"line\">92</span><br><span class=\"line\">93</span><br><span class=\"line\">94</span><br><span class=\"line\">95</span><br><span class=\"line\">96</span><br><span class=\"line\">97</span><br><span class=\"line\">98</span><br><span class=\"line\">99</span><br><span class=\"line\">100</span><br><span class=\"line\">101</span><br><span class=\"line\">102</span><br><span class=\"line\">103</span><br><span class=\"line\">104</span><br><span class=\"line\">105</span><br><span class=\"line\">106</span><br><span class=\"line\">107</span><br><span class=\"line\">108</span><br><span class=\"line\">109</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"></span><br><span class=\"line\">function marriedDeal($arr, $change_number)</span><br><span class=\"line\">{</span><br><span class=\"line\"> $default_total = count($arr);</span><br><span class=\"line\"> if ($default_total <= 0 || $change_number<=0) return [];</span><br><span class=\"line\"> $blance = $i = 0;</span><br><span class=\"line\"> arsort($arr);</span><br><span class=\"line\"> while ($default_total >= 1) {</span><br><span class=\"line\"> $i += 1;</span><br><span class=\"line\"> if ($i > $default_total || $change_number <= 0) break;</span><br><span class=\"line\"> $head = array_shift($arr);</span><br><span class=\"line\"> if ($head > $change_number) {</span><br><span class=\"line\"> array_push($arr, $head);</span><br><span class=\"line\"> continue;</span><br><span class=\"line\"> }</span><br><span class=\"line\"> $change_number -= $head;</span><br><span class=\"line\"> $blance = $change_number;</span><br><span class=\"line\"> yield ['finsh'=>$head,"blance"=>$blance,'last_arr'=>[]];</span><br><span class=\"line\"> }</span><br><span class=\"line\"> //yield ['last_arr'=>$arr];</span><br><span class=\"line\">}</span><br><span class=\"line\"></span><br><span class=\"line\">$t1 = microtime(true);</span><br><span class=\"line\">$list = [120, 200, 4353, 43543, 435, 546, 56, 435, 3443, 5435,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 180, 11, 223, 2342, 345, 435, 456, 4564, 55, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 232, 3243, 23424, 6576, 7897, 2342, 21342, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 456, 345, 435, 345, 456, 57, 4345, 345435, 435,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 220, 400, 20, 320, 43435, 4564, 45645, 456, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 180, 11, 223, 2342, 345, 435, 456, 4564, 55, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 232, 3243, 23424, 6576, 7897, 2342, 21342, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 456, 345, 435, 345, 456, 57, 4345, 345435, 435, 180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 56546, 566, 456452324, 34543, 435, 435, 435, 45180, 11, 223, 2342, 345, 435, 456, 4564, 55,</span><br><span class=\"line\"> 2344, 3242, 342, 4, 34543, 435, 24, 456, 6, 456, 745</span><br><span class=\"line\">];</span><br><span class=\"line\">foreach($get_number as $k){</span><br><span class=\"line\"> var_dump($k);</span><br><span class=\"line\">}</span><br><span class=\"line\">$t2 = microtime(true);</span><br><span class=\"line\">echo '耗时' . round($t2 - $t1, 4) . '秒';</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure></p>\n",
"tags": [
"撮合算法",
"撮合",
"php撮合算法"
]
}
]
}