缓存双写java队列_实现缓存与数据库双写一致性保障

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> queues=

new ArrayList>();

//标示位map

private Map flagMap =new ConcurrentHashMap();

//采用线程安全的方式实现单例

//静态内部类的方法,去初始化单例

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 queue){

this.queues.add(queue);

}

//获取内存队列的数量

public int queueSize(){

return queues.size();

}

//获取内存队列

public ArrayBlockingQueue getQueue(int index){

return queues.get(index);

}

public Map 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 {

//自己监控的内存队列

private ArrayBlockingQueue queue;

//初始化的方法

public RequestProcessorThread(ArrayBlockingQueue 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 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 queue=new ArrayBlockingQueue(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 queue = getRoutingQueue(request.getProductId());

// 将请求放入对应的队列中,完成路由操作

queue.put(request);

} catch (Exception e) {

e.printStackTrace();

}

}

//获取路由到的内存队列

private ArrayBlockingQueue 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的值,此时还是需要从数据库中重新加载一次数据到缓存中的

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值