启动原理
首先,我们需要了解对于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 英尺】)