springboot下redis的使用

  1. 启动原理
  2. 使用 -redisTemplate
    1. redis 字符串类型的应用
    2. redis set 集合
    3. redis zset 有序集合

启动原理

  首先,我们需要了解对于redis是如何在spring中进行注入的,在springboot启动时, 会加载 MEAT-INF下的spring.factories文件将常用的类注入到spring的beanfactory中,其中就包括redis ,下面是一段该文件的内容,其中的org.springframework.boot.autoconfigure.EnableAutoConfiguration就是关键。

...
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
...

RedisAutoConfiguration 就是redis自动注入所用的类。该类中将redisTemplate 注入到spring中以供我们使用 ,同时可以通过RedisProperties找到redis在springboot中可以配置的参数有哪些。可以看到 无论是redisTemplate 还是stringRedisTemplate 都需要一个RedisConnectionFactory 而连接工程则是通过@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) 这个注解 导入的类主动注入redisConnectionFactory的获取的。 ok

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}
@Bean
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
		return createJedisConnectionFactory();
	}

	private JedisConnectionFactory createJedisConnectionFactory() {
		JedisClientConfiguration clientConfiguration = getJedisClientConfiguration();
		if (getSentinelConfig() != null) {
			return new JedisConnectionFactory(getSentinelConfig(), clientConfiguration);
		}
		if (getClusterConfiguration() != null) {
			return new JedisConnectionFactory(getClusterConfiguration(),
					clientConfiguration);
		}
		return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
	}

使用 -redisTemplate

redis 字符串类型的应用


    @Autowired
    RedisTemplate redisTemplate;
    
    @RequestMapping(value = "/getkey",method = RequestMethod.GET)
    @ResponseBody
    public String getkey(@RequestParam String key){
        String val=(String)redisTemplate.boundValueOps(key).get();
        return val;
    }

    @RequestMapping(value = "/setkey/{v}",method = RequestMethod.POST)
    @ResponseBody
    public String setkey(@PathVariable String v){
        redisTemplate.boundValueOps("dd").set(v);
        return "ok";
    }

    //设定key val 值 及过期时间 使用lua脚本保证原子性
    private static final String lock_lua= "if (redis.call('SETNX' ,KEYS[1],ARGV[1]) ==1 ) then if (redis.call('EXPIRE' ,KEYS[1],ARGV[2])==1 ) then return 1 end end  return 0";
    // 获取 key 对应的值 判断是否是自己设定的 是就删除
    private static final String unlock_lua="local getVal = redis.call('get', KEYS[1]) if getVal == false then return 1 end if getVal ~= ARGV[1] then return 0 end return redis.call('del', KEYS[1])";




    private static final String rdlock="redis_lock";
    //redis 分布式锁 模拟操作redis共享数据
    @RequestMapping(value = "/rdlock/",method = RequestMethod.POST)
    @ResponseBody
    public String rdlock() throws InterruptedException {
        //模拟被操做的共享数据
        redisTemplate.boundValueOps("count").set(1);
        //向下的计数器 countDown() 向下计数 await()阻塞线程
        CountDownLatch countDownLatch = new CountDownLatch(10);
        ThreadPoolExecutor threadPool =  new ThreadPoolExecutor(10,10,10,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
        long time= 50L;
        for(int i=0 ;i<10 ;i++){
            //模拟多个请求操作
            threadPool.execute(()->{

                String val = RandomStringUtils.randomAlphanumeric(10)+Thread.currentThread().getId();
                //加锁
                int count=0;
                while(true){
                    boolean islock = (boolean) redisTemplate.execute(new DefaultRedisScript(lock_lua,Boolean.class), Arrays.asList(new String[]{rdlock}),val,new Integer(30));
                    System.out.println(Thread.currentThread().getName()+"加锁:"+islock);
                    //加锁失败操作
                    if(!islock){
                        if(count==50){
                            //todo 50次都没获得锁 。。。 do something 扔MQ里?
                        }
                        count+=1;
                        try {
                            Thread.sleep(100);
                            continue;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //获得锁就跳出循环
                    break;
                }
                //操作数据
                redisTemplate.boundValueOps("count").set((int)redisTemplate.boundValueOps("count").get()+1);
                //解锁
                boolean isunlock = (boolean) redisTemplate.execute(new DefaultRedisScript(unlock_lua,Boolean.class), Arrays.asList(new String[]{rdlock}),val);
                System.out.println(Thread.currentThread().getName()+"解锁:"+isunlock);
                if(isunlock){
                    countDownLatch.countDown();
                }
                else{
                    //todo 解锁失败 do something
                }
            });
        }
        countDownLatch.await();
        return String.valueOf(redisTemplate.boundValueOps("count").get());
    }

redis set 集合

set 就像java的hashset似的使用一个value为空的map作为内部存储 多数情况下会使用他的 交 并 差 三种 例如 好友之间的共同关注内容 或者 推荐可能认识的好友 等等


 //set 提供 交集、并集、差集 操作较为常见
    @RequestMapping(value = "/setoption",method = RequestMethod.POST)
    @ResponseBody
    public Map setoption(){

        Map map = new HashMap();
        String[] set1 =new String[]{"张三","李四","王五","李白","杜甫"};
        String[] set2 =new String[]{"张三","李四","李白","杜甫","孟浩然","米开朗基罗"};
        //准备测试数据
        redisTemplate.opsForSet().add("set1",set1);
        redisTemplate.opsForSet().add("set2",set2);
        //判断元素是否存在
         boolean isMember=  redisTemplate.opsForSet().isMember("set1","米开朗基罗");
         map.put("isMember",isMember);//false 不存在
        //交集
        Set intersect= redisTemplate.opsForSet().intersect("set1" ,Arrays.asList("set2"));
        map.put("intersect",intersect);//返回set1 和其他set的相交部分
        //并集
        Set union= redisTemplate.opsForSet().union("set1" ,Arrays.asList("set2"));
        map.put("union",union);//返回set1 和其他set的相并集合
        //差集
        Set diff= redisTemplate.opsForSet().difference("set1" ,Arrays.asList("set2"));
        map.put("diff",diff);//返回set1 和其他set的差异
        return map;
    }

redis zset 有序集合

zset 和set类似 只不过是使用score对set进行排序,同时也可用以 并集 交集的计算 会把 计算的结果存放在一个新的集合中以供使用。

//有序集合  通过score 进行排序
    @PostMapping("/zsetoption")
    @ResponseBody
    public Map zsetoption(){
        Map map = new HashMap();
        Set<ZSetOperations.TypedTuple> sets= new HashSet<>();
        Set<ZSetOperations.TypedTuple> sets1= new HashSet<>();
        List<DefaultTypedTuple> list = new ArrayList<>();
        list.add(new DefaultTypedTuple("张三",10.00));
        list.add(new DefaultTypedTuple("李四",7.00));
        list.add(new DefaultTypedTuple("王五",11.00));
        list.add(new DefaultTypedTuple("李白",15.00));
        list.add(new DefaultTypedTuple("杜甫",3.00));
        list.add(new DefaultTypedTuple("孟浩然",2.00));
        list.add(new DefaultTypedTuple("米开朗基罗",1.00));
        sets.addAll(list);
        //准备测试数据
        redisTemplate.opsForZSet().add("zset1",sets);
        list.remove(6);
        list.add(new DefaultTypedTuple("卡夫卡",9.00));
        sets.addAll(list);
        redisTemplate.opsForZSet().add("zset2",sets);
        // 获取 7条数据
        //start  从0开始
        //从小到大
        Set range =  redisTemplate.opsForZSet().range("zset1",1,7);
        map.put("range",range);

        //从大到小
        Set reverseRange =  redisTemplate.opsForZSet().reverseRange("zset1",1,7);
        map.put("reverseRange",reverseRange);

        //并集 zset3
        redisTemplate.opsForZSet().unionAndStore("zset1",Arrays.asList("zset2"),"zset3");
        Set unionAndStore =redisTemplate.opsForZSet().range("zset3",0,10);
        map.put("unionAndStore",unionAndStore);
        //交集 zset4
        redisTemplate.opsForZSet().intersectAndStore("zset1",Arrays.asList("zset2"),"zset4");
        Set intersectAndStore =redisTemplate.opsForZSet().range("zset4",0,10);
        map.put("intersectAndStore",intersectAndStore);

        return map;
    }

除此之外还包括对地理位置的存储等功能

  • GEOADD Sicily 13.361389 38.115556 “Palermo” 15.087269 37.502669 “Catania”
  • GEOPOS Sicily Palermo Catania 不存在返回nil
  • GEODIST Sicily Palermo Catania km (计算两点距离【单位包括 m米 km千米 mi英里 ft 英尺】)