1.List的拆分的几种方式
2.说下你可能没用过的EventBus
3.浅析本地缓存技术 - Guava Cache | 京东物流技术团队
4.限速神器RateLimiter源码解析
5.RateLimiter 限流底层原理解析
List的拆分的几种方式
在开发过程中,处理大型集合时常常需要将其拆分成小块,这种操作被称作分片或List的分割。其实,许多大神已经为我们提供了现成的工具,省去了自定义代码的纸巾影院 源码繁琐。以下是Java中常用的三种分片实现方法:
1. Google的Guava框架:在pom.xml中添加Guava支持后,通过Lists.partition()方法实现切片,代码示例如下:
...
2. Apache的commons框架:同样在pom.xml添加支持,使用ListUtils.partition(),代码示例如下:
...
3. Hutool工具类:引入Hutool框架后,利用ListUtil.partition()完成切片,代码示例如下:
...
值得注意的是,选择哪种方法取决于项目的实际需求和已有的依赖。例如,批量数据处理时,如果数据量大,可以考虑将数据拆分以避免一次性插入数据库导致的性能问题或超限错误。具体操作时,可以根据数据库的配置进行调整。
这些工具包如Guava、Apache Commons和Hutool,提供了丰富的实用工具,性能优良,是开发者的得力助手。无需深入源码,直接使用即可大大简化工作。
以上内容源于程序员xiaozhang的文章,原文链接:cnblogs.com/scott/p...
说下你可能没用过的EventBus
最近在Code Review过程中,我发现了一个业务场景:业务处理完成后,需要通知审核人员,通知方式包括短信和邮件。代码如下:
这个方法对吗?
针对这种常见的业务场景,我们首先会考虑同步或异步发送的溯源码售价问题。
如果选择同步,会对接口的响应时间产生影响,并且与业务逻辑紧密耦合,这显然不是好的做法。
一般情况下,我们会选择异步方式,使用消息队列(MQ)进行消息的发送和消费,或者使用线程池来处理,这样不会影响主业务逻辑,可以提高性能,并且实现了解耦。
此外,我们还需要考虑数据一致性的问题,邮件是否一定要发送成功?
大多数情况下,我们并不要求邮件必须%发送成功,失败了就失败了,只要监控告警的失败率不超过阈值即可。同时,消息服务一旦收到请求,应该自行保证消息的投递。
因此,总的来说,使用MQ发送消息并自行处理,或者使用线程池进行异步处理,最后自行处理补偿逻辑,就能解决这类问题。
那么,今天要介绍的是这两种解决方案之外的处理方式。对于这种场景,我们可以使用EventBus来解决。
EventBus是事件总线的意思,它是Google Guava库的一个工具,基于观察者模式,资源码训练可以在进程内实现代码的解耦。
以上面的例子来说,引入MQ可能会有些过于复杂,其实使用EventBus也能达到相同的效果。与MQ相比,EventBus只能提供进程内的消息事件传递,但对于我们的业务场景来说,这已经足够了。
接下来,我们来看一下EventBus的使用方法。首先,创建一个EventBus实例。
第二步,创建一个事件消息订阅者,处理方式非常简单,只需在希望处理事件的方法上添加@Subscribe注解即可。
形参只能有一个,如果定义0个或多个,运行时将会报错。
第三步,注册事件。
第四步,发送事件。
这就是EventBus使用的最简单例子。下面我们看看如何结合开头的例子进行处理。
比如上面提到的案例,比如注册和用户下单的场景,都需要发送消息和邮件给用户。
EventBus并不强制要求我们使用单例模式,因为其创建和销毁成本较低,所以更多是根据我们的业务场景和上下文来选择。
在业务逻辑处理完成后,分别注册了邮件和短信两个事件订阅者。家有儿女源码
最后,我们发送事件,用户注册时发送一个用户ID,下单成功时发送一个订单ID。
然后写一个测试类进行测试,分别创建两个service,然后分别调用方法。
执行测试类,我们可以看到输出,分别执行了事件订阅的方法。
使用起来你会发现非常简单,对于希望轻量级简单地实现解耦,使用EventBus非常合适。
注意,例子中的参数都是Long类型。如果事件的参数是其他类型,那么消息是无法接收到的。比如,如果我们将下单中发送的订单ID改为String类型,会发现没有消费。因为我们没有定义一个参数类型为String的方法。
去EmailMsgHandler和SmsMsgHandler都新增一个接收String类型的订阅方法,这样就可以接收到了。
除此之外,我们还可以定义一个DeadEvent来处理这种情况,它相当于是一个默认的处理方式。当没有匹配的事件类型参数时,就会默认发送一个DeadEvent事件。
定义一个默认处理器。
给BookingService新增一个pay()支付方法,下单完成后去支付,注册我们的默认事件。
执行测试bookingService.pay(),菲特云源码看到输出结果:
OK,简单的介绍就到这里。到目前为止,我们所说的都是同步调用,这并不符合我们的要求。我们当然希望使用异步处理更好。
那就看看源码它是如何实现的。
identifier是一个名字,标记,默认是default。
executor是执行器,默认创建一个MoreExecutors.directExecutor(),事件订阅者根据你提供的executor来决定如何执行事件订阅的处理方式。
exceptionHandler是异常处理器,默认创建的就是打点日志。
subscribers是我们的消费者,订阅者。
dispatcher用来做事件分发。
默认创建的executor是一个MoreExecutors.directExecutor(),看到command.run()你就会发现这其实是同步执行。
同步执行并不太好。我们希望不仅解耦,还要异步执行。EventBus提供了AsyncEventBus,我们可以自己传入executor。
上面的代码我们改成异步的,这样不就好起来了嘛。实际上,我们可以结合自己的线程池来处理。
OK,这个说清楚了。我们可以顺便再看看事件分发的处理。看到DeadEvent了吗?没有当前事件的订阅者,就会发送一个DeadEvent事件,bingo!
OK,这个使用和源码都比较简单,有兴趣的同学可以自己去看看,花不了多少功夫。
总的来说,EventBus提供了一个更优雅的代码解耦方式,你在实际工作中的业务中肯定能用得上它!
浅析本地缓存技术 - Guava Cache | 京东物流技术团队
本地缓存技术,特别是 Guava Cache,作为 Java 开发中的重要工具,其在实际项目中的应用广受好评。Guava Cache 提供了高效的缓存管理机制,大大提升了应用性能。本文从应用场景、使用方式、源码分析以及总结四个方面,深入解析 Guava Cache 的特性及其在开发过程中的应用。应用场景
本地缓存的优势在于数据读写都在同一个进程中进行,避免了网络传输的延迟,访问速度得到显著提升。然而,这也意味着它受到 JVM 内存的限制,不适用于数据量特别庞大的场景。因此,Guava Cache 主要适用于以下场景: 参数配置存储:在应用程序中,参数配置通常频繁访问,但改动较少,此时缓存配置可以显著提升性能。使用方式
Guava Cache 的核心类包括 CacheBuilder 和 Cache。CacheBuilder 用于构建缓存,而 Cache 则用于存放缓存数据。引入 Maven 依赖后,你可以按照以下步骤创建和使用缓存:实例化缓存
设置缓存初始化参数,如初始容量、最大缓存数、并发等级、写入后刷新时间等。
使用 get 方法获取数据,若不存在则通过指定的 Callable 方法构造缓存。
实现数据的被动删除与主动删除。
存储原理
Guava Cache 的数据结构基于 ConcurrentHashMap,但其设计更为灵活,能够通过设置自动回收机制限制内存占用。核心类 LocalCache 实现了 ConcurrentMap 接口,其数据结构主要由 Segment 数组、ReferenceEntry 链表和 AtomicReferenceArray 组成。通过 Segment 数组实现并发操作,每个 Segment 拥有独立的锁,确保了高并发下的数据安全。总结
本文对 Guava Cache 的应用场景、使用方式、存储原理进行了深入探讨,帮助开发者理解其在实际开发中的应用。通过阅读本文,你将对常见的 Guava Cache 有一个清晰的认识,并能够在项目中高效地应用它,提升系统性能。限速神器RateLimiter源码解析
软件系统中一般有两种场景会用到限流:一是管理并发访问,控制多个请求同时执行的数量;二是控制数据生成或传输速率,避免过快消耗资源。常见的限流算法有漏桶算法、令牌桶算法等。本文将介绍谷歌Guava包中的限流组件RateLimiter,它基于令牌桶算法,通过控制令牌的生成和消费,实现对系统资源的合理分配。
RateLimiter的实现简单,只需要引入guava jar,适用于各种场景。本文介绍的源码基于版本.1-jre。使用时,RateLimiter提供直观的示例,帮助用户快速上手。例如,控制任务列表的提交速率不超过每秒2个,或者以不超过5kb/s的速率产生数据流。
RateLimiter的核心功能是限速,通过令牌桶算法实现。在使用时,系统会根据预先设定的速率生成令牌,并在请求时消费令牌。如果当前没有足够的令牌,系统会等待直至获取令牌。在等待期间,系统会记录等待时间,确保不会因为等待而损失性能。此外,RateLimiter还考虑了资源利用不足的场景,通过存储令牌(storedPermits)来提高系统的灵活性和效率。
RateLimiter内部实现包括RateLimiter类和SmoothRateLimiter类。RateLimiter类是顶级类,提供创建RateLimiter的方法,以及获取令牌的接口。SmoothRateLimiter类是一个抽象类,提供了平滑限速器的功能。SmoothBursty类和SmoothWarmingUp类分别是平滑突发限速器和平滑预热限速器的实现,分别适用于突发和预热场景。
获取令牌的主体流程涉及令牌的存储和更新。在平滑突发限速器中,令牌的存储和更新由一个核心方法实现,该方法通过计算令牌的剩余量和下次令牌发放的时间,确定请求的等待时间。平滑预热限速器则在此基础上进一步实现预热算法,以适应不同场景的性能需求。
在使用RateLimiter时,主要关注获取令牌的方法,如accquire和tryAccquire。这些方法通过计算令牌的剩余量和下次令牌发放的时间,决定请求是否等待以及等待多长时间。在具体实现中,平滑突发限速器和预热限速器在令牌的管理策略上有所不同,平滑突发限速器的实现相对直观,而预热限速器则需要深入理解其背后的算法逻辑。
总之,RateLimiter提供了一种简单而高效的限流机制,通过灵活的算法和接口设计,满足不同场景的需求。在使用过程中,需要注意RateLimiter的实现细节,如令牌的存储和更新策略,以及如何根据实际需求调整限流参数,以达到最佳的性能和资源利用效果。
RateLimiter 限流底层原理解析
学习RateLimiter限流器原理的重要性在于项目中的实际应用。面试官提问时的尴尬经历促使作者深入研究。本文重点解析其限流算法原理、问题及在项目中的使用方式,而非源码。限流原理
RateLimiter基于Google Guava的令牌桶算法,持续均匀产生令牌,消费时需持有令牌,无则需等待。核心是区分SmoothBursty和SmoothWarmingUp两种类型。常用API与示例
SmoothBursty限流器如一秒生成5令牌,即使一次请求超过桶中令牌,可透支未来令牌,但会导致后续请求等待时间增加。如通过RateLimiter.create(2)创建,初始可获取超过限值的令牌,但后续会有所延迟。SmoothWarmingUp预热机制
SmoothWarmingUp限流器有预热期,资源逐渐达到指定速度,适用于资源懒加载等场景,有助于降低重启后服务压力。预热在高并发中的重要性
预热对于高并发场景至关重要,避免因资源未初始化而引发服务故障,比如线程池和数据库缓存等问题。线程安全与设计模式
RateLimiter是线程安全的,通过synchronized和双重检测单例模式确保多线程安全。缺陷与优化
RateLimiter的透支未来令牌设计可能导致请求等待时间延长,需采用拒绝策略控制请求量,避免过度透支。限流算法比较
漏桶算法与令牌桶算法各有优缺点,漏桶算法可能抛弃突发请求,而令牌桶算法如RateLimiter能处理突发流量,但需避免持续高并发导致的延迟。项目使用
项目中可采用AOP方式结合Guava实现限流,通过SpringBoot starter简化配置,减少对代码的侵入性。