一、Spring Cache和Redis的区别

  1. Spring cache是代码级的缓存,一般是使用一个ConcurrentMap,也就是说实际上还是是使用JVM的内存来缓存对象的,这势必会造成大量的内存消耗。但好处是显然的:使用方便。
  2. Redis 作为一个缓存服务器,是内存级的缓存。它是使用单纯的内存来进行缓存。
  3. 集群环境下,每台服务器的spring cache是不同步的,这样会出问题的,spring cache只适合单机环境。
  4. Redis是设置单独的缓存服务器,所有集群服务器统一访问redis,不会出现缓存不同步的情况。
  1. 缓存级别不同
    Spring cache是代码级的缓存,他一般是使用一个ConcurrentMap。也就是说实际上还是是使用JVM的内存来缓存对象的,
    那么肯定会造成大量的内存消耗。但是使用方便。
    Redis 作为一个缓存服务器,是内存级的缓存。它是使用单纯的内存来进行缓存。
  2. sprirng cache+redis的好处

那么Spring cache +redis的好处显而易见了。既可以很方便的缓存对象,同时用来缓存的内存的是使用redis的内存,不会消耗JVM的内存,提升了性能。当然这里Redis不是必须的,换成其他的缓存服务器一样可以,只要实现Spring的Cache类,并配置到XML里面就行了。

  1. 集群环境下的springcache+redis

集群环境下,每台服务器的spring cache是不同步的,这样会出问题的,spring cache只适合单机环境redis是设置单独的缓存服务器,所有集群服务器统一访问redis,不会出现缓存不同步的情况spring cache是很早就有的东西,现在+redis是为了顺应时代,更好的兼容集群环境,加强保留spring cache功能,不如直接使用redis

二、Spring cache的基本原理:

和 spring 的事务管理类似,spring cache 的关键原理就是 spring AOP,通过 spring AOP,其实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。

三、什么样的数据需要放进缓存呢?

把数据放入缓存,有三个标准:

  1. 数据量不大
  2. 访问频率高
  3. 数据更改频率低

四、为什么要使用SpringCache

先看一下我们使用缓存步骤:

  1. 查寻缓存中是否存在数据,如果存在则直接返回结果
  2. 如果不存在则查询数据库,查询出结果后将结果存入缓存并返回结果
  3. 数据更新时,先更新数据库
  4. 然后更新缓存,或者直接删除缓存

此时我们会发现一个问题,所有我们需要使用缓存的地方都必须按照这个步骤去书写,这样就会出现很多逻辑上相似的代码。并且我们程序里面也需要显示的去调用第三方的缓存中间件的API,如此一来就大大的增加了我们项目和第三方中间件的耦合度。

因此我们需要将查询缓存存入缓存这类似的代码封装起来用框架来替我们实现,让我们更好的去处理业务逻辑。那么我们如何让框架去帮我们自动处理呢,这不就是典型的AOP思想吗?

是的,Spring Cache就是一个这样的框架。它利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,用户可以3秒钟就使用上一个很不错的缓存功能。

五、redis实现排行榜功能

由于一个玩家名次上升x位将会引起x+1位玩家的名次发生变化(包括该玩家),如果采用传统数据库(比如MySQL)来实现排行榜,当玩家人数较多时,将会导致对数据库的频繁修改,性能得不到满足,所以我们只能另想它法。

redis的zset结构有着天然的排序功能,十分适合并发量大的排行功能。通过key值确定排行榜的范围,使用members来作为排序的标识,score作为排序的依据。

5.1 排行榜的key设计

排行榜一般按照时间段进行分类,分别有周榜月榜年榜。这样要区分不同的榜单就需要和时间关联上,通过时间判断来将数据都保存到对应的zset中。

例如年榜可以用年份来标识,今年的key就是2020,去年的key是2019。月榜的话通过月数来标识。周榜的情况有点特殊,可以通过当天处于本年的第几周来确定zset的key。这样设置的key为周数当,就可以统计当周的数据了

java中有直接获取周数的方法:

虽然可以快速获取到当天的所属周数,但是有的排行榜需要查看上一周的数据。那么上周周榜的key,即获取上一周的周数只需在本周的周数减去一就可以。

问题

假如今天是本年的第一周,那么上一周的周榜的key就等于0,显然这是不合理的。那么这个应该怎么处理呢?

其实本年的第0周就是上一年的最后一周,只需要获取到上一年的最后一周的周数就可以。所以第一步先把当前日期往前推7天在获取那天所属的周数,就可以解决跨年获取周数的问题。

5.2 ZSet同分排序规则

score相同的情况下,哪条数据会排在前面呢?

经查阅资料验证确实当score值相同时,按照member的字符顺序进行排列。

5.3 ZSet多字段排序

zset只能根据score进行排序,也就是单字段排序

既然只有一个排序字段,那么根据排序规则的权重重新组合这个数值。通过把权重高的数值放在组合数的最前面来达到数值比较上的优势。

score通过拼接获得,通过不同字段的重要性划分权重,从而把不同字段放到对应上的数位上的位置,这样就可以根据不同字段构造出不同大小的score,从而实现排序。

缺点:这个方法需要注意的点就是zset的score字段是double类型。在double类型中的数值类型,需要注意整数的精度和小数点精度。double能保存最多16位的数字,如果复合排序字段中有时间的话,用于其他字段排序的数字只有6位,这就是复合数值排序的限制之处。倒序排序的字段需要知道排名期间的最大值,才能进行取反

5.4 Redis数据备份

redis数据库属于内存性数据库,虽然速度快,也有持久化策略来保障高可用。但是大量的数据需要设置过期时间来腾出内存空间,所以需要通过定时任务将数据落到数据库中来保证数据,同时可以方便导出这些数据。实现起来也比较简单,通过当前所属周获取到redis的数据,对位落库就可以。

需要注意的就是定时任务的时间节点。为了保证本周的数据落库是最完整的数据,需要在下一周的第一次同步时再进行一次备份。这个时候就可以通过在redis中设置一个标志位,每次更新前判断是有上周数据已经同步的标志。(写时复制,RDB