木偶's Blog

如果发现能力无法支撑自己的野心,那就静下心来学习吧

总结

概要列表

  1. cleanmypc:全方面电脑文件垃圾清理
  2. geek:最轻便且卸载最干净的卸载软件(直接下载解压后,双击 exe 文件即可使用,无需安装)
  3. snipaste:最牛逼的截屏软件,用过才知道
  4. uTools:各种小工具小插件,还是个微型的软件中心,只有想不到,没有它没有的。
  5. SpaceSniffer:对 C 盘等磁盘空间进行优化。能够可视化显示每个文件的占磁盘空间。

Windows

  1. 离线视频播放器 PotPlayer(官网下载)
  2. 录屏软件 Bandicam
  3. xshell,ssh终端连接
  4. Bandizip 压缩软件,推荐 7.0 之前的版本,免费且无广告
  5. listary,虽然 uTools 有基于 everything 的插件,但 listary 依然有用武之处

PDF专用

  • Drawboard PDF(已拥有,微软账号)

    Drawboard 在 Win10 应用商店限时免费。Drawboard 更适合触摸屏电磁笔手写,但是启动速度非常慢,文字渲染不够清晰。(支持手写的 PDF 阅读编辑器)

    大名鼎鼎的 DRAWBOARD PDF,正价 88 元,有时候微软应用商店特价至 0 元,适合移动办公/SURFACE 平板人群使用。

  • 万兴PDF专家(PDFelement),PDFelement功能齐全、极其易用、界面美观,是目前(2020年4月)用过的PDF软件中综合表现最好的。

  • 嗨格式阅读器。小巧简洁,主打阅读。

uTools

非常好用!强烈推荐!!!!

Hello,各位小伙伴们好,又到周末了,给大家分享一款神器:『utools』。

官网及下载地址:https://u.tools/

uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。当你熟悉它后,能够为你节约大量时间,让你可以更加专注地改变世界。

utools 支持 Windows/Mac/Linux

安装成功之后,可以通过快捷键『Alt+Space』,可以唤起 utools 窗口。

以下页面都是 mac 上截图,其他平台大同小异。

image-20210301125232328

这个界面比较像 Alfred,刚开始没有安装任何插件,只能简单根据关键字查找系统中已安装的应用。

image-20210301125250491

我们可以通过安装插件,扩展 『utools』的功能,提高我们的生产力。

uTools安装插件

相关插件都可以在插件中心安装,只需要在窗口输入插件,选择插件中心

image-20210301125334138

进入插件中心,选择安装相应的插件。

image-20210301125404210

插件推荐

在线翻译

这个插件整合有道翻译、金山词霸、谷歌翻译、必应翻译。

关键字: translate/翻译

image-20210301125532976

剪贴板历史

该插件可代替 Ditto

系统的剪贴板,只能查找最近一次的复制记录,这就比较麻烦。使用功能可以查找最近文本、图片、文件的复制记录,非常有用。

关键字:clipboard

斗图

这个功能么,聊天摸鱼神器,点击图片双击即可复制。

偷偷告诉你:很多骚表情都是可以从上面直接复制的。

关键字:doutu

JSON编辑器

通过这个插件,我们可以将复制过来的 Json 格式化,可以将 xmlyaml 转义成 json

关键字:json

编码小助手

日常开发经常遇到需要一些编码转化,比如 URL 编码。以前每次都需要谷歌搜个工具网页,现在直接使用这个插件就可以了。并且这个插件集合非常多转化小工具,

关键字:很多很多。。。。自己找吧!

base64

  • 字符串 base64 编码
  • base64 字符解码
  • 图片 base64 编码
  • 图片 base64 解码
  • uTools 输入框自动识别 base64 字符串

data/unixtime/timestamp

  • 时间格式转换
  • 时间戳获取
  • 标准时间
  • uTools 输入框自动识别时间戳和 yyyy-MM-dd HH:mm:SS 格式时间

UUID/GUID

  • 唯一 ID

Hash加密

  • MD5
  • SHA1
  • SHA224
  • SHA256
  • SHA384
  • SHA512
  • 文件 HASH 加密

URL

  • URL 编解码

常见文档(vue、Python等等)

image-20210301130219067

可以快速查找 Linux 命令使用方法,这真是极好的。

image-20210301130243069

人生进度条

image-20210301130316529

件中心还有很多插件,小伙伴们可以自行查找。没找到想要的插件,小伙伴也考虑自己开发。

CleanMyPC

用起来非常爽,完全可以代替电脑管家等扫描垃圾的软件。

收费,但可破解。到网盘找压缩包

  1. 解压
  2. 安装 CleanMyPC.exe,安装完成后不要打开
  3. 复制 Patch.exe 到 CleanMyPC 的安装路径
  4. 打开激活工具 Patch.exe,点击 Patch,完成。

如果弹出这个,说明 CleanMyPC 处于打开状态

image-20210302170104534
  1. 任务管理器,终止 “CleanMyPC NT Service” 进程,
  2. 系统服务,找到“CleanMyPC观察程序”服务,将服务停止,并将服务改为“手动”启动。

再打开 Patch.exe,点击 patch 激活。出现下方红框字样,说明激活成功:

image-20210509114351092

SpaceSniffer

SpaceSniffer 是一款能够可视化分析磁盘空间占用情况的磁盘清理软件!免费,有用且可靠的,可以扫描Windows PC 上的文件。使用此工具,可以清楚地了解计算机硬盘中文件和文件夹的结构。为了检查磁盘空间,该程序使用 Treemap 可视化布局,该布局使您可以基于颜色感知大型文件和文件夹在设备上的位置。 由于此磁盘清洁器速度很快,因此您可以在几秒钟内清楚地了解整体情况。只需单击一下,SpaceSniffer 便会详细显示所选文件,包括大小,文件名,创建日期等。

下载地址和官网为:http://www.uderzo.it/main_products/space_sniffer/

下载后得到一个 zip 压缩包,直接解压后以“管理员”的身份打开 exe 文件

image-20210404213809678

打开后,选择要分析的目录,就可以很快地得到该目录的磁盘空间占用情况:

image-20210404214135028

点击方格后,还会继续往下显示目录的磁盘占用情况,鼠标右键方格,则可以对该目录进行操作,打开、删除等等。

AVG

对电脑进行实时的性能监控

解锁猎人

解决文件无法删除的问题

天诺OCR

快速识别图片上的文字

USBSafelyRemove

解决 U盘 弹出失败问题

Recuva

恢复回收站删除的文件

录屏软件

Bandicam

做教程视频的时候是必须要用到录屏工具的,如果在 Windows 平台,那我的首选一定是这款来自韩国的Bandicam,因为它简单易用,功能强大,并且没有花哨多余的附加项,是一款纯粹的录屏软件。

OBS

录屏功能比 Bandicam 要丰富、强大,但使用起来比 Bandicam 略复杂。

总结

一般需求,使用 Bandicam 就足够了

右键管理神器

https://github.com/BluePointLilac/ContextMenuManager

开源免费,非常方便地管理鼠标右键菜单,而不用自己去面对枯燥的注册表。

image-20221012201505681

跨平台

NDM

可能我们都知道,下载届有一个非常牛叉的神器:Inet Download Manager —— IDM。这个下载神器不仅支持多线程加速下载功能,还具备网页视频嗅探功能。当然,它还具备其他很多我尚未尝试过的功能,平时用得最多也就是这两个了,用过的也都说 nice,真香。

但在我切换到 macOS 系统后,发现 IDM 不兼容 macOS,所以找了一些可以替代 IDM 的下载器,最后发现有一款与 IDM 很相似的下载器,同样具备 IDM 那两个我个人常用的功能。这个下载器就是今天要介绍的:NDM

介绍与安装使用

NDM(Neat Download Manager)这款软件完全免费无广告,支持 Windows 和 macOS

官方网站:https://www.neatdownloadmanager.com/index.php/en/

可以直接上官网下载,也可以通过以下链接下载:

链接: https://pan.baidu.com/s/1dxJHR0GhSgEGhGFTFEQ5rQ 提取码: sae6

安装后打开的界面如下:

image-20211128234641307

这样子就可以使用了,APP 非常简洁。配合上浏览器插件,可以实现视频嗅探功能

嗅探功能

给谷歌浏览器安装以下插件:

image-20211129090733277

接着随便打开一个视频网站测试

image-20211129090930346

可以看到上面的红色方框中有三个可下载文件:1、2、3,视频越大,说明越高清。直接点击想要的清晰度视频,就可以实现下载。

image-20211129091209091

qBitTorrent

官网:https://github.com/qbittorrent/qBittorrent

下载地址:https://github.com/c0re100/qBittorrent-Enhanced-Edition/releases

种子与磁力链接的下载神器,推荐使用。

在 Windows 上如果迅雷不是很好用,可以用它来代替迅雷;在 MacOS 上可以用它来代替 Folx

MacOS

Downie

专门用来下载视频的,支持很多主流网站视频的一键下载

跟 NDM 相比,NDM 属于通用型,Downie 属于专业性

Folx

不管是 NDM 还是 Downie 都无法下载磁链或 bt,所以还是需要 Folx、迅雷 或者 uTorrent 来补充

但个人使用后的感觉是 Folx 在 bt 搜索方面做得非常好,但是下载速度,且一言难尽,亦或者是个人配置问题,有哪位大佬知道的,能不能指导一下,不胜感激。

Windows

IDM

最强下载神器,除了不能下载磁链和 bt,其他都可以,速度飞快且稳定,配置还非常简单。

迅雷

弥补 IDM 无法下载磁链和 bt 的不足。也可以用 Motrix 或 qBitTorren 等替换

蜈蚣文件

蜈蚣文件是一款免费开源的下载神器,支持 Windows 和 Linux 平台(目前为 1.9 版本,暂不支持 Mac OS)

其官网为:http://www.filecxx.com/zh_CN/index.html

蜈蚣文件具备 IDM 和 NDM 的所有功能,而 IDM 和 NDM 所没有的功能但常用的,它也基本都有,例如 BT 下载、磁力链接下载等等。

同时,它还与 IDM 和 NDM 类似,支持在各个主流浏览器中安装插件,捕获下载链接,进行下载。

总结与推荐

Windows

迅雷 + IDM

很多人都说 QBitTorrent 好,但是在一些冷门种子上,迅雷还是很能打的,QBitTorrent 下载不了的,迅雷也能下载。对于热门种子来说,两者的差异并不大。

蜈蚣文件的功能很全面,不仅能进行普通下载,也能下载种子,但是体验上,很多时候都没有速度,不是很行。

对于普通下载来说,在 Windows 上,IDM 还是最强的,虽然 NDM 也很强(两者功能相差无几),但是如果让我选,我选 IDM

MacOS

NDM + qBitTorrent

因为 IDM 没有 MacOS 版本,所以只能用 NDM 代替

如果下载种子,则推荐用 QBitTorrent,Folx 说是 MacOS 上最强的种子下载神器,但在体验上,我并没有感受到(可能是因为在国内,亦或者是配置问题?)但是 Folx 提供了另一个很强大的功能:种子搜索。不过只能搜索国外的种子。总之,个人还是觉得 QBitTorrent 更好用些

Downie 更适合于专门下载视频的,如果需要时常从油管、B站等这类网站上下载视频,那这个软件确实是神器,下载速度快、可批量、可自定义视频链接,还方便对下载下来的视频进行自动化后续处理。(如果简单的下载,NDM 和 IDM 也能做到)

Magnet

适用于 macOS,Windows 自带的分屏功能已经足够强大

非常简单粗暴好用!

image-20211222164957305

Magnet 除了拖拽和快捷键调整布局,没有更多功能,也就没有了自己设置的麻烦,胜在简单。不想自己配置的用户,看一眼快捷键表就能上手。

image-20211222165037679

但需要收费,可以在打折的时候购入。破解版在阿里云盘

目前已知的可远程控制计算机的品牌有:

向日葵、花生壳、natapp.cn

向日葵远程控制

国内市场老大哥,个人免费版十分强大,满足个人日常需求。

  1. 白板模式

    在该模式下,可以在屏幕上进行涂写,被远程控制的电话上会同步显示除涂写的内容

  2. 兼容几乎所有系统

    兼容了市面上所有常见的系统,包括 Linux 和移动端都是可以使用的。

ToDesk

目前正在使用的远程控制软件,轻量,好用

不为人知的搜索技巧

一张照片查出你的拍摄地点

随着智能手机的普及,现在人们大部分使用手机进行拍照,而大多数相机已经默认开启地理位置。在开启了这种功能的情况下拍摄的照片会自动存有你所拍照地点的经纬度信息:

image-20210904125524453

有了经纬度信息,我们可以在 MagicExif 软件里查询到照片拍摄地的具体地址(精确到门牌号的那种地步!!!)

语音通话获取IP地址

我们在 QQ 聊天时都是通过数据进行传输,那么使用一个抓包工具,只要数据传输到对方并且对方在线,我们就可以获取到对方的 IP 信息:

image-20210904125621675

当我们打开这个工具的时候,只需要给对方拨打一个电话,不管对方有没有接听,你都可以获取到对方的 IP 地址:

通过IP进行定位

当我们获取到了对方的 IP 地址之后,也可以通过 IP 进行大概位置的定位(有五公里以内的误差):

IP 查询网址:www.ipplus360.com

image-20210904125722445

利用经纬度进行二次解析

当我们通过IP地址进行查询后可以获得对方的大概地理位置,如果想进行二次精确定位的话,我们可以复制经纬度,在解析网站里面进行查询:

经纬度解析网站:map.yanue.net

image-20210904125804786

通过邮箱/手机号查询你注册过的网站

查询网址:www.reg007.com

这个网站就是利用你提交的信息去进行模拟注册来查询是否在该平台已经注册过:

image-20210904125847384

利用支付宝查询你的名字

随意转账一笔大额资金给对方,这时,为了资金安全,支付宝会显示对方的名字,要求你补全对方的姓氏。

那么,我们可以使用常用的姓氏一个个进行尝试来暴力破解获取到对方全名。

image-20210904125928662

把知道的信息丢给百度查询

image-20210904130004300

这只是一个例子,不仅是QQ,还有 微信/常用ID/手机号 全部都可以进行查询,不法分子就可以利用这些信息进行电信诈骗!

防止被人肉的措施

  1. 不要在网络上留下自己的QQ,手机号码等相关个人信息,否则会被搜索引擎保存成快照,从而被不法分子所获取;
  2. 不要在所有网站使用同一个用户名;
  3. 最好使用两张手机卡,一张日常通话使用,另一张在网上注册信息时用于接收验证码等操作;
  4. QQ,微信等社交软件最好不要使用同一个账户。

1.1 场景

下面举例:

当通过组件调用 van-dialog 时,dialog 内容过多,对话框没有出现滚动样式,现通过以下的 css 语句解决该问题

1
2
3
4
.van-dialog__content {
max-height: 400px;
overflow: scroll;
}

结果发现,样式没有生效。

1.2 解决方案

采用以下方式,添加 /deep/,让其作用域往下钻

1
2
3
4
/deep/ .van-dialog__content {
max-height: 400px;
overflow: scroll;
}

样式生效,问题解决

1.3 拓展说明

在 Vue 中,关于样式有几个规范:

  • 一是样式写在文件的最后。
  • 二是使用 class,而不直接为标签名称写样式。
  • 三是要 scoped。

尤其是 scoped 很重要,因为 Vue 并不是我们传统的一个页面一个页面的文件,如果不 scoped,会发生样式干扰。

假如我们使用了一个 van-grid-item 组件,我们通过检查,发现生成 HTML 的结构是:

  • 外层 div 套了一个内层 div。
  • 外层 div 的 class 是:van-grid-item。
  • 内层 div 的 class 是:van-grid-item__content。

于是我们写样式:

1
2
3
4
5
6
7
8
9
<style lang="less" scoped>
.van-grid-item
border:1px solid red;
}
.van-grid-item .van-grid-item__content
background: #f00
color: #fff;
}
</style>

我们会发现第一个生效了,但是第二个没生效,这是因为 Vue 只认组件本身那层 class,其内部继续产生的 class 是不认的,怎么解决呢?加上 /deep/ 让其作用域往下钻,如下:

1
2
3
4
5
6
<style lang="less" scoped>
/deep/.van-grid-item .van-grid-item__content
background: #f00
color: #fff;
}
</style>

SIT 与 UAT 的区别

在企业级软件的测试过程中,经常会划分为三个阶段:单元测试、SIT 和 UAT。

如果开发人员足够,通常还会在 SIT 之前引入代码审查机制(Code Review)来保证软件符合客户需求且流程正确。

下面简单介绍一下 SIT 和 UAT 的基本情况。

SIT(System Integration Testing)系统集成测试,也叫做集成测试,是软件测试的一个术语,在其中单独的软件模块被合并和作为一个组测试。它在单元测试以后和在系统测试之前。集成测试在已经被单元测试检验后进行作为它的输入模式,组织它们在更大的集合,和递送,作为它的输出,集成系统为系统测试做准备。集成测试的目的是校验功能、性能和可靠性要求,配置在主设计项目中。

UAT(User Acceptance Testing)用户验收测试,通常是由最终软件的用户(通常这些用户不了解软件的具体逻辑,而对业务逻辑却相当熟悉)进行的测试,因此是面向最终用户的测试,结束之后通常就可以发布生产环境。

区别与联系

SIT 是集成测试,UAT 是验收测试

  • 从时间上看,UAT 要在 SIT 后面,UAT 测试要在系统测试完成后才开始。

  • 从测试人员看,SIT 由公司的测试员来测试,而 UAT 一般是由用户来测试。

它们两个之间的专注点是不一样的。UAT 主要是从用户层面这些去考虑和着手测试,而 SIT 主要是系统的各个模块的集成测试。这在整个软件过程理论的基础知识中相当重要的。理论上讲 SIT 是由专业的测试人员去完成,UAT 是由用户去做的。

如果按照规范来的话,做 UAT 测试的人一定是要对业务很精通的,并且是具有代表性的用户,关注的东西就是业务流程是否通畅是否符合业务的需要,主要以需求分析文档为重要参考,还有一些用户的操作习惯等等一系列的东西。

从0到网站部署_硬件准备

购买域名

网站上线运营就必须要购买域名,而万网是选择相对比较多的,万网目前在阿里旗下,首先你要有一个阿里云的账号,阿里云官方网站 https://www.aliyun.com/,然后进入管理控制台,然后购买域名。域名的价格不是很贵 ,一年也就几十元吧。

不过起域名是件很头疼的事情,域名要简单,好记,朗朗上口,最好和自己以后的网站昵称紧密相关。不过你会发现,你能想到的域名基本上都被注册过了,域名购买之后建议去实名认证。

ICP备案

Why

网站备案是国家相关部门要求的,在国内的所有网站都必须备案(使用海外服务器则不需要备案),备案之后,域名备案的主体信息及运营者将对域名提供的内容负法律责任的。

未备案的域名不能使用国内服务器;未备案的域名不能使用很多主流的推广手段,例如百度推广和微信推广,同时也会影响网站信誉度。

How

像阿里云,百度云,腾讯云都会提供域名备案的业务,但建议你使用哪里的服务器就在哪里备案,例如你使用的是阿里云,那你就在阿里云进行备案。因为有些云服务器提供商会要求只能接入自己平台上备案的域名。我记得我之前是在百度云备案的域名,但是在接入阿里云服务器无法接入,那就蛋疼了。

备案没有想象中那么复杂,但也不简单,填写各种信息,各个平台都有自己的备案流程和教程,自己可以看看,大体相同。整个备案流程下来基本上 要20天左右。因此,只要你想运营个人网站,第一步就是准备域名和备案,省的耽误你网站上线。

服务器

要把网站代码部署到服务器中才能让别人访问。

关于服务器的选择

  1. 云虚拟主机:价格便宜,虚拟主机只能使用其预装好的web server和数据库

    例如,阿里云虚拟主机 Window版本下是IIS和SQL Server,Linux版本下是Apache和MySQL,这两种都不支持Java Web应用。通过可视化界面控制,用户不需要自己搭建网站的运行环境和数据库等,只需用户上传代码即可。适合访问量较低的个人网站使用。具体的使用方法可百度关键词“云虚拟部署网站”

  2. 云服务:价格较贵,相当于用户远程控制的一台电脑,用户可根据需要搭建不同的应用环境,

    例如用户可在云服务器上搭建javaweb php asp.net等多种网站部署环境,在购买云服务器时,可以购买镜像(即网站或应用程序的开发环境)勿需用户自己搭建,支持一键部署,用户也可以选择公共镜像,自己搭建网站的运行环境和数据库等。

  3. 类似百度 BAE 的PaaS平台,此平台可集成多种应用的运行环境,例如下图

    image-20210904131539628

    只能选择一种集成环境环境,一旦选择不能改变,此平台的好处是用户不用关心开发环境,只要将代码上传发布即可。缺点就是,使用了此平台还要购买云数据库,因此最后,购买平台+购买云数据库的价格相当于使用云服务器的价钱。

域名解析

域名解析的意思,就是把申请的已备案成功后的域名指向部署网站服务器的ip地址或二级域名,解析成功后,别人就可以通过域名访问你的网站了。

首先,在哪里购买的域名,就在哪设置域名解析,假如你的域名是在阿里万网购买的,登陆阿里云的控制台后,进入域名管理界面,如下图

image-20210904131653752

点击域名后面的‘解析’后,如下图

image-20210904131709884

点击‘添加解析’,如下图,这里你要注意以下两点:

  1. 记录类型的选择

  2. 记录值

一般记录类型选择 A 或者 CNAME。

  • 当记录类型选择 A 时,那么记录值就填写部署网站服务器的 ip 地址

  • 当记录类型选择 CNAME 时,那么记录值就填写部署网站服务器的二级域名

一般如果你部署网站的环境选择的是类似百度 BAE 的 PaaS 平台,那么平台会提供一个二级域名,如果选择的是云虚拟机或云服务器则会提供一个 ip 地址。

image-20210904131852710

从0到网站部署_软件准备

技术选型

以下推荐适合有点编程基础,或者想快速搭建个人网站的,如果你是想锻炼自己的开发能力,那你就自己从 0 开始 codeing。

我个人非常推荐基于 PHP 开源的 CMS 系统 WordPress,虽然不少人鄙视 php,但不得不承认 php 在个人网站和中小企业网站上还是很值得选择的。

WordPress 基本上满足个人网站的功能,用户注册,登陆,内容发布,管理,评论等,而且扩展性强,主题模板和功能插件非常丰富,即使没有 php 编程的基础也可以搭建一套完美的网站系统,如果有 php 开发技术也可以根据需要进行二次开发。

这里就不多介绍了,有需要的可以自己百度。当然 Python,Java 等也都有类似 WordPress 开源的系统,具体的大家可以百度关键词,例如‘Python 开源cms系统’。

SEO搜索

  1. 网站上线后,要主动向各个搜索引擎提交自己的网站入口,http://www.baidusourl.com/,这样别人就可以通过网站名称搜索到你的网站。

  2. 生成自己的网站页面地图。有利于搜索引擎收录网站内容,以便用户使用关键词搜索到你的网站内容。如果你使用的是 WordPress,那极力推荐你使用 Baidu Sitemap Generator 这个插件,可以自动生成一个静态的站点地图页面,可以手动提交给搜索引擎加快百度的收录速度。

以上只是最入门也是必备的 SEO 搜索优化,更多的优化,大家可以百度搜索关键词“网站SEO搜索优化”

网站安全

必备安全功能

  1. 记录注册用户的 IP 地址。这个简单,一个字段存数据库就好了。
  2. 防止访客 F5 频繁刷新页面。网站使用的技术不同,实现方式也不相同,大致原理就是,根据访问用户的ip,用session记录1秒内访问的次数,如果访问的过多就输出拒绝访问的提示。如果这个功能没有,长按F5就可以把你的网站服务器刷爆。
  3. 黑名单功能。就是来防止用户恶意评论等不友好的操作或直接防止用户访问(一般这个可以通过服务器配置文件,配置禁止访问的IP地址)。基本上都是在用户使用某种网站功能时 先判断用户IP是否在黑名单中,如果在,禁止使用,否则就放行。如果没有这种功能,遇到了变态用户会恶心到你吐。如果你使用的是WordPress,这里我也推荐你使用WP-Ban这个插件。

接入第三方的云加速平台

当然这个平台的高级功能是收费的,但个人网站使用其免费功能也就够用了,例如 360 云加速百度云加速抗 D 保等平台。

这里我首推 360 云加速,毕竟是做安全出身;个人认为使用加速平台的免费功能最大的好处就是可以隐藏网站真实的 IP 地址,其次是支持配置 IP 黑名单(这个功能有些平台是收费的),也有静态文件缓存,加速,图片防盗链等功能。

用户统计

  1. 根据自己需要开发用户统计的功能。这个不多讲,根据选择的技术方案实现也各不相同。
  2. 接入第三方的统计平台。个人使用过百度统计CNZZ 数据专家。推荐使用后者,功能很强大。日访问量,独立IP,访客ip….等等

最后

以上只是经验之谈,都是些最基本,但也是必备的功能。

如何实现延迟任务

场景描述

假设现在有一个需求:生成订单 30 分钟未支付,则自动取消,如何实现?又或者,生成订单 60秒 后,给用户发短信。

像上面这样子的需求,就属于延时任务。

这个延时任务和定时任务的区别究竟在哪里呢?

  1. 定时任务有明确的触发时间,延时任务没有
  2. 定时任务有执行周期,而延时任务在某事件触发后一段时间内执行,没有执行周期
  3. 定时任务一般执行的是批处理操作是多个任务,而延时任务一般是单个任务

解决方案总结

解决方案有很多种,但各有优缺点,目前来看,采用 Redis 是最优的解法。

分别如下:

  • 数据库轮询
  • JDK的延迟队列

数据库轮询

思路

该方案通常是在小型项目中使用,通过一个线程定时去扫描数据库,通过订单时间来判断是否有超时的订单,然后进行 update 或 delete 等操作

实现

用 quartz 来实现的

依赖引入

1
2
3
4
5
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency>
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
public class MyJob implements Job {
public void execute(JobExecutionContext context)
throws JobExecutionException {
System.out.println("要去数据库扫描啦。。。");
}

public static void main(String[] args) throws Exception {
// 创建任务
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1").build();
// 创建触发器 每 3秒 钟执行一次
Trigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("trigger1", "group3")
.withSchedule(
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3).repeatForever())
.build();
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
// 将任务及其触发器放入调度器
scheduler.scheduleJob(jobDetail, trigger);
// 调度器开始调度任务
scheduler.start();
}
}

优缺点

优点:

简单易行,支持集群操作

缺点:

  1. 对服务器内存消耗大
  2. 存在延迟,比如你每隔 3分钟 扫描一次,那最坏的延迟时间就是 3分钟
  3. 假设你的订单有几千万条,每隔几分钟这样扫描一次,数据库损耗极大

JDK的延迟队列

思路

该方案是利用 JDK 自带的 DelayQueue 来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入 DelayQueue 中的对象,是必须实现 Delayed 接口的。

DelayedQueue 实现工作流程如下图所示

image-20210830091336105
  • Poll():获取并移除队列的超时元素,没有则返回空
  • take():获取并移除队列的超时元素,如果没有,则 wait 当前线程,直到有元素满足超时条件,返回结果。

实现

定义一个类 OrderDelay 实现 Delayed

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
public class OrderDelay implements Delayed {
private String orderId;
private long timeout;

OrderDelay(String orderId, long timeout) {
this.orderId = orderId;
this.timeout = timeout + System.nanoTime();
}

public int compareTo(Delayed other) {
if (other == this)
return 0;
OrderDelay t = (OrderDelay) other;
long d = (getDelay(TimeUnit.NANOSECONDS) - t
.getDelay(TimeUnit.NANOSECONDS));
return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
}

// 返回距离你自定义的超时时间还有多少
public long getDelay(TimeUnit unit) {
return unit.convert(timeout - System.nanoTime(),TimeUnit.NANOSECONDS);
}

void print() {
System.out.println(orderId+"编号的订单要删除啦。。。。");
}
}

延迟队列,使用

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
public class DelayQueueDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> list = new ArrayList<String>();
list.add("00000001");
list.add("00000002");
list.add("00000003");
list.add("00000004");
list.add("00000005");
DelayQueue<OrderDelay> queue = newDelayQueue<OrderDelay>();
long start = System.currentTimeMillis();
for(int i = 0; i < 5; i++){
// 延迟三秒取出
queue.put(new OrderDelay(list.get(i), TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS)));
try {
queue.take().print();
System.out.println("After " + (System.currentTimeMillis() - start) + " MilliSeconds");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

demo 运行结果:

image-20210830091925538

可以看到都是延迟 3秒,订单被删除

优缺点

优点

效率高,任务触发时间延迟低。

缺点

  1. 服务器重启后,数据全部消失,怕宕机
  2. 集群扩展相当麻烦
  3. 因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现 OOM异常
  4. 代码复杂度较高

时间轮算法

思路

image-20210830092103301

时间轮算法可以类比于时钟,如上图箭头(指针)按某一个方向按固定频率轮动,每次跳动称为一个 tick。定时轮有 3个 重要的属性参数:

  • ticksPerWheel(一轮的 tick 数),
  • tickDuration(一个 tick 的持续时间)
  • timeUnit(时间单位)

当 ticksPerWheel=60,tickDuration=1,timeUnit=秒,就和现实中的秒针走动完全类似了

如果当前指针指在 1 上面,有一个任务需要 4秒 以后执行,那么这个执行的线程回调或者消息将会被放在 5 上。那如果需要在20秒之后执行怎么办?这个环形结构槽数只到 8,如果要 20秒,指针需要多转2圈。位置是在 2圈之后 的 5 上面(20 % 8 + 1 = 2...5,即 2 余 5)

实现

用 Nett y的 HashedWheelTimer 来实现

1
2
3
4
5
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.24.Final</version>
</dependency>
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
public class HashedWheelTimerTest {
static class MyTimerTask implements TimerTask{
boolean flag;
public MyTimerTask(boolean flag){
this.flag = flag;
}
public void run(Timeout timeout) throws Exception {
// TODO Auto-generated method stub
System.out.println("要去数据库删除订单了。。。。");
this.flag =false;
}
}
public static void main(String[] argv) {
MyTimerTask timerTask = new MyTimerTask(true);
Timer timer = new HashedWheelTimer();
timer.newTimeout(timerTask, 5, TimeUnit.SECONDS);
int i = 1;
while(timerTask.flag){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i + "秒过去了");
i++;
}
}
}

优缺点

优点

  • 效率高
  • 任务触发时间延迟时间比 delayQueue 低
  • 代码复杂度比 delayQueue 低。

缺点

  • 服务器重启后,数据全部消失,怕宕机
  • 集群扩展相当麻烦
  • 因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现 OOM异常

Redis缓存

Redis 实现延时任务会有两种思路

思路一:zset

利用 redis 的 zset。zset 是一个有序集合,每一个元素(member)都关联了一个 score,通过 score 排序来取集合中的值。

将订单超时时间戳与订单号分别设置为 score 和 member,系统扫描第一个元素判断是否超时,具体如下图所示:

image-20210830094356834

实现一

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
public class AppTest {
private static final String ADDR = "127.0.0.1";
private static final int PORT = 6379;
private static JedisPool jedisPool = new JedisPool(ADDR, PORT);

public static Jedis getJedis() {
return jedisPool.getResource();
}

// 生产者,生成 5个 订单放进去
public void productionDelayMessage() {
for(int i = 0; i < 5; i++) {
//延迟3秒
Calendar cal1 = Calendar.getInstance();
cal1.add(Calendar.SECOND, 3);
int second3later = (int) (cal1.getTimeInMillis() / 1000);
AppTest.getJedis().zadd("OrderId", second3later, "OID0000001" + i);
System.out.println(System.currentTimeMillis() + "ms:redis生成了一个订单任务:订单ID为" + "OID0000001" + i);
}
}

// 消费者,取订单
public void consumerDelayMessage() {
Jedis jedis = AppTest.getJedis();
while(true){
Set<Tuple> items = jedis.zrangeWithScores("OrderId", 0, 1);
if(items == null || items.isEmpty()) {
System.out.println("当前没有等待的任务");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
continue;
}
int score = (int) ((Tuple)items.toArray()[0]).getScore();
Calendar cal = Calendar.getInstance();
int nowSecond = (int) (cal.getTimeInMillis() / 1000);
if(nowSecond >= score){
String orderId = ((Tuple)items.toArray()[0]).getElement();
jedis.zrem("OrderId", orderId);
System.out.println(System.currentTimeMillis() + "ms:redis消费了一个任务:消费的订单OrderId为" + orderId);
}
}
}

public static void main(String[] args) {
AppTest appTest = new AppTest();
appTest.productionDelayMessage();
appTest.consumerDelayMessage();
}

}

image-20210830094535676

可以看到,几乎都是3秒之后,消费订单。

然而,这一版存在一个致命的硬伤,在高并发条件下,多消费者会取到同一个订单号,我们上测试代码 ThreadTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ThreadTest {
private static final int threadNum = 10;
private static CountDownLatch cdl = newCountDownLatch(threadNum);
static class DelayMessage implements Runnable {
public void run() {
try {
cdl.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
AppTest appTest =new AppTest();
appTest.consumerDelayMessage();
}
}
public static void main(String[] args) {
AppTest appTest =new AppTest();
appTest.productionDelayMessage();
for(int i = 0; i < threadNum; i++) {
new Thread(new DelayMessage()).start();
cdl.countDown();
}
}
}

image-20210830094659763

显然,出现了多个线程消费同一个资源的情况。

解决方案:

  1. 用分布式锁,但是用分布式锁,性能下降了,该方案不细说。
  2. 对 ZREM 的返回值进行判断,只有大于 0 的时候,才消费数据

于是将 consumerDelayMessage() 方法里的

1
2
3
4
5
if(nowSecond >= score) {
String orderId = ((Tuple)items.toArray()[0]).getElement();
jedis.zrem("OrderId", orderId);
System.out.println(System.currentTimeMillis() + "ms:redis消费了一个任务:消费的订单 OrderId 为" + orderId);
}

修改为:

1
2
3
4
5
6
7
if(nowSecond >= score) {
String orderId = ((Tuple)items.toArray()[0]).getElement();
Long num = jedis.zrem("OrderId", orderId);
if( num != null && num > 0){
System.out.println(System.currentTimeMillis()+ "ms: redis 消费了一个任务:消费的订单 OrderId 为" + orderId);
}
}

在这种修改后,重新运行 ThreadTest 类,发现输出正常了

思路二:键空间机制

利用 Redis 的 Keyspace Notifications,即键空间机制,就是利用该机制可以在 key 失效之后,提供一个回调,实际上是 Redis 会给客户端发送一个消息。需要 Redis 版本 2.8 以上。

实现二

配置 redis.conf

1
notify-keyspace-events Ex
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
public class RedisTest {
private static final String ADDR = "127.0.0.1";
private static final int PORT = 6379;
private static JedisPool jedis = new JedisPool(ADDR, PORT);
private static RedisSub sub = new RedisSub();

public static void init() {
new Thread(new Runnable() {
public void run() {
jedis.getResource().subscribe(sub, "__keyevent@0__:expired");
}
}).start();
}

public static void main(String[] args) throws InterruptedException {
init();
for(int i = 0; i < 10; i++) {
String orderId = "OID000000"+i;
jedis.getResource().setex(orderId, 3, orderId);
System.out.println(System.currentTimeMillis() + "ms: " + orderId + "订单生成");
}
}

static class RedisSub extends JedisPubSub {
<ahref='http://www.jobbole.com/members/wx610506454'>@Override</a>
public void onMessage(String channel, String message) {
System.out.println(System.currentTimeMillis() + "ms: " + message + "订单取消");
}
}
}

结果如下:

image-20210830093745571

优缺点

优点

  • 使用 Redis 作为消息通道,消息都存储在 Redis 中。如果发送程序或者任务处理程序挂了,重启之后,还有重新处理数据的可能性。
  • 做集群扩展相当方便
  • 时间准确度高

缺点:方案二

redis 的 pub/sub 机制存在一个硬伤,官网内容如下:

Because Redis Pub/Sub is fire and forget currently there is no way to use this feature if your application demands reliable notification of events, that is, if your Pub/Sub client disconnects, and reconnects later, all the events delivered during the time the client was disconnected are lost.

因此,方案二不是太推荐。当然,如果对可靠性要求不高,可以使用。

消息队列

可以采用 rabbitMQ 的延时队列。RabbitMQ 具有以下两个特性,可以实现延迟队列:

  • RabbitMQ 可以针对 Queue 和 Message 设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为 dead letter
  • RabbitMQ 的 Queue 可以配置 x-dead-letter-exchange 和 x-dead-letter-routing-key(可选)两个参数,用来控制队列内出现了 dead letter,则按照这两个参数重新路由。

结合以上两个特性,就可以模拟出延迟消息的功能

优缺点

优点

  • 高效
  • 可以利用 rabbitMQ 的分布式特性轻易的进行横向扩展
  • 消息支持持久化增加了可靠性。

缺点

本身的易用度要依赖于 rabbitMQ 的运维。因为要引用 rabbitMQ,所以复杂度和成本变高

0%