作者:热黄油啤酒
高频数据流处理系统中,Redis的压力也会很大,同时I/0开销才是耗时的主要原因。
前言
我们开发中经常用到Redis作为缓存,将高频数据放在Redis中能够提高业务性能,降低MySQL等关系型数据库压力,甚至一些系统使用Redis进行数据持久化,Redis松散的文档结构非常适合业务系统开发,在精确查询,数据统计业务有着很大的优势。
但是高频数据流处理系统中,Redis的压力也会很大,同时I/0开销才是耗时的主要原因,这时候为了降低Redis读写压力我们可以用到本地缓存,Guava为我们提供了优秀的本地缓存API,包含了过期策略等等,编码难度低,个人非常推荐。
设计示例
Redis懒加载缓存
数据在新增到MySQL不进行缓存,在精确查找进行缓存,做到查询即缓存,不查询不缓存。
1)流程图
2)代码示例
//伪代码示例Xx代表你的的业务对象如UserGoods等等publicclassXxLazyCache{
AutowiredprivateRedisTemplateString,XxredisTemplate;AutowiredprivateXxServicexxService;//你的业务service/***查询通过查询缓存是否存在驱动缓存加载建议在前置业务保证id对应数据是绝对存在于数据库中的*/publicXxgetXx(intid){//1.查询缓存里面有没有数据XxxxCache=getXxFromCache(id);if(xxCache!=null){returnxxCache;//卫语句使代码更有利于阅读}//2.查询数据库获取数据我们假定到业务这一步,传过来的id都在数据库中有对应数据Xxxx=xxService.getXxById(id);//3.设置缓存、这一步相当于Redis缓存懒加载,下次再查询此id,则会走缓存setXxFromCache(xx);returnxx;}}/***对xx数据进行修改或者删除操作操作数据库成功后删除缓存*删除请求-删除数据库数据删除缓存*修改请求-更新数据库数据删除缓存下次在查询时候就会从数据库拉取新的数据到缓存中*/publicvoiddeleteXxFromCache(longid){Stringkey="Xx:"+xx.getId();redisTemplate.delete(key);}privatevoidsetXxFromCache(Xxxx){Stringkey="Xx:"+xx.getId();redisTemplate.opsForValue().set(key,xx);}privateXxgetXxFromCache(intid){//通过缓存前缀拼装唯一主键作为缓存Key如Xxx信息就是Xxx:idStringkey="Xx:"+id;returnredisTemplate.opsForValue().get(key);}}//业务类publicclassXxServie{AutowiredprivateXxLazyCachexxLazyCache;//查询数据库publicXxgetXxById(longid){//省略实现returnxx;}publicvoidupdateXx(Xxxx){//更新MySQL数据省略//删除缓存xxLazyCache.deleteXxFromCache(xx.getId());}publicvoiddeleteXx(longid){//删除MySQL数据省略//删除缓存xxLazyCache.deleteXxFromCache(xx.getId());}}//实体类DatapublicclassXx{//业务主键privateLongid;//...省略}3)优点
保证最小的缓存量满足精确查询业务,避免冷数据占用宝贵的内存空间;
对增删改查业务入侵小、删除即同步;
可插拔,对于老系统升级,历史数据无需在启动时初始化缓存。
4)缺点
数据量需可控,在无限增长业务场景不适用;
在微服务场景不利于全局缓存应用。
5)总结
空间最小化;
满足精确查询场景;
总数据量可控推荐使用;
微服务场景不适用。
Redis结合本地缓存
微服务场景下,多个微服务使用一个大缓存,流数据业务下,高频读取缓存对Redis压力很大,我们使用本地缓存结合Redis缓存使用,降低Redis压力,同时本地缓存没有连接开销,性能更优。
1)流程图
2)业务场景
在流处数处理过程中,微服务对多个设备上传的数据进行处理,每个设备有一个code,流数据的频率高,在消息队列发送过程中使用分区发送,我们需要为设备code生成对应的自增号,用自增号对kafka中topic分区数进行取模,这样如果有台设备,自增号就是0~,在取模后就进行分区发送就可以做到每个分区均匀分布,这个自增号我们使用redis的自增数生成,生成后放到redis的hash结构进行缓存,每次来一个设备,我们就去这个hash缓存中取,没有取到就使用自增数生成一个,然后放到redis的hash缓存中,这时候每个设备的自增数一经生成是不会再发生改变的,我们就想到使用本地缓存进行优化,避免高频的调用redis去获取,降低redis压力。
3)代码示例
/***此缓存演示如何结合redis自增数hash本地缓存使用进行设备自增数的生成、缓存、本地缓存*本地缓存使用GuavaCache*/publicclassDeviceIncCache{/***本地缓存*/privateCacheString,IntegerlocalCache=CacheBuilder.newBuilder().concurrencyLevel(16)//并发级别.initialCapacity()//初始容量.maximumSize()//缓存最大长度.expireAfterAccess(1,TimeUnit.HOURS)//缓存1小时没被使用就过期.build();
AutowiredprivateRedisTemplateString,IntegerredisTemplate;/***redis自增数缓存的key*/privatestaticfinalStringDEVICE_INC_COUNT="device_inc_count";/***redis设备编码对应自增数的hash缓存key*/privatestaticfinalStringDEVICE_INC_VALUE="device_inc_value";/***获取设备自增数*/publicintgetInc(StringdeviceCode){//1.从本地缓存获取Integerinc=localCache.get(deviceCode);if(inc!=null){returninc;}//2.本地缓存未命中,从redis的hash缓存获取inc=(Integer)redisTemplate.opsForHash().get(DEVICE_INC_VALUE,deviceCode);//3.redis的hash缓存中没有,说明是新设备,先为设备生成一个自增号if(inc==null){inc=redisTemplate.opsForValue().increment(DEVICE_INC_COUNT).intValue;//添加到redishash缓存redisTemplate.opsForHash().put(DEVICE_INC_VALUE,deviceCode,inc);}//4.添加到本地缓存localCache.put(deviceCode,inc);//4.返回自增数returninc;}}4)优点
redis保证数据可持久,本地缓存保证超高的读取性能,微服务共用redis大缓存的场景能有效降低redis压力;
guava作为本地缓存,提供了丰富的api,过期策略,最大容量,保证服务内存可控,冷数据不会长期占据内存空间;
服务重启导致的本地缓存清空不会影响业务进行;
微服务及分布式场景使用,分布式情况下每个服务实例只会缓存自己接入的那一部分设备的自增号,本地内存空间最优;
在示例业务中,自增数满足了分布区发送的均匀分布需求,也可以满足统计设备接入数目的业务场景,一举两得。
5)缺点
增加编码复杂度,不直接;
只适用于缓存内容只增不改的场景。
6)总结
本地缓存空间可控,过期策略优;
适用于微服务及分布式场景;
缓存内容不能发生改变;
性能优。
后记
redis提供了丰富的数据类型及api,非常适合业务系统开发,统计计数(increment,decrement),标记位(bitmap),松散数据(hash),先进先出、队列式读取(list);guava缓存作为本地缓存,能够高效的读取的同时,提供了大量api方便我们控制本地缓存的数据量及冷数据淘汰;我们充分的学习这些特性能够帮助我们在业务开发中更加轻松灵活,在空间与时间上找到一个平衡点。
来源:dbaplus社群