Java8 中Stream尝鲜

December 16th, 2019 by JasonLe's Tech 1,213 views

Stream简介

  • Java 8引入了全新的Stream API。这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同。
  • stream是对集合对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。
  • 只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

为什么要使用Stream

  • 函数式编程带来的好处尤为明显。这种代码更多地表达了业务逻辑的意图,而不是它的实现机制。易读的代码也易于维护、更可靠、更不容易出错。
  • 高端

Filter

  • 遍历数据并检查其中的元素时使用。
  • filter接受一个函数作为参数,该函数用Lambda表达式表示。

/**
     * 过滤所有的男性
     */
    public static void fiterSex(){
        List<PersonModel> data = Data.getData();

        //old
        List<PersonModel> temp=new ArrayList<>();
        for (PersonModel person:data) {
            if ("男".equals(person.getSex())){
                temp.add(person);
            }
        }
        System.out.println(temp);
        //new
        List<PersonModel> collect = data
                .stream()
                .filter(person -> "男".equals(person.getSex()))
                .collect(toList());
        System.out.println(collect);
    }

    /**
     * 过滤所有的男性 并且小于20岁
     */
    public static void fiterSexAndAge(){
        List<PersonModel> data = Data.getData();

        //old
        List<PersonModel> temp=new ArrayList<>();
        for (PersonModel person:data) {
            if ("男".equals(person.getSex())&&person.getAge()<20){
                temp.add(person);
            }
        }

        //new 1
        List<PersonModel> collect = data
                .stream()
                .filter(person -> {
                    if ("男".equals(person.getSex())&&person.getAge()<20){
                        return true;
                    }
                    return false;
                })
                .collect(toList());
        //new 2
        List<PersonModel> collect1 = data
                .stream()
                .filter(person -> ("男".equals(person.getSex())&&person.getAge()<20))
                .collect(toList());

    }

Map

  • map生成的是个一对一映射,for的作用
  • 比较常用
  • 而且很简单

 /**
     * 取出所有的用户名字
     */
    public static void getUserNameList(){
        List<PersonModel> data = Data.getData();

        //old
        List<String> list=new ArrayList<>();
        for (PersonModel persion:data) {
            list.add(persion.getName());
        }
        System.out.println(list);

        //new 1
        List<String> collect = data.stream().map(person -> person.getName()).collect(toList());
        System.out.println(collect);

        //new 2
        List<String> collect1 = data.stream().map(PersonModel::getName).collect(toList());
        System.out.println(collect1);

        //new 3
        List<String> collect2 = data.stream().map(person -> {
            System.out.println(person.getName());
            return person.getName();
        }).collect(toList());
    }

FlatMap

  • 顾名思义,跟map差不多,更深层次的操作
  • 但还是有区别的
  • map和flat返回值不同
  • Map 每个输入元素,都按照规则转换成为另外一个元素。
    还有一些场景,是一对多映射关系的,这时需要 flatMap。
  • Map一对一
  • Flatmap一对多
  • map和flatMap的方法声明是不一样的
    • <r> Stream<r> map(Function mapper);
    • <r> Stream<r> flatMap(Function> mapper);
  • map和flatMap的区别:我个人认为,flatMap的可以处理更深层次的数据,入参为多个list,结果可以返回为一个list,而map是一对一的,入参是多个list,结果返回必须是多个list。通俗的说,如果入参都是对象,那么flatMap可以操作对象里面的对象,而map只能操作第一层。
public static void flatMapString() {
        List<PersonModel> data = Data.getData();
        //返回类型不一样
        List<String> collect = data.stream()
                .flatMap(person -> Arrays.stream(person.getName().split(" "))).collect(toList());

        List<Stream<String>> collect1 = data.stream()
                .map(person -> Arrays.stream(person.getName().split(" "))).collect(toList());

        //用map实现
        List<String> collect2 = data.stream()
                .map(person -> person.getName().split(" "))
                .flatMap(Arrays::stream).collect(toList());
        //另一种方式
        List<String> collect3 = data.stream()
                .map(person -> person.getName().split(" "))
                .flatMap(str -> Arrays.asList(str).stream()).collect(toList());
}

Collect

  • collect在流中生成列表,map,等常用的数据结构
  • toList()
  • toSet()
  • toMap()
  • 自定义
/**
     * toList
     */
    public static void toListTest(){
        List<PersonModel> data = Data.getData();
        List<String> collect = data.stream()
                .map(PersonModel::getName)
                .collect(Collectors.toList());
    }

    /**
     * toSet
     */
    public static void toSetTest(){
        List<PersonModel> data = Data.getData();
        Set<String> collect = data.stream()
                .map(PersonModel::getName)
                .collect(Collectors.toSet());
    }

    /**
     * toMap
     */
    public static void toMapTest(){
        List<PersonModel> data = Data.getData();
        Map<String, Integer> collect = data.stream()
                .collect(
                        Collectors.toMap(PersonModel::getName, PersonModel::getAge)
                );

        data.stream()
                .collect(Collectors.toMap(per->per.getName(), value->{
            return value+"1";
        }));
    }

    /**
     * 指定类型
     */
    public static void toTreeSetTest(){
        List<PersonModel> data = Data.getData();
        TreeSet<PersonModel> collect = data.stream()
                .collect(Collectors.toCollection(TreeSet::new));
        System.out.println(collect);
    }

    /**
     * 分组
     */
    public static void toGroupTest(){
        List<PersonModel> data = Data.getData();
        Map<Boolean, List<PersonModel>> collect = data.stream()
                .collect(Collectors.groupingBy(per -> "男".equals(per.getSex())));
        System.out.println(collect);
    }

    /**
     * 分隔
     */
    public static void toJoiningTest(){
        List<PersonModel> data = Data.getData();
        String collect = data.stream()
                .map(personModel -> personModel.getName())
                .collect(Collectors.joining(",", "{", "}"));
        System.out.println(collect);
    }

    /**
     * 自定义
     */
    public static void reduce(){
        List<String> collect = Stream.of("1", "2", "3").collect(
                Collectors.reducing(new ArrayList<String>(), x -> Arrays.asList(x), (y, z) -> {
                    y.addAll(z);
                    return y;
                }));
        System.out.println(collect);
    }

调试

  • list.map.fiter.map.xx 为链式调用,最终调用collect(xx)返回结果
  • 分惰性求值和及早求值
  • 判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是 Stream,那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果。
  • 通过peek可以查看每个值,同时能继续操作流
private static void peekTest() {
        List<PersonModel> data = Data.getData();

        //peek打印出遍历的每个per
        data.stream().map(per->per.getName()).peek(p->{
            System.out.println(p);
        }).collect(toList());
}

理解 Linux backlog/somaxconn 内核参数

November 21st, 2019 by JasonLe's Tech 1,305 views

引言

之前线上TcpExt.ListenOverflows,然后通过ss -tlnp 查看Send-Q偏小导致后端服务器 Socket accept 队列满,系统的 somaxconn 内核参数默认太小。

TCP SYN_REVD, ESTABELLISHED 状态对应的队列

TCP 建立连接时要经过 3 次握手,在客户端向服务器发起连接时,
对于服务器而言,一个完整的连接建立过程,服务器会经历 2 种 TCP 状态:SYN_REVD, ESTABELLISHED。

对应也会维护两个队列:
1. 一个存放 SYN 的队列(半连接队列)
2. 一个存放已经完成连接的队列(全连接队列)

当一个连接的状态是 SYN RECEIVED 时,它会被放在 SYN 队列中。
当它的状态变为 ESTABLISHED 时,它会被转移到另一个队列。
所以后端的应用程序只从已完成的连接的队列中获取请求。

如果一个服务器要处理大量网络连接,且并发性比较高,那么这两个队列长度就非常重要了。
因为,即使服务器的硬件配置非常高,服务器端程序性能很好,
但是这两个队列非常小,那么经常会出现客户端连接不上的现象,
因为这两个队列一旦满了后,很容易丢包,或者连接被复位。
所以,如果服务器并发访问量非常高,那么这两个队列的设置就非常重要了。

Linux backlog 参数意义

对于 Linux 而言,基本上任意语言实现的通信框架或服务器程序在构造 socket server 时,都提供了 backlog 这个参数,
因为在监听端口时,都会调用系统底层 API: int listen(int sockfd, int backlog);

listen 函数中 backlog 参数的定义如下:

Now it specifies the queue length for completely established sockets waiting to be accepted,
instead of the number of incomplete connection requests.
The maximum length of the queue for incomplete sockets can be set using the tcp_max_syn_backlog sysctl.
When syncookies are enabled there is no logical maximum length and this sysctl setting is ignored.
If the socket is of type AF_INET, and the backlog argument is greater than the constant SOMAXCONN(128 default),
it is silently truncated to SOMAXCONN.

backlog 参数描述的是服务器端 TCP ESTABELLISHED 状态对应的全连接队列长度。

全连接队列长度如何计算?
如果 backlog 大于内核参数 net.core.somaxconn,则以 net.core.somaxconn 为准,
即全连接队列长度 = min(backlog, 内核参数 net.core.somaxconn),net.core.somaxconn 默认为 128。
这个很好理解,net.core.somaxconn 定义了系统级别的全连接队列最大长度,
backlog 只是应用层传入的参数,不可能超过内核参数,所以 backlog 必须小于等于 net.core.somaxconn。

半连接队列长度如何计算?
半连接队列长度由内核参数 tcp_max_syn_backlog 决定,
当使用 SYN Cookie 时(就是内核参数 net.ipv4.tcp_syncookies = 1),这个参数无效,
半连接队列的最大长度为 backlog、内核参数 net.core.somaxconn、内核参数 tcp_max_syn_backlog 的最小值。
即半连接队列长度 = min(backlog, 内核参数 net.core.somaxconn,内核参数 tcp_max_syn_backlog)。
这个公式实际上规定半连接队列长度不能超过全连接队列长度。

其实,对于 Nginx/Tomcat 等这种 Web 服务器,都提供了 backlog 参数设置入口,
当然它们都会有默认值,通常这个默认值都不会太大(包括内核默认的半连接队列和全连接队列长度)。
如果应用并发访问非常高,只增大应用层 backlog 是没有意义的,因为可能内核参数关于连接队列设置的都很小,
一定要综合应用层 backlog 和内核参数一起看,通过公式很容易调整出正确的设置。

 

ACID/CAP/BASE/二段提交/三段提交/TCC

November 6th, 2019 by JasonLe's Tech 1,319 views

关系型数据库完全满足ACID的特性。

ACID指的是:A: Atomicity 原子性 C: Consistency 一致性 I: Isolation 隔离性 D: Durability 持久性

具有ACID的特性的数据库支持强一致性,强一致性代表数据库本身不会出现不一致,每个事务是原子的,或者成功或者失败,事物间是隔离的,互相完全不影响,而且最终状态是持久落盘的,因此,数据库会从一个明确的状态到另外一个明确的状态,中间的临时状态是不会出现的,如果出现也会及时的自动的修复,因此是强一致的。

如果你在为交易相关系统做技术选型,交易的存储应该只考虑关系型数据库,对于核心系统,如果需要较好的性能,可以考虑使用更强悍的硬件,这种向上扩展(升级硬件)虽然成本较高,但是是最简单粗暴有效的方式,另外,Nosql完全不适合交易场景,Nosql主要用来做数据分析、ETL、报表、数据挖掘、推荐、日志处理等非交易场景。

CAP(帽子理论)

C:Consistency,一致性, 数据一致更新,所有数据变动都是同步的

A:Availability,可用性, 好的响应性能,完全的可用性指的是在任何故障模型下,服务都会在有限的时间处理响应

P:Partition tolerance,分区容错性,可靠性

帽子理论证明,任何分布式系统只可同时满足二点,没法三者兼顾。关系型数据库由于关系型数据库是单节点的,因此,不具有分区容错性,但是具有一致性和可用性,而分布式的服务化系统都需要满足分区容错性,那么我们必须在一致性和可用性中进行权衡,具体表现在服务化系统处理的异常请求在某一个时间段内可能是不完全的,但是经过自动的或者手工的补偿后,达到了最终的一致性。

BASE模型与ACID模型截然不同,满足CAP理论,通过牺牲强一致性,获得可用性。

一般应用在服务化系统的应用层或者大数据处理系统,通过达到最终一致性来尽量满足业务的绝大部分需求。

BASE模型包含个三个元素:

1.BA:Basically Available,基本可用

2.S:Soft State,软状态,状态可以有一段时间不同步

3.E:Eventually Consistent,最终一致,最终数据是一致的就可以了,而不是时时保持强一致

分布式一致性协议

两阶段如下:

1.准备阶段:协调者向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,参与者会写redo或者undo日志(这也是前面提起的Write-Ahead Log的一种),然后锁定资源,执行操作,但是并不提交

2.提交阶段:如果每个参与者明确返回准备成功,也就是预留资源和执行操作成功,协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;如果任何一个参与者明确返回准备失败,也就是预留资源或者执行操作失败,协调者向参与者发起中止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源

两阶段提交协议

我们看到两阶段提交协议在准备阶段锁定资源,是一个重量级的操作,并能保证强一致性,但是实现起来复杂、成本较高,不够灵活,更重要的是它有如下致命的问题:

1.阻塞:从上面的描述来看,对于任何一次指令必须收到明确的响应,才会继续做下一步,否则处于阻塞状态,占用的资源被一直锁定,不会被释放

2.单点故障:如果协调者宕机,参与者没有了协调者指挥,会一直阻塞,尽管可以通过选举新的协调者替代原有协调者,但是如果之前协调者在发送一个提交指令后宕机,而提交指令仅仅被一个参与者接受,并且参与者接收后也宕机,新上任的协调者无法处理这种情况

3.脑裂:协调者发送提交指令,有的参与者接收到执行了事务,有的参与者没有接收到事务,就没有执行事务,多个参与者之间是不一致的

上面所有的这些问题,都是需要人工干预处理,没有自动化的解决方案,因此两阶段提交协议在正常情况下能保证系统的强一致性,但是在出现异常情况下,当前处理的操作处于错误状态,需要管理员人工干预解决,因此可用性不够好,这也符合CAP协议的一致性和可用性不能兼得的原理。

三阶段提交协议

三阶段提交协议是两阶段提交协议的改进版本。它通过超时机制解决了阻塞的问题,并且把两个阶段增加为三个阶段:

1.询问阶段:协调者询问参与者是否可以完成指令,协调者只需要回答是还是不是,而不需要做真正的操作,这个阶段超时导致中止

2.准备阶段:如果在询问阶段所有的参与者都返回可以执行操作,协调者向参与者发送预执行请求,然后参与者写redo和undo日志,执行操作,但是不提交操作;如果在询问阶段任何参与者返回不能执行操作的结果,则协调者向参与者发送中止请求,这里的逻辑与两阶段提交协议的的准备阶段是相似的,这个阶段超时导致成功

3.提交阶段:如果每个参与者在准备阶段返回准备成功,也就是预留资源和执行操作成功,协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;如果任何一个参与者返回准备失败,也就是预留资源或者执行操作失败,协调者向参与者发起中止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源,这里的逻辑与两阶段提交协议的提交阶段一致

三阶段提交协议

然而,这里与两阶段提交协议有两个主要的不同:

1.增加了一个询问阶段,询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为,但是它并不能发现所有的这种行为,只会减少这种情况的发生

2.在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,协调者和参与者都继续提交事务,默认为成功,这也是根据概率统计上超时后默认成功的正确性最大

三阶段提交协议与两阶段提交协议相比,具有如上的优点,但是一旦发生超时,系统仍然会发生不一致,只不过这种情况很少见罢了,好处就是至少不会阻塞和永远锁定资源。

TCC

无论两阶段还是三阶段方案中都包含多个参与者、多个阶段实现一个事务,实现复杂,性能也是一个很大的问题,因此,在互联网高并发系统中,鲜有使用两阶段提交和三阶段提交协议的场景。

阿里巴巴提出了新的TCC协议,TCC协议将一个任务拆分成Try、Confirm、Cancel,正常的流程会先执行Try,如果执行没有问题,再执行Confirm,如果执行过程中出了问题,则执行操作的逆操Cancel,从正常的流程上讲,这仍然是一个两阶段的提交协议,但是,在执行出现问题的时候,有一定的自我修复能力,如果任何一个参与者出现了问题,协调者通过执行操作的逆操作来取消之前的操作,达到最终的一致状态。

可以看出,从时序上,如果遇到极端情况下TCC会有很多问题的,例如,如果在Cancel的时候一些参与者收到指令,而一些参与者没有收到指令,整个系统仍然是不一致的,这种复杂的情况,系统首先会通过补偿的方式,尝试自动修复的,如果系统无法修复,必须由人工参与解决。

从TCC的逻辑上看,可以说TCC是简化版的三阶段提交协议,解决了两阶段提交协议的阻塞问题,但是没有解决极端情况下会出现不一致和脑裂的问题。然而,TCC通过自动化补偿手段,会把需要人工处理的不一致情况降到到最少,也是一种非常有用的解决方案,根据线人,阿里在内部的一些中间件上实现了TCC模式。

我们给出一个使用TCC的实际案例,在秒杀的场景,用户发起下单请求,应用层先查询库存,确认商品库存还有余量,则锁定库存,此时订单状态为待支付,然后指引用户去支付,由于某种原因用户支付失败,或者支付超时,系统会自动将锁定的库存解锁供其他用户秒杀。

总结一下,两阶段提交协议、三阶段提交协议、TCC协议都能保证分布式事务的一致性,他们保证的分布式系统的一致性从强到弱,TCC达到的目标是最终一致性,其中任何一种方法都可以不同程度的解决案例2:转账、案例3:下订单和扣库存的问题,只是实现的一致性的级别不一样而已,对于案例4:同步超时可以通过TCC的理念解决,如果同步调用超时,调用方可以使用fastfail策略,返回调用方的使用方失败的结果,同时调用服务的逆向cancel操作,保证服务的最终一致性。

分布式一致性的技术包括:paxos算法、raft算法、zab算法、nwr算法、一致性哈希等。

参考:

案例2:转账
转账是经典的不一致案例,设想一下银行为你处理一笔转账,扣减你账户上的余额,然后增加别人账户的余额;如果扣减你的账户余额成功,增加别人账户余额失败,那么你就会损失这笔资金。反过来,如果扣减你的账户余额失败,增加别人账户余额成功,那么银行就会损失这笔资金,银行需要赔付。对于资金处理系统来说,上面任何一种场景都是不允许发生的,一旦发生就会有资金损失,后果是不堪设想的,严重情况会让一个公司瞬间倒闭,可参考案例。

案例3:下订单和扣库存
电商系统中也有一个经典的案例,下订单和扣库存如何保持一致,如果先下订单,扣库存失败,那么将会导致超卖;如果下订单没有成功,扣库存成功,那么会导致少卖。两种情况都会导致运营成本的增加,严重情况下需要赔付。

案例4:同步超时
服务化的系统间调用常常因为网络问题导致系统间调用超时,即使是网络很好的机房,在亿次流量的基数下,同步调用超时也是家常便饭。系统A同步调用系统B超时,系统A可以明确得到超时反馈,但是无法确定系统B是否已经完成了预定的功能或者没有完成预定的功能。于是,系统A就迷茫了,不知道应该继续做什么,如何反馈给使用方。(曾经的一个B2B产品的客户要求接口超时重新通知他们,这个在技术上是难以实现的,因为服务器本身可能并不知道自己超时,可能会继续正常的返回数据,只是客户端并没有接受到结果罢了,因此这不是一个合理的解决方案)。

https://blog.csdn.net/qq_31854907/article/details/92796788

docker 常用操作

November 4th, 2019 by JasonLe's Tech 1,095 views

sudo docker ps

sudo docker images

sudo docker run –name mysql -p 3306:3306 -v ~/mysql/data:/var/lib/mysql -e MYSQL_DATABASE=spider -e MYSQL_USER=admin -e MYSQL_PASSWORD=admin -e MYSQL_ROOT_PASSWORD=admin -d mysql

sudo docker run –name redis -p 6379:6379 -d redis

sudo docker run –name mongo -p 27017:27017 -v ~/mongo/db:/data/db -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=admin -d mongo

sudo docker exec -it mysql bash

sudo docker exec -it redis bash

 

docker run -it –name openjdk -d lzz5235/openjdk-8u202 /bin/bash 这样可以保证容器不被杀死

docker exec -it [name] /bin/bash 进入终端

 

 

 

GC调优

October 12th, 2019 by JasonLe's Tech 1,036 views

1.问题的发现与定位

1.1统计nginx每秒中流量时发现轮询到每台机器上的流量不均衡,找到后端机器,看gc 日志发现出现大量的full gc,对比gc时间和流量统计的时间,发现时间是完全吻合的,从而定位流量不均衡问题是因为线上服务full gc引起的。

2019-08-30T11:33:06.130+0800: 780570.463: [GC pause (G1 Evacuation Pause) (young)2019-08-30T11:33:06.352+0800: 780570.685: [SoftReference, 0 refs, 0.0000547 secs]2019-08-30T11:33:06.352+0800: 780570.685: [WeakReference, 0 refs, 0.0000128 secs]2019-08-30T11:33:06.352+0800: 780570.685: [FinalReference, 0 refs, 0.0000103 secs]2019-08-30T11:33:06.352+0800: 780570.685: [PhantomReference, 0 refs, 0 refs, 0.0000147 secs]2019-08-30T11:33:06.352+0800: 780570.685: [JNI Weak Reference, 0.0000183 secs] (to-space exhausted), 0.2410054 secs]
   [Parallel Time: 212.0 ms, GC Workers: 23]
      [GC Worker Start (ms): Min: 780570465.3, Avg: 780570465.5, Max: 780570465.9, Diff: 0.6]
      [Ext Root Scanning (ms): Min: 0.4, Avg: 0.8, Max: 2.2, Diff: 1.9, Sum: 18.4]
      [Update RS (ms): Min: 33.8, Avg: 37.4, Max: 40.3, Diff: 6.4, Sum: 861.1]
         [Processed Buffers: Min: 9, Avg: 30.3, Max: 137, Diff: 128, Sum: 697]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 169.7, Avg: 173.0, Max: 176.7, Diff: 7.0, Sum: 3979.6]
      [Termination (ms): Min: 0.0, Avg: 0.3, Max: 0.4, Diff: 0.4, Sum: 5.8]
         [Termination Attempts: Min: 1, Avg: 1.3, Max: 3, Diff: 2, Sum: 30]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.4, Diff: 0.4, Sum: 0.8]
      [GC Worker Total (ms): Min: 211.2, Avg: 211.6, Max: 211.8, Diff: 0.7, Sum: 4865.7]
      [GC Worker End (ms): Min: 780570677.0, Avg: 780570677.1, Max: 780570677.1, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.2 ms]
   [Other: 28.8 ms]
      [Evacuation Failure: 18.1 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 7.5 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.4 ms]
      [Humongous Register: 1.5 ms]
      [Humongous Reclaim: 0.2 ms]
      [Free CSet: 0.1 ms]
   [Eden: 44.0M(6116.0M)->0.0B(6144.0M) Survivors: 28.0M->0.0B Heap: 9482.2M(12.0G)->9482.2M(12.0G)]
Heap after GC invocations=266041 (full 14579):
 garbage-first heap   total 12582912K, used 9709773K [0x00000004c0000000, 0x00000004c0406000, 0x00000007c0000000)
  region size 4096K, 0 young (0K), 0 survivors (0K)
 Metaspace       used 59825K, capacity 60428K, committed 62016K, reserved 1103872K
  class space    used 6597K, capacity 6776K, committed 7056K, reserved 1048576K
}
 [Times: user=0.57 sys=0.08, real=0.24 secs]
2019-08-30T11:33:06.372+0800: 780570.705: Total time for which application threads were stopped: 0.2499226 seconds, Stopping threads took: 0.0007422 seconds
{Heap before GC invocations=266041 (full 14579):
 garbage-first heap   total 12582912K, used 9709773K [0x00000004c0000000, 0x00000004c0406000, 0x00000007c0000000)
  region size 4096K, 0 young (0K), 0 survivors (0K)
 Metaspace       used 59825K, capacity 60428K, committed 62016K, reserved 1103872K
  class space    used 6597K, capacity 6776K, committed 7056K, reserved 1048576K
2019-08-30T11:33:06.380+0800: 780570.712: [GC pause (G1 Evacuation Pause) (young) (initial-mark)2019-08-30T11:33:06.392+0800: 780570.725: [SoftReference, 0 refs, 0.0000740 secs]2019-08-30T11:33:06.392+0800: 780570.725: [WeakReference, 0 refs, 0.0000127 secs]2019-08-30T11:33:06.392+0800: 780570.725: [FinalReference, 0 refs, 0.0000103 secs]2019-08-30T11:33:06.392+0800: 780570.725: [PhantomReference, 0 refs, 0 refs, 0.0000147 secs]2019-08-30T11:33:06.392+0800: 780570.725: [JNI Weak Reference, 0.0000174 secs], 0.0144402 secs]
   [Parallel Time: 10.2 ms, GC Workers: 23]
      [GC Worker Start (ms): Min: 780570714.5, Avg: 780570715.2, Max: 780570716.2, Diff: 1.7]
      [Ext Root Scanning (ms): Min: 1.1, Avg: 1.9, Max: 2.8, Diff: 1.7, Sum: 44.1]
      [Update RS (ms): Min: 6.1, Avg: 6.5, Max: 7.3, Diff: 1.2, Sum: 150.3]
         [Processed Buffers: Min: 3, Avg: 9.9, Max: 18, Diff: 15, Sum: 227]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.2, Max: 0.4, Diff: 0.4, Sum: 4.7]
      [Termination (ms): Min: 0.0, Avg: 0.5, Max: 0.7, Diff: 0.7, Sum: 12.5]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 23]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
      [GC Worker Total (ms): Min: 8.3, Avg: 9.2, Max: 10.0, Diff: 1.7, Sum: 211.9]
      [GC Worker End (ms): Min: 780570724.4, Avg: 780570724.4, Max: 780570724.4, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.3 ms]
   [Other: 3.9 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.7 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.3 ms]
      [Humongous Register: 1.4 ms]
      [Humongous Reclaim: 0.2 ms]
      [Free CSet: 0.0 ms]
   [Eden: 0.0B(6144.0M)->0.0B(6144.0M) Survivors: 0.0B->0.0B Heap: 9482.2M(12.0G)->9482.2M(12.0G)]
Heap after GC invocations=266042 (full 14579):
 garbage-first heap   total 12582912K, used 9709773K [0x00000004c0000000, 0x00000004c0406000, 0x00000007c0000000)
  region size 4096K, 0 young (0K), 0 survivors (0K)
 Metaspace       used 59825K, capacity 60428K, committed 62016K, reserved 1103872K
  class space    used 6597K, capacity 6776K, committed 7056K, reserved 1048576K
}
 [Times: user=0.13 sys=0.01, real=0.02 secs]
2019-08-30T11:33:06.394+0800: 780570.727: [GC concurrent-root-region-scan-start]
2019-08-30T11:33:06.394+0800: 780570.727: [GC concurrent-root-region-scan-end, 0.0000314 secs]
2019-08-30T11:33:06.394+0800: 780570.727: [GC concurrent-mark-start]
2019-08-30T11:33:06.394+0800: 780570.727: Total time for which application threads were stopped: 0.0225686 seconds, Stopping threads took: 0.0003015 seconds
{Heap before GC invocations=266042 (full 14579):
 garbage-first heap   total 12582912K, used 9709773K [0x00000004c0000000, 0x00000004c0406000, 0x00000007c0000000)
  region size 4096K, 0 young (0K), 0 survivors (0K)
 Metaspace       used 59825K, capacity 60428K, committed 62016K, reserved 1103872K
  class space    used 6597K, capacity 6776K, committed 7056K, reserved 1048576K
2019-08-30T11:33:06.402+0800: 780570.735: [Full GC (Allocation Failure) 2019-08-30T11:33:06.812+0800: 780571.145: [SoftReference, 155 refs, 0.0000827 secs]2019-08-30T11:33:06.812+0800: 780571.145: [WeakReference, 4919 refs, 0.0004444 secs]2019-08-30T11:33:06.812+0800: 780571.145: [FinalReference, 38797 refs, 0.0256226 secs]2019-08-30T11:33:06.838+0800: 780571.171: [PhantomReference, 0 refs, 4 refs, 0.0000267 secs]2019-08-30T11:33:06.838+0800: 780571.171: [JNI Weak Reference, 0.0000255 secs] 9482M->3317M(12G), 1.3316541 secs]
   [Eden: 0.0B(6144.0M)->0.0B(6144.0M) Survivors: 0.0B->0.0B Heap: 9482.2M(12.0G)->3317.3M(12.0G)], [Metaspace: 59825K->59825K(1103872K)]
Heap after GC invocations=266043 (full 14580):
 garbage-first heap   total 12582912K, used 3396899K [0x00000004c0000000, 0x00000004c0406000, 0x00000007c0000000)
  region size 4096K, 0 young (0K), 0 survivors (0K)
 Metaspace       used 59825K, capacity 60428K, committed 62016K, reserved 1103872K
  class space    used 6597K, capacity 6776K, committed 7056K, reserved 1048576K
}
 [Times: user=1.83 sys=0.12, real=1.34 secs]

2 问题分析与处理

2.1 分析日志发现在新生代回收时触发了老年代的担保机制,导致新生代对象直接晋升到了老年代,然后触发并发周期,这时由于老年代对象太多已经不足以触发mixed GC,而是采用了最耗时的Full GC进行垃圾回收,由于Full GC耗时很长导致,后续的新生代在垃圾回收时还是会触发老年代担保机制,从而整个程序陷入循环Full GC的状态。

2.2 根据to-space exhausted我们知道,调整空闲区域预留百分比是能缓解新生代垃圾回收空间溢出问题,我们尝试增加XX:G1ReservePercent=15;另外我们为了降低Full GC触发概率,减小了InitiatingHeapOccupancyPercent的大小,这样我们可以用更加频繁的mixed gc降低 full gc问题

2.3 根据G1的启发式算法,我们为了控制gc时间,决定放弃原有的gc用法,让g1决定新生代和老年代大小,我们删除了Xmn参数设置

2-4 在gc日志中我们也发现了大对象分配与回收的现象,考虑到我们线上视频大小,我们调整region大小到16M

3 观察对比后再次处理

3.1 通过上述调整对比观察四台机器,两天以后,原有的机器出现Full GC 调整后的机器运行正常

3.2 观察调整后日志发现新调整的gc没有出现Full gc,但是mixed gc问题出现频率在20min一次,这个频率出现理论上对整个gc的吞吐几乎没有影响,但是分析后会发现mixed gc回收效率很低,所以我们尝试改善mixed gc的效率

3.3 根据上述我们对InitiatingHeapOccupancyPercent参数的调整,我们根据线上观察到的老年代大小和新生代大小,调整值为50

3.4 对比观察后发现调整后mixed gc频率在流量高峰期间为2小时一次,流量低峰期间为6小时一次,并且mixed gc 回收效率大大提高

4.调优后效果

经过调优后线上没有触发 full gc, yang gc 和mixed gc 效率都很高,整个gc的吞吐达到 99%以上