博客

  • MQ延迟消息

    在各类业务中有一种很常见的要求就是在XX小时之后,执行XX。

    简单常用一点的就是用户点了外卖取餐以后24小时内可以给骑手/商家评价,超时不可评价且修改评价为X星。

    简单粗暴的办法肯定是来一个定时任务,扫描为评价的且超过评价时间的数据,并修改相关值。

    这个方法很直接,主要的缺陷如下:

    • 实效性低
    • 效率低

    实效性主要是定时任务执行间隔,这个主要看任务每次运行的间隔是多少。效率问题主要是扫表引起的,特别是数据量较大的时候,加上查询条件复杂,那速度和效率肯定是上不来的。

    比较方便的方案是把延迟这个事情本身让外部控制,比如MQ,发送一个延迟MQ消息,在XX小时过后消费即可。

    开源RocketMQ

    RocketMQ的开源版本提供了延迟消息,但是它的延迟消息并不是任意精度的,而是所谓的延迟时间级别,具体时间是可以配置的,默认配置是

    public class MessageStoreConfig {
    
        private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
        
    }

    具体实现可以参考文末的链接。如果这种模式是可以满足业务的,其实也是可以的。

    阿里云RocketMQ

    如果实在需要精确的延迟控制,目前没有合适的开源版本可用。阿里云的收费RocketMQ是支持秒级别的任意时间延迟。…

  • 数据库主键的生成和处理

    数据库主键的生成和处理

    数据库主键的作用是唯一标识一条记录,所以在同一张表中,任意一条记录的主键都是唯一的。如果没有唯一的主键,数据库就无法根据主键直接定位记录。

    在各种流行的数据库中都存在主键这个概念,一般在建表的时候就需要指定主键,不过不同的数据库有不同语法而已

    MySQL

    CREATE TABLE People
    (
     Id_P int NOT NULL,
     PRIMARY KEY (Id_P)
    )

    Oracle

    CREATE TABLE People
    (
      Id_P int NOT NULL PRIMARY KEY
    )

    对于数据库来说,主键是可以修改的,只要不和保证唯一性即可。但是对于一般应用来说修改主键通常会引起很多问题。比如表A的主键,作为外键在表B中。

    主键策略

    主键的使用可以考虑两个大方向:整数或者字符串。

    先来看看整数,无非两个选项

    • 数据库自增
    • 自己生成

    数据库自增是数据库提供的功能,稳定性可靠性高,自增主键的主要问题是在于把公司业务的关键运营数据完全暴露给了外部,可能导致信息泄露或者潜在的攻击。

    虽然相关配置是可以变更的,比如MySQL可以自定义auto_increment_offset和auto_increment_increment,但是这个始终治标不治本。

    自己生成的话常见的算法有Snowflake,美团开源的leaf也是容易上手的一个选项。

    Snowflake算法是为了分布式ID生成的,用于数据库主键对于唯一性的保证不难,且本身不想自增长ID那么容易暴露业务信息。

    如果使用字符串呢?

    为了保证唯一性,最简单的手段就是UUID,当然字符串的随机性由于数据库索引的结构,可能影响插入性能。这个简单一点的方案就是在前面再加时间戳。GUID也是类似。

    其他选择

  • 新版Spring Boot项目创建工具

    新版Spring Boot项目创建工具

    Spring Initializr是一个非常实用的工具,从工具创建的项目可以快速启动,非常适合新手上手或者新建大量项目的时候。但是有时候可能会遇到一些问题,比如一些特定情况需要手动创建项目,但是创建以后运行不起来,这个时候就需要参考一下。

    新版的Spring Initializr提供了预览功能,即选择了版本,功能等以后不需要下载ZIP包然后解压查看,可以直接在线预览,排查问题。

  • 一个好的定时任务

    一个好的定时任务

    定时任务在各类的系统中很常见。

    以下是平时比较常接触到各类场景:

    1. 定时发送邮件/消息
    2. 周期行生成报表数据
    3. 定时数据同步
    4. 定时业务补偿,比如失败流程重新启动,超时流程自动关闭等

    而大部分定时任务在运行上都比较偏向于低峰期,白天开始是高峰期,晚上又是发版期,在加上业务的要求,比如每天凌晨产出前一日的XX指标等,大量任务都是每天0~8点运行,这就极大增加了值班成本,出问题很难第一时间解决问题。

    所以写一个好的定时任务,一方面减少线上问题的发生,另一方面也加快了解决问题的速度。

    个人理解对于定时任务,“好”的标准应该是以下几个方面:

    1. 时间
    2. 执行成功率
    3. 通知和监控
    4. 处理异常

    时间

    对于时间有预估,超时报警,发现性能问题
    这里的时间首先是最长执行耗时,当然对于耗时特别短的情况,也要有报警,避免异常结束。

    执行成功率

    外部依赖前置检测,比如关键外部依赖数据源,必须预先查询数据是否就绪。
    对于外部接口如果幂等,可以增加调用重试机制,以降低失败概率
    如果对于批量处理,可以接受少量失败的,那就可以做好异常隔离,避免某一条数据失败,造成整个任务失败。
    另外支持指定参数,这个主要是针对一些特殊情况,包括修复数据,或者线上测试使用。

    通知和监控

    通知首先异常要杜绝漏报。其次就是关键指标的监控,可以使用

    1. 绝对值合理范围区间;
    2. 数据分布合理范围区间;
    3. 同环比变更合理范围区间;

    处理异常

    提供异常挂牌预案
    异常之后任务应该可以重跑,至少做到以下几点:

    1. 幂等
    2. 数据回退
    3. 一键执行
    4. 对外透明
  • 点评Cat 3.0快速上手

    CAT 是基于 Java 开发的实时应用监控平台,为美团点评提供了全面的实时监控告警服务。相比于普通APM平台,主要优势在于了日志告警,多种client和实时性。

    服务端

    CAT从2015年开源,最新的大版本是3.0,同时提供了相关的client包也发布到了仓库不再需要用户自己打包了。

    CAT的部署算不上简单,要快速实验的话最好还是使用Docker镜像的方式来启动。最小依赖就是CAT自身外加一个MySQL数据库,当然这种单机模型是谈不上什么高可用的,只是简单的使用和集成调试。

    由于官方没有提供镜像,我自己打包了一个放在dockerhub上,如果使用国内镜像应该速度也还行。

    执行以下命令

    wget https://raw.githubusercontent.com/htynkn/dockerfiles/master/dianping-cat/stack.yml
    
    wget https://raw.githubusercontent.com/htynkn/dockerfiles/master/dianping-cat/V1__initCatDatabase.sql
    
    docker-compose -f stack.yml up

    由于CAT的数据库需要先初始化,所以使用了flyway作为数据库版本管理工具。最好新建一个文件夹,然后在其中执行,不然容易混入其他SQL文件。

    启动后,注意查看端口,2280端口是CAT上报的端口,必须强绑定,8080那个则会使用随机端口,注意查看。直接访问 localhost:{port}/cat 就行了。

    客户端

    如果要测试client链接的效果,首先在/data/appdatas/cat/目录下新建client.xml文件

    <?xml version="1.0" encoding="utf-8"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="config.xsd">
        <servers>
            <server ip="127.0.0.1" port="2280" http-port="你的端口" />
        </servers>
    </config>

    然后在你要接入的项目中写入应用名称 src/main/resources/META-INF/app.properties

    app.name={appkey}
  • Apache Dubbo使用Nacos作为注册中心

    Nacos是阿里开源的用于发现、配置和管理微服务的基础设施。目前迭代到1.0版本了。

    Apache Dubbo从2.7.1版本开始提供使用Nacos作为注册中心的功能,如果之前使用的zookeeper作为注册中心,需要以下几步切换以下。

    首先添加新的依赖

    compile 'org.apache.dubbo:dubbo-registry-nacos:2.7.1'
    compile 'com.alibaba.nacos:nacos-client:1.0.0'

    这里解释一下,因为Apache Dubbo 2.7.1版本构建的时候没有把dubbo-registry-nacos打到all-in-one的包中,这里只有手动处理一下。而2.7.1依赖的是nacos-client不是最新版,这里也升级到最新版。

    然后更新注册中心配置,这里用Docker启动一个单机版的Nacos。

    dubbo.registry.address=nacos://localhost:8848

    然后在Nacos中就可以看到服务和元信息了

  • Elasticsearch的游标查询

    在做大批量查询的时候,比如批量导出数据的时候可以使用游标(scroll)查询,单纯的setFrom分页的方式在数据量变大以后性能下降。

    用游标可以用来对 Elasticsearch 有效地执行大批量的文档查询,而又不用付出深度分页那种代价。

    具体的时候很简单就是需要维持一个scrollId,也就是首次查询以后就会获得一个scrollId,然后后面的查询传递这个scrollId就可以查询下一批数据。

    游标查询的本质其实就是缓存了一个查询结果,然后分批返回,scrollId用base64解码可以查看,本质上对应了缓存的结果。

    既然用到了缓存那肯定存在缓存管理的问题,这里有两个点

    1. 所有游标查询都需要设置一个过期时间,超过过期时间以后的查询会报错。这里的过期时间在每次请求之后会延长,所以这里的过期时间是每批次的过期时间,而不是整个查询集合的时间
    2. 到了过期时间就会自动过期,释放空间,如果要提前释放可以主动调用ClearScrollRequest

    如果缓存有效时间设置的过长,且没有回收,容易导致整个集群出现空间,所以这个值最好精确设置。

    在当前场景(批量导出数据)下其实请求是连续的,只需要设置一个比较小的值,考虑本身的响应时间和可能的网络问题,根据具体场景1s~2s都是可以的。文档示例中是1分钟,最好根据自己的情况修改。…

  • 开源软件依赖License检测

    开源软件依赖License检测

    开源软件的License是一个很重要的声明,这代表了软件是基于何种许可证向世人开放的,对于其的使用又需要遵循何种规则。

    开源软件自身又会依赖其他的开源软件,而那些依赖又会有自己的License,所以对于依赖的License检查是非常重要的。因为有些License是不兼容的,比如一个基于Apache License的开源软件是不能包含一个依赖于GPLv3的库。

    所以检查依赖的License就变得很重要。

    如果你精通各类开源软件,看名字就知道软件对应的License和兼容性,那自然不需要其他辅助,但是大部分情况这个很难做到。

    License Maven Plugin是一个专门处理这种问题的插件。对于任何一个maven项目,简单地运行

    mvn license:add-third-party -Dlicense.useMissingFile -Dlicense.includeOptional=true

    输出如下:

    Lists of 67 third-party dependencies.
    (BSD License) AntLR Parser Generator (antlr:antlr:2.7.7 – http://www.antlr.org/)
    (Apache Software License 2.0) A Swiss Army Knife for OSGi (biz.aQute.bnd:bndlib:2.4.0 – http://www.aQute.biz/Code/Bnd/bndlib)
    (Eclipse

  • Knime中集成Java代码

    Knime中集成Java代码

    Knime是一个非常强大的数据分析平台,支持常用的各种数据清洗,分析等功能。

    但是有些时候数据的预处理,比如打标记等等需要一些外部逻辑,这种时候用java代码可能更快。

    为了让数据处理更流畅,而不是在Knime中输出一个中间结果,然后在放回Knime中继续这样断断续续的方法,Knime提供了各种语言的集成,比如Java,Python等。其中Java代码的集成是原生支持的,而Python等语言是需要安装插件的。

    这里用阿里天池全球城市计算AI挑战赛的数据作为说明。

    假定我们读取文件每次只读取同一天的数据,比如要计算星期二的,就只读取同样为星期二的历史数据。workflows需要用到

    • List Files
    • Java Snippet
    • Rule-based Row Filter
    • Table Row To Variable Loop Start
    • CSV Reader

    其中List Files可以将指定目录的文件全部列出

    然后我们在Java Snippet中写逻辑过滤就行了,这里追加一个星期标识

    // system imports
    import org.knime.base.node.jsnippet.expression.AbstractJSnippet;
    import org.knime.base.node.jsnippet.expression.Abort;
    import org.knime.base.node.jsnippet.expression.Cell;
    import org.knime.base.node.jsnippet.expression.ColumnException;
    import org.knime.base.node.jsnippet.expression.TypeException;
    import static org.knime.base.node.jsnippet.expression.Type.*;
    
  • Docker Hub镜像构建测试

    Docker Hub提供了镜像构建服务,特别是和代码仓库关联起来的时候可以自动构建最新的镜像,这个功能可以保证你的镜像时刻是最新的。

    但是有时候构件本身可能是成功的,比如你成功生成了相关的jar,并放到了合适的运行容器中,但是可能由于第三方依赖或其他原因导致你的镜像其实并不能正常工作。

    人工验证可以发现问题,但是这里使用Docker Hub提供的测试服务更加快捷。

    我们以dubbo-admin为例,这是一个非常标准的Spring Boot项目,构建以后生成jar包可以直接运行。而我们的测试目标就是构建出的镜像可以正常对外提供服务,这里我们检测swagger文档地址是否返回200。不同的项目可以使用不同的检测标准。

    首先添加一个检测脚本test.sh

    LOOP_SIZE=60
    i=0
    
    while [[ $i -lt LOOP_SIZE ]]; do
    	status_code=$(curl --write-out %{http_code} --silent --output /dev/null http://admin:8080/swagger-resources)
    
      if [[ "$status_code" -eq 200 ]] ; then
        echo "Tests passed!"
        exit 0
      else
        curl -v