{"id":20269169,"url":"https://github.com/pig-mesh/multilevel-cache-spring-boot-starter","last_synced_at":"2025-04-06T09:10:37.490Z","repository":{"id":37385876,"uuid":"298807182","full_name":"pig-mesh/multilevel-cache-spring-boot-starter","owner":"pig-mesh","description":"spring cache 多级缓存扩展 ","archived":false,"fork":false,"pushed_at":"2023-01-06T06:49:32.000Z","size":121,"stargazers_count":259,"open_issues_count":10,"forks_count":107,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-30T08:10:27.604Z","etag":null,"topics":["caffeine","java","redis","spring","springcache"],"latest_commit_sha":null,"homepage":"https://pig4cloud.com","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pig-mesh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-09-26T12:14:42.000Z","updated_at":"2025-03-29T12:30:15.000Z","dependencies_parsed_at":"2023-02-05T13:45:39.447Z","dependency_job_id":null,"html_url":"https://github.com/pig-mesh/multilevel-cache-spring-boot-starter","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pig-mesh%2Fmultilevel-cache-spring-boot-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pig-mesh%2Fmultilevel-cache-spring-boot-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pig-mesh%2Fmultilevel-cache-spring-boot-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pig-mesh%2Fmultilevel-cache-spring-boot-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pig-mesh","download_url":"https://codeload.github.com/pig-mesh/multilevel-cache-spring-boot-starter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247457803,"owners_count":20941906,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caffeine","java","redis","spring","springcache"],"created_at":"2024-11-14T12:23:44.162Z","updated_at":"2025-04-06T09:10:37.472Z","avatar_url":"https://github.com/pig-mesh.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"## 为什么多级缓存\n\n缓存的引入是现在大部分系统所必须考虑的\n\n- redis 作为常用中间件，虽然我们一般业务系统（毕竟业务量有限）不会遇到如下图 在随着 data-size 的增大和数据结构的复杂的造成性能下降，但网络 IO 消耗会成为整个调用链路中不可忽视的部分。尤其在 微服务架构中，一次调用往往会涉及多次调用 例如[pig oauth2.0 的 client 认证](https://gitee.com/log4j/pig \"pig oauth2.0 的 client 认证\")\n\n![](https://gitee.com/pig4cloud/oss/raw/master/2020-9-27/1601165312076-image.png)\n\n- Caffeine 来自未来的本地内存缓存,性能比如常见的内存缓存实现性能高出不少[详细对比](https://github.com/ben-manes/caffeine/wiki/Benchmarks \"详细对比\")。\n\n![](https://gitee.com/pig4cloud/oss/raw/master/2020-9-27/1601165199107-image.png)\n\n**综合所述：我们需要构建 L1 Caffeine JVM 级别内存 ， L2 Redis 内存。**\n\n## 设计难点\n\n目前大部分应用缓存都是基于 Spring Cache 实现,基于注释（annotation）的缓存（cache）技术,存在的问题如下：\n\n- Spring Cache 仅支持 单一的缓存来源，即：只能选择 Redis 实现或者 Caffeine 实现，并不能同时使用。\n- 数据一致性：各层缓存之间的数据一致性问题，如应用层缓存和分布式缓存之前的数据一致性问题。\n- 缓存过期：Spring Cache 不支持主动的过期策略\n\n## 业务流程\n\n![](https://i.loli.net/2020/09/27/dbMiYhwTBurZK4y.png)\n\n## 如何使用\n| 版本 | 支持 |\n|-------|--|\n| 3.0.0 | 适配 SpringBoot3.x |\n| 1.0.1 | 适配 SpringBoot2.x |\n\n- 1. 引入依赖\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.pig4cloud.plugin\u003c/groupId\u003e\n    \u003cartifactId\u003emultilevel-cache-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e${lastVersion}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n- 2. 开启缓存支持\n\n```java\n@EnableCaching\npublic class App {\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(App.class, args);\n\t}\n}\n```\n\n- 3. 目标接口声明 Spring Cache 注解\n\n```\n@Cacheable(value = \"get\",key = \"#key\")\n@GetMapping(\"/get\")\npublic String get(String key){\n    return \"success\";\n}\n```\n\n## 性能比较\n\n为保证性能 redis 在 127.0.0.1 环路安装\n\n- OS: macOS Mojave\n- CPU: 2.3 GHz Intel Core i5\n- RAM: 8 GB 2133 MHz LPDDR3\n- JVM: corretto_11.jdk\n\n| Benchmark  | Mode  | Cnt | Score    | Units |\n| ---------- | ----- | --- | -------- | ----- |\n| 多级实现   | thrpt | 2   | 2716.074 | ops/s |\n| 默认 redis | thrpt | 2   | 1373.476 | ops/s |\n\n## 代码原理\n\n- 1. 自定义 CacheManager 多级缓存实现\n\n```java\npublic class RedisCaffeineCacheManager implements CacheManager {\n\n\t@Override\n\tpublic Cache getCache(String name) {\n\t\tCache cache = cacheMap.get(name);\n\t\tif (cache != null) {\n\t\t\treturn cache;\n\t\t}\n\t\tcache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(), cacheConfigProperties);\n\t\tCache oldCache = cacheMap.putIfAbsent(name, cache);\n\t\tlog.debug(\"create cache instance, the cache name is : {}\", name);\n\t\treturn oldCache == null ? cache : oldCache;\n\t}\n}\n```\n\n- 2. 多级读取、过期策略实现\n\n```java\npublic class RedisCaffeineCache extends AbstractValueAdaptingCache {\n\tprotected Object lookup(Object key) {\n\t\tObject cacheKey = getKey(key);\n\n    // 1. 先调用 caffeine 查询是否存在指定的值\n\t\tObject value = caffeineCache.getIfPresent(key);\n\t\tif (value != null) {\n\t\t\tlog.debug(\"get cache from caffeine, the key is : {}\", cacheKey);\n\t\t\treturn value;\n\t\t}\n\n    // 2. 调用 redis 查询在指定的值\n\t\tvalue = stringKeyRedisTemplate.opsForValue().get(cacheKey);\n\n\t\tif (value != null) {\n\t\t\tlog.debug(\"get cache from redis and put in caffeine, the key is : {}\", cacheKey);\n\t\t\tcaffeineCache.put(key, value);\n\t\t}\n\t\treturn value;\n\t}\n}\n```\n\n- 3. 过期策略，所有更新操作都基于 redis pub/sub 消息机制更新\n\n```java\npublic class RedisCaffeineCache extends AbstractValueAdaptingCache {\n\t@Override\n\tpublic void put(Object key, Object value) {\n\t\tpush(new CacheMessage(this.name, key));\n\t}\n\n\t@Override\n\tpublic ValueWrapper putIfAbsent(Object key, Object value) {\n\t\t\t\tpush(new CacheMessage(this.name, key));\n\t}\n\n\t@Override\n\tpublic void evict(Object key) {\n\t\tpush(new CacheMessage(this.name, key));\n\t}\n\n\t@Override\n\tpublic void clear() {\n\t\tpush(new CacheMessage(this.name, null));\n\t}\n\n\tprivate void push(CacheMessage message) {\n\t\tstringKeyRedisTemplate.convertAndSend(topic, message);\n\t}\n}\n```\n\n- 4. MessageListener 删除指定 Caffeine 的指定值\n\n```java\npublic class CacheMessageListener implements MessageListener {\n\n\tprivate final RedisTemplate\u003cObject, Object\u003e redisTemplate;\n\n\tprivate final RedisCaffeineCacheManager redisCaffeineCacheManager;\n\n\t@Override\n\tpublic void onMessage(Message message, byte[] pattern) {\n\t\tCacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody());\n\t\t\t\tcacheMessage.getCacheName(), cacheMessage.getKey());\n\t\tredisCaffeineCacheManager.clearLocal(cacheMessage.getCacheName(), cacheMessage.getKey());\n\t}\n}\n```\n\n## 源码地址\n\n[https://github.com/pig-mesh/multilevel-cache-spring-boot-starter\n](https://github.com/pig-mesh/multilevel-cache-spring-boot-starter)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpig-mesh%2Fmultilevel-cache-spring-boot-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpig-mesh%2Fmultilevel-cache-spring-boot-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpig-mesh%2Fmultilevel-cache-spring-boot-starter/lists"}