1 高性能限流器GuavaRateLimiter

令牌桶算法:记录一个下一令牌产生的时间,并动态更新它,就能够轻松完成限流功能

与信号量区别:信号量是“一次性可以有多少个线程一起执行”,限流器是“每秒最多允许几个请求通过”“1个请求/xxx秒”

申请令牌时调用acquire方法
1.如果请求时间now在下⼀令牌产⽣时间next之后:
resync:通过(now-next)/interval来新增令牌,并且因为next到now这段时间的令牌发放的逻辑处理完毕了,所以将下一次发放令牌的时间next重置为当前时间now
reserve:然后根据令牌数来判断next:如果令牌桶中还有令牌,则next不用变,减去一个令牌当作被预支了;如果没有则需要变:因为当前next发放的这个令牌会给当前请求使用(sleep掉next-now的时间后再返回,等价于获取此令牌),next需要变为下一个令牌的发放时间,等待下一个请求;
2.如果请求时间now在下⼀令牌产⽣时间next之前:
reserve:同上

class SimpleLimiter {
    //当前令牌桶中的令牌数量
    long storedPermits = 0;
    //令牌桶的容量
    long maxPermits = 3;
    //下⼀令牌产⽣时间
    long next = System.nanoTime();
    //发放令牌间隔:纳秒
    long interval = 1000_000_000;
    //请求时间在下⼀令牌产⽣时间之后,则
	// 1.重新计算令牌桶中的令牌数
	// 2.将下⼀个令牌发放时间重置为当前时间
    void resync(long now) {
        if (now > next) {
			//新产⽣的令牌数
            long newPermits = (now-next)/interval;
			//新令牌增加到令牌桶
            storedPermits = min(maxPermits,storedPermits + newPermits);
			//将下⼀个令牌发放时间重置为当前时间
            next = now;
        }
    }
    //预占令牌,返回能够获取令牌的时间
    //如果令牌桶中还有令牌,则next不用变
    //如果没有令牌,则next需要变
    synchronized long reserve(long now){
        resync(now);
        //能够获取令牌的时间
        long at = next;
		//令牌桶中能提供的令牌
        long fb=min(1, storedPermits);
		//令牌净需求:⾸先减掉令牌桶中的令牌
        long nr = 1 - fb;
		//重新计算下⼀令牌产⽣时间
        next = next + nr*interval;
		//重新计算令牌桶中的令牌
        this.storedPermits -= fb;
        return at;
    }
    //申请令牌
    void acquire() {
		//申请令牌时的时间
        long now = System.nanoTime();
		//预占令牌
        long at = reserve(now);
        long waitTime=max(at - now, 0);
		//按照条件等待
        if(waitTime > 0) {
            try {
                TimeUnit.NANOSECONDS.sleep(waitTime);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

漏桶算法:
请求就像水一样注入漏桶,漏桶会按照一定的速率自动将水漏掉,只有漏桶里还能注入水的时候,请求才能通
过限流器。

2 高性能网络应用框架Netty

线程模型直接影响着网络程序的性能,所以这里分析一下

BIO线程模型:

为每个socket分配一个独立的线程,这样就不会因为线程阻塞在一个socket上而影响对其他socket的读写

每个连接上的请求并不频繁,所以线程大部分时间都在等待I/O就绪。也就是说线程大部分时间都阻塞在那里,这完全是浪费

Java并发数计算 java并发量怎么算的_网络连接


理想的线程模型:

用一个线程来处理多个连接

Java并发数计算 java并发量怎么算的_Java_02


使用Reactor模式实现理想线程模型:

Java并发数计算 java并发量怎么算的_Java_03


核心逻辑:

首先通过同步事件多路选择器提供的select()方法监听网络事件

当有网络事件就绪后,就遍历事件处理器来处理该网络事件

由于网络事件是源源不断的,所以在主程序中启动Reactor模式,需要以while(true){} 的方式调用handle_events()方法。

void Reactor::handle_events(){
    //通过同步事件多路选择器提供的
    //select()⽅法监听⽹络事件
    select(handlers);
    //处理⽹络事件
    for(h in handlers){
    	h.handle_event();
    }
}
// 在主程序中启动事件循环
while (true) {
	handle_events();

Netty线程模型:

Java并发数计算 java并发量怎么算的_Java_04


Netty中最核心的概念是事件循环(EventLoop),其实也就是Reactor模式中的Reactor,负责监听网络事件并调用事件处理器进行处理;

网络连接和EventLoop是稳定的多对1关系,而EventLoop和Java线程是1对1关系;

稳定指的是关系一旦确定就不再发生变化。也就是说一个网络连接只会对应唯一的一个EventLoop,而一个EventLoop也只会对应到一个Java线程,所以一个网络连接只会对应到一个Java线程;

一个网络连接对应到一个Java线程上,最大的好处就是对于一个网络连接的事件处理是单线程的,这样就避免了各种并发问题

一个EventLoopGroup由一组EventLoop组成;
实际使用中,一般都会创建两个EventLoopGroup,一个称为bossGroup,一个称为workerGroup;
bossGroup就用来处理连接请求的,workerGroup是用来处理读写请求的;
bossGroup处理完连接请求后,会将这个连接提交给workerGroup来处理

使用轮询算法来决定新的连接会交给workerGroup的哪个EventLoop来处理

3 高性能队列Disruptor

在高并发场景下,用来代替ArrayBlockingQueue 和 LinkedBlockingQueue的有界队列

高性能的原因:

1.内存分配更加合理,使用RingBuffer数据结构,数组元素在初始化时一次性全部创建,提升缓存命中率;

对象循环利用,避免频繁GC:

消费者线程在消费的时候,是遵循空间局部性原理的;

消费完第1个元素,很快就会消费第2个元素;

当消费第1个元素E1的时候,CPU会把内存中E1后面的数据也加载进Cache;

如果E1和E2在内存中的地址是连续的,那么E2也就会被加载进Cache中,然后当消费第2个元素的时候,由于E2已经在Cache中了,所以就不需要从内存中加载了,这样就能大大提升性能。

Java并发数计算 java并发量怎么算的_网络连接_05


2.能够避免伪共享,提升缓存利用率。(缓存行填充)

3.采用无锁算法,避免频繁加锁、解锁的性能消耗。

4.支持批量消费,消费者可以无锁方式消费多个消息。

4 高性能数据库连接池HiKariCP

性能好的原因:
微观上HiKariCP程序编译出的字节码执行效率更高,站在字节码的角度去优化Java代码
宏观上主要是和两个数据结构有关,一个是FastList,另一个是ConcurrentBag。

FastList适用于逆序删除场景;而ConcurrentBag通过ThreadLocal做一次预分配,避免直接竞争共享资源,非常适合池化资源的分配。