博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
0121 spring-boot-redis的使用
阅读量:4209 次
发布时间:2019-05-26

本文共 16636 字,大约阅读时间需要 55 分钟。

redis是什么呢?redis,属于NoSQL的一种,在互联网时代,起到加速系统的作用。

redis是一种内存数据库,支持7种数据类型的存储,性能1S 10w次读写;

redis提供的简单的事务保证了高并发场景下数的一致性。

redis在2.6版本之后增加了lua支持,命令是原子性的;

本篇文章主要基于springboot的redis-starter。

HELLO, 性能利器Redis.

spring-boot-starter-redis

这个是springboot提供的redis操作工具包,底层的redis驱动使用的是lettus,而不是jedis;

依赖

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

序列化

主要通过RedisTemplate来操作redis;

 

当然也支持自定义序列化器,比如效率比较高的kyto序列化器;

StringRedisTemplate:key,value都是按照字符串存储的。

TypedTuple 保存集合中的有序元素;

可以查看一下StringRedisTemplate的源码:

public StringRedisTemplate() {    setKeySerializer(RedisSerializer.string());    setValueSerializer(RedisSerializer.string());    setHashKeySerializer(RedisSerializer.string());    setHashValueSerializer(RedisSerializer.string());}

数据类型操作接口

功能 单个操作接口 批量操作接口
有序集合 ZSetOperations BoundZsetOperations
字符串 ValueOperations BoundValueOpetations
集合 SetOperations BoundSetOperations
列表 ListOperations BoundListOperations
散列 HashOperations BoundHashOperations
基数 HyperLogLogOperations BoundHyperLogLogOperaions
地理位置 GeoOperations BoundGeoOperations

使用代码

@Autowired    private RedisTemplate redisTemplate;    @Test    void stringRedisTest() {        final ValueOperations valueOperations = redisTemplate.opsForValue();        valueOperations                .set("key1", "value1", Duration.ofMinutes(1));        final Object value = valueOperations.get("key1");        Assert.isTrue(Objects.equals("value1", value), "set失败");        final HashOperations hashOperations = redisTemplate.opsForHash();        hashOperations.put("hash1", "f1", "v1");        hashOperations.put("hash1", "f2", "v2");        hashOperations.values("hash1").forEach(System.out::println);    }

在同一条连接中进行多次操作

1. SessionCallback 高级操作对象

1. RedisCallback   低级操作对象

代码中直接使用的java8的lambda表达式。

使用代码

@Testvoid redisCallbackTest() {    redisTemplate.execute((RedisCallback) connection -> {        connection.set("rkey1".getBytes(), "rv1".getBytes());        connection.set("rkey2".getBytes(), "rv2".getBytes());        return null;    });}@Testvoid sessionCallbackTest() {    redisTemplate.execute(new SessionCallback() {        @Override        public Object execute(RedisOperations operations) throws DataAccessException {            final ListOperations listOperations = operations.opsForList();            listOperations.leftPush("sk1", "sv1");            listOperations.leftPush("sk1", "sv2");            listOperations.getOperations().expire("sk1", 1, TimeUnit.MINUTES);            listOperations.range("sk1", 0, 2).forEach(System.out::println);            return 1;        }    });}

字符串操作

最为常用的数据类型

实际情况使用的不多,现实的场景一般是放一个对象或者对象列表 转换为字符串 进行存储,取出的时候再转换为对象;

代码:

@Test    void stringTest() {        redisTemplate.opsForValue().set("stringKey1", "value1", 5, TimeUnit.MINUTES);        //字符串类型的整数,不能进行数字运算;        redisTemplate.opsForValue().set("stringKey2", "1", 5, TimeUnit.MINUTES);        //进行数字运算,增加,减少        redisTemplate.opsForValue().set("stringKey3", 1, 5, TimeUnit.MINUTES);        redisTemplate.opsForValue().increment("stringKey3",1);        redisTemplate.opsForValue().decrement("stringKey3",1);        //其它操作方法        final Long keySize = redisTemplate.opsForValue().size("stringKey1");        System.out.println(keySize);                //批量设置        Map
map = new HashMap<>(4); map.put("sk1",1L); map.put("sk2",2L); map.put("sk3",3L); map.put("sk4",4L); redisTemplate.opsForValue().multiSet(map); redisTemplate.opsForValue().multiSetIfAbsent(map); //批量获取 redisTemplate.opsForValue().multiGet(map.keySet()).forEach(System.out::println); //getAndSet final Object sk5Value = redisTemplate.opsForValue().getAndSet("sk5", 100); System.out.println("sk5Value:" sk5Value); redisTemplate.opsForValue().append("sk5","hello redis"); System.out.println("sk5Value2:" redisTemplate.opsForValue().get("sk5")); //按照情况设置,可以省去了之前查询出来之后判断是否存在再操作的代码; redisTemplate.opsForValue().setIfAbsent("sk6",1000,5,TimeUnit.MINUTES); redisTemplate.opsForValue().setIfPresent("sk6",100,5,TimeUnit.MINUTES); }

其它方法:

更多提供的方法需要在业务场景中多使用

列表操作

@Test    void listTest() {        stringRedisTemplate.opsForList().leftPush("lk1","lkv1");        stringRedisTemplate.opsForList().leftPushAll("lk2","lk2v1","lk2v2");        stringRedisTemplate.opsForList().leftPushAll("lk2",Arrays.asList("lk2v3","lk2v4"));        stringRedisTemplate.opsForList().leftPushIfPresent("lk3","lk3v1");        final List
lk2ValuesList = stringRedisTemplate.opsForList().range("lk2", 0, 3); System.out.println(lk2ValuesList); }

集合操作

@Test    void setTest() {        stringRedisTemplate.opsForSet().add("sk1","sk1v1","sk1v2","sk1v3");        stringRedisTemplate.opsForSet().add("sk2","sk1v1","sk2v2","sk2v3");        final Set
sk1 = stringRedisTemplate.opsForSet().members("sk1"); final Set
sk2 = stringRedisTemplate.opsForSet().members("sk2"); System.out.println("sk1: " sk1); System.out.println("sk2: " sk2); final Set
intersect = stringRedisTemplate.opsForSet().intersect("sk1", "sk2"); System.out.println("交集是:" intersect); final Set
union = stringRedisTemplate.opsForSet().union("sk1", "sk2"); System.out.println("并集:" union); final Set
difference = stringRedisTemplate.opsForSet().difference("sk1", "sk2"); System.out.println("差集:" difference); final Long size = stringRedisTemplate.opsForSet().size("sk1"); System.out.println("size for sk1 : " size); stringRedisTemplate.delete("sk1"); stringRedisTemplate.delete("sk2"); }

有序集合操作

@Test    void zsetTest() {        IntStream.rangeClosed(1,100).forEach(i->{            stringRedisTemplate.opsForZSet().add("zsk1",String.valueOf(i),i*10);        });        final Set
> typedTupleSet = IntStream.rangeClosed(1, 100).mapToObj(i -> new DefaultTypedTuple
(String.valueOf(i), (double) i * 11)).collect(Collectors.toSet()); stringRedisTemplate.opsForZSet().add("zsk2",typedTupleSet); final Set
zsk1 = stringRedisTemplate.opsForZSet().rangeByLex("zsk1", RedisZSetCommands.Range.range().gte(20).lte(100)); System.out.println("范围内的集合:" zsk1); }

散列表操作

@Test    void hashTest() {        stringRedisTemplate.opsForHash().put("hashk1","k1","v1");        stringRedisTemplate.opsForHash().put("hashk1","k2","v1");        stringRedisTemplate.opsForHash().put("hashk1","k3","v1");        stringRedisTemplate.opsForHash().putIfAbsent("hashk1","k4","new V1");        final List multiGet = stringRedisTemplate.opsForHash().multiGet("hashk1", Arrays.asList("k1", "k2"));        System.out.println("一次获取多个:"   multiGet); }

springboot中redis的配置

配置分两个部分,连接池和连接信息;下表列出必须给出的配置:

spring.redis.port=6379spring.redis.host=localhostspring.redis.password=spring.redis.timeout=1000#最小空闲连接数spring.redis.lettuce.pool.min-idle=2#最大空闲连接数spring.redis.lettuce.pool.max-idle=4#最大活跃连接数spring.redis.lettuce.pool.max-active=8#连接最长分配等待时间spring.redis.lettuce.pool.max-wait=2000#回收线程间隔毫秒数spring.redis.lettuce.pool.time-between-eviction-runs=100

注解操作redis

配置CacheManager

spring.redis.cache.type=redis

spring.redis.cache.name=redisCache

通过注解@EnableCaching启用;

更新缓存

清除缓存

使用查询缓存

缓存在一个类中互相调用失效 : 基于AOP的动态代理,没有生成代理类;

package com.springbootpractice.demo.redis.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.cache.RedisCacheConfiguration;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.cache.RedisCacheWriter;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;import org.springframework.data.redis.serializer.RedisSerializationContext;import java.time.Duration;/** * 说明:代码方式配置缓存管理器 * @author carter * 创建时间: 2020年01月21日 7:00 下午 **/@Configurationpublic class RedisConfig {    @Autowired private RedisTemplate redisTemplate;    @Bean    public RedisCacheManager redisCacheManager(){        RedisCacheWriter redisWrite = RedisCacheWriter.lockingRedisCacheWriter(redisTemplate.getConnectionFactory());        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();        configuration.prefixKeysWith("_demo_redis_");        configuration.entryTtl(Duration.ofMinutes(10));        configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));        RedisCacheManager redisCacheManager = new RedisCacheManager(redisWrite,configuration);        return redisCacheManager;    }}

用法

package com.springbootpractice.demo.redis.biz;import com.springbootpractice.demo.redis.dao.entity.UserLoginExtEntity;import com.springbootpractice.demo.redis.dao.mapper.UserLoginExtEntityMapper;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;/** * 说明:操作user的数据增强层 * @author carter * 创建时间: 2020年01月21日 6:40 下午 **/@Servicepublic class UserLoginExtBiz {    private final UserLoginExtEntityMapper userLoginExtEntityMapper;    public UserLoginExtBiz(UserLoginExtEntityMapper userLoginExtEntityMapper) {        this.userLoginExtEntityMapper = userLoginExtEntityMapper;    }    @Cacheable(value = "redisCache",key = "'getById:' #id")    public UserLoginExtEntity getById(Integer id){        return userLoginExtEntityMapper.selectByPrimaryKey(id);    }    @CachePut(value = "redisCache",key = "'getById:' #param.id")    public UserLoginExtEntity updateUserLoginExt(UserLoginExtEntity param){        userLoginExtEntityMapper.updateByPrimaryKeySelective(param);        return param;    }    @CacheEvict(value = "redisCache",key = "'getById:' #id")    public int deleteUserLoginExt(Integer id){       return userLoginExtEntityMapper.logicalDeleteByPrimaryKey(id);    }}

redis的特殊用法

redis中事务的用法

利用的是SessionCallback的RedisOperations 的 watch-multi-exec 连环操作;

watch: 监控某些key;

multi:开始事务;

exec: 执行事务

如果watch的key对应的值发生变化(设置为原值也是发生了变化),则会回滚事务;否则,正常的执行事务 ;

redis在执行事务的时候,要么全部执行,要么全部失败,不会被其它的redis客户端打断,保证了redis事务下数据的一致性;

@Test    void transactionTest() {        final String ttk1 = "ttk1";        stringRedisTemplate.opsForValue().set(ttk1,"ttk1v1");        final List list = stringRedisTemplate.execute(new SessionCallback
() { @Override public List execute(RedisOperations operations) throws DataAccessException { System.out.println("监听" ttk1); //如果ttk1的值发生了变化,重新set一样的值也是发生了变化,则回滚事务,否则正常执行 operations.watch(ttk1); //开启事务 System.out.println("开启事务"); operations.multi(); operations.opsForList().leftPushAll("xxx_lk1", "v1", "v2", "v3"); final List xxx_lk1 = operations.opsForList().range("xxx_lk1", 0, 2); System.out.println(xxx_lk1); operations.opsForSet().add("xxx_sk1", "v1", "v2", "v3"); final Set xxx_sk1 = operations.opsForSet().members("xxx_sk1"); System.out.println(xxx_sk1); //提交事务 final List list = operations.exec(); System.out.println("提交事务"); return list; } }); System.out.println("执行结果:" list); }

批量执行redis操作

redisTemplate.executePipelined();

@Test    void pipelineTest() {        StopWatch stopWatch = new StopWatch("pipelineTest");        stopWatch.start();        final List objectList = stringRedisTemplate.executePipelined(new SessionCallback() {            @Override            public Object execute(RedisOperations operations) throws DataAccessException {                for (int i = 1; i <= 10000; i  ) {                    operations.opsForValue().set("pk"   i, "pkv"   i, 5, TimeUnit.MINUTES);                }                return null;            }        });        stopWatch.stop();        System.out.println(stopWatch.prettyPrint());    }

消息队列

需要定义一个一个RedisMessageListenerContainer,配置topic和监听器; 作为消费者;

通过redisTemplate.convertAndSend方法发送消息;

定义监听器

package com.springbootpractice.demo.redis.listener;import org.springframework.data.redis.connection.Message;import org.springframework.stereotype.Component;/** * 说明:redis的监听器 * @author carter * 创建时间: 2020年01月21日 5:51 下午 **/@Componentpublic class MyRedisMessageListener implements org.springframework.data.redis.connection.MessageListener {        @Override    public void onMessage(Message message, byte[] pattern) {        System.out.println("MyRedisMessageListener topic:" new String(pattern)  " 消息:"  new String(message.getBody()));    }}

注册监听器容器

package com.springbootpractice.demo.redis.listener;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.listener.ChannelTopic;import org.springframework.data.redis.listener.RedisMessageListenerContainer;import org.springframework.data.redis.listener.Topic;import java.util.concurrent.ExecutorService;import java.util.concurrent.LinkedBlockingDeque;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;/** * 说明:配置队列监听器,对应的主题 * @author carter * 创建时间: 2020年01月21日 5:55 下午 **/@Configurationpublic class RedisListenerConfig {    public static final String MY_CHANNEL = "myChannel";    private final MyRedisMessageListener myRedisMessageListener;    private final MyRedisMessageListener2 myRedisMessageListener2;    private final RedisTemplate redisTemplate;    public RedisListenerConfig(MyRedisMessageListener myRedisMessageListener, MyRedisMessageListener2 myRedisMessageListener2, RedisTemplate redisTemplate) {        this.myRedisMessageListener = myRedisMessageListener;        this.myRedisMessageListener2 = myRedisMessageListener2;        this.redisTemplate = redisTemplate;    }    @Bean    public RedisMessageListenerContainer redisMessageListenerContainer() {        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();        redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory());        final ExecutorService taskExecutor = new ThreadPoolExecutor(1,                2, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2000));        redisMessageListenerContainer.setTaskExecutor(taskExecutor);        final Topic myChannel = new ChannelTopic(MY_CHANNEL);        redisMessageListenerContainer.addMessageListener(myRedisMessageListener, myChannel);        redisMessageListenerContainer.addMessageListener(myRedisMessageListener2, myChannel);        System.out.println("注册redis的消息队列成功!");        return redisMessageListenerContainer;    }}

测试代码

  1. publish myChannel helloworld 
  2. redisTemplate.convertAndSend(channel,message);
package com.springbootpractice.demo.redis.web;import com.springbootpractice.demo.redis.listener.RedisListenerConfig;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;/** * 说明:TODO * @author carter * 创建时间: 2020年01月21日 6:22 下午 **/@RestControllerpublic class TestController {    private final StringRedisTemplate stringRedisTemplate;    public TestController(StringRedisTemplate stringRedisTemplate) {        this.stringRedisTemplate = stringRedisTemplate;    }    @GetMapping(path = "/send/{message}")    public void publishMessage(@PathVariable("message") String message){        stringRedisTemplate.convertAndSend(RedisListenerConfig.MY_CHANNEL,message);    }}

使用lua脚本

使用的redisTemplate.execute(RedisScript,List,List);

@GetMapping(path = "/lua/{k1}/{v1}/{k2}/{v2}")    public Long publishMessage(@PathVariable("k1") String k1,@PathVariable("k2") String k2,@PathVariable("v1") String v1,@PathVariable("v2") String v2){        DefaultRedisScript
redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(LuaScript.lua1); redisScript.setResultType(Long.class); return stringRedisTemplate.execute(redisScript, Arrays.asList(k1, k2), v1, v2); }

小结

通过本篇文章,你可以学会:

1. 学会使用spring-boot-redis-starter熟练的进行各种数据类型的操作;

1. 学会了使用注解的方式使用redis缓存;

1. redis的特殊用法,事务,消息队列,批量操作,lua脚本支持;

美女还是要给看的。

image.png

原创不易,转载请注明出处,欢迎多沟通交流

你可能感兴趣的文章
九度OJ 1088:剩下的树 (线段树)
查看>>
九度OJ 1089:数字反转 (数字反转)
查看>>
九度OJ 1090:路径打印 (树、DFS)
查看>>
九度OJ 1091:棋盘游戏 (DP、BFS、DFS、剪枝)
查看>>
九度OJ 1092:Fibonacci (递归)
查看>>
九度OJ 1093:WERTYU (翻译)
查看>>
九度OJ 1094:String Matching(字符串匹配) (计数)
查看>>
九度OJ 1095:2的幂次方 (递归)
查看>>
九度OJ 1471-1480(10/10)
查看>>
九度OJ 1481-1490(7/10)
查看>>
九度OJ 1491-1500(5/10)
查看>>
九度OJ 1501-1510(10/10)
查看>>
业务系统中,报表统计功能如何组织--统计分析模块参考
查看>>
面向数据集成的ETL技术研究
查看>>
DataStage(ETL)技术总结 -- 介绍篇(转载)
查看>>
Greenplum技术浅析--vs oracle RAC
查看>>
框架一
查看>>
Oracle-内存管理解读
查看>>
Oracle-PFILE和SPFILE解读
查看>>
leetcode 13: Roman to Integer
查看>>