• springboot入门01 – 缓存的使用

    前两天解决了一个Spring缓存的问题,因此就想到记录一下spring-boot缓存的使用。 开启缓存 SpringBoot开启缓存也容易,在启动类上添加@EnableCaching注解就可以了,不需要过多的配置。不过此时开启的缓存是比较简单的缓存,即基于ConcurrentHashMap实现的缓存。虽然简单,但对于负载不高的应用也足够用了。 SpringBoot缓存的两个关键类是:CacheAutoConfiguration和CacheAspectSupport。这两个类分别位于spring-boot-autoconfigure和spring-context包。也就是说不使用spring-boot-starter-cache包也能在spring-boot中使用简单的缓存。 在CacheType类中可以看到SpringBoot支持的缓存类型。懒得一个一个介绍了,直接看下代码好了: 可以看到,支持了差不多所有的主流缓存。 缓存注解 spring boot的缓存主要是由注解支持实现的。下面是几个常见的注解: @Cacheable:针对方法进行配置,根据方法的请求参数缓存返回值,如无缓存值则执行方法; @CacheEvict:用于移除缓存记录,调用方法时会执行移除操作; @CachePut:用于更新缓存记录;保证方法一定会被调用,同时缓存方法返回值;与@Cacheable的区别在于是否每次都调用方法; @CacheConfig:统一配置管理类内部所有缓存注解的属性。 @Cacheable和@CachePut注解属性说明 keyGenerator:缓存数据key生成策略; CacheManager:缓存管理器,管理缓存组件; CacheResolver:缓存解析器,根据实际情况动态解析决定使用哪个缓存实例; value:缓存实例(一个缓存实例可以存储多个缓存KV对)的名称,至少需要设置一个; key:缓存的 key,如果不为空需要要按照 SpEL 表达式编写;如果不为空则缺省按照方法的所有参数进行组合; condition:缓存条件,使用 SpEL 编写;若不为空,只在条件结果为 true 时才进行缓存; unless:缓存排除条件,当条件结果为TRUE时不会缓存; sync:是否同步操作,如为true,那么多个线程访问同一条记录时,会同步执行调用的方法。 @CacheEvict注解属性说明 @CacheEvict注解的属性大致同@Cacheable一致,不过@CacheEvict还有两个独有的属性: allEntries:是否清空所有缓存内容,缺省为 false;如果指定为 true,则方法调用后将立即清空所有缓存; beforeInvocation:是否在方法执行前就清空,缺省为 false;如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存。 @CacheConfig注解属性说明 @CacheConfig注解的属性@Cacheable都有。因为@CacheConfig注解的作用本来就是统一管理相同类下所有方法的缓存配置。 需要提一下的就是在spring4.1之前是没有@CacheConfig注解的,那时候需要在每个方法的@Cacheable注解中设置cacheNames属性。 SpEL表达式 下面简单介绍下SpEL表达式的语法——如下表所示,摘自Spring官方文档: 名称 位置 描述 示例 methodName root对象 当前被调用的方法名 #root.methodname method root对象 当前被调用的方法 #root.method.name target root对象 当前被调用的目标对象实例 #root.target targetClass root对象 当前被调用的目标对象的类 #root.targetClass args root对象 当前被调用的方法的参数列表 #root.args[0] caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result 注意事项: 当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性, 如@Cacheable(key = “targetClass + methodName +#p0″); 使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”,如:@Cacheable(value=”users”, key=”#id”)和@Cacheable(value=”users”, key=”#p0″)。 类型 运算符 关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne 算术 +,- ,* ,/,%,^ 逻辑 &&,||,!,and,or,not,between,instanceof 条件 ?: (ternary),?: (elvis) 正则表达式 matches 其他类型 ?.,?[…],![…],^[…],$[…] 使用Caffeine缓存 前面的配置,即只在在启动类上添加@EnableCaching注解的情形下,我们只能使用最基础的SIMPLE缓存。现在我们尝试使用Caffeine缓存,就需要再多做些配置了。 我在做这个配置的时候遇到了些问题,所以在这里也简单记录下解决问题的过程。 首先,需要在配置文件中指明使用Caffine缓存并添加Caffeine缓存的配置: 然后加入Caffeine缓存依赖: 然而此时调用方法会发现启动失败,报错如下: 提示缺少CacheManager,说实话一开始看到这个错误提示我是不知咋回事儿的。所以尝试从debug信息中找些提示。将spring-boot的debug配置设置为true,再重新执行,在一长串的autoconfigure解析日志中有如下提示: 提示Caffeine缓存的自动配置需要两个类com.github.benmanes.caffeine.cache.Caffeine和org.springframework.cache.caffeine.CaffeineCacheManager。前者在引入caffeine的jar以后已经不是问题,后者则需要引入新的依赖。 CaffeineCacheManager这个类位于spring-context-support中,我们可以直接引入这个依赖,不过更推荐通过spring-boot-starter-cache来间接引入,这样指向性会更明确一些。

    [阅读更多...]
  • FileInputStream与BufferedInputStream

    程序中有一块儿功能是读取硬盘文件到内存里。这块儿代码执行得异常缓慢,读4个G左右的文件竟会用上十几分钟。平时也不着急,但是今天催得紧,就得优化下了。 上传代码大致如下: 这里的问题在于直接使用了FileInputStream,就是说没有使用任何缓冲(buffer)。因此是一个字节一个字节地从磁盘里面读取文件,又一个字节一个字节地写到内存里,这样子效率可想而知。 尝试优化下,每次多读一些数据: 这样效果明显好了些,因为不再是一个字节一个字节的读了,而是每次读取了1024个字节的数据,也就是每1024字节内存才会与硬盘进行一次交互。因为大量减少了硬盘与内存的交互,速度自然就快了。 继续优化,使用BufferedInputStream: BufferedInputStream是FilterInputStream的子类,实现了装饰设计模式。BufferedInputStream是带缓冲区的输入流,默认缓冲区大小是8M,也就是说这个类会一次读取8M数据到内存缓存里以便进行后续操作。其原理与程序二差不多。 通过这三段程序分别测试读取了一个不太大的文件(1.57M),这三段程序的耗时分别是 8825、15、5,时间单位毫秒。 做一次调整:将程序二的缓存数组长度改为2048 * 1024,会看到执行时长也是5ms。 再做一次调整:这次读取一个34.6M的文件,并将程序二的缓存数组长度调整为8 * 1024 * 1024。可以看到程序二与程序三的执行时长分别是73ms与97ms。二者的性能还是比较接近的。 就这样!

    [阅读更多...]
  • Metrics学习03 – Histogram

    Histogram用来统计数据的分布。Histogram可以提供收集到的数据的最大值、最小值、平均值和中值,此外还能提供百分比分布,如75%,95%,99.9%等等。 Histogram是我学习Metrics的驱动之一。最初是想使用Histogram来优化接口处理统计能力。 看下下面的类图: 类图表示了Histogram需要用到的几类和个接口之间的关系,简单说明下: Sampling接口:意思是取样器,只有一个方法,作用是取出某一阶段的统计结果快照(Snapshot); Reservoir接口:数据池,所有的记录最终都会写入Reservoir实例并完成运算;Metrics提供了多种数据池来执行不同的抽样运算; Snapshot接口:快照接口,作用是对Reservoir中的数据进行二次计算并生成统计结果;Snapshot提供了统计数据的最大值、最小值、标准差、平均值、中值、95分位值等指标; Histogram类:实现了Sampling接口,是对外交互的入口。 老规矩,看一段示例代码: 这段代码中定义了一个delayedMethod()方法,该方法会随机sleep一段时间来模拟方法执行时长。代码主体就是使用Histogram报表来统计每三秒钟内这个方法的执行状态。 MetricRegistry也提供了Histogram实例的创建注册方法,不过为这里了更直观一些,还是使用了直接new关键字来构建Histogram实例。可以看到,每次创建Histogram对象都需要传入一个Reservoir接口的实例。 看下执行结果片段: 统计结果即是由Snapshot提供。 简单介绍下metrics提供的几种Reservoir: UniformReservoir:默认保存1028条记录,每次进行update操作的时候,首先会依次地将值填入1028条记录中,当记录满了之后,就会使用随机替换0 – 1027中的一条(随机抽样1028条记录)。因为是随机替换,所以也不需要进行加锁和解锁。 SlidingWindowReservoir:固定大小的数据池,从0到n-1填入数据,但是不会对数据进行更新,也不会进行加锁和解锁(固定抽样n条记录)。 SlidingTimeWindowReservoir:非固定大小的数据池,但是只会存储过去N秒的数据(抽样N秒内的记录)。使用ConcurrentSkipListMap进行存储。 ExponentiallyDecayingReservoir:固定大小的数据池。首先会逐个数据填满数据池,随后会将老的数据替换为新的数据(抽样n条最新的记录),使用ConcurrentSkipListMap进行存储。可以说是SlidingWindowReservoir与SlidingTimeWindowReservoir的结合。 就这样。

    [阅读更多...]
  • dependencyManagement导致版本冲突

    今天遇到了一个问题: 程序中某处报了ClassNoDefineError。这个类属于jna框架。检查jna的jar,发现确实没有那个类。关键在于这个报错是在一个依赖内部发生的,jna的jar是这个依赖的内部依赖,即当前应用的一个间接依赖。因为使用的jar是一个比较小众的服务,所以第一印象就是这个服务依赖的jna版本错了。进入到服务的pom看了后,发现该服务使用的jna版本是正确的,但是当前应用的jna版本却是错误的。 首先想到的是依赖冲突,使用如下命令检查了当前应用的全部依赖: 检查后可以确认当前应用的依赖(包括间接依赖)中只有一个jna框架,可以排除依赖冲突的问题。 仔细思索了一会儿,猜测是spring框架的问题。 退到工程最外部的pom文件,找到了spring-boot-dependencies: 检查spring-boot-dependencies的pom果然看到了jna的版本变量: 这个版本和应用的版本是一致的。随后在spring-boot-dependencies的dependencyManagement中找到了jna框架: 现在问题清晰了。问题出在maven的dependencyManagement上。 dependencyManagement的作用主要是依赖版本控制。其影响的范围包括: 其范围内的未直接声明版本的依赖; 其范围内的所有间接依赖(不论声明版本与否)。 在这个应用中,spring-boot-dependencies是应用pom的dependencyManagement成员,因此spring-boot-dependencies自己的dependencyManagement成员也会传递生效。jna框架是应用通过一个依赖的间接依赖,虽然在这个依赖中指明了jna的版本号,但是受到spring-boot-dependencies的dependencyManagement的影响,还是使用了一个早期的版本。 解决方式两个: 将jna框架也添加到dependencyManagement中; 先exclude掉jna间接依赖,再直接添加jna依赖并声明版本号

    [阅读更多...]