pox文件:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.5.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestone</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
Application:
import java.util.HashSet;
import java.util.Set;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ServletListenerRegistrationBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import com.roncoo.eshop.inventory.listener.InitListener;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
@EnableAutoConfiguration //自动载入应用程序所需的所有Bean
@SpringBootApplication //启动的类
@ComponentScan //扫描包
@MapperScan("com.roncoo.eshop.inventory.mapper")
//启动入口函数
public class Application {
//构建数据源
@Bean
@ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource() {
return new DataSource();
}
//构建MyBatis的入口类:SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactoryBean1() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/mybatis/*.xml"));
return sqlSessionFactoryBean.getObject();
}
//构建事务管理器
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public JedisCluster JedisClusterFactory() {
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
jedisClusterNodes.add(new HostAndPort("192.168.31.19", 7003));
jedisClusterNodes.add(new HostAndPort("192.168.31.19", 7004));
jedisClusterNodes.add(new HostAndPort("192.168.31.227", 7006));
JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes);
return jedisCluster;
}
//注册监听器 线程池+内存队列初始化
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean(){
ServletListenerRegistrationBean servletListenerRegistrationBean=
new ServletListenerRegistrationBean();
//添加listener
servletListenerRegistrationBean.setListener(new InitListener());
return servletListenerRegistrationBean;
}
//SpringBoot 启动入口的方法
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.roncoo.eshop.inventory.thread.RequestProcessorThreadPool;
//系统初始化监听器
public class InitListener implements ServletContextListener{
@Override
public void contextInitialized(ServletContextEvent sce){
// 初始化工作线程池和内存队列
RequestProcessorThreadPool.init();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
public interface Request {
void process();
Integer getProductId();
boolean isForceRefresh();
}
请求队列:
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
//请求内存队列
public class RequestQueue {
//内存队列
private List<ArrayBlockingQueue<Request>> queues=
new ArrayList<ArrayBlockingQueue<Request>>();
//标示位map
private Map<Integer, Boolean> flagMap =new ConcurrentHashMap<Integer,Boolean>();
//采用线程安全的方式实现单例
//静态内部类的方法,去初始化单例
private static class Singleton{
private static RequestQueue instance;
static{
instance=new RequestQueue();
}
public static RequestQueue getInstance(){
return instance;
}
}
//jvm的机制,保证多线程并发安全
//内部类的初始化 ,一定只发生一次,不管多少个线程并发去初始化
public static RequestQueue getInstance(){
return Singleton.getInstance();
}
//添加一个内存队列
public void addQueue(ArrayBlockingQueue<Request> queue){
this.queues.add(queue);
}
//获取内存队列的数量
public int queueSize(){
return queues.size();
}
//获取内存队列
public ArrayBlockingQueue<Request> getQueue(int index){
return queues.get(index);
}
public Map<Integer, Boolean> getFlagMap(){
return flagMap;
}
}
执行请求的工作线程:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import com.roncoo.eshop.inventory.request.ProductInventoryCacheRefreshRequest;
import com.roncoo.eshop.inventory.request.ProductInventoryDBUpdateRequest;
import com.roncoo.eshop.inventory.request.Request;
import com.roncoo.eshop.inventory.request.RequestQueue;
import java.util.Map;
//执行请求的工作线程
public class RequestProcessorThread implements Callable<Boolean> {
//自己监控的内存队列
private ArrayBlockingQueue<Request> queue;
//初始化的方法
public RequestProcessorThread(ArrayBlockingQueue<Request> queue) {
this.queue = queue;
}
//请求处理封装
public Boolean call() throws Exception{
try {
while(true) {
// ArrayBlockingQueue
// Blocking就是说明,如果队列满了,或者是空的,那么都会在执行操作的时候,阻塞住
Request request=queue.take();
boolean forceRfresh=request.isForceRefresh();
//先做读请求的去重
if(!forceRfresh){
RequestQueue requestQueue=RequestQueue.getInstance();
Map<Integer,Boolean> flagMap=requestQueue.getFlagMap();
if(request instanceof ProductInventoryDBUpdateRequest){
// 如果是一个更新数据库的请求,那么就将那个productId对应的标识设置为true
flagMap.put(request.getProductId(), true);
}else if(request instanceof ProductInventoryCacheRefreshRequest){
Boolean flag=flagMap.get(request.getProductId());
//如果flag是null
if(flag==null){
flagMap.put(request.getProductId(), false);
}
// 如果是缓存刷新的请求,那么就判断,如果标识不为空,而且是true,就说明之前有一个这个商品的数据库更新请求
if(flag!=null&&flag){
flagMap.put(request.getProductId(), false);
}
// 如果是缓存刷新的请求,而且发现标识不为空,但是标识是false
// 说明前面已经有一个数据库更新请求+一个缓存刷新请求了
if(flag!=null&&!flag){
// 对于这种读请求,直接就过滤掉,不要放到后面的内存队列里面去了
return true;
}
}
}
// 执行这个request操作
request.process();
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
请求处理的线程池:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.roncoo.eshop.inventory.request.Request;
import com.roncoo.eshop.inventory.request.RequestQueue;
//请求处理线程池:单例
public class RequestProcessorThreadPool {
// 在实际项目中,你设置线程池大小是多少,每个线程监控的那个内存队列的大小是多少
// 都可以做到一个外部的配置文件中
// 直接写死了
//线程池
private ExecutorService threadPool=Executors.newFixedThreadPool(10);
//构造函数
public RequestProcessorThreadPool(){
RequestQueue requestQueue =RequestQueue.getInstance();
for(int i=0;i<10;i++){
ArrayBlockingQueue<Request> queue=new ArrayBlockingQueue<Request>(100);
requestQueue.addQueue(queue);
threadPool.submit(new RequestProcessorThread(queue));
}
}
//静态内部类的方式去初始化线程的方式
public static class Singleton{
private static RequestProcessorThreadPool instance;
static{
instance=new RequestProcessorThreadPool();
}
public static RequestProcessorThreadPool getInstance(){
return instance;
}
}
//jvm的方式去保证多线程并发安全
//内部类的初始化,一定只发生一次,不管多少个线程并发初始化
public static RequestProcessorThreadPool getInstance(){
return Singleton.getInstance();
}
//初始化的便捷方法
public static void init(){
getInstance();
}
}
请求的响应:
//请求的响应
public class Response {
public static final String SUCCESS = "success";
public static final String FAILURE = "failure";
private String status;
private String message;
public Response() {
}
public Response(String status) {
this.status = status;
}
public Response(String status, String message) {
this.status = status;
this.message = message;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
2、两种请求对象封装
import com.roncoo.eshop.inventory.model.ProductInventory;
import com.roncoo.eshop.inventory.service.ProductInventoryService;
/**
* 比如说一个商品发生了交易,那么就要修改这个商品对应的库存
* 此时就会发送请求过来,要求修改库存,那么这个可能就是所谓的data update request,数据更新请求
* cache aside pattern
* (1)删除缓存
* (2)更新数据库
* **/
//请求对象的封装,删除redis的缓存,修改数据库中的库存
public class ProductInventoryDBUpdateRequest implements Request{
// 商品库存
private ProductInventory productInventory;
//商品库存Service
private ProductInventoryService productInventoryService;
public ProductInventoryDBUpdateRequest(ProductInventory productInventory,
ProductInventoryService productInventoryService){
this.productInventory=productInventory;
this.productInventoryService=productInventoryService;
}
@Override
public void process(){
// 删除redis中的缓存
productInventoryService.removeProductInventoryCache(productInventory);
// 修改数据库中的库存
productInventoryService.updateProductInventory(productInventory);
}
//获取商品id
public Integer getProductId(){
return productInventory.getProductId();
}
@Override
public boolean isForceRefresh() {
return false;
}
}
import com.roncoo.eshop.inventory.model.ProductInventory;
import com.roncoo.eshop.inventory.service.ProductInventoryService;
//重新加载商品库存的缓存(请求对象的封装)
public class ProductInventoryCacheRefreshRequest implements Request {
//商品id
private Integer productId;
//商品库存Service
private ProductInventoryService productInventoryService;
//是否强制刷新缓存
private boolean forceRefresh;
public ProductInventoryCacheRefreshRequest(Integer productId,
ProductInventoryService productInventoryService,
boolean forceRefresh) {
this.productId=productId;
this.productInventoryService=productInventoryService;
this.forceRefresh=forceRefresh;
}
@Override
public void process() {
// 从数据库中查询最新的商品库存数量
ProductInventory productInventory = productInventoryService.findProductInventory(productId);
// 将最新的商品库存数量,刷新到redis缓存中去
productInventoryService.setProductInventoryCache(productInventory);
}
//返回商品的id
public Integer getProductId() {
return productId;
}
public boolean isForceRefresh() {
return forceRefresh;
}
}
3、请求异步执行Service封装
import java.util.concurrent.ArrayBlockingQueue;
import org.springframework.stereotype.Service;
import com.roncoo.eshop.inventory.request.Request;
import com.roncoo.eshop.inventory.request.RequestQueue;
import com.roncoo.eshop.inventory.service.RequestAsyncProcessService;
//请求异步处理的service实现
@Service("requestAsyncProcessService")
public class RequestAsyncProcessServiceImpl implements RequestAsyncProcessService{
//做请求的的路由,根据每个请求的商品id,路由到对应的内存队列中
@Override
public void process(Request request) {
try {
// 做请求的路由,根据每个请求的商品id,路由到对应的内存队列中去
ArrayBlockingQueue<Request> queue = getRoutingQueue(request.getProductId());
// 将请求放入对应的队列中,完成路由操作
queue.put(request);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取路由到的内存队列
private ArrayBlockingQueue<Request> getRoutingQueue(Integer productId){
RequestQueue requestQueue=RequestQueue.getInstance();
//获取productId的hash值
String key=String.valueOf(productId);
int h;
int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
// 对hash值取模,将hash值路由到指定的内存队列中,比如内存队列大小8
// 用内存队列的数量对hash值取模之后,结果一定是在0~7之间
// 任何一个商品id都会被固定路由到同样的一个内存队列中去的
int index=(requestQueue.queueSize()-1)&hash;
return requestQueue.getQueue(index);
}
}
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.roncoo.eshop.inventory.dao.RedisDAO;
import com.roncoo.eshop.inventory.mapper.ProductInventoryMapper;
import com.roncoo.eshop.inventory.model.ProductInventory;
import com.roncoo.eshop.inventory.service.ProductInventoryService;
//商品库存Service实现类
@Service("productInventoryService")
public class ProductInventoryServiceImpl implements ProductInventoryService {
@Resource
private ProductInventoryMapper productInventoryMapper;
@Resource
private RedisDAO redisDAO;
@Override
public void updateProductInventory(ProductInventory productInventory) {
productInventoryMapper.updateProductInventory(productInventory);
}
@Override
public void removeProductInventoryCache(ProductInventory productInventory){
String key="product:inventory:"+productInventory.getProductId();
redisDAO.delete(key);
}
//根据商品id查询商品库存
public ProductInventory findProductInventory(Integer productId) {
return productInventoryMapper.findProductInventory(productId);
}
//设置商品库存的缓存
public void setProductInventoryCache(ProductInventory productInventory){
String key = "product:inventory:" + productInventory.getProductId();
redisDAO.set(key, String.valueOf(productInventory.getInventoryCnt()));
}
// 获取商品库存的缓存
public ProductInventory getProductInventoryCache(Integer productId){
Long inventoryCnt = 0L;
String key="product:inventory:" + productId;
String result=redisDAO.get(key);
if(result!=null&&!"".equals(result)){
try {
inventoryCnt = Long.valueOf(result);
return new ProductInventory(productId, inventoryCnt);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
import com.roncoo.eshop.inventory.request.Request;
//请求异步执行的server
public interface RequestAsyncProcessService {
void process(Request request);
}
import com.roncoo.eshop.inventory.model.ProductInventory;
public interface ProductInventoryService {
/**
* 更新商品库存
* @param productInventory 商品库存
*/
void updateProductInventory(ProductInventory productInventory);
/**
* 删除Redis中的商品库存的缓存
* @param productInventory 商品库存
*/
void removeProductInventoryCache(ProductInventory productInventory);
/**
* 根据商品id查询商品库存
* @param productId 商品id
* @return 商品库存
*/
ProductInventory findProductInventory(Integer productId);
/**
* 设置商品库存的缓存
* @param productInventory 商品库存
*/
void setProductInventoryCache(ProductInventory productInventory);
/**
* 获取商品库存的缓存
* @param productId
* @return
*/
ProductInventory getProductInventoryCache(Integer productId);
}
控制器:
import com.roncoo.eshop.inventory.model.ProductInventory;
import com.roncoo.eshop.inventory.request.ProductInventoryCacheRefreshRequest;
import com.roncoo.eshop.inventory.request.ProductInventoryDBUpdateRequest;
import com.roncoo.eshop.inventory.request.Request;
import com.roncoo.eshop.inventory.service.ProductInventoryService;
import com.roncoo.eshop.inventory.service.RequestAsyncProcessService;
import com.roncoo.eshop.inventory.vo.Response;
//商品库存Controller
/*
*(1)一个更新商品库存的请求过来,然后此时会先删除redis中的缓存,然后模拟卡顿5秒钟
*(2)在这个卡顿的5秒钟内,我们发送一个商品缓存的读请求,因为此时redis中没有缓存,就会来请求将数据库中最新的数据刷新到缓存中
*(3)此时读请求会路由到同一个内存队列中,阻塞住,不会执行
*(4)等5秒钟过后,写请求完成了数据库的更新之后,读请求才会执行
*(5)读请求执行的时候,会将最新的库存从数据库中查询出来,然后更新到缓存中
* */
public class ProductInventoryController {
@Resource
private RequestAsyncProcessService requestAsyncProcessService;
@Resource
private ProductInventoryService productInventoryService;
//更新商品库存
@RequestMapping("/updateProductInventory")
@ResponseBody
public Response updateProductInventory(ProductInventory productInventory){
Response response=null;
try{
Request request=new ProductInventoryDBUpdateRequest(
productInventory, productInventoryService);
requestAsyncProcessService.process(request);
response = new Response(Response.SUCCESS);
}
catch (Exception e) {
e.printStackTrace();
response = new Response(Response.FAILURE);
}
return response;
}
//获取商品库存
@RequestMapping("/getProductInventory")
@ResponseBody
public ProductInventory getProductInventory(Integer productId){
ProductInventory productInventory = null;
try{
Request request = new ProductInventoryCacheRefreshRequest(
productId, productInventoryService, false);
requestAsyncProcessService.process(request);
requestAsyncProcessService.process(request);
// 将请求扔给service异步去处理以后,就需要while(true)一会儿,在这里hang住
// 去尝试等待前面有商品库存更新的操作,同时缓存刷新的操作,将最新的数据刷新到缓存中
long startTime = System.currentTimeMillis();
long endTime = 0L;
long waitTime = 0L;
// 等待超过200ms没有从缓存中获取到结果
while(true){
if(waitTime>200){
break;
}
// 尝试去redis中读取一次商品库存的缓存数据
productInventory =productInventoryService.getProductInventoryCache(productId);
// 如果读取到了结果,那么就返回
if(productInventory != null) {
return productInventory;
}
// 如果没有读取到结果,那么等待一段时间
else {
Thread.sleep(20);
endTime = System.currentTimeMillis();
waitTime = endTime - startTime;
}
}
// 直接尝试从数据库中读取数据
productInventory = productInventoryService.findProductInventory(productId);
if(productInventory != null) {
// 将缓存刷新一下
productInventoryService.setProductInventoryCache(productInventory);
return productInventory;
}
} catch (Exception e) {
e.printStackTrace();
}
return new ProductInventory(productId, -1L);
}
}
public interface RedisDAO {
void set(String key, String value);
String get(String key);
void delete(String key);
}
import javax.annotation.Resource;
import org.springframework.stereotype.Repository;
import redis.clients.jedis.JedisCluster;
import com.roncoo.eshop.inventory.dao.RedisDAO;
@Repository("redisDAO")
public class RedisDAOImpl implements RedisDAO {
@Resource
private JedisCluster jedisCluster;
@Override
public void set(String key, String value) {
jedisCluster.set(key, value);
}
@Override
public String get(String key) {
return jedisCluster.get(key);
}
@Override
public void delete(String key) {
jedisCluster.del(key);
}
}
6、读请求去重优化
如果一个读请求过来,发现前面已经有一个写请求和一个读请求了,那么这个读请求就不需要压入队列中了
因为那个写请求肯定会更新数据库,然后那个读请求肯定会从数据库中读取最新数据,然后刷新到缓存中,自己只要hang一会儿就可以从缓存中读到数据了
7、空数据读请求过滤优化
可能某个数据,在数据库里面压根儿就没有,那么那个读请求是不需要放入内存队列的,而且读请求在controller那一层,直接就可以返回了,不需要等待
如果数据库里都没有,就说明,内存队列里面如果没有数据库更新的请求的话,一个读请求过来了,就可以认为是数据库里就压根儿没有数据吧
如果缓存里没数据,就两个情况,第一个是数据库里就没数据,缓存肯定也没数据; 第二个是数据库更新操作过来了,先删除了缓存,此时缓存是空的,但是数据库里是有的
但是的话呢,我们做了之前的读请求去重优化,用了一个flag map,只要前面有数据库更新操作,flag就肯定是存在的,你只不过可以根据true或false,判断你前面执行的是写请求还是读请求
但是如果flag压根儿就没有呢,就说明这个数据,无论是写请求,还是读请求,都没有过
那这个时候过来的读请求,发现flag是null,就可以认为数据库里肯定也是空的,那就不会去读取了
或者说,我们也可以认为每个商品有一个最最初始的库存,但是因为最初始的库存肯定会同步到缓存中去的,有一种特殊的情况,就是说,商品库存本来在redis中是有缓存的
但是因为redis内存满了,就给干掉了,但是此时数据库中是有值得
那么在这种情况下,可能就是之前没有任何的写请求和读请求的flag的值,此时还是需要从数据库中重新加载一次数据到缓存中的