1. Ehcache简介

        EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

        Spring 提供了对缓存功能的抽象:即允许绑定不同的缓存解决方案(如Ehcache),但本身不直接提供缓存功能的实现。它支持注解方式使用缓存,非常方便。

     Ehcache的特点:

  1. 快速
  2. 简单
  3. 多种缓存策略
  4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题
  5. 缓存数据会在虚拟机重启的过程中写入磁盘
  6. 可以通过RMI、可插入API等方式进行分布式缓存
  7. 具有缓存和缓存管理器的侦听接口
  8. 支持多缓存管理器实例,以及一个实例的多个缓存区域
  9. 提供Hibernate的缓存实现

2.Redis简介

        Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

        Redis是一个key-value存储系统。它支持存储的value类型很多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。Redis将数据都是缓存在内存中。Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

        Redis的特点:

        1、速度快 

        Redis是用C语言实现的; Redis的所有数据存储在内存中。 

        2、持久化 

        Redis的所有数据存储在内存中,对数据的更新将异步地保存到磁盘上。 

        3、支持多种数据结构 

         Redis支持五种数据结构:String、List、Set、Hash、Zset 

        4、支持多种编程语言 

        Java、php、Python、Ruby、Lua、Node.js 

        5、功能丰富 

        除了支持五种数据结构之外,还支持事务、流水线、发布/订阅、消息队列等功能。 

        6、源码简单 

        约23000行C语言源代码。 

        7、主从复制 

        主服务器(master)执行添加、修改、删除,从服务器执行查询。 

        8、高可用及分布式 
        Redis-Sentinel(v2.8)支持高可用 。Redis-Cluster(v3.0)支持分布式

3. Ehcache 和 Redis 比较


 EhcacheRedis
存取速度Ehcache直接在jvm虚拟机中缓存,速度快,效率高Redis是通过socket访问到缓存服务,效率比ecache低
集群和分布式Ehcache有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。Redis有成熟的分布式解决方案。适合大规模分布式集群部署。
操作复杂度Ehcache提供的接口非常简单明了,从Ehcache的搭建到运用运行仅仅需要的是你宝贵的几分钟。其实很多开发者都不知道自己用在用Ehcache,Ehcache被广泛的运用于其他的开源项目。比如:Hibernate至少需要安装服务端和客户端才能使用。操作略比Ehcache复杂一些。
   

4.SpringBoot与Ehcache整合方案

配置Ehcache

@Configuration
@EnableCaching
public class EhCacheConfiguration implements CachingConfigurer {

    @Bean(destroyMethod="shutdown")
    public net.sf.ehcache.CacheManager ehCacheManager() {
        CacheConfiguration cacheConfiguration = new CacheConfiguration();
        cacheConfiguration.setName("HelloWorldCache");
        cacheConfiguration.setMemoryStoreEvictionPolicy("LRU");
        cacheConfiguration.setMaxEntriesLocalHeap(1000);
        net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
        //可以创建多个cacheConfiguration,都添加到Config中
        config.addCache(cacheConfiguration);
        return net.sf.ehcache.CacheManager.newInstance(config);
    }

    @Bean
    @Override
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheManager());
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new SimpleKeyGenerator();
    }

    @Override
    public CacheResolver cacheResolver() { return null; }

    @Override
    public CacheErrorHandler errorHandler() {
        return null;
    }

}

写一个测试Service和其实现类

public interface EhcacheService {
    // 测试失效情况,有效期为5秒
    public String getTimestamp(String param);
}
@Service
public class EhcacheServiceImpl implements EhcacheService {
    /**
     * @Cacheable 表明所修饰的方法是可以缓存的:当第一次调用这个方法时,它的结果会被缓存下来,在缓存的有效时间内,
     * 以后访问这个方法都直接返回缓存结果,不再执行方法中的代码段。
     * 这个注解可以用condition属性来设置条件,如果不满足条件,就不使用缓存能力,直接执行方法。
     * 可以使用key属性来指定key的生成规则。
     *
     * value:缓存位置名称,不能为空,如果使用EHCache,就是ehcache.xml中声明的cache的name, 指明将值缓存到哪个Cache中
     *
     * key:缓存的key,默认为空,既表示使用方法的参数类型及参数值作为key,支持SpEL,如果要引用参数值使用井号加参数名,如:#userId,
     * 一般来说,我们的更新操作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的清除掉特定的缓存,而不会影响到其它缓存值 ,
     *  本例子中使用实体加冒号再加ID组合成键的名称,如”user:1”、”order:223123”等
     *
     * condition:触发条件,只有满足条件的情况才会加入缓存,默认为空,既表示全部都加入缓存,支持SpEL
     * @param param
     * @return
     */
    @Cacheable(value="HelloWorldCache", key="#param")
    @Override
    public String getTimestamp(String param) {
        Long timestamp = System.currentTimeMillis();
        return timestamp.toString();
    }
}

写个单元测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class EhcacheServiceTest {
    @Autowired
    EhcacheService ehcacheService;

    /**
     * 返回第一次调用getTimestamp方法的时间
     * @throws InterruptedException
     */
    @Test
    public void testGetTimestamp() throws InterruptedException {
        System.out.println("第一次调用:" + ehcacheService.getTimestamp("param"));
        Thread.sleep(2000);
        System.out.println("2秒之后调用:" + ehcacheService.getTimestamp("param"));
        Thread.sleep(4000);
        System.out.println("再过4秒之后调用:" + ehcacheService.getTimestamp("param"));
    }
}

可以发现后两次调用返回的都是第一次方法调用的时间。

5. SpringBoot整合 Redis 方案

配置Redis

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String,Object>();
        redisTemplate.setConnectionFactory(factory);
        // key序列化方式;(不然会出现乱码;),但是如果方法上有Long等非String类型的话,会报类型转换错误;
        // 所以在没有自己定义key生成策略的时候,以下这个代码建议不要这么写,可以不配置或者自己实现ObjectRedisSerializer
        // 或者JdkSerializationRedisSerializer序列化方式;
        RedisSerializer<String> keyRedisSerializer = new StringRedisSerializer();
        RedisSerializer<Object> valueRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置序列化Key的实例化对象
        redisTemplate.setKeySerializer(keyRedisSerializer);
        //设置序列化Value的实例化对象
        redisTemplate.setValueSerializer(valueRedisSerializer);
        redisTemplate.setHashKeySerializer(keyRedisSerializer);
        redisTemplate.setHashValueSerializer(valueRedisSerializer);
        return redisTemplate;
    }

}

写个测试Service和其实现类。redis中没有数据的是会通过dao从数据库捞取数据然后存入redis

public interface CityService {
    /**
     * 根据城市 ID,查询城市信息
     *
     * @param id
     * @return
     */
    City findCityById(Long id);

    /**
     * 新增城市信息
     *
     * @param city
     * @return
     */
    Long saveCity(City city);

    /**
     * 更新城市信息
     *
     * @param city
     * @return
     */
    Long updateCity(City city);

    /**
     * 根据城市 ID,删除城市信息
     *
     * @param id
     * @return
     */
    Long deleteCity(Long id);
}
@Service
public class CityServiceImpl implements CityService {

    private static final Logger LOGGER = LoggerFactory.getLogger(CityServiceImpl.class);

    private static final String CITY_REDIS_KEY_PREFIX = "city_";
    @Autowired
    private CityDao cityDao;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取城市逻辑:
     * 如果缓存存在,从缓存中获取城市信息
     * 如果缓存不存在,从 DB 中获取城市信息,然后插入缓存
     */
    @Override
    public City findCityById(Long id) {
        // 从缓存中获取城市信息
        String key = CITY_REDIS_KEY_PREFIX + id;
        ValueOperations<String, City> operations = redisTemplate.opsForValue();

        // 缓存存在
        boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey) {
            City city = operations.get(key);

            LOGGER.info("CityServiceImpl.findCityById() : 从缓存中获取了城市 >> " + city.toString());
            return city;
        }

        // 从 DB 中获取城市信息
        City city = cityDao.findById(id);

        // 插入缓存
        operations.set(key, city, 10, TimeUnit.MINUTES);
        LOGGER.info("CityServiceImpl.findCityById() : 城市插入缓存 >> " + city.toString());

        return city;
    }

    /**
     * 保存城市信息
     * @param city
     * @return
     */
    @Override
    public Long saveCity(City city) {
        // 从 DB 中获取城市信息,如果已经存在就不在保存
        City cityInDB = cityDao.findById(city.getId());
        if (cityInDB != null) {
            return null;
        }
        return cityDao.saveCity(city);
    }

    /**
     * 更新城市逻辑:
     * 如果缓存存在,删除
     * 如果缓存不存在,不操作
     */
    @Override
    public Long updateCity(City city) {
        Long ret = cityDao.updateCity(city);

        // 缓存存在,删除缓存
        String key = CITY_REDIS_KEY_PREFIX + city.getId();
        boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey) {
            redisTemplate.delete(key);

            LOGGER.info("CityServiceImpl.updateCity() : 从缓存中删除城市 >> " + city.toString());
        }

        return ret;
    }

    /**
     * 删除城市信息
     * @param id
     * @return
     */
    @Override
    public Long deleteCity(Long id) {

        Long ret = cityDao.deleteCity(id);

        // 缓存存在,删除缓存
        String key = CITY_REDIS_KEY_PREFIX + id;
        boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey) {
            redisTemplate.delete(key);

            LOGGER.info("CityServiceImpl.deleteCity() : 从缓存中删除城市 ID >> " + id);
        }
        return ret;
    }

}

dao类

epublic interface CityDao {

    /**
     * 获取城市信息列表
     *
     * @return
     */
    List<City> findAllCity();

    /**
     * 根据城市 ID,获取城市信息
     *
     * @param id
     * @return
     */
    City findById(@Param("id") Long id);

    /**
     * 保存城市信息
     * @param city
     * @return
     */
    Long saveCity(City city);

    /**
     * 修改城市信息
     * @param city
     * @return
     */
    Long updateCity(City city);

    /**
     * 删除城市信息
     * @param id
     * @return
     */
    Long deleteCity(Long id);
}

写个测试Controller

@RestController
public class CityRestController {

    @Autowired
    private CityService cityService;


    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET)
    public City findOneCity(@PathVariable("id") Long id) {
        return cityService.findCityById(id);
    }

    @RequestMapping(value = "/api/city/create", method = RequestMethod.POST)
    public void createCity(@RequestBody City city) {
        cityService.saveCity(city);
    }

    @RequestMapping(value = "/api/city", method = RequestMethod.PUT)
    public void modifyCity(@RequestBody City city) {
        cityService.updateCity(city);
    }

    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.DELETE)
    public void modifyCity(@PathVariable("id") Long id) {
        cityService.deleteCity(id);
    }
}
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐