作者: htynkn

  • 使用JGit获取变更细节

    有时候多个项目在一个代码仓库中,可能每个文件夹都是一个项目。如果每次使用CI自动部署的话就需要判断最近的改动属于哪个文件夹/项目。

    通过JGit直接获取两个commit之间的diff然后判断是比较直接的方法,具体使用哪两个commit就看需要的。可以使用CI系统上一次成功构建的commit sha,也可以直接使用最近两天的commit的。

    代码如下:

    Repository repo = FileRepositoryBuilder.create(new File(basePath + ".git"));
    Git git = new Git(repo);
    ObjectReader reader = repo.newObjectReader();
    ObjectId newObjectId = repo.resolve("HEAD^{tree}");
    ObjectId oldObjectId = repo.resolve(successSha + "^{tree}");
    
    
    System.out.println("Starting diff " + oldObjectId + " : " + 
  • 压测前的影子表预热

    影子表和buffer pool

    影子表并不是一个数据库的特殊功能,简单来说只是和原表结构一致但是名字不一样的普通表而已。所以创建影子表并不需要特别的操作,根据数据库中间件的支持规则创建就行了。

    buffer pool是一个缓存数据和索引信息到内存的存储区,简单理解就是一个热数据区,或者叫缓存。具体的配置和计算比较复杂,但是总体而言,buffer pool命中率越高,性能越好。

    预热的价值

    因为影子表的数据是冷数据,这一部分数据并不在buffer pool中,需要逐步加载到内存中,因此需要预热过程。如果没有预热,会出现性能问题,但是这一部分并不是我们期待的,因为线上buffer pool中是包含了真实数据的。

    执行预热

    执行预热就是低流量的压测,让影子表的数据被访问到,并加载到buffer pool中。

    其次就是执行预热前,需要先dump出线上的buffer pool,然后在压测完成重新load之前导出的buffer pool。

    其他要点

    预热的时候要注意因为buffer pool的空间是公用的,要注意不要让影子表的数据影响线上数据了。业务高峰期很难成功,因为线上的流量大,真实数据占用buffer pool的量比较大,预热数据的buffer很快就被线上请求踢出。

    预热时要密切观察慢查询的量,有问题就需要降速或者停止,严重的情况还需要在DB上kill查询,并重新加载buffer pool。…

  • 内联第三方依赖到自己的包中

    Java世界一个很难绕过的问题就是依赖包,绝大部分项目总会或多或少的依赖第三方包。Maven或者Gradle等可以帮助我们轻松的管理第三方依赖,但是对于自己对外提供的包,特别是作为中间件或者基础组件等提供出去的包,还是会遇到一些第三方依赖的问题,主要有两个问题

    • 包依赖比较少,而且依赖第三方包中的极少部分代码
    • 一些关键依赖版本敏感,不希望使用方随意变更

    第一个情况可以先打一个fatjar,然后通过ProGuard精简代码。

    第二个情况就需要把第三方包的代码拷贝到自己的包中,在自己的代码中直接使用拷贝进来的三方包代码(pakcage name不一样),这样就可以最大程度保证稳定性。

    当然大部分情况下对于第三方包的版本依赖没有那么敏感,上面说到的情况其实是大部分针对asm,cglib和objenesis等几个字节码增强的包。

    具体的操作可以借助jarjar工具,先将三方包重新打包,然后修改Gradle的Jar任务,将重新打包后的jar复制到自己的包中。

    task cglibRepackJar(type: Jar) { repackJar -repackJar.baseName = "cglib-repack" repackJar.version = cglibVersion doLast() {
            project.ant {
                taskdef name: "jarjar", classname: "org.pantsbuild.jarjar.JarJarTask", classpath: configurations.jarjar.asPath
                jarjar(destfile: repackJar.archivePath) {
                    configurations.cglib.each { originalJar -zipfileset(src: originalJar) 
  • 全链路压测改造

    全链路压测是基于实际的生产业务场景、系统环境,模拟海量的用户请求和数据对整个业务链进行压力测试,并持续调优的过程。

    全链路压测针对是线上真实环境,主要的目的是针对业务场景越发复杂化、海量数据冲击下整个业务系统链的可用性、服务能力的瓶颈,让技术更好的服务业务,创造更多的价值。简单来说就是为了快速准确找出系统的优化点。

    全链路压测听起来很好,各个大厂都可以找到相关的博客或者分享文章,但是要实施全链路压测需要很大的工作量,对于系统需要进行改造。

    由于全链路压测涉及整个技术生态,不同的公司使用的技术和基础设施各不相同,所以改造内容也不同,但是大致的改造是类似的,主要在以下几个方面:

    流量标记

    流量标记的作用是让所有服务或者说是代码在任何一个时刻都能知道当前是否为压测。因为实际业务中链路一般都比较长,从头开发这样的一个功能还是比较复杂。

    目前大部分系统为了全链路追踪一般都是用了OpenTracing标准的某种实现,那么在OpenTracing中的Span中的Context就是一个绝好的信息载体。

    对于对外暴露的HTTP API请求,需要从URL中获取到压测信息,然后将其传递到RPC或者其他调用层,RPC层依次传递对应的标志就行了。

    RPC框架

    RPC上的改动比较上,主要是需要用上下文传递压测标记。这里可以用RPC框架自带的上下文,当然用OpenTracing的比较方便,这一步改动不大。

    数据库中间件

    既然要做全链路压测,那么肯定不能只测试读接口,对于写接口,需要考虑到数据隔离的问题。

    这个时候影子表是比较合适的选项,影子表是与真实表对应的一个概念,与真实表同构,只是名称不同。在MySQL的innodb引擎中,影子表与真实表存储的文件不同,但是共用buffer pool。总体而言,影子表的使用是安全,能够有效模拟真实表,同时又能起到逻辑隔离的效果:

    数据库中间件需要支持的是影子表偏移的,而且还需要支持配置,因为根据业务和系统情况,并不是所有表都需要影子表,而且影子表的偏移各不相同。

    其次就是和影子表有关的其他改造,比如初始化,预热,buffer pool恢复,数据构造和数据清理等都是需要考虑的。

    MQ中间件

    MQ中间件主要是两个问题,一个是压侧标识传递,另外一个是队列隔离。

    日志组件

    日志组件上主要是压测流量日志是否需要打印特定标识,是否需要单独的降级开关。

    线程池

    业务系统或多或少用到了线程池,而线程池一般都需要改造用于支持压测标识的传递。

    Mock

    一个完整的业务流程很难只和自己的系统打交道,外部系统一般不纳入压测中,比如第三方保险/语音电话服务/短信等等,有时候也会遇到下游系统并没有准备好加入压测链路,所以需要Mock。

    具体的Mock返回数据要根据情况创造。

    降级

    降级并不是全链路压测必须的,但是提供降级开关可以在压测出现问题的时候中断部分接口,依然执行全链路压测,以免压测失败。…

  • 快速搭建本地OpenWhisk环境

    快速搭建本地OpenWhisk环境

    OpenWhisk是IBM开源的FAAS平台。OpenWhisk 简化了微服务的部署,消除了管理自己的消息代理或部署自己的工作服务器的需求。

    有时候为了方便本地开发验证,需要搭建一个OpenWhisk环境,但是OpenWhisk依赖多,配置比较复杂,要从头搭建一个还是比较繁琐。

    官方提供了快速搭建工具,可以快速启动一整套环境来

    git clone --depth=1 https://github.com/apache/incubator-openwhisk-devtools.git
    
    cd incubator-openwhisk-devtools/docker-compose
    
    make quick-start

    会基于Docker启动openwhisk/controller,openwhisk/invoker,zookeeper,kafka,redis等组件。…

  • MySQL下的CommunicationsException问题

    这周的一天早上一个定时任务突然报错,错误如下

    ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet successfully received from the server was 67,409 milliseconds ago. The last packet sent successfully to the server was 60,060 milliseconds ago. ### The error may exist in 
  • ubuntu快速安装docker

    最近换了一个硬盘,重新装了一下系统。
    自然各种环境和工具也要重新装。

    apt install没法安装docker-ce,官方文档虽然详细,但是步骤挺多的。然后就发现官方的安装脚本,还支持镜像。

    curl -fsSL get.docker.com -o get-docker.sh

    sudo sh get-docker.sh –mirror Aliyun

  • Gatling使用的几个小技巧

    Gatling是一个基于Scala,Akka和Netty的开源负载和性能测试框架,从功能上和JMeter很类似,只不过因为是基于DSL书写,从配置上来说灵活性比较好,当然相对的上手难度其实要大一些。

    最近项目上使用了一下Gatling,用的时候还是遇到一些小问题。

    Gradle插件运行

    Gatling官网的运行方式是下载整个Gatling,然后编写脚本以后,直接运行。这个操作不是很方便的,找到一个Gradle插件可以快速运行,其中Gatling的版本,参数等都可以通过build.gradle的参数配置,使用上是比较灵活的。

    插件的配置如下:

    plugins {
        id 'scala'
        id "com.github.lkishalmi.gatling" version "3.0.2"
    }
    
    gatling {
        toolVersion = '3.0.0'
        jvmArgs = [ '-server', '-Xms512M', '-Xmx512M' ]
        simulations = {
            include "**/*.scala"
        }
    }

    目录结构的要求是src/gatling/simulations,这个需要自己改一下

    读取配置

    配置可以是很多东西,比如比较常见的是不同环境不同的数据,比如URL,用户认证信息,或者是压测的压力情况都应该在配置,而不是硬编码在代码中。

    配置文件地址src/galting/resource/application.properties

    使用的时候直接调用

    val conf 
  • MySQL索引

    MySQL索引

    MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在各大互联网公司中,MySQL即其变种是最好的使用最多的关系型数据库。

    MySQL所使用的 SQL 语言是用于访问数据库的最常用标准化语言。MySQL 软件采用了双授权政策,分为社区版和商业版。因为License的问题,还有兼容MySQL的MariaDB分支可选。

    磁盘IO与预读

    因为业务上对于数据库的要求其实是比较高,主要是速度上的,经常是几毫秒的。在这种情况下硬盘IO就需要关注了。机械硬盘是需要机械运动的,比如常说的7200转。IO的消费是很大的,所以操作系统做了一些额外的优化,当执行一次IO时,不光读取当前磁盘地址的数据,也会把相邻数据都读取到内存缓冲区内,每一次IO读取的数据称之为一页,一般为4k或8k。

    所以数据库在设计上有一个重要的方向就是减少IO。

    索引

    最容易的想到的方案就是记录一些数据的IO块的位置,比如自增ID为主键,前1000条在块N,后一千条在块N+1,当然这个方案有很多弊端,所以数据库专门抽象了索引这个概念。

    索引按照官方文档的说法是帮助MySQL高效获取数据的数据结构,具体到不同的存储引擎层面,具体的数据结构是有区别的。

    数据库查询是数据库的最主要功能之一,而应用都希望查询数据的速度能尽可能的快,因此数据库系统从设计层面会对查询进行优化。而不同的算法都只能应用于特定的数据结构之上,比如二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构。

    所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用或者指向的数据,同时在这些数据结构上实现高级查找算法。这种数据结构就是索引。

    具体的索引结构支持又和引擎绑定,比如最常说B树或者B+树在MyISAM,InnoDB上都是支持的。

    索引 MyISAM引擎 InnoDB引擎 Memory引擎
    B-Tree 索引 支持 支持 支持
    HASH 索引 不支持 不支持 支持
    R-Tree 索引 支持 不支持 不支持
    Full-text 索引
  • Java中的toString

    Java中的toString

    Java中的Object包含一个toString方法,该方法默认实现是输出类名+hashcode,所以一般业务上都需要覆盖此方法实现。

    toString大部分时候都不影响功能,很大程度上是用于日志输出的时候,比如

    logger.info("user {} query keyword:{}", userId, searchParams)

    覆写该方法是必要的,我们以一个简单的类为例

    public class Hello   {
        private long id;
        private String message;
    }

    常见方法主要有以下几种:

    使用StringBuilder

    @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("Hello{");
            sb.append("id=").append(id);
            sb.append(", message='").append(message).append('\'');
            sb.append('}');
            return sb.toString();
        }