Plumecache一个常见分布式缓存封装工具类库
为什么有plumecache?
日常项目开发中使用缓存的时候,无论选择redis、memcached,或是其它类型缓存,我们总要在做一些封装,才能很好的使用。plumecache是我个人缓存使用经验的一些总结,旨在于解决日常使用缓存时的一些问题和痛点,写出来和大家分享,也欢迎大家的star和issue Gitee地址简介
Plumecache 是一个常见分布式缓存封装工具类库,降低相关client api学习成本,提高工作效率,当前支持reids(standalone,sentinel,cluster)、memcached
Plumecache解决什么问题?1.用于替换项目中CacheService、CacheHelper、CacheHandler、CacheUtils等缓存工具类或胶水代码2.支持多缓存实例管理,解决既要还要的问题3.支持缓存key前缀、缓存版本的配置4.支持缓存序列化统一处理,不需要在每个调用的地方写序列化,支持自定义5.支持大缓存压缩处理,支持自定义6.支持自定义拦截器的AOP扩展,可以处理参数、返回值、异常快速使用通用接入1.maven(因域名问题,暂没有上传中央仓库),可以下载源码install到本地或者上传到私服,也可以先下载jar org.plume plumecache-core 1.0.0 复制代码2.配置plumecache: instances: - type: redis endpoint: 127.0.0.1:6379 prefix: order 复制代码3.使用@Test public void test(){ CacheService cacheService=CacheServiceFactory.getInstance(); cacheService.set("name","anson.yin"); System.out.println(cacheService.get("name")); } 复制代码spring boot接入1.maven(因域名问题,暂没有上传中央仓库),可以下载源码install到本地或者上传到私服,也可以先下载jar org.plume plumecache-spring-boot-starter 1.0.0 复制代码2.配置plumecache: instances: - type: redis endpoint: 127.0.0.1:6379 prefix: order 复制代码3.使用@Autowired private CacheService cacheService; @RequestMapping(value = "/hello") public String hello(){ cacheService.set("name","anson.yin"); return"hello,".concat(cacheService.get("name")); } 复制代码功能说明配置plumecache: instances: - type: redis #必填 name: redis #非必填,缺省为和type一样 endpoint: 127.0.0.1:6379 #必填 prefix: order #非必填,like "prefix@key" serializer: org.plumecache.samples.FastjsonCacheSerializer #非必填,指定序列化类,缺省使用Gson序列化 compressor: org.plumecache.samples.NoneCacheCompressor #非必填,指定压缩类,缺省使用Gzip压缩 exclude-interceptors: SlowCacheInterceptor,LogCacheInterceptor #非必填,排除不需要的拦截器 复制代码多实例
配置plumecache: instances: - type: redis #必填 name: redis #非必填,缺省为和type一样 endpoint: 127.0.0.1:6379 #必填 prefix: order #非必填,like "prefix@key" serializer: org.plumecache.samples.FastjsonCacheSerializer #非必填,指定序列化类,缺省使用Gson序列化 compressor: org.plumecache.samples.NoneCacheCompressor #非必填,指定压缩类,缺省使用Gzip压缩 exclude-interceptors: SlowCacheInterceptor,LogCacheInterceptor #非必填,排除不需要的拦截器 - type: memcached endpoint: 127.0.0.1:11211 - type: memcached name: memcached2 endpoint: 127.0.0.1:11222 - type: rediscluster endpoint: 127.0.0.1:6379,127.0.0.1:6389,127.0.0.1:6399 复制代码
说明0.上面的配置了四个缓存实例1.默认的缓存实例是第一个配置2.同一种类型多个实例,需要配置不同name来区分
代码示例@Test public void testMultipleInstances() { //和CacheService redis =CacheServiceFactory.getInstance("redis")效果一样 CacheService redis = CacheServiceFactory.getInstance(); redis.set("instance", "redis"); System.out.println(redis.get("instance")); CacheService memcached = CacheServiceFactory.getInstance("memcached"); memcached.set("instance", "memcached"); System.out.println(memcached.get("instance")); } 复制代码序列化1.默认使用Gson作为序列化2.自定义序列化,需要实现CacheSerializer接口,并且在配置配置序列化类
下面是FastjsonCacheSerializer的实现示例public class FastjsonCacheSerializer implements CacheSerializer { @Override public String serialize(T value) { return JSON.toJSONString(value); } @Override public T deserialize(String value, Class clazz) { return JSON.parseObject(value, clazz); } } 复制代码缓存压缩1.实体类添加 @CacheCompress 注解@Data @CacheCompress public class User { private String name; private Integer age; private String address; } 复制代码2.调用方式不变 @Test public void testCompress() { User user = new User(); user.setAge(100); user.setName("zhangsanfeng"); user.setAddress("zhangsanfengzhangsanfengzhangsanfengzhangsanfengzhangsanfengzhangsanfengzhangsanfengzhangsanfengzhangsanfengzhangsanfengzhangsanfeng"); cacheService.set("user", user); User cacheUser = cacheService.get("user", User.class); System.out.println(cacheUser); } 复制代码127.0.0.1:6379> get order@user " >]H4sIAAAAAAAAAKtWSkxJKUotLlayUqrKSMxLL07MS0vNSx8otpKOUmJ6qpKVoYGBjlJeYm4qmruU agHJDU9isgAAAA==" 复制代码3.自定义压缩器
默认使用gzip压缩,也可以自定义实现,且添加配置
参考GzipCacheCompressor.java缓存版本1.实体类添加 @CacheVersion 注解@Data @CacheVersion("2.0") public class User { private String name; private Integer age; private String address; } 复制代码2.调用方式不变 @Test public void testVersion() { User user = new User(); user.setAge(100); user.setName("zhangsanfeng"); user.setAddress("address"); cacheService.set("user", user); User cacheUser = cacheService.get("user", User.class); System.out.println(cacheUser); } 复制代码127.0.0.1:6379> get order@user@2.0 " >5{"name":"zhangsanfeng","age":100,"address":"address"}" 复制代码拦截器
拦截器是对缓存操作的切面处理,自定义拦截器操作如下1.实现CacheInterceptor,或者继承BaseCacheInterceptor,举例如下@Slf4j public class SlowCacheInterceptor extends BaseCacheInterceptor { @Override public boolean preHandle(CacheService target, Method method, Object[] args, Map context) { Instant begin = Instant.now(); context.put("SlowCacheInstantBegin", begin); return true; } @Override public void postHandle(CacheService target, Method method, Object[] args, Map context, Object result) { Instant begin = (Instant) context.get("SlowCacheInstantBegin"); if (Duration.between(begin, Instant.now()).toMillis() > 500) { log.warn("[SlowCacheInterceptor]slow cache, method:{},args:{},result:{},cost:{}" , method.getDeclaringClass().getName() + "." + method.getName() , JSON.toJSONString(args) , JSON.toJSONString(result) , Duration.between(begin, Instant.now()).toMillis()); //do something others } } } 复制代码2.添加SPI的配置
path:resources/services/com.plumecache.core.interceptor.CacheInterceptororg.plumecache.samples.SlowCacheInterceptor 复制代码
或者参考core包实现LogCacheInterceptor.javaVersionCacheInterceptor.javaPrefixCacheInterceptor.java管理接口
在spring boot stater且为web项目时生效1.list 显示实例列表curl -X GET localhost:8080/plumecache/list 复制代码[{"properties":{"name":"memcached","type":"memcached","endpoint":"127.0.0.1:11211","prefix":null,"serializer":null,"compressor":null,"excludeInterceptors":null},"statistics":{"cmd_touch":"0","moves_to_cold":"5","incr_hits":"1","get_flushed":"0","evictions":"0","touch_hits":"0","expired_unfetched":"0","pid":"1","time_in_listen_disabled_us":"0","response_obj_bytes":"65536","cas_badval":"0","cmd_flush":"0","total_items":"6","read_buf_oom":"0","round_robin_fallback":"0","slab_reassign_rescues":"0","log_watcher_skipped":"0","cas_hits":"0","accepting_conns":"1","auth_errors":"0","slab_reassign_evictions_nomem":"0","log_watcher_sent":"0","reserved_fds":"20","slab_reassign_running":"0","response_obj_oom":"0","crawler_items_checked":"12","direct_reclaims":"0","conn_yields":"0","slab_reassign_busy_deletes":"0","version":"1.6.10","read_buf_count":"8","listen_disabled_num":"0","slab_global_page_pool":"0","get_misses":"0","hash_is_expanding":"0","touch_misses":"0","get_expired":"0","auth_cmds":"0","cas_misses":"0","delete_misses":"0","cmd_meta":"0","get_hits":"4","slab_reassign_inline_reclaim":"0","malloc_fails":"0","delete_hits":"0","log_worker_written":"0","read_buf_bytes_free":"49152","lru_bumps_dropped":"0","curr_connections":"2","bytes_written":"4650","slab_reassign_busy_items":"0","hash_bytes":"524288","libevent":"2.1.8-stable","read_buf_bytes":"131072","lrutail_reflocked":"0","crawler_reclaimed":"0","decr_hits":"0","limit_maxbytes":"67108864","max_connections":"1024","decr_misses":"0","lru_crawler_running":"0","reclaimed":"0","rejected_connections":"0","cmd_get":"4","hash_power_level":"16","curr_items":"3","threads":"4","cmd_set":"5","bytes_read":"360","slab_reassign_chunk_rescues":"0","lru_crawler_starts":"27","uptime":"22253","log_worker_dropped":"0","unexpected_napi_ids":"0","total_connections":"9","evicted_active":"0","incr_misses":"1","connection_structures":"4","bytes":"221","lru_maintainer_juggles":"30247","evicted_unfetched":"0","rusage_system":"12.600134","time":"1638171110","slabs_moved":"0","moves_within_lru":"0","pointer_size":"64","moves_to_warm":"0","rusage_user":"8.090528","response_obj_count":"1"}},{"properties":{"name":"redis","type":"redis","endpoint":"127.0.0.1:6379","prefix":"order","serializer":"org.plumecache.samples.FastjsonCacheSerializer","compressor":"com.plumecache.core.compressor.NoneCacheCompressor","excludeInterceptors":["SlowCacheInterceptor","LogCacheInterceptor"]},"statistics":{"io_threaded_reads_processed":"0","tracking_clients":"0","uptime_in_seconds":"22224","cluster_connections":"0","current_cow_size":"0","maxmemory_human":"0B","aof_last_cow_size":"0","master_replid2":"0000000000000000000000000000000000000000","mem_replication_backlog":"0","aof_rewrite_scheduled":"0","total_net_input_bytes":"19507","rss_overhead_ratio":"1.58","hz":"10","current_cow_size_age":"0","redis_build_id":"69ab6eec4665acbc","aof_last_bgrewrite_status":"ok","multiplexing_api":"epoll","client_recent_max_output_buffer":"0","allocator_resident":"5181440","mem_fragmentation_bytes":"6549960","repl_backlog_first_byte_offset":"0","tracking_total_prefixes":"0","redis_mode":"standalone","cmdstat_get":"calls=10,usec=342,usec_per_call=34.20,rejected_calls=0,failed_calls=0","redis_git_dirty":"0","allocator_rss_bytes":"3031040","repl_backlog_histlen":"0","io_threads_active":"0","rss_overhead_bytes":"2990080","total_system_memory":"2082197504","loading":"0","evicted_keys":"0","maxclients":"10000","cmdstat_set":"calls=10,usec=1941,usec_per_call=194.10,rejected_calls=0,failed_calls=0","cluster_enabled":"0","redis_version":"6.2.5","repl_backlog_active":"0","mem_aof_buffer":"0","allocator_frag_bytes":"447160","io_threaded_writes_processed":"0","instantaneous_ops_per_sec":"0","used_memory_human":"1.59M","cmdstat_incr":"calls=1,usec=68,usec_per_call=68.00,rejected_calls=0,failed_calls=0","total_error_replies":"0","role":"master","maxmemory":"0","used_memory_lua":"37888","rdb_current_bgsave_time_sec":"-1","used_memory_startup":"809880","used_cpu_sys_main_thread":"146.683765","lazyfree_pending_objects":"0","used_memory_dataset_perc":"38.70%","allocator_frag_ratio":"1.26","arch_bits":"64","used_cpu_user_main_thread":"38.482678","mem_clients_normal":"512400","expired_time_cap_reached_count":"0","unexpected_error_replies":"0","mem_fragmentation_ratio":"5.04","aof_last_rewrite_time_sec":"-1","master_replid":"9c1aa9fc501b889e35727910956569cac1a2fda1","aof_rewrite_in_progress":"0","lru_clock":"10781132","maxmemory_policy":"noeviction","run_id":"d05aa7e4384c6ccd09ee83e919c9e9edcd2d8450","latest_fork_usec":"439","tracking_total_items":"0","total_commands_processed":"1349","expired_keys":"0","used_memory":"1664360","module_fork_in_progress":"0","dump_payload_sanitizations":"0","mem_clients_slaves":"0","keyspace_misses":"3","server_time_usec":"1638171084987972","executable":"/data/redis-server","lazyfreed_objects":"0","db0":"keys=232,expires=0,avg_ttl=0","used_memory_peak_human":"4.53M","keyspace_hits":"7","rdb_last_cow_size":"520192","used_memory_overhead":"1333640","active_defrag_hits":"0","tcp_port":"6379","uptime_in_days":"0","used_memory_peak_perc":"35.03%","current_save_keys_processed":"0","blocked_clients":"0","total_reads_processed":"1900","expire_cycle_cpu_milliseconds":"7066","sync_partial_err":"0","used_memory_scripts_human":"0B","aof_current_rewrite_time_sec":"-1","aof_enabled":"0","process_supervised":"no","cmdstat_info":"calls=2,usec=559,usec_per_call=279.50,rejected_calls=0,failed_calls=0","master_repl_offset":"0","used_memory_dataset":"330720","used_cpu_user":"38.497928","rdb_last_bgsave_status":"ok","tracking_total_keys":"0","cmdstat_ping":"calls=1325,usec=19580,usec_per_call=14.78,rejected_calls=0,failed_calls=0","atomicvar_api":"c11-builtin","allocator_rss_ratio":"2.41","client_recent_max_input_buffer":"16","clients_in_timeout_table":"0","aof_last_write_status":"ok","mem_allocator":"jemalloc-5.1.0","cmdstat_incrby":"calls=1,usec=21,usec_per_call=21.00,rejected_calls=0,failed_calls=0","used_memory_scripts":"0","used_memory_peak":"4751720","process_id":"1","master_failover_state":"no-failover","used_cpu_sys":"146.736539","repl_backlog_size":"1048576","connected_slaves":"0","current_save_keys_total":"0","gcc_version":"8.3.0","total_system_memory_human":"1.94G","sync_full":"0","connected_clients":"25","module_fork_last_cow_size":"0","total_writes_processed":"1349","allocator_active":"2150400","total_net_output_bytes":"18709","pubsub_channels":"0","current_fork_perc":"0.00","active_defrag_key_hits":"0","rdb_changes_since_last_save":"0","instantaneous_input_kbps":"0.00","configured_hz":"10","used_memory_rss_human":"7.79M","expired_stale_perc":"0.00","active_defrag_misses":"0","used_cpu_sys_children":"0.029570","number_of_cached_scripts":"0","sync_partial_ok":"0","used_memory_lua_human":"37.00K","rdb_last_save_time":"1638169845","pubsub_patterns":"0","slave_expires_tracked_keys":"0","redis_git_sha1":"00000000","used_memory_rss":"8171520","rdb_last_bgsave_time_sec":"0","os":"Linux 5.10.47-linuxkit x86_64","mem_not_counted_for_evict":"0","active_defrag_running":"0","rejected_connections":"0","total_forks":"3","active_defrag_key_misses":"0","allocator_allocated":"1703240","instantaneous_output_kbps":"0.00","second_repl_offset":"-1","rdb_bgsave_in_progress":"0","used_cpu_user_children":"0.012939","total_connections_received":"575","migrate_cached_sockets":"0"}}] 复制代码2.execute 执行缓存操作curl localhost:8080/plumecache/execute -X POST -H "Content-Type: application/json" -d "{"command":"get","key":"name"}" 复制代码anson.yin 复制代码接口说明
参考CacheService.javaRoadMap
version 1.0.01.支持spring boot 项目接入, 基于spring boot starter和 enable 以及 conditional注解完成初始化 --done2.支持spring项目介入 提供初始化工具类 --done3.支持key前缀功能 --done4.支持多个cache实例 --done5.支持get、set、delete、exists、expire、ttl命令 --done6.支持dashboard api(实例列表,实例参数,命令执行)7.支持自定义拦截器 --done8.支持自定义序列化 --done9.返回具体实例(redissonclient、MemcachedClient)对象 --done10.实现hget,hset,hgetall,lock 命令 --done11.支持缓存压缩功能(大value压缩) --done12.支持版本功能(version 注解) --done
version 2.0.00.缓存参数配置(timeout,poolsize,idlecount,attempts)1.支持线程内缓存(基于threadlocal)2.支持字段序列化(基于ObjectOutputStream)项目结构
作者:广陵笑笑生
链接:https://juejin.cn/post/7038850628965105672
2021年家用吸尘器哪个牌子好?扫把和簸箕,在中华文明上下五千年的历史中一直扮演着一个很重要的角色,而当时间来到现代,这种老旧且效率较低的清扫方式就会让人感觉到诸多不便。地面上的落灰,必须用柔和的力气慢慢打扫,否
今年双11不用定闹钟!京东天猫晚8点即可开抢9月18日,资本邦了解到,今年双11玩法已经迫不及待的剧透了一波。往年,双十一的晚上,万家灯火通明,部分消费者定好闹钟在等12点好清空购物车。今年,大家终于不再熬夜抢购了,京东天猫
广电总局要求停止宣传推销槟榔及制品iPhone13秒没苹果官网崩溃今日头条1。微信聊天将可访问外链,在保证安全的情况下更好地实现互联互通9月17日,腾讯对外公布关于互联互通第一阶段进展。腾讯表示,自9月17日起,用户升级最新版本微信后,可以在一对
阿里健康大药房开通24小时执业药师咨询中国青年报客户端讯(中青报中青网记者王聪聪)夜间孩子发烧怎么用药?盐酸氨基葡萄糖,70岁老人睡前吃几粒?孩子晚上老起湿疹,为什么用药没效果?为了更好满足用户夜间用药需求,阿里健康大
扒一扒阿里商业帝国现金流到底有多么的恐怖?今天我们按照天猫商城的入驻保证金标准来计算,入驻天猫保证金旗舰店10万专营店15万专卖店5万再看一下三种类型的店铺各有多少家旗舰店总计395280家(三十九万五千二百八十)专营店总
看华为血压手表,悟华为商业模式送给父母给长辈最好的礼物是什么呢?是钱吗?不是!是健康!相信不久的将来,过节不收礼,收礼收健康,会成为健康送礼的一种形式。那送什么呢?程哥推荐你一款见了,你见了绝对会喜欢的一款产品
挑选家用监控摄像头如何选焦距,选多少合适摄像机镜头的焦距指的是镜片中心到成像传感器的距离,焦距代表着看到的视角。一般而言,焦距越小的话,角度也就越大。焦距越大,代表可以把远处的景物拉近,使主体在画面上成像大,表现出细节,
互联网平台围墙花园破拆进行时私域业态早已商机涌动经济观察网记者钱玉娟作为苏州魔方互动网络科技有限公司的创始人兼CEO,杨阳在过去的一个月里见了很多家投资机构,他充分感受到自己所在的私域营销这个细分赛道热了起来。就在接受经济观察报
240多家平台围剿滴滴,破局之道在哪里?面对同行围剿和合规风险,滴滴四面楚歌,Robotaxi能助其脱离困境,还是将它推向深渊?文东篱网约车从来不是一个有宽大护城河的行业。9月17日,交通运输部发布消息称,据全国网约车监
买不到华为,只能买苹果,这场持续50年的供应链阴谋发挥作用了?天下熙熙,皆为利来天下攘攘,皆为利往。苹果iPhone13如约而至,今年的宣传方向是苹果不仅配置升级还降价了。这个点选得不错,直接击中消费者的命门,为其在各平台赢得上千万的预订人数
谁说千元机配置都很差?这三款表示不服,完美适合学生党以前安卓系统还没有发展起来,流畅度什么的比iPhone要差,特别是千元机更为明显,不少用户都被坑过,也把这个刻板印象带到现在,但其实安卓手机的配置已经跟上,甚至某些方面超越了iPh