第五篇:spring boot整合redis实现分布式锁和缓存

1. 集成Redis

首先在pom.xml中加入需要的redis依赖



    org.springframework.boot
    spring-boot-starter-data-redis

SpringBoot的yml配置文件下增加redis的配置:

  redis:
    host: 127.0.0.1
    port: 6379
    # password:
    jedis:
      pool:
        max-active: 8 #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms  #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 #连接池中的最大空闲连接
        min-idle: 5 #连接池中的最小空闲连接

2. 实现Redis分布式锁

注入Redis的操作类

@Autowired
private StringRedisTemplate stringRedisTemplate;

进行加锁,传入key值和value值

public boolean lock(String key,String value){
        /**
         * 键值不存在则新增,并返回true
         * 键值存在则不改变原来的值,并返回false
         */
        boolean res = stringRedisTemplate.opsForValue().setIfAbsent(key,value,10, TimeUnit.SECONDS);
        return res;
    }

进行解锁,传入key值,先判断需要解锁的值是否等于加锁的value值,如果是的话进行解锁

public void unlock(String key){

        if (stringRedisTemplate.opsForValue().get(key).equals("testlock")){
            //删除key
            stringRedisTemplate.opsForValue().getOperations().delete(key);
        }
    }

3. 简单的例子

模拟一下网上下单,先对下单之前进行加锁,防止其他线程进来下单,不然库存量就会有异常,在下单之后进行解锁,让其他线程进来下单,下面是下单的代码

package com.anytxn.cms.service.impl;

import com.anytxn.cms.common.RedisLock;
import com.anytxn.cms.service.SeckillService;
import com.anytxn.cms.util.HelpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * @author zjx
 * @date 2019/12/24 19:37
 */
@Service
    public class SeckillServiceImpl implements SeckillService {

        private static final Logger logger = LoggerFactory.getLogger(SeckillServiceImpl.class);

        @Autowired
        private RedisLock redisLock;

        /**
         * 活动,特价,限量100000份
         */
        private static Map products;//模拟商品信息表
        private static Map stock;//模拟库存表
        private static Map orders;//模拟下单成功用户表

        static {
            /**
             * 模拟多个表,商品信息表,库存表,秒杀成功订单表
             */
            products = new HashMap<>();
            stock = new HashMap<>();
            orders = new HashMap<>();
            products.put("123456",100000);
            stock.put("123456",100000);
        }

        private String queryMap(String productId){//模拟查询数据库
            return "国庆活动,皮蛋特教,限量"
                    +products.get(productId)
                    +"份,还剩:"+stock.get(productId)
                    +"份,该商品成功下单用户数:"
                    +orders.size()+"人";
        }

        private String querySecKillProductInfo(String productId) {
            return this.queryMap(productId);
        }

        @Override
        public void orderProductMocckDiffUser(String productId) {
            //加锁
            if(!redisLock.lock("123456","testlock")){
                logger.warn("很抱歉,人太多了,换个姿势再试试~~");
                return;
            }
            //1.查询该商品库存,为0则活动结束
            int stockNum = stock.get(productId);
            if(stockNum == 0){
                logger.warn("活动已经结束");
                return;
            }else {
                //2.下单
                orders.put(HelpUtil.getUniqueKey(),productId);
                //3.减库存
                stockNum = stockNum - 1;
                try{
                    Thread.sleep(100);//模拟减库存的处理时间
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                stock.put(productId,stockNum);
            }

            String result = this.querySecKillProductInfo("123456");
            System.out.println(Thread.currentThread().getName() + ":" + result + ",成功下单");
            //解锁
            redisLock.unlock("123456");
        }
}

开启多个线程进行同时下单,看库存是否有异常

package com.anytxn;

import com.anytxn.cms.CmsApp;
import com.anytxn.cms.service.SeckillService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.concurrent.*;

/**
 * @author zjx
 * @date 2019/12/24 20:11
 */
@SpringBootTest(classes = CmsApp.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SeckillServiceTest {

    private static final Logger logger = LoggerFactory.getLogger(SeckillServiceTest.class);

    @Autowired
    private SeckillService seckillService;

    /**
     * 开启多个线程模拟高并发,测试redis分布式锁
     */
    @Test
    public void test(){
        //线程数量
        int threadSize = 3;
        ExecutorService executorService = Executors.newFixedThreadPool(threadSize);
        CompletionService completionService = new ExecutorCompletionService<>(executorService);
        for (int i = 0;i < threadSize;i++){
            completionService.submit(new Callable() {
                @Override
                public Object call() throws Exception {
                    for (int i = 0;i < 1000; i++){
                        seckillService.orderProductMocckDiffUser("123456");
                    }
                    return true;
                }
            });
        }
        for (int i = 0; i < threadSize; i++){
            try {
                boolean result = (boolean)completionService.take().get();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        //关闭线程池
        executorService.shutdown();
    }

}

4. 实现Redis缓存

先注入Redis操作类,直接用这个类进行存数据和取数据就可以了

package com.anytxn.cms.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author zjx
 * @date 2019/12/25 16:02
 */
@Service
public class RedisService {

    /**
     * @Autowired 默认按照类型装配的
     * @Resource  默认按照名字装配
     * 由于RedisTemplate的默认类型是RedisTemplate,如果使用@Autowired则只能注入RedisTemplate
     * 若注入RedisTemplate只能用注解 @Resource
     */

    //private RedisTemplate redisTemplate;

    /*@Resource
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        this.redisTemplate = redisTemplate;
    }*/

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 存数据到redis
     */
    public void pushVaule(Map mapVale, String key, long expireMinutes, TimeUnit timeUnit){
        try {
            redisTemplate.opsForHash().putAll(key,mapVale);
            redisTemplate.expire(key,expireMinutes,timeUnit);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 从redis中取数据
     */
    public Map getVaule(String key){
        Map map = null;
        try {
            map = redisTemplate.opsForHash().entries(key);
        }catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }

}

单元测试Redis

package com.anytxn;

import com.alibaba.fastjson.JSON;
import com.anytxn.cms.CmsApp;
import com.anytxn.cms.common.RedisService;
import com.anytxn.cms.model.CreditBill;
import com.anytxn.cms.repository.CreditBillRepository;
import com.anytxn.cms.util.DateUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author zjx
 * @date 2019/12/25 16:35
 */
@SpringBootTest(classes = CmsApp.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class RedisTest {

    @Autowired
    private RedisService redisService;

    @Autowired
    private CreditBillRepository creditBillRepository;

    /**
     * 测试往redis存数据
     */
    @Test
    public void test(){
        CreditBill creditBill = new CreditBill();
        creditBill.setName("jx");
        creditBill.setAccountID("888888");
        creditBill.setAddress("zjx");
        creditBill.setAmount(new BigDecimal("0.00"));
        creditBill.setDate(DateUtils.format(new Date(),DateUtils.DATE_PATTERN));

        String strCreditBill = JSON.toJSONString(creditBill);
        Map map = new HashMap<>();
        map.put("1",strCreditBill);
        redisService.pushVaule(map,"model_key",20L, TimeUnit.MINUTES);
        creditBillRepository.save(creditBill);
    }

    /**
     * 测试往redis取数据
     * 若取不到则查数据库
     */
    @Test
    public void test1(){
        CreditBill creditBill;
        Map map = redisService.getVaule("model_key");
        if (!CollectionUtils.isEmpty(map)){
            String strCreditBill = map.get("1").toString();
            creditBill = JSON.toJavaObject(JSON.parseObject(strCreditBill),CreditBill.class);
        }else {
            Optional CreResult = creditBillRepository.findById(2);
            creditBill = CreResult.isPresent() ? CreResult.get() : null;
        }
        System.out.println(creditBill.toString());
    }

}

 

你可能感兴趣的:(spring,boot)