在MacOS中使用AB测试工具

  一直以来都是用JMeter来做压力测试,GUI界面功能虽然强大,报表齐全,但有时候只是想简单测试下,启动JMeter过于繁琐,于是想到用ab来测试,一条命令搞定。MacOS自带了ab,却因版本问题无法正常使用,需要升级到最新版,本文简单记录下过程。

1,先下载httpd、apr、apr-util、pcre,若有已安装的可以忽略
http://httpd.apache.org/download.cgi
http://apr.apache.org/download.cgi
https://ftp.pcre.org/pub/pcre/

2,由于httpd依赖apr、pcre,所以先安装apr、apr-util、pcre

$ cd apr-1.5.2/
$ ./configure
$ make
$ make test
$ sudo make install

$ cd apr-util-1.5.4/
$ ./configure --with-apr=../apr-1.5.2/
$ make
$ sudo make install

$ cd pcre-8.40/
$ ./configure
$ make
$ sudo make install

$ cd ../httpd-2.4.25/
$ make
$ sudo make install

3,在恢复模式下替换系统ab,关闭MacOS Rootless,开机按住Cmd+R键,进入恢复模式,打开终端执行:

$ csrutil disable
Successfully disabled System Integrity Protection. Please restart the machine for the changes to take effect.

重启
$ reboot

4,备份并替换,以下路径是httpd默认安装位置

$ cd /usr/local/apache2/bin/
$ sudo mv /usr/sbin/ab /usr/sbin/ab.bak
$ sudo cp ab /usr/sbin/

5,重新进入恢复模式,开启Rootless

$ csrutil enable
Successfully enabled System Integrity Protection. Please restart the machine for the changes to take effect.

重启
$ reboot

  到此,工具算是准备好了,但MacOS对文件打开有限制,无法满足ab需求,通过以下命令临时调整:(重启后需重新执行)

$ sysctl kern.maxfiles
kern.maxfiles: 12288

$ sysctl kern.maxfilesperproc
kern.maxfilesperproc: 10240

$ sudo sysctl -w kern.maxfiles=1048600
kern.maxfiles: 12288 -> 1048600

$ sudo sysctl -w kern.maxfilesperproc=1048576
kern.maxfilesperproc: 10240 -> 1048576

$ ulimit -n
256

$ ulimit -n 1048576

$ ulimit -n
1048576

  搞定!

Java并发编程一些笔记

《Java并发编程》

  • 自旋锁与互斥锁

  两者非常类似,只是调度策略的不同。对于独占资源的访问,互斥锁在获得锁之前将一直处于休眠状态,自旋锁则是不断的自我循环来等待锁。对于线程切换没有损失,但消耗CPU,等待过长影响系统性能。

  • 并发包中的信号量与有界阻塞容器

  Semaphore用来控制对某种资源的访问数量,可以用来实现资源池化访问,也可以将任何一种容器变成有界阻塞容器。

  • 线程的关闭

  大多数时候使用原生线程都是等到运行结束而自动关闭,然而有时候也需要提前结束线程,比如用户取消了操作。但Java没有提供任何机制来安全地终止线程。仅提供了中断(Interruption),这是一种协作机制,能够使一个线程终止另一个线程(Thread.stop和suspend存在缺陷,避免使用)。

  解决方案有:非阻塞情况下使用volatile类型的变量来做标记,阻塞框架又存在可中断和不可中断,可中断调用阻塞框架中断方法,例如对阻塞队列的操作。处理好中断异常,保证数据完整性。不可中断的阻塞如IO的操作或者等待获得锁而阻塞,在取消方法中先关闭IO,或者调用Lock类的lockInterruptibly。

  • 线程饥饿死锁

  在线程池中,任务依赖其他任务,那么可能产生死锁。在单线程Executor中,一个任务将另一个任务提交到同一个Executor,并且等待这个被提交任务的结果,会死锁。

  • 线程池大小

  线程池过大,大量的线程将在相对很少的CPU和内存资源上发生竞争,导致更高的内存占用量。线程池过小导致处理器空闲,减低吞吐率。

  • synchronized与ReentrantLock

  两者jvm层语义一致,Java 6及更高版本两者效率差别已经不是很大,Lock具有公平与非公平两种选择,除本身特性之外,非公平锁吞吐率高于公平。其原因是恢复一个被挂起的线程与该线程真正开始运行之间存在较大延时。Lock具有定时锁等待,可中断锁等待,非结构化加锁。

花图里程碑3简要报告

  双12那天,花图里程碑3顺利上线了,经过一个月左右的开发,重新设计的UI,交互体验上有了质的飞跃,全面兼容移动端和PC端。后端主要是代码重构和基础框架升级,以及提高安全性等一些功能。现已经全站开启Http 2.0的支持,在最新版Chrome和Safari下加载速度得到了很大的提升。

  这里列出下本次升级前端所用到的开源框架:

1. Semmantic-UI
2. RequireJS
花图上一个版本用的基于Bootstrap二次开发的Flat-UI,由于当时只是简单的用了几个组件,并没有仔细去研究究竟能复用多少,以至于很多地方是自定义style,代码繁杂不堪,没有发挥出Bootstrap优势。所以这次决定完全重写,并引入requirejs模块化加载,css和js彻底从html中分离。

3. Creative Commons Font
用于显示Creative Commons共同创作许可协议的Icons,本着能用font icon就绝不用image。显然字体的显示效果绝对是好过图片的,大小,颜色都可以随意改变,加上操作系统对字体平滑的优化,效果棒棒的。

4. Elastislide
用于照片详情页的缩略图预览,俗称跑马灯效果。上个版本已开始用,增加了些鼠标移动的效果,移动端展示的一些优化。

5. Grayscale
一个Landing page,前期内容不够丰富的时候放一个用作功能介绍,黑白风格,个人蛮喜欢的,也就是现在看到的没登录首页。

6. lightGallery
用于详情页的照片全屏展示,新的版本在缩放动画上略微的有抖动,老版本则比较平滑,但老版本会在url中增加一个hash tag,导致复制出去的链接直接就是全屏。有时间再来研究。

7. Noty
一个基于jQuery的notification组件,各种ajax地方用到。

8. Plupload
大名鼎鼎的Html Uploader,功能强大不说,主要是能支持阿里OSS,Amazon S3进行Web直传。

9. Sortable
upload页面照片排序用,使用比较简单。

10. Underscore
一个小巧而精致的Javascript工具包,目前只用到里面的一个小功能debounce,对浏览器的宽度改变事件做一些延迟处理。

前端大致就这么多了,改天再分享后端的。

SSL证书更换为Let’s Encrypt

  StartSSL的免费证书总不太稳定,尤其在Chrome下表现更是离奇,这不最近在将macOS Chrome升级到最新版的时候,花图影铺的一个二级域名证书就被列为不信任了,但根域名又是正常的,而在火狐或Safari或iOS 10中都是正常的,真是莫名其妙。正好最近Let’s Encrypt比较火,干脆趁这个国庆节将证书换一下。

1. 下载安装证书

git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt/
./letsencrypt-auto certonly --standalone --email admin@snapast.com -d snapast.com -d www.snapast.com -d abc.snapast.com

  这里Let’s Encrypt简化了安装步骤,只需要确保域名解析到本机服务器,并且未占用服务器的80和443端口即可创建成功,默认位置在/etc/letsencrypt/live/snapast.com里面,fullchain.pem即是公钥,包含证书链,privkey.pem私钥。

  需要注意的是,有二级域名未解析到本机的不可以,比如使用了CDN,临时改一下问题不大,本机有nginx之类的占用80端口的,也得临时停一下,反正很快,网络好的情况下几秒钟就可以了。如果站点流量大到几分钟都不能停,那想必也没必要用Let’s Encrypt了,毕竟一次申请的有效期也才三个月。

2. nginx配置

ssl_certificate                 /etc/letsencrypt/live/snapast.com/fullchain.pem;
ssl_certificate_key             /etc/letsencrypt/live/snapast.com/privkey.pem;

  配置好nginx,reload一下就生效了,CDN后台的证书也一并替换掉,大功告成。

玩转Java字节码(下)

  接上篇文章,操作字节码的框架有很多Javassist,ASM,BCEL等,这里我用ASM来举例。在了解字节码组成后,很容易通过ASM构建一个Class出来,代码如下:

package classloader;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by dorole.com on 2016/6/13.
 */
public class HelloClassGeneratorTest {
    public static void main(String[] args) throws IOException {
        ClassWriter classWriter = new ClassWriter(0);
        classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, "Hello", null, "java/lang/Object", null);

        // 构造方法
        MethodVisitor constructorMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        constructorMethod.visitCode();
        constructorMethod.visitVarInsn(Opcodes.ALOAD, 0);
        constructorMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        constructorMethod.visitInsn(Opcodes.RETURN);
        constructorMethod.visitMaxs(1, 1);
        constructorMethod.visitEnd();

        // sayHello方法
        MethodVisitor helloMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;", null, null);
        helloMethod.visitCode();
        helloMethod.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        helloMethod.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
        helloMethod.visitInsn(Opcodes.DUP);
        helloMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
        helloMethod.visitVarInsn(Opcodes.ALOAD, 0);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        helloMethod.visitLdcInsn(" -> ");
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        helloMethod.visitVarInsn(Opcodes.ALOAD, 1);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
        helloMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        helloMethod.visitVarInsn(Opcodes.ALOAD, 1);
        helloMethod.visitInsn(Opcodes.ARETURN);
        helloMethod.visitMaxs(3, 2);
        helloMethod.visitEnd();

        classWriter.visitEnd();

        byte[] data = classWriter.toByteArray();
        File file = new File("D://Hello.class");
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(data);
        fileOutputStream.close();
    }
}

  内容很简单,通过ClassWriter创建了一个Hello类,定义了一个构造方法和sayHello方法,接收一个String参数,返回String类型。将得到的Class字节码保存到D://Hello.class文件中,这个文件可以用jd-gui反编译回Java源代码。

  ASM设计模式很有意思,访问者模式(Visitor Pattern),我常常把它比作一个拥有多层的抽屉的模具,一层一层打开,放原材料,最后产出一个东西。MethodVisitor的用法可以参考文档,这里不详述,大致上都是一些字节码指令操作。

  以上代码也是模仿了javap反编译出来的内容,运行后无误的话可以看到在D盘有了一个Hello.class文件,接下来我们要加载这个文件,并运行其中的方法。代码如下:

package classloader;

/**
 * Created by dorole.com on 2016/6/13.
 */
public class MyLoader extends ClassLoader {
    public Class<?> defineMyClass(byte[] b, int off, int len) {
        return super.defineClass(null, b, off, len);
    }
}

  自定义一个Classloader,仅调用父类的defineClass即可。

package classloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by dorole.com on 2016/6/13.
 */
public class MyLoaderTest {
    public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
        File file = new File("D://Hello.class");
        InputStream input = new FileInputStream(file);
        byte[] result = new byte[1024];

        int count = input.read(result);
        MyLoader loader = new MyLoader();
        Class clazz = loader.defineMyClass(result, 0, count);
        System.out.println(clazz.getCanonicalName());

        Object o = clazz.newInstance();
        try {
            Method method = clazz.getMethod("sayHello", String.class);
            Object returnObject = method.invoke(o, "World!");
            System.out.println("returnObject = " + returnObject);
        } catch (IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }
}

  将class以字节数组形式读取给Classloader来加载,通过反射调用sayHello方法,专递一个World!参数进去,可以看到输出以下结果:

Hello
Hello -> World!
returnObject = World!

  这样一个从Java代码编译与反编译,自己生成字节码到加载运行,还能反编译回去。这样一个完整的循环就算完成了,虽然谈不上很有深度,但从这里能引发很多值得思考的地方。更多内容还是要参考Java虚拟机规范。