上一张说到了Ehcache的简单使用,但是Ehcache一般作为本地缓存来使用,而在一个系统可能会根据服务的不用会部署在不同的机器上,那么在每一台机器都设置Ehcache,又要把一些公用的信息缓存一遍,这样不利于使用Ehcache。 而这时,我们可以再做一个缓存,这个缓存保存了一些经常使用,而且可以较大的数据。这个就是Redis。Redis已经成为了最常用的几种NoSql之一了,不仅开源,而且简单易用。
准备工作:
安装zookeeper:
安装zookeeper 极为简单,
http://blog.csdn.net/u014104286/article/details/79165916
在安装好zookeeper、Redis之后,我们需要一个springboot的工程,以便于来实现我们的Redis+Ehcache缓存的系统:其中
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ys.test.ehcache</groupId>
<artifactId>SomeTest-ehcache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SomeTest-ehcache</name>
<url>http://maven.apache.org</url>
<!-- <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent> -->
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.4.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.43</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>0.10.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin </artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Ehcache.xml:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!-- diskStore:ehcache其实是支持内存+磁盘+堆外内存,几个层级的缓存 -->
<!-- 在这里设置一下,但是一般不用的 -->
<diskStore path="java.io.tmpdir/Tmp_EhCache" />
<!-- defaultCache,是默认的缓存策略 -->
<!-- 如果你指定的缓存策略没有找到,那么就用这个默认的缓存策略 -->
<!-- external:如果设置为true的话,那么timeout就没有效果,缓存就会一直存在,一般默认就是false -->
<!-- maxElementsInMemory:内存中可以缓存多少个缓存条目,在实践中,你是需要自己去计算的,比如你计算你要缓存的对象是什么?有多大?最多可以缓存多少MB,或者多少个G的数据?除以每个对象的大小,计算出最多可以放多少个对象 -->
<!-- overflowToDisk:如果内存不够的时候,是否溢出到磁盘 -->
<!-- diskPersistent:是否启用磁盘持久化的机制,在jvm崩溃的时候和重启之间,不用 -->
<!-- timeToIdleSeconds:对象最大的闲置的时间,如果超出闲置的时间,可能就会过期,我们这里就不用了,缓存最多闲置5分钟就被干掉了 单位:秒 -->
<!-- timeToLiveSeconds:对象最多存活的时间,我们这里也不用,超过这个时间,缓存就过期,就没了 单位:秒 -->
<!-- memoryStoreEvictionPolicy:当缓存数量达到了最大的指定条目数的时候,需要采用一定的算法,从缓存中清除一批数据,LRU,最近最少使用算法,最近一段时间内,最少使用的那些数据,就被干掉了 -->
<defaultCache
eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="300"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU" />
<!-- 手动指定的缓存策略 -->
<!-- 比如你一个应用吧,可能要缓存很多种不同的数据,比如说商品信息,或者是其他的一些数据 -->
<!-- 对不同的数据,缓存策略可以在这里配置多种 -->
<cache
name="local"
eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="300"
timeToLiveSeconds="0"
memoryStoreEvictionPolicy="LRU" />
<!-- ehcache这种东西,简单实用,是很快速的,1小时上手可以用在项目里了,没什么难度的 -->
<!-- ehcache这个技术,如果讲深了,里面的东西还是很多的,高级的feature,但是我们这里就不涉及了 -->
</ehcache>
加载Ehcache,使得spring中有EhCacheCacheManager的Bean:
EhcacheConfiguration.java:
package com.ys.test.ehcache.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
/**
* 配置ehcache
*
*/
@Configuration
@EnableCaching
public class EhcacheConfiguration {
@Bean
public EhCacheManagerFactoryBean cacheManagerFactoryBean(){
EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
bean.setConfigLocation(new ClassPathResource("ehcache.xml"));
bean.setShared(true);
return bean;
}
@Bean
public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean){
return new EhCacheCacheManager(bean.getObject());
}
}
配置Redis:RedisConfig.java: (说来惭愧,ruby 和 gem安装不上 所以做不了Redis cluster的集群 )
package com.ys.test.ehcache.config;
import java.util.HashSet;
import java.util.Set;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
@Configuration
public class RedisConfig {
@Bean
public JedisCluster JedisClusterFactory() {
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
jedisClusterNodes.add(new HostAndPort("192.168.5.112", 4564));
JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes);
return jedisCluster;
}
@Bean
public Jedis JedisFactory() {
Jedis jedis = new Jedis("192.168.5.112", 6379);
jedis.auth("root");
return jedis;
}
}
Application.properties:
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ecacheTest?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for eachsql query
spring.jpa.show-sql = true
#Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto =update
# Naming strategy
#[org.hibernate.cfg.ImprovedNamingStrategy #org.hibernate.cfg.DefaultNamingStrategy|ImprovedNamingStrategy]
#<version>1.3.3.RELEASE</version> 使用下面 根据Column注解生成列名
#spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
#<version>1.4.3.RELEASE</version> 使用下面的,根据Column注解生成列名
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5Dialect
数据对象类:ProductInfo.java
package com.ys.test.ehcache.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class ProductInfo {
@Id @GeneratedValue
@Column(name="productId")
private Long product_id;
@Column(name="productName")
private String product_name;
@Column(name="price")
private Double price;
@Column(name="modifyTime")
private String modifyTime;
public ProductInfo() {
}
public ProductInfo(Long product_id, String product_name, Double price) {
this.product_id = product_id;
this.product_name = product_name;
this.price = price;
}
public ProductInfo(Long product_id, String product_name, Double price,String modifyTime) {
this.product_id = product_id;
this.product_name = product_name;
this.price = price;
this.modifyTime = modifyTime;
}
public String getModifyTime() {
return modifyTime;
}
public void setModifyTime(String modifyTime) {
this.modifyTime = modifyTime;
}
public Long getProduct_id() {
return product_id;
}
public void setProduct_id(Long product_id) {
this.product_id = product_id;
}
public String getProduct_name() {
return product_name;
}
public void setProduct_name(String product_name) {
this.product_name = product_name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "ProductInfo [product_id=" + product_id + ", product_name=" + product_name + ", price=" + price
+ ", modifyTime=" + modifyTime + "]";
}
}
一个使用Redis+Ehcache做缓存的系统,因为Ehcache是本地的,所以使用者就是本机器而已,但是Redis是分布式的,每台服务机器都可以访问和操作,所以我们还得保证每一个放入Redis缓存中应该是最新的,这样才能保证再其他机器读取的时候拿到的数据是最新的。
1和3:客服端的请求可能会被路由到服务1或者服务2
2和4:当请求到来的时候(假想为查询请求) 服务1、服务2都先从本地的Ehcache中根据约定的key去寻找缓存对象。当Ehcache中有缓存的对象,就返回给请求客服端。
9和11:当 Ehcache中没有缓存对象的时候,要去Redis中找。如果找到了就通过10和12返回给服务1、服务2,之后服务1和服务2在通过15、16返回给请求客服端。
5和6:当Redis中,及9和11没有查询缓存数据时,我们要去数据库区查询数据,并通过7和8返回给服务1和服务2,同时服务1、服务2需要将从数据库查询的数据放到自己本地的Ehcache中。
13和14:在上一步设置了本地的Ehcache之后,我们还要设置Redis缓存。(但是当请求和修改高峰,并刚好查询的数据有变动的情况,两个查询请求先后查询的结果不同,得到一个旧值和一个新值,查询到旧值的服务准备设置到Redis的时候网络或者机器资源不够了,任务被暂停了一会儿,这个时候新值的服务先把新的数据设置到了Redis,这个时候旧值再去设置的话就会把数据还原到了修改之前,这样从缓存里面拿到的是无效的数据。这时我们需要zookeeper作为一个分布式锁,要设置Redis先要拿到这个对象key对应的锁,才能设置,而且当开始设置的时候再去查一下是否此时有将要设置的缓存对象,没有的话直接设置,如果有的话我们要和自己比较这个对象的修改时间,如果自己是最新的时间就设置这个对象为自己的数据,如果已经存在比自己数据新的时间则不做操作。)
15和16:设置缓存对象到Redis中。
17和18:返回查询结果给请求客服端。
实现以上思路:
我们在上面的配置步骤中获取到了Redis、数据库连接、Ehcache管理对象了,我们还需要zookeeper连接,并要有获取和删除分布式锁的方法:
ZookeeperLockSingle.java:
package com.ys.test.ehcache.zk;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
public class ZookeeperLockSingle {
private final static int COUNTS = 5;
private final Log log = LogFactory.getLog(ZookeeperLockSingle.class);
private ZooKeeper keeper;
//zk链接是异步的,我们需要等待链接上zk才进行操作
private CountDownLatch latch = new CountDownLatch(1);
private ZookeeperLockSingle (){
try {
this.keeper = new ZooKeeper("192.168.5.112:2181,192.168.5.113:2181", 50000,new MyWater());
log.error("等待链接zk...");
latch.await();
} catch (Exception e) {
log.error(e.getMessage());
}
}
private class MyWater implements Watcher{
@Override
public void process(WatchedEvent arg0) {
//状态是链接
if (arg0.getState() == KeeperState.SyncConnected) {
log.error("zk 已经链接成功");
latch.countDown();
}
}
}
private static class GetZookeeperLockSingle{
private static ZookeeperLockSingle zklockSingle = null;
static {
zklockSingle = new ZookeeperLockSingle();
}
private static ZookeeperLockSingle getZookeeperLock(){
return zklockSingle;
}
}
public static ZookeeperLockSingle getSingleZKLock(){
return GetZookeeperLockSingle.getZookeeperLock();
}
/**
* 循环获取分布式锁
* @Function: ZookeeperLockSingle.java
* @Description:
*
* @param productid
* @return
* @return boolean
* @version: v1.0.0
*/
public boolean acquireDistrbutedLock(Long productid){
String path = "/product-lock-"+productid;
boolean flag = false;
int counts = 1;
//这里一直等带获取锁是不是不太好哦
while (true) {
if (counts > COUNTS) {
break;
}
try {
Thread.sleep(200);
String create = this.keeper.create(path, "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println(create);
log.info("创建:"+path+"成功");
flag = true;
break;
} catch (Exception e) {
counts++;
log.error("锁:"+path+"创建失败!!!"+"正在进行第"+counts+"次尝试!");
continue;
}
}
return flag;
}
/**
* 释放分布式锁
* @Function: ZookeeperLockSingle.java
* @Description:
*
* @param productid
* @return void
* @version: v1.0.0
*/
public void releaseDistrbuteProductLock(long productid) throws Exception{
String path = "/product-lock-"+productid;
try {
this.keeper.delete(path, -1);
} catch (Exception e) {
log.error(e.getMessage());
throw e;
}
}
}
当能创建指定的目录,则说明拿到了锁。创建失败说明目前有服务在操作对应的product_id的对象。
缓存对象ProductInfo.java:
package com.ys.test.ehcache.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class ProductInfo {
@Id @GeneratedValue
@Column(name="productId")
private Long product_id;
@Column(name="productName")
private String product_name;
@Column(name="price")
private Double price;
@Column(name="modifyTime")
private long modifyTime;
public ProductInfo() {
}
public ProductInfo(Long product_id, String product_name, Double price) {
this.product_id = product_id;
this.product_name = product_name;
this.price = price;
}
public ProductInfo(Long product_id, String product_name, Double price,long modifyTime) {
this.product_id = product_id;
this.product_name = product_name;
this.price = price;
this.modifyTime = modifyTime;
}
public long getModifyTime() {
return modifyTime;
}
public void setModifyTime(long modifyTime) {
this.modifyTime = modifyTime;
}
public Long getProduct_id() {
return product_id;
}
public void setProduct_id(Long product_id) {
this.product_id = product_id;
}
public String getProduct_name() {
return product_name;
}
public void setProduct_name(String product_name) {
this.product_name = product_name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "ProductInfo [product_id=" + product_id + ", product_name=" + product_name + ", price=" + price
+ ", modifyTime=" + modifyTime + "]";
}
}
缓存服务接口ICacheService.java:
package com.ys.test.ehcache.service;
import com.ys.test.ehcache.model.ProductInfo;
/**
* 缓存服务
*/
public interface ICacheService {
/**
* 保存商品信息到本地的ehcache
* @Description:
*
* @param info
* @return
* @throws Exception
* @return ProductInfo
* @version: v1.0.0
*/
ProductInfo saveProductInfo(ProductInfo info) throws Exception;
/**
* 根据商品ID查询商品信息
* @Description:
*
* @param id
* @return
* @throws Exception
* @return ProductInfo
* @version: v1.0.0
*/
ProductInfo getProductInfoById(Long id) throws Exception;
/**
* 根据Id清楚本地Ehcache缓存
* @Function: ICacheService.java
* @Description:
*
* @param id
* @throws Exception
* @return void
* @version: v1.0.0
*/
void releaseProductById(Long id) throws Exception;
/**
*
* @Function: ICacheService.java
* @Description: 把信息保存到Redis
*
* @param info
* @throws Exception
* @return void
* @version: v1.0.0
* @date: 2018年1月29日 下午7:34:19
*/
void saveProductInfoToRedis(ProductInfo info) throws Exception;
/**
* 从Redis中获取缓存信息
* @Function: ICacheService.java
* @Description:
*
* @param id
* @return
* @throws Exception
* @return ProductInfo
* @version: v1.0.0
*/
ProductInfo getProductInfoByIdToRedis(Long id) throws Exception;
/**
* 根据Id清楚本地Redis缓存
* @Function: ICacheService.java
* @Description:
*
* @param id
* @throws Exception
* @return void
* @version: v1.0.0
*/
void releaseProductByIdToRedis(Long id) throws Exception;
}
缓存实现类CacheServiceImpl.java:
package com.ys.test.ehcache.service.impl;
import javax.annotation.Resource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.ys.test.ehcache.mapper.ProductService;
import com.ys.test.ehcache.model.ProductInfo;
import com.ys.test.ehcache.service.ICacheService;
import com.ys.test.ehcache.zk.ZookeeperLockSingle;
import redis.clients.jedis.Jedis;
@Service
public class CacheServiceImpl implements ICacheService {
private Log log = LogFactory.getLog(CacheServiceImpl.class);
@Resource
private Jedis cluster;
@Resource
private ProductService productService;
private static final String CACHE_STRATEGY = "local";
private static final String KET_PREFIX = "key_";
@CachePut(value=CACHE_STRATEGY,key="'key_'+#info.getProduct_id()")
@Override
public ProductInfo saveProductInfo(ProductInfo info) throws Exception {
log.error("*********************保存数据********************");
info.setModifyTime(System.currentTimeMillis());
productService.saveProductInfo(info);
return info;
}
@Cacheable(value=CACHE_STRATEGY,key="'key_'+#id")
@Override
public ProductInfo getProductInfoById(Long id) throws Exception {
log.error("*********************没有Ehcache缓存********************");
log.error("*********************查询Redis********************");
ProductInfo productInfoById = null;
try {
productInfoById = getProductInfoByIdToRedis(id);
if (null != productInfoById) {
return productInfoById;
}
} catch (Exception e) {
log.error("*********************查询了Redis无缓存********************");
}
log.error("*********************查询了数据库********************");
productInfoById = productService.getProductInfoById(id);
//保存到Redis
commonSaveToRedis(productInfoById);
return productInfoById;
}
/**
* 以安全的方式保存到Redis中
* @Function: CacheServiceImpl.java
* @Description:
*
* @param id
* @param productInfoById
* @throws Exception
* @return void
* @version: v1.0.0
* @date: 2018年1月29日 下午7:53:29
*/
private void commonSaveToRedis(ProductInfo productInfoById) throws Exception {
long product_id = productInfoById.getProduct_id();
//保存到Redis
ZookeeperLockSingle lockSingle = ZookeeperLockSingle.getSingleZKLock();
boolean acquireDistrbutedLock = lockSingle.acquireDistrbutedLock(product_id);
//获取zookeeper锁
if (acquireDistrbutedLock) {
//比较Redis中缓存对象是否存在
ProductInfo productInfoByIdToRedis = getProductInfoByIdToRedis(product_id);
String key = KET_PREFIX + productInfoById.getProduct_id();
if (null != productInfoByIdToRedis) {
long redisTime = productInfoByIdToRedis.getModifyTime();
long nowTime = productInfoById.getModifyTime();
//比较缓存中数据是否为最新
if (redisTime < nowTime) {
String jsonStr = JSONObject.toJSONString(productInfoById);
String set = cluster.set(key,jsonStr);
log.error("*********************保存数据结果********************"+set);
}
} else {
String jsonStr = JSONObject.toJSONString(productInfoById);
String set = cluster.set(key,jsonStr);
log.error("*********************保存数据结果********************"+set);
}
}
//释放锁
lockSingle.releaseDistrbuteProductLock(product_id);
}
@CacheEvict(value=CACHE_STRATEGY, key="'key_'+#id")
@Override
public void releaseProductById(Long id) throws Exception {
}
@Override
public void saveProductInfoToRedis(ProductInfo info) throws Exception {
commonSaveToRedis(info);
}
@Override
public ProductInfo getProductInfoByIdToRedis(Long id) throws Exception {
String key = KET_PREFIX + id;
String string = cluster.get(key);
ProductInfo parseObject = JSONObject.parseObject(string, ProductInfo.class);
return parseObject;
}
@Override
public void releaseProductByIdToRedis(Long id) throws Exception {
String key = KET_PREFIX + id;
Long del = cluster.del(key);
log.error("*********************删除数据结果********************"+del);
}
}
控制类:CacheServiceController.java:
package com.ys.test.ehcache.controller;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ys.test.ehcache.model.ProductInfo;
import com.ys.test.ehcache.service.ICacheService;
@RestController
public class CacheServiceController {
@Resource
private ICacheService cahceService;
@RequestMapping("/saveInfo")
public boolean saveTest(ProductInfo info) throws Exception {
System.out.println(info.getProduct_name() + ":" + info.getProduct_id());
return cahceService.saveProductInfo(info) == null?false:true;
}
@RequestMapping("/getProductByid")
public ProductInfo getTest(Long id) throws Exception {
System.out.println(id);
return cahceService.getProductInfoById(id);
}
@RequestMapping("/getProductByidTORedis")
public ProductInfo getToRedisTest(Long id) throws Exception {
System.out.println(id);
return cahceService.getProductInfoByIdToRedis(id);
}
}
数据库:
运行日志:
日志说明:
我们的http://localhost:8080/getProductByid?id=8请求匹配到控制类的:
@RequestMapping("/getProductByid")
public ProductInfo getTest(Long id) throws Exception {
System.out.println(id);
return cahceService.getProductInfoById(id);
}
方法,其中调用了我们缓存服务的cahceService.getProductInfoById(id)方法,但是这个方法使用了注解:
@Cacheable(value=CACHE_STRATEGY,key="'key_'+#id"),所以会先从我们本地的Ehcache缓存中查询是否有key 为 key_8的缓存对象,如果找到了就返回找到的对象,(在上面的运行结果中是没有在本地Ehcache中找到key_8的缓存对象的)所以进入了getProductInfoById的方法体中执行了:(从数据库找到缓存对象之后注解@Cacheable 也会把这个对象保存到Ehcache中)
log.error("*********************没有Ehcache缓存********************");
log.error("*********************查询Redis********************");
但是在redis中也没有找到缓存对象,所以执行了:log.error("*********************查询了数据库********************");
在数据库中找到了我们要查询的对象,之后我们就开始设置到Redis中:
1.先链接zookeeper,获取zookeeper的锁,保证在某个时间段中只有一个服务去操作product_id 为8的对象,要获取锁:
2018-01-31 13:35:27.508 INFO 92024 --- [nio-8080-exec-1] c.y.test.ehcache.zk.ZookeeperLockSingle : 创建:/product-lock-8成功
2.获取锁成功之后就先检查redis中是否有product_id 为8的缓存对象,如果没有直接设置从数据库获取的对象保存到redis中,如果redis中有数据,则取出,比较当前的对象的modifytime字段,哪个最新就保存哪个。
而在
当再次请求:http://localhost:8080/getProductByid?id=8 时候,在本地Ehcache中能找到缓存对象,所以直接返回Ehcache中的对象,不再进入getProductInfoById方法中。