高并发下缓存数据库数据一致性解决方案及实现代码

12 篇文章 0 订阅

一、技术栈

springboot+mybatis+redis

二、实现原理

以商品库存为例:

更新商品库存的时候,根据数据(如商品ID )的唯一标识,将操作路由之后,发送到一个jvm内部的队列中

读取数据(库存)的时候,如果发现数据不在缓存(redis)中,那么将执行重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个jvm内部的队列中

一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,一条一条的执行

一个数据变更(更改库存)的操作,先执行删除缓存,然后再去更新数据库,但是还没完成更新,此时如果一个读请求(读取库存)过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成

优化点: 一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可,待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中,如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回; 如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值

 

三、实现步骤

1:线程池+内存队列初始化
2:两种请求(读写)对象封装
3:请求异步执行Service封装
4:请求处理的工作线程封装
5:两种请求Controller接口封装
6:读请求去重优化

 

四、代码实现:

线程池+内存队列初始化:

在springboot启动类中注册监听器,启动时初始化线程池与内存队列

    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean(){
        ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
        servletListenerRegistrationBean.setListener(new InitListener());
        return servletListenerRegistrationBean;
    }
public class InitListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        //初始化方法
        RequestProcessorThreadPool.init();
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

初始化线程池,采用内部类的方式单例初始化,初始化内存队列,并将内存队列与线程绑定

/**
 * 请求处理线程池
 */
public class RequestProcessorThreadPool {

    /**
     * 线程池
     */
    private ExecutorService threadPool = Executors.newFixedThreadPool(10);

    /**
     * 构造方法绑定线程与队列
     */
    public RequestProcessorThreadPool(){
        for(int i = 0; i < 10; i++){
            ArrayBlockingQueue<Request> queue = new ArrayBlockingQueue<Request>(100);
            RequestQueue requestQueue = RequestQueue.getInstance();
            requestQueue.addQueues(queue);
            threadPool.submit(new RequestProcessorThread(queue));
        }
    }


    private static class Singleton{

        private static RequestProcessorThreadPool instance;

        static {
            instance = new RequestProcessorThreadPool();
        }

        public static RequestProcessorThreadPool getInstance(){
            return instance;
        }

    }

    public static RequestProcessorThreadPool getInstance(){
        return Singleton.getInstance();
    }

    public static RequestProcessorThreadPool init(){
        return getInstance();
    }

}
/**
 * 请求内存队列
 */
public class RequestQueue {

    private List<ArrayBlockingQueue<Request>> queues = new ArrayList<>();

    //维护标识过滤请求优化
    private Map<Integer,Boolean> flagMap = new ConcurrentHashMap<>();


    private static class Singleton{

        private static RequestQueue queues;

        static {
            queues = new RequestQueue();
        }

        public static RequestQueue getInstance(){
            return queues;
        }

    }

    /**
     * 初始化
     * @return
     */
    public static RequestQueue getInstance(){
        return Singleton.getInstance();
    }

    /**
     * 添加队列
     * @param queue
     */
    public void addQueues(ArrayBlockingQueue<Request> queue){
        this.queues.add(queue);
    }

    /**
     * 获取内存队列大小
     * @return
     */
    public int queuesSize(){
        return this.queues.size();
    }

    /**
     * 获取内存队列
     * @param index
     * @return
     */
    public ArrayBlockingQueue<Request> getQueue(int index){
        return this.queues.get(index);
    }

    /**
     * 获取标识
     * @return
     */
    public Map<Integer, Boolean> getFlagMap() {
        return flagMap;
    }

}

两种请求(读写)对象封装

接口类


/**
 * 请求对象
 */
public interface Request {

    void process();

    Integer getProductId();

    Boolean getFlag();

}

读请求

public class ProductStockCacheRefreshRequest implements Request {

    //库存操作service
    private ProductStockService productStockService;
    //商品id
    private Integer productId;
    //强制刷新缓存标识位
    private Boolean flag;

    public ProductStockCacheRefreshRequest(ProductStockService productStockService,Integer productId,Boolean flag){
        this.productId = productId;
        this.productStockService = productStockService;
        this.flag = flag;
    }

    @Override
    public void process() {
        //获取DB库存
        ProductStock productStock = productStockService.findProductStock(productId);
        //将DB库存刷新至缓存
        productStockService.setProductStockCache(productStock);
    }

    @Override
    public Integer getProductId() {
        return this.productId;
    }

    @Override
    public Boolean getFlag() {
        return this.flag;
    }
}

写请求

public class ProductStockDBUpdateRequest implements Request {

    //库存操作service
    private ProductStockService productStockService;
    //库存信息
    private ProductStock productStock;

    public ProductStockDBUpdateRequest(ProductStockService productStockService,ProductStock productStock){
        this.productStock = productStock;
        this.productStockService = productStockService;
    }

    @Override
    public void process() {
        //删除缓存
        productStockService.removeProductStockCache(productStock);
        //更新数据库库存
        productStockService.updateProductStock(productStock);
    }

    @Override
    public Integer getProductId() {
        return this.productStock.getProductId();
    }

    @Override
    public Boolean getFlag() {
        return false;
    }

}

请求异步执行Service封装

public interface RequestAsyncProcessService {

    void process(Request request);

}
@Service
public class RequestAsyncProcessServiceImpl implements RequestAsyncProcessService {

    @Override
    public void process(Request request) {
        try {
            //获取到商品指定的路由队列
            ArrayBlockingQueue<Request> queue = getRoutingQueue(request.getProductId());
            //将商品添加到该队列等待处理
            queue.put(request);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
    /**
     * 获取路由到的内存队列
     * @param productId 商品id
     * @return 内存队列
     */
    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.queuesSize() - 1) & hash;
        //log.info("路由内存队列,商品id:{},索引:{}",productId,index);
        return requestQueue.getQueue(index);
    }


}

请求处理的工作线程封装(包含读请求去重优化)

public class RequestProcessorThread implements Callable<Boolean> {

    private ArrayBlockingQueue<Request> queue;

    public RequestProcessorThread(ArrayBlockingQueue<Request> queue){
        this.queue = queue;
    }

    @Override
    public Boolean call(){
        try {
            while (true){
                Request request = queue.take();
                Map<Integer,Boolean> flagMap = RequestQueue.getInstance().getFlagMap();
                if(request instanceof ProductStockDBUpdateRequest){
                    //写请求
                    flagMap.put(request.getProductId(),true);
                    request.process();
                }
                else{
                    //读请求过滤
                    if(!request.getFlag()){ //不需强制刷新缓存的做读请求过滤
                        Boolean flag = flagMap.get(request.getProductId());
                        //读请求
                        if(null == flag){
                            //空请求算作读缓存
                            flagMap.put(request.getProductId(),false);
                            request.process();
                        }
                        if(null != flag && flag){
                            flagMap.put(request.getProductId(),false);
                            request.process();
                        }
                        System.out.println("当前读请求已过滤...");
                        //if(null != flag && !flag){//过滤不做处理}
                    }else{//强制刷新缓存
                        flagMap.put(request.getProductId(),false);
                        request.process();
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }
}

两种请求Controller接口封装

@RestController
public class ProductStockController {

    @Autowired
    private ProductStockService productStockService;
    @Autowired
    private RequestAsyncProcessService requestAsyncProcessService;

    /**
     * 更新商品库存
     */
    @PutMapping("/product/stock")
    public Response updateProductInventory(ProductStock productStock) {
        Response response = null;
        try {
            Request request = new ProductStockDBUpdateRequest(productStockService,productStock);
            requestAsyncProcessService.process(request);
            response = new Response(Response.SUCCESS);
        } catch (Exception e) {
            e.printStackTrace();
            response = new Response(Response.FAIL);
        }

        return response;
    }

    @GetMapping("/product/{id}")
    public Response getProductStock(@PathVariable("id")Integer id){
        try {
            Request request = new ProductStockCacheRefreshRequest(productStockService,id,false);
            requestAsyncProcessService.process(request);

            long startTime = System.currentTimeMillis();
            long endTime = 0L;
            long waitTime = 0L;

            while (waitTime < 200){
                //尝试从缓存获取数据
                ProductStock productStock = productStockService.getProductStockCache(id);
                if(null != productStock){
                    return new Response(Response.SUCCESS,productStock);
                }
                try {
                    Thread.sleep(20);
                }catch (Exception e){
                    e.printStackTrace();
                }
                endTime = System.currentTimeMillis();
                waitTime = endTime - startTime;
            }
            //200毫秒内未获取到数据,从DB中获取并返回
            ProductStock productStock = productStockService.findProductStock(id);
            if(null != productStock){
                // 将缓存刷新一下,并放入队列串行处理
                // 为防止redis的LRU算法清理掉数据,导致上次的读请求的标志位在false上被过滤,这里需要强制刷新缓存
                request = new ProductStockCacheRefreshRequest(productStockService,id,true);
                requestAsyncProcessService.process(request);
                return new Response(Response.SUCCESS,productStock);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return new Response(Response.SUCCESS,-1L);
    }



}

五、说明

核心代码在文中已经贴出,其余service代码等,就不在这里放上去了,完整实现demo见GitHub地址

https://github.com/dwhhome/stock-server

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值