Spring Boot 使用Redis和Ehcache做拥有二级缓存的系统 - Go语言中文社区

Spring Boot 使用Redis和Ehcache做拥有二级缓存的系统


上一张说到了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方法中。







版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u014104286/article/details/79149578
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-08 11:50:08
  • 阅读 ( 886 )
  • 分类:Redis

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢