# demo-ehcache2 **Repository Path**: debug_life/demo-ehcache2 ## Basic Information - **Project Name**: demo-ehcache2 - **Description**: SpringBoot之ehcache2学习和使用 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2021-11-12 - **Last Updated**: 2023-06-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Ehcache ## SpringBoot集成Ehcache并配置集群服务 ### maven依赖 ```xml net.sf.ehcache ehcache org.springframework spring-context-support ``` ### yml配置 ```yml spring: cache: type: ehcache # 缓存类型 ehcache: config: classpath:ehcache.xml # EhCache配置文件 manager-peer-listener-port: 40001 # 监听其他节点发送到当前CacheManager的信息的监听器端口 ``` PS:如果多个集群应用部署在同一服务器则在启动时需要设置不同的`spring.cache.ehcache.manager-peer-listener-port`,具体可参考源码下的`cluster-run-1.sh`和`cluster-run-2.sh` ### ehcache.xml 根目录下ehcache.xml是Ehcache配置文件,其中包含集群配置,集群节点采用“自动发现”的方式。 ```xml ``` ### 开启缓存并设置一些可配置参数 ```java /** * 缓存配置 * @author sword * @date 2020/9/30 17:26 */ @Configuration @EnableCaching public class CacheConfig { /** * rmi方式的节点监听器类型 */ public static final String RMI_CACHE_MANAGER_PEER_LISTENER_FACTORY_CLASS_NAME = "net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"; /** * 主机名称参数键名 */ public static final String HOST_NAME = "hostName"; /** * 监听其他节点发送到当前CacheManager的信息的监听器端口 */ @Value("${spring.cache.ehcache.manager-peer-listener-port}") private String cacheManagerPeerListenerPort; /** * 监听其他节点发送到当前CacheManager的信息的监听器端口参数键名 */ public static final String CACHE_MANAGER_PEER_LISTENER_PORT_NAME = "port"; /** * 如果缓存用的是EhCache则需要自定义CacheManager * 因为在设置cacheManagerPeerListener时,如果hostName为空则默认使用InetAddress.getLocalHost().getHostAddress() * 来获取服务器的IP,但Linux服务器用这个方法只能获取到127.0.0.1,所以在hostName为空时需要换种方法获取IP * @param cacheProperties 缓存配置 * @return net.sf.ehcache.CacheManager * @author sword * @date 2020/10/9 14:49 */ @Bean @Primary @ConditionalOnProperty(name = "spring.cache.type", havingValue = "ehcache") public CacheManager ehCacheCacheManager(CacheProperties cacheProperties) { // 配置文件,文件路径对应spring.cache.ehcache.config,默认为classpath:ehcache.xml Resource location = cacheProperties.resolveConfigLocation(cacheProperties.getEhcache().getConfig()); // 如果配置文件不为空则根据配置文件生成CacheManager,否则返回默认CacheManager if (location != null) { // 解析配置文件生存配置参数对象 net.sf.ehcache.config.Configuration configuration = EhCacheManagerUtils.parseConfiguration(location); // 计算本机ip地址 calculateHostAddress(configuration); return new CacheManager(configuration); } else { return EhCacheManagerUtils.buildCacheManager(); } } /** * 计算本机ip地址 * @param configuration EhCache配置参数对象 * @author sword * @date 2020/10/10 9:15 */ public void calculateHostAddress(net.sf.ehcache.config.Configuration configuration) { configuration.getCacheManagerPeerListenerFactoryConfigurations().forEach(factoryConfiguration -> { // 如果不是RMI方式的节点监听器则直接跳过 if (!RMI_CACHE_MANAGER_PEER_LISTENER_FACTORY_CLASS_NAME.equals(factoryConfiguration.getFullyQualifiedClassPath())) { return; } // cacheManagerPeerListener的配置参数 Properties properties = PropertyUtil.parseProperties( factoryConfiguration.getProperties(), factoryConfiguration.getPropertySeparator() ); // 如果主机名称不为空则直接返回 if (StringUtils.isNotEmpty(properties.getProperty(HOST_NAME))) { return; } // 设置主机名称,即本机的Ipv4地址 properties.setProperty(HOST_NAME, CommonUtil.getHostAddressIpv4()); // 顺便设置一下监听其他节点发送到当前CacheManager的信息的监听器端口 properties.setProperty(CACHE_MANAGER_PEER_LISTENER_PORT_NAME, cacheManagerPeerListenerPort); // 将修改后的配置参数重新设置回去 try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { properties.store(outputStream, null); factoryConfiguration.setProperties(outputStream.toString()); } catch (IOException e) { e.printStackTrace(); } }); } } ``` ### 使用ehcache.xml中配置的缓存`user` ```java /** * 用户接口 * @author sword * @date 2021/11/11 17:25 */ @RestController @RequestMapping("/user") @Api(tags = "用户接口") public class UserApi { /** * 新增用户,同时以用户id为key将用户信息设置到缓存名称为“user”的缓存中 * @param user 用户信息 * @return com.sword.ehcache.model.User * @author sword * @date 2021/11/28 21:18 */ @PostMapping @CachePut(cacheNames = "user", key = "#user.id") @ApiOperation("新增用户") public User insert(@RequestBody User user) { return user; } /** * 删除用户,同时以用户id为key将缓存名称为“user”的缓存中对应的数据删除 * @param id 用户id * @return boolean * @author sword * @date 2021/11/28 21:19 */ @DeleteMapping("/{id}") @CacheEvict(cacheNames = "user", key = "#id") @ApiOperation("删除用户") public boolean delete(@PathVariable String id) { return true; } /** * 查询指定用户,同时以用户id为key到缓存名称为“user”的缓存中查询对应的数据,如果找到则不执行查询方法直接返回缓存数据 * 否则先执行查询方法,并将返回结果设置到缓存中 * @param id 用户 * @return com.sword.ehcache.model.User * @author sword * @date 2021/11/28 21:19 */ @GetMapping("/{id}") @ApiOperation("查询指定用户") @Cacheable(cacheNames = "user", key = "#id") public User get(@PathVariable String id) { return null; } } ``` ### 注意事项 * 缓存对象需要实现序列化接口 * 两台windows系统的服务器做集群可能会因为防火墙的原因导致无法发现集群成员节点 * cacheManagerPeerListenerFactory的hostName如果不填的话会使用InetAddress.getLocalHost().getHostAddress()去获取ip,但在linux下InetAddress.getLocalHost().getHostAddress()通常会返回127.0.0.1 ## [相关源码详见gitee](https://gitee.com/debug_life/demo-ehcache2.git) ## Demo应用启动 ### 环境准备 * jdk8 * maven3 ### 构建 * Windows:执行build.bat * Linux:执行build.sh ### 集群节点启动 * Windows:执行cluster-run-1.bat和cluster-run-2.bat * Linux:执行cluster-run-1.sh和cluster-run-2.sh ## 缓存测试 ### swagger地址 * 节点1:http://127.0.0.1:8001/swagger-ui/ * 节点2:http://127.0.0.1:8002/swagger-ui/ ### 集群缓存同步 * 在节点1查询id为sword的用户信息 请求: ```shell curl -X GET "http://127.0.0.1:8001/user/sword" -H "accept: */*" ``` 响应: ```shell { "code": "000000", "msg": "调用成功", "data": null } ``` * 在节点2新增id为sword的用户信息 请求: ```shell curl -X POST "http://127.0.0.1:8002/user" -H "accept: */*" -H "Content-Type: application/json" -d "{\"id\":\"sword\",\"name\":\"程序猿\"}" ``` 响应: ```shell { "code": "000000", "msg": "调用成功", "data": { "id": "sword", "name": "程序猿" } } ``` * 在节点1再次查询id为sword的用户信息 请求: ```shell curl -X GET "http://127.0.0.1:8001/user/sword" -H "accept: */*" ``` 响应: ```shell { "code": "000000", "msg": "调用成功", "data": { "id": "sword", "name": "程序猿" } } ```