什么是Stream

Stream是Java 1.8版本开始提供的一个接口,主要提供对数据集合使用流的方式进行操作,流中的元素不可变且只会被消费一次,所有方法都设计成支持链式调用。使用Stream API可以极大生产力,写出高效率、干净、简洁的代码。

如何获得Stream实例

Stream提供了静态构建方法,可以基于不同的参数创建返回Stream实例
使用Collection的子类实例调用stream()或者parallelStream()方法也可以得到Stream实例,两个方法的区别在于后续执行Stream其他方法的时候是单线程还是多线程

1
2
3
4
5
6
7
8
9
10
Stream<String> stringStream = Stream.of("1", "2", "3");
//无限长的偶数流
Stream<Integer> evenNumStream = Stream.iterate(0, n -> n + 2);

List<String> strList = new ArrayList<>();
strList.add("1");
strList.add("2");
strList.add("3");
Stream<String> strStream = strList.stream();
Stream<String> strParallelStream = strList.parallelStream();

filter

filter方法用于根据指定的条件做过滤,返回符合条件的流

1
2
3
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//获得只包含正数的流,positiveNumStream -> (1,2,3)
Stream<Integer> positiveNumStream = numStream.filter(num -> num > 0);

map

map方法用于将流中的每个元素执行指定的转换逻辑,返回其他类型元素的流

1
2
3
Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//转换成字符串流
Stream<String> strStream = numStream.map(String::valueOf);
阅读全文 »

背景

线上环境一个后台项目,提供基于dubbo实现的事件分发服务,最近突然出现心跳超时。

问题分析

检查内存是否溢出

1
jstat -gcutil 8166 1000

意料之中,内存正常,因为内部有接入内存溢出告警,如果是内存溢出应该有收到通知,但是这次没有溢出通知。

查看线程栈

1
jstack -l 8166

发现有大量DubboServerHandler开头的线程阻塞在一个同样的地方,脱敏简化后信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"DubboServerHandler-192.168.160.42:9184-thread-200" #372 daemon prio=5 os_prio=0 tid=0x0000000002342000 nid=0x252b waiting on condition [0x00007f2ce8deb000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000008a51c930> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.▇▇▇▇▇▇.QueueMiddleComponent.save(QueueMiddleComponent.java:317)
...
at com.alibaba.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:81)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

可以看到线程正在申请加锁,找到相关人员确认了这个不是业务代码而是我们自己内部封装的一个框架逻辑,于是找到对应的项目代码拉到本地。

阅读全文 »

前提

有一台装有Ubuntu系统的服务器和一个可以部署的War包

安装Java

创建文件夹

1
2
sudo mkdir /usr/java
cd /usr/java

进入https://www.oracle.com/technetwork/java/javase/downloads/index.html找到需要安装的JDK版本下载地址

下载JDK

1
sudo wget --no-check-certificate -c --header "Cookie: oraclelicense=accept-securebackup-cookie" https://download.oracle.com/otn-pub/java/jdk/13+33/5b8a42f3905b406298b72d750b6919f6/jdk-13_linux-x64_bin.tar.gz

有些版本不支持这种方式下载,所以只能手动下载后再上传到服务器

解压JDK

1
sudo tar -xvzf jdk-13_linux-x64_bin.tar.gz

安装Java软链

1
sudo update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-13/bin/java" 0
1
sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-13/bin/javac" 0
1
sudo update-alternatives --set java /usr/java/jdk-13/bin/java
1
sudo update-alternatives --set javac /usr/java/jdk-13/bin/javac

其中jdk-13是上一步解压后的文件夹名,根据实际版本做替换

阅读全文 »

前言

最近在研究布隆过滤器(如果不了解什么是布隆过滤器的,推荐看这篇如何判断一个元素在亿级数据中是否存在?了解),发现Guava提供了封装好的类,但是只能单机使用,一般现在的应用都是部署在分布式系统的,所以想找个可以在分布式系统下使用的布隆过滤器,找了半天只找到一个基于redis开发的模块项目ReBloom,但是这个是需要额外安装的,而且文档里只说了怎么在docker下运行,没研究过docker所以放弃了。后来找到一篇博客讲怎么利用布隆过滤器统计消息未读数的(博客地址不记得了,是一位淘宝同学写的),博客最后放了一份整合redis和bloomFilter的代码demo,详见BloomFilter.java,看了下实现比较简单,但是使用方式不是我想要的,所以参考着自己整理了一份。

BloomFilterHelper

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
55
package com.doodl6.springmvc.service.cache.redis;

import com.google.common.base.Preconditions;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;

public class BloomFilterHelper<T> {

private int numHashFunctions;

private int bitSize;

private Funnel<T> funnel;

public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
Preconditions.checkArgument(funnel != null, "funnel不能为空");
this.funnel = funnel;
bitSize = optimalNumOfBits(expectedInsertions, fpp);
numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
}

int[] murmurHashOffset(T value) {
int[] offset = new int[numHashFunctions];

long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
int hash1 = (int) hash64;
int hash2 = (int) (hash64 >>> 32);
for (int i = 1; i <= numHashFunctions; i++) {
int nextHash = hash1 + i * hash2;
if (nextHash < 0) {
nextHash = ~nextHash;
}
offset[i - 1] = nextHash % bitSize;
}

return offset;
}

/**
* 计算bit数组长度
*/
private int optimalNumOfBits(long n, double p) {
if (p == 0) {
p = Double.MIN_VALUE;
}
return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
}

/**
* 计算hash方法执行次数
*/
private int optimalNumOfHashFunctions(long n, long m) {
return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
}
}

BloomFilterHelper是实现功能的关键,包含了计算bitmap的核心算法,其实大部分代码都是来源于Guava库里面的BloomFilterStrategies类,但是因为这个类是专门为Guava的BloomFilter类使用的,所以没有对外暴露一些重要的算法逻辑。

再来看怎么结合redis一起使用BloomFilterHelper

阅读全文 »

低性能版

1
2
3
4
5
6
7
8
9
SELECT
*
FROM table
where condition1 = 0
and condition2 = 0
and condition3 = -1
and condition4 = -1
order by id asc
LIMIT 2000 OFFSET 50000

当offset特别大时,这条语句的执行效率会明显减低,而且效率是随着offset的增大而降低的。
原因为:
MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,当offset特别大,然后单条数据也很大的时候,每次查询需要获取的数据就越多,自然就会很慢。

优化版本

1
2
3
4
5
6
7
8
9
10
11
12
SELECT
*
FROM table
JOIN
(select id from table
where condition1 = 0
and condition2 = 0
and condition3 = -1
and condition4 = -1
order by id asc
LIMIT 2000 OFFSET 50000)
as tmp using(id)

或者

1
2
3
4
5
6
7
8
9
SELECT a.* FROM table a, 
(select id from table
where condition1 = 0
and condition2 = 0
and condition3 = -1
and condition4 = -1
order by id asc
LIMIT 2000 OFFSET 50000) b
where a.id = b.id

先获取主键列表,再通过主键查询目标数据,即使offset很大,也是获取了很多的主键,而不是所有的字段数据,相对而言效率会提升很多。

0%