springboot2.x +redis使用和源码分析二(RedisTemplate)

Redis教程四(Springboot集成Redis) Springboot操作数据:spring-data jpa jdbc mongodb redis Springdata也是和springboot齐名的项目 说明:在springboot2.X之后,原来使用的jedis被替换为lettuce Jedis:采用的直连,多个线程操作的话,是不安全的,如果响应避免不安全的,使用jedis pool连接池,更像BIO模式 Lettuce:采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据,更像是NIO模式 1. 源码分析 阅读详情

目录

序言:本文讲述RedisTemplate对象如何构建以及该对象对于redis提供的功能的支持

1:定义RedisTemplate

2:基本使用Ddemo

3:RedisTemplate对Pipelined支持

4:RedisTemplate对事务支持

5:RedisTemplate对Lua语言支持

5.1:使用内置字符串形式

5.2:以文件的形式

5.3:使用Lua的一些应用场景

6:RedisTemplate对于redis中MQ功能支持

6.1:简单demo代码

6.2:走读MessageListenerAdapter源码

6.3:redis-MQ应用场景

 Demo代码:https://github.com/fangyuan94/redisDemo


6.2:走读MessageListenerAdapter源码

 Demo代码:https://github.com/fangyuan94/redisDemo


序言:本文讲述RedisTemplate对象如何构建以及该对象对于redis提供的功能的支持

在实际需求中我们会将用户的基础信息存放到redis作为缓存,在项目中我们定义PersonInfo用于存储用户信息

 

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PersonInfo implements Serializable {

    private static final long serialVersionUID = -5666930682610937456L;

    @NotNull
    private String userId;

    @NotNull
    private String name;

    @Max(100)
    private Integer age;

    @NotNull
    private String sex;
}

如何优雅的操作person信息

1:定义RedisTemplate

@Configuration
@AutoConfigureAfter(RedisCustomizerConfiguration.class)
public class RedisConfiguration {

    @Bean
    public RedisTemplate<String, PersonInfo> personInfoRedisTemplate(ObjectProvider<RedisConnectionFactory> redisConnectionFactory){

        RedisTemplate<String, PersonInfo> personInfoRedisTemplate = new RedisTemplate<String, PersonInfo>();

        personInfoRedisTemplate.setConnectionFactory(redisConnectionFactory.getObject());
        //字符串序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //设置JDK序列化器
        JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer(PersonInfo.class.getClassLoader());
        //设置key value序列化器
        personInfoRedisTemplate.setKeySerializer(stringRedisSerializer);
        personInfoRedisTemplate.setValueSerializer(jdkSerializationRedisSerializer);
        personInfoRedisTemplate.setHashKeySerializer(stringRedisSerializer);
        personInfoRedisTemplate.setHashValueSerializer(jdkSerializationRedisSerializer);

        return personInfoRedisTemplate;
    }
}

2:基本使用Ddemo

    //向redis中写数据(五种基本类型操作,和redis命令行操作基本一致)
    @RequestMapping("addPersonInfo")
    public Map<String,Object> addPersonInfo(){

        String key = "personInfo";
        PersonInfo personInfo = new PersonInfo();
        personInfo.setUserId("1");
        personInfo.setAge(18);
        personInfo.setName("小花");
        personInfo.setSex("女");
        //操作string
        personInfoRedisTemplate.opsForValue().set(key+"_str",personInfo);
        //操作list
        personInfoRedisTemplate.opsForList().leftPush(key+"list",personInfo);
        //操作set
        personInfoRedisTemplate.opsForSet().add(key+"_set",personInfo);
        //操作有序set
        personInfoRedisTemplate.opsForZSet().add(key+"_ZSet",personInfo,100);
        //操作hash散列
        personInfoRedisTemplate.opsForHash().put(key+"_Map",personInfo.getUserId(),personInfo);

        Map<String,Object> map = new HashMap<>(3);
        map.put("success",true);
        map.put("data","1");
        return map;
    }

    @RequestMapping("getPersonInfo")
    public Map<String,Object> getPersonInfo(){

        String key = "personInfo";

        //操作string
        PersonInfo personInfo1 = personInfoRedisTemplate.opsForValue().get(key+"_str");
        //操作list
        PersonInfo personInfo2 = personInfoRedisTemplate.opsForList().leftPop(key+"list");
        //操作set
        PersonInfo personInfo3 =personInfoRedisTemplate.opsForSet().pop(key+"_set");
        //操作有序set
        Set<PersonInfo> personInfos = personInfoRedisTemplate.opsForZSet().range(key+"_ZSet",0,100);
        //操作hash散列
        PersonInfo personInfo5 = (PersonInfo) personInfoRedisTemplate.opsForHash().get(key+"_Map","1");

        Map<String,Object> map = new HashMap<>(3);
        map.put("str",personInfo1);
        map.put("list",personInfo2);
        map.put("set",personInfo3);
        map.put("zSet",personInfos);
        map.put("hash",personInfo5);
        return map;
    }

3:RedisTemplate对Pipelined支持

使用redis中pipelined可以优化批量处理需求的性能,不过pipelined不具有原子性,当执行到某一条命令时失败时会丢弃此条命令

测试Demo:

/**
     * RedisTemplate 对于pipeline支持
     * 提供SessionCallback与RedisCallback两种 作用一样
     * @return
     */
    @RequestMapping("pipelineTest")
    public  Map<String,Object> pipelineTest(){

        //测试数据
       final List<PersonInfo> personInfosTest = new ArrayList<>();

        for (int i=0;i<50;i++){
            PersonInfo personInfo = new PersonInfo();
            personInfo.setUserId(""+i);
            personInfo.setAge(i);
            personInfo.setName("小花"+i);
            personInfo.setSex("女");
            personInfosTest.add(personInfo);
       }

        final String key = "personInfo_pipeline_";

        //SessionCallback 属于高级 代码书写非常友好
        personInfoRedisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public  Object execute(RedisOperations operations) throws DataAccessException {
                for (int i=0;i<personInfosTest.size();i++){

                    PersonInfo personInfo = personInfosTest.get(i);
                    int j = i%5;
                    //使用不同命令
                    if(j==0){
                        operations.opsForValue().set(key+"_str",personInfo);
                    }else if(j==1){
                        operations.opsForHash().put(key+"_Map",personInfo.getUserId(),personInfo);
                    }else if(j==2){
                        operations.opsForList().leftPush(key+"list",personInfo);
                    }else if(j==3){
                        operations.opsForZSet().add(key+"_ZSet",personInfo,100);
                    }else {
                        operations.opsForSet().add(key+"_set",personInfo);
                    }
                }
                return null;
            }
        },personInfoRedisTemplate.getValueSerializer());

        //RedisCallback偏底层些 处理byt[]类型
        List<Object> ts = personInfoRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {

                for (int i=0;i<personInfosTest.size();i++){

                    PersonInfo personInfo = personInfosTest.get(i);
                    int j = i%5;

                    RedisSerializer stringSerializer = personInfoRedisTemplate.getKeySerializer();

                    RedisSerializer valueSerializer = personInfoRedisTemplate.getValueSerializer();
                    //使用不同命令
                    if(j==0){
                        connection.set(stringSerializer.serialize(key+"str"),valueSerializer.serialize(personInfo));
                    }else if(j==1){
                        connection.hSet(stringSerializer.serialize(key+"hash"),stringSerializer.serialize(personInfo.getUserId()),valueSerializer.serialize(personInfo));
                    }else if(j==2){
                        connection.lPush(stringSerializer.serialize(key+"list"),valueSerializer.serialize(personInfo));
                    }else if(j==3){
                        connection.zAdd(stringSerializer.serialize(key+"zSet"),100,valueSerializer.serialize(personInfo));
                    }else {
                        connection.sAdd(stringSerializer.serialize(key+"set"),valueSerializer.serialize(personInfo));
                    }
                }
                //返回结果必须为null
                return null;
            }
        },personInfoRedisTemplate.getValueSerializer());

        Map<String,Object> map = new HashMap<>(3);
        map.put("success",true);
        map.put("data",ts);

        return map;
    }

查看结果可以看到操作不同命令返回结果是不一样:

"data": [
        true,
        true,
        1,
        true,
        1,
        true,
        true,
        2,
        true,
        1,
        。。。。。
    ],

4:RedisTemplate对事务支持

redis中的事务本质是一组命令的集合。在事务执行的过程会保证其它客户端提交的命令插入到当前序列中(排它性),这组命令会顺序执行,当个命令会是原子性的,但整组命令并不是原子性的,某个命令的失败(如果是错误是命令性错误类似于java编译性错误,则整个命令都不会执行)不会影响其它命令的执行也不会对之前执行的命令进行回滚。

redis提供了5组命令(MULTI, EXEC, DISCARD, WATCH和UNWATCH)对于事务支持

对于RedisTemplate中使用execute(SessionCallback)方法支持

demo代码:

@RequestMapping("transactionalTest")
    public  Map<String,Object> transactionalTest(){

        //这里模拟用户取钱的场景 初始银行是10000 用户是10 每次用户取10
        //这里通过压测工具模拟100个并发 5000个请求是否能保证数据的一致性
        final String bank_key = "transactional_bank"; //默认为10000
        final String person_key = "transactional_person";//默认为100
        List<String> watchs = new ArrayList<>();
        watchs.add(bank_key);
        watchs.add(person_key);
        //执行事务
        personInfoRedisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                List rs;
                do {
                    //监控需要变化key
                    operations.watch(watchs);
                    //对应multi命令
                    operations.multi();
                    operations.opsForValue().decrement(bank_key,10);
                    //需要执行命令
                    operations.opsForValue().increment(person_key,10);
                    //提交
                    rs = operations.exec();

                    System.out.println(rs);

                    //和CAS中自旋概念类似
                }while (rs!=null && rs.size()==0);

                return null;
            }
        });


        Map<String,Object> map = new HashMap<>(3);
        map.put("success",true);
        map.put("data",1);

        return map;
    }

这里使用了简单的压测工具ab来进行测试: ab -n 5000 -c 100 'http://host.docker.internal:18670/transactionalTest'

最终结果为:

在100个并发情况下共5000个请求平均耗时为: 

5:RedisTemplate对Lua语言支持

上述说到了redis中的Pipelined和事务都支持多组命令一起执行但是使用Pipelined不保证整体命令的原子性,事务在某些条件下也不包含原子性。在redis2.8之后引入了新的方式可以解决此类问题——Lua,它在执行的时候是不会被中断的,整体具有原子性。因为它的这种特性,一般将它用于高并发的场景下。(对Lua语言不了解的可以现百度了解下这门轻型的脚本语言)

5.1:使用内置字符串形式

     /**
     * redis对于Lua支持
     * @return
     */
    @RequestMapping("LuaTest")
    public  Map<String,Object> LuaTest(){

        //创建脚本
        DefaultRedisScript<String> defaultRedisScript = new DefaultRedisScript();

        //redis.call  redis执行命令 KEYS代表redis中key ARGV代表参数 return指需要返回数据
        String luaText = "redis.call('set',KEYS[1],ARGV[1]) return redis.call('get',KEYS[1])";
        //设置脚本内容
        defaultRedisScript.setScriptText(luaText);
        //也可以通过将lue写入文件方式进行调用易于维护
//        defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/demo.lua")));
        //设置脚本数据返回类型
        defaultRedisScript.setResultType(String.class);
        //执行命令
        //第一个代表的需要执行的脚本
        //第二对应着lua中的KEYS信息 KEYS下标从1开始
        //第三个是对应ARGV中指 ARGV下标从1开始
        String rs = stringRedisTemplate.execute(defaultRedisScript,Collections.singletonList("lua_test"),"测试lua数据");

        Map<String,Object> map = new HashMap<>(3);
        map.put("success",true);
        map.put("data",rs);

        return map;
    }

返回结果:

{
    "data": "测试lua数据",
    "success": true
}

5.2:以文件的形式

如果需要执行的逻辑较复杂,此时我们可以通过将lua写入文件的方式进行处理例如(去银行存钱)。

需求:某银行现余额10000元,现存在一批人(100)向银行存储共8000次,每次一元,使用lua脚本是否可以保证数据一致性

代码:

/**
     * 测试
     * @return
     */
    @RequestMapping("LuaFileTest")
    public  Map<String,Object> LuaFileTest(){

        //创建脚本
        DefaultRedisScript<String> defaultRedisScript = new DefaultRedisScript();

        //也可以通过将lue写入文件方式进行调用易于维护
        defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/demo.lua")));
        //设置脚本数据返回类型
        defaultRedisScript.setResultType(String.class);
        //redis 需要操作key
        List<String> keys = new ArrayList<>();
        keys.add("lua_bank");
        keys.add("lua_person");
        //
        Object[] args = new Object[]{"10000","10000","1"} ;
        //执行命令
        //第一个代表的需要执行的脚本
        //第二对应着lua中的KEYS信息 KEYS下标从1开始
        //第三个是对应ARGV中指 ARGV下标从1开始
        String rs = stringRedisTemplate.execute(defaultRedisScript,keys,args);

        System.out.println("-------结果集-----"+rs);

        Map<String,Object> map = new HashMap<>(3);
        map.put("success",true);
        map.put("data",rs);

        return map;
    }

一样使用ab压测进行模拟测试: ab -n 8000 -c 100 'http://host.docker.internal:18670/LuaFileTest'

最终结果与预期一致,可以保证数据的一致性:

在100个并发情况下共8000个请求平均耗时为: 

整体的吞吐量高于事务

5.3:使用Lua的一些应用场景

场景一:使用redis构建分布式锁

场景二:针对于电商中秒杀场景下保证数据一致性

假设某电商中某店家为某一个产品做秒杀,每一个用户只能在规定时间内抢夺总共50个商品,且每人限购一份,如何在高并发下保证商品的数量不会出现溢出的现象,这里使用redis结合lue来实现。

java代码:

/**
     * 高并发下秒杀商品代码实现
     * @param userId 这里是模拟代码 所以userId 以参数的形式
     * @return
     */
    @RequestMapping("seckillMerchandise")
    public  Map<String,Object> seckillMerchandise(@RequestParam("userId") String userId){

        //创建脚本
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript();

        //也可以通过将lue写入文件方式进行调用易于维护
        defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/seckillMerchandise.lua")));
        //设置脚本数据返回类型
        defaultRedisScript.setResultType(Long.class);
        //redis 需要操作key
        List<String> keys = new ArrayList<>();
        //记录商品总数 这里测试数据20个
        keys.add("seckill_merchandise_count"+"_spid");
        //记录抢购到的用户记录
        keys.add("seckill_merchandise_user"+"_spid");

        //用户id
        Object[] args = new Object[]{userId,System.currentTimeMillis()+""} ;
        //执行命令
        //第一个代表的需要执行的脚本
        //第二对应着lua中的KEYS信息 KEYS下标从1开始
        //第三个是对应ARGV中指 ARGV下标从1开始
        Long rs = stringRedisTemplate.execute(defaultRedisScript,keys,args);
        //根据不同结果进行不同处理 ---
        System.out.println("-------结果集-----:"+rs);

        Map<String,Object> map = new HashMap<>(3);
        map.put("success",true);
        map.put("data",rs);

        return map;
    }

lua代码:

--秒杀商品lua代码
--返回状态:
-- 0 当前商品已售空
-- -1 该用户已经抢购了该商品了
-- -2 代表存储秒杀商品总数的key不存在 非法数据无法处理或者抢购时间已结束
-- 1代表正常返回
--用户key
local userId = ARGV[1]
--存储秒杀商品总数的key 该值在设定抢购开始时写入缓存 并设置过期时间
local seckillMerchandiseCountKey = tostring(KEYS[1])
--目前商品剩余总数
local count =  redis.call('GET',seckillMerchandiseCountKey)

if count == false then
    --代表存储秒杀商品总数的key不存在 非法数据无法处理或者抢购时间已结束
   return -2
end
--转换为数值类型
count = tonumber(count)
if count  <= 0 then
    --代表当前商品已售空
    return 0
end

--记录已抢够成功用户列表 用于判断用户是否抢购
--因为秒杀活动不是针对于一种商品 所以这里以商品id+一些唯一标示组合为key value为hash散列
local seckillMerchandiseUserKey = tostring(KEYS[2])

local flag = redis.call('HGET',seckillMerchandiseUserKey,userId)
if flag == true then
    --该用户已经抢购了该商品了
    return -1
end
--该用户抢购了该商品了 需要对数据进行变更
--商品 -1
redis.call('DECRBY',seckillMerchandiseCountKey,1)
--将用户写入 并记录抢购成功时间
redis.call('HSET',seckillMerchandiseUserKey,userId,ARGV[2])
return 1

模拟测试代码:

#!/bin/bash
###使用ab构建测试脚本 因为博主用的是mac电脑 使用docker 运行ab的所以这里主机名用/、host.docker.internal
for (( i = 0; i < 100; i++ )); do
    ab -n 3 -c 1 'http://host.docker.internal:18670/seckillMerchandise?userId=userId_'+i
done

最终结果(在100个并发下共300个请求下并不存在超卖的情况):

其实上述只是描述下lua的使用的一些业务场景,真正的秒杀业务设计没这么简单。后续会针对于秒杀系统讲述自己的一些想法

6:RedisTemplate对于redis中MQ功能支持

redis提供轻量级的mq功能

6.1:简单demo代码

/**
 * 用于初始化依赖类
 * @author fangyuan
 */
@Configuration
public class RedisListenerConfiguration {

    /**
     * MessageListenerAdapter此类需要被注入到spring中执行
     * 因为MessageListenerAdapter实现了InitializingBean接口 执行afterPropertiesSet()
     * @return
     */
    @Bean
    public MessageListenerAdapter messageListenerAdapter(){
        //将代理类注入到MessageListenerAdapter 代理执行
        return new MessageListenerAdapter(new Recevetor(),"receve");
    }

    /**
     * 创建监听容器 用于管理监听器
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(
            StringRedisSerializer stringRedisSerializer,
            MessageListenerAdapter messageListenerAdapter,
            ObjectProvider<RedisConnectionFactory> redisConnectionFactory){

        RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer();
        //设置基础配置
        redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory.getObject());
        redisMessageListenerContainer.setBeanName("redisMessageListenerContainer");
        redisMessageListenerContainer.setTopicSerializer(stringRedisSerializer);
        //创建多监听者 监听不同topic
        //使用自定义监听方式
        MessageListener messageListener =  new ConsumerMessageListener();
        redisMessageListenerContainer.addMessageListener(messageListener,new  ChannelTopic("redis_mq_test_1"));
        //使用适配器方式(通过代理执行设计模式+反射来做到)
        messageListenerAdapter.setStringSerializer(stringRedisSerializer);
        messageListenerAdapter.setSerializer(stringRedisSerializer);
        redisMessageListenerContainer.addMessageListener(messageListenerAdapter,new ChannelTopic("redis_mq_test_2"));

        return redisMessageListenerContainer;
    }
}

定义两种接受方式:

/**
 * 监听消息
 * @author fangyuan
 */
public class ConsumerMessageListener implements MessageListener {

    private StringRedisSerializer stringRedisSerializer;

    public  ConsumerMessageListener(){
        stringRedisSerializer = new StringRedisSerializer();
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {

        String data = stringRedisSerializer.deserialize(message.getBody());

        String channel = stringRedisSerializer.deserialize(message.getChannel());
        System.out.println("从"+channel+"中---收到消息----:"+data);

    }
}

 


/**
 * 定义被MessageListenerAdapter被代理执行类
 * @author fangyuan
 */
public class Recevetor {

    /**
     * 接受message 此方法的参数只能为两个参数message,topic) 或者是一个参数(message)
     * @param message
     */
    public void receve(String message,String channel){

        System.out.println("通过MessageListenerAdapter代理方式从"+channel+"中---收到消息----:"+message);
    }
}

测试消息类:

/**
     * 发送信息到mq
     * @return
     */
    @RequestMapping("produceMessage")
    public  Map<String,Object> produceMessage(){

        String channel = "redis_mq_test_1";

        int count = atomicInteger.incrementAndGet();

        if(count%2==0){
            channel = "redis_mq_test_2";
        }

        String message = "测试使用redis作为MQ发送数据__"+count;
        //发布信息
        stringRedisTemplate.convertAndSend(channel,message);

        Map<String,Object> map = new HashMap<>(3);
        map.put("success",true);
        map.put("data",1);

        return map;
    }

 最终结果:

6.2:走读MessageListenerAdapter源码

此类通过代理设计模式+反射调用来实现需求

//此类需要被注入到spring中 因为实现了该接口InitializingBean 需要初始化MethodInvoker对象
public void afterPropertiesSet() {
		String methodName = getDefaultListenerMethod();

		if (!StringUtils.hasText(methodName)) {
			throw new InvalidDataAccessApiUsageException("No default listener method specified: "
					+ "Either specify a non-null value for the 'defaultListenerMethod' property or "
					+ "override the 'getListenerMethodName' method.");
		}

		invoker = new MethodInvoker(delegate, methodName);
	}

//实现MessageListener接口 
public void onMessage(Message message, @Nullable byte[] pattern) {
		try {
			
            //判断代理类本质是否是MessageListener类如果是 强制转换下调用即可
			if (delegate != this) {
				if (delegate instanceof MessageListener) {
					((MessageListener) delegate).onMessage(message, pattern);
					return;
				}
			}

            //获取message 和channel信息
			Object convertedMessage = extractMessage(message);
			String convertedChannel = stringSerializer.deserialize(pattern);
			// 组装成Invoke调用方法参数形式
			Object[] listenerArguments = new Object[] { convertedMessage, convertedChannel };
            //通过反射 调用代理类指定方法
			invokeListenerMethod(invoker.getMethodName(), listenerArguments);
		} catch (Throwable th) {
			handleListenerException(th);
		}
	}

6.3:redis-MQ应用场景

redis-MQ作为一个轻量型mq,它的应用场景是和redis本质有很大关系,因为redis的核心技术在于内存的使用,那么代表着redis-MQ的实时性很高;但也带来着对应的缺点,例如无法存储大量信息(内存资源宝贵);信息的可用性难保证(第一种情况:消息被发送,如果没有订阅者接收,那么消息就会丢失;第二种情况:redis挂掉此条消息存在丢失数据可能);不支持断点消费;每个消费者会消费所有数据(无法支持组消费或者说不支持负载均衡消费,会存在单点压力过大)。所以针对需求技术选型的时候需要考虑上述问题。

 Demo代码:https://github.com/fangyuan94/redisDemo

SpringBoot2.x系列教程(六十八)Spring Boot集成Redis的坑,踩了! 最近项目中使用SpringBoot集成Redis,踩到了一个坑:从Redis中获取数据为null,但实际上Redis中是存在对应的数据的。是什么原因导致此坑的呢? 本文就带大家从SpringBoot集成Redis、所踩的坑以及自动配置源码分析来学习一下SpringBoot中如何正确的使用Redis。 SpringBoot集成Redis 在SpringBoot项目中只需在pom文件中引入Redis对应的starter,配置Redis连接信息即可进行使用了。pom依赖引入: <dependency> 阅读详情

相关推荐

【Redis】三、SpringBoot整合redis

默认序列化是jdk的序列化方式,自定义RedisTemplete配置config可以自定义序列化方式可以自己配置具体的序列化方式,代码如下所示我们来编写一个自己的 RedisTemplete(下面是一个固定的模板,可以直接用)自己写一个配置类,注入自己定义的RedisTemplateimport com// 这是我给大家写好的一个固定模板,大家在企业中,拿去就可以直接使用!

hhb442的博客 768

SpringBoot 2.x 项目中使用RedisTemplate实例操作不同Redis库(使用新版本驱动)

背景:我们都知道比较新版本的Redis有64个数据库可以使用,在项目中需要用到redis的多个库,每次使用时再去通过一堆代码切换未免觉得太过麻烦,所以直接通过配置注入多个RedisTemplate,需要用到哪个库时直接使用对应的RedisTemplate即可。 第一步:配置文件: 在application.properties中添加redis的相关配置 #redis多数据配置 redis.database.test1=1 redis.database.test2=2 redis.host=127.0

海子@幸福人 3017

ROS新手必看:5分钟搞定URDF小车建模(附Rviz可视化调试技巧)

本文为ROS新手提供了快速构建URDF机器人模型的实战指南。通过一个完整的两轮差速小车建模案例,详细解析了URDF的核心语法、Rviz可视化调试技巧以及常见问题排查方法,帮助初学者快速掌握ROS机器人建模与可视化调试的核心技能。

weixin_29079643的博客 250

玩转 SpringBoot 2.x 之 RedisTemplate 操作

Spring 专门提供了进行 Redis 操作的抽象 RedisTemplate,底层依然使用 Jedis 或 lettuce。SpringBoot 2 默认使用 lettuce。

程序员小奎的博客 3840

gcc/g++ 链接库的编译与链接

gcc/g++ 链接库的编译与链接 surgewong@gmail.com http://blog.csdn.net/surgewong       程序编译一般需要经预处理、编译、汇编和链接几个步骤。在实际应用中,有些公共代码需要反复使用,就把这些代码编译成为“库”文件。在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中,这种库称为静态(链接)库,其特点是可

Surge 4万+

解决银河麒麟V10分辨率问题:xrandr命令详解与常见错误排查

本文详细解析了在银河麒麟V10系统中使用xrandr命令调整屏幕分辨率的方法,包括核心功能解析、常见问题排查及持久化配置技巧。通过实战指南帮助用户解决分辨率缺失、配置失效等典型问题,提升多显示器环境下的工作效率。

weixin_30642561的博客 505

springboot2.2 集成redis哨兵

springboot版本2.2.0.RELEASE jedis版本 3.1.0 一、pom依赖: <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0</version> </dependency> <dependency> ...

scanklm的专栏 1165

WIFI中CSI的读取

学习目标: 目前正在做基于WIFI的无设备定位,准备使用信道状态信息(CSI),学习一下。 目前采集数据环境已经搭建好,准备研究信号的预处理。 学习内容: 1、 信号的读取——read_bf_file函数的研究 2、 3、 4、 1. read_bf_file详解: %READ_BF_FILE Reads in a file of beamforming feedback logs. % This version uses the *C* version of read_bfee, ...

sz3076799的博客 8892

Spring Boot中RedisTemplate的使用

RedisTemplate是SpringDataRedis中对JedisApi的高度封装,提供了redis各种操作、异常处理及序列化,支持发布订阅。

北辰 5025

Universal Robot——在Gazebo中模拟UR5机器人

今天的目标是在Gazebo安装我们的ur5机器人。Gazebo是一个流行的模拟工具,它不仅可以让你像在Rviz中一样可视化你的机器人,而且还内置了一个物理引擎。这样你就可以在更真实的环境中测试你的机器人应用程序。我们将查看我们需要的属性 添加对我们的机器人——我们的order的描述,以模拟它以及如何在Gazebo中生成我们的机器人。

如果想成为中心,那么就到中心去吧。 1970

Android 11多窗口+分屏+APP窗口嵌套TaskView实现原理一

然而随着版本的迭代升级,google对分屏的定制差异化也越来越大,Androd 11的分屏用的是split-screen-primary和split-screen-secondary的方式,在systemui中去实现的,其定制化的灵活度不是很高,很多业务都是放到sustemui或者fw去实现,但是随着版本的升高,后面出现了TaskView来替代了老版本的分屏方式,但是Task的核心原理其实和之前的方式是一样的,TaskView来实现分屏。

qq_18420641的博客 2615

SpringBoot集成Redis之RedisTemplate

​#================================redis单机版================================redis-server /myredis/redis7.conf #开启redis服务 必须加载配置文件​redis-cli -a 123456 -p 6379 #开启客户端运行启动类--->访问http://localhost:7777/swagger-ui.html,端口改为自己设置的server.port访问。

2202_75317512的博客 1879

linux 内核OOM问题定位-SLAB_DEBUG

首先看slabinfo按的size排序查看 slub object,可以查看 space 总占用大小,及objects个数判断泄漏点 为kmalloc-128。那个泄露多看/sys/kernel/debug的slab/kmalloc*-的alloc-traces和free-traces。SLAB或者SLUB有一个用就可以,改menuconfig没有clean会覆盖之前的更改,现在看一下编译的过程。或者配置defconfig,配置完之后需要clean后再编译。1,配置menuconfig。

陌上花开缓缓归以的博客 900

SpringBoot整合Redis和自定义RedisTemplate、RedisUtil和中文乱码解决

SpringBoot操作数据库:Spring Data jpa jdbc mongodb redis Spring Data也是和SpringBoot齐名的项目! 说明:在SpringBoot2.x之后,原来使用的jedis被替换成了 lettuce(莴苣) 从我们导入的pom文件中去看: 可以看到,底层还是spring data的redis,下面的是lettuce包 为什么要替换呢? jedis:采用的是直连的方式,多个线程操作的话,是不安全的,要解决不安全的问题,需要采用jedis pool线程池。BI

dayuiicghaid的博客 1425

RandLA-Net Tensorrt 推理 c++

RandLA-Net 实现 Tensorrt 部署.

J。 1110

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

SpringBoot整合Redis,RedisUtil自定义工具类,自定义的RedisTemplate

SpringBoot整合Redis SpringBoot操作数据:spring-data jpa jdbc mongodb SpringData也是和SpringBoot齐名的项目! 说明:在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce? jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不完全,使用jedis pool连接池!更像 BIO模式 lettuce:采用netty,实例可以再多个线程中进行共享,不存在线程不完全的情况!可以减少线程数据了,更

yang13676084606的博客 728

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

SpringBoot集成Redis

SpringBoot集成Redis 文章目录SpringBoot集成Redis1、概述2、测试Redis3、自定义redisTemplate 1、概述 Redis是什么? Redis(Remote Dictionary Server ),即远程字典服务。 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操

... 1310

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

SpringBoot整合Redis

springboot整合redis

奔走的王木木Sir的博客 2150

Redis学习--SpringBoot整合Redis

SpringBoot整合Redis1. 导入依赖2. 配置application.propertie3. RedisAutoConfiguration 源码分析4. 自定义RedisConfig5. 简单测试一下6. 使用StringRedisTemplate7. 使用RedisTemplate8. 使用boundXXXOps()9. 封装Redis工具类 1. 导入依赖 SpringBoot2.x之后,原来使用的jedis被替换为了 lettuce <!--操作Redis-->

463

Jedis使用和SpringBoot与Redis整合

Jedis 我们要使用java 来操作redis 什么是Jedis: Jedis是Redis官方推荐的java连接开发工具,使用java操作Redis中间件。 1、导入jar包 <!--导入jedis的jar包--> <dependencies> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency>

qq_43430343的博客 628

SpringBoot(十九)集成Redis

如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。 创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客

星海IT学习之路 1435

Springboot Redis客户端配置及初始化过程源码解读

spring boot集成redis,配置比较简单,网上存在不同的配置。为什么有不同的配置文件? spring boot是如何加载redis的呢? 为了搞清楚背后的机制,阅读源码一探究竟。 1: spring boot 集成redis 为什么存在不同的配置文件 在spring boot 1.x.x集成redis时,默认jedis作为连接客户端。由于jedis是直连模式,在...

wiseyl的专栏 4954

Redis系统学习之SpringBoot集成Redis操作API(集成SpringDataRedis及其分析)

SpringDataRedis调用Redis底层解读 在SpringBoot2.X之前还是直接使用的官方推荐的Jedis连接的Redis 在2.X之后换为了lettuce Jedis:采用直接连接,多线程操作不安全,如果想要避免不安全,使用Jedis pool连接池;BIO lettuce:底层采用Netty,实例可以在多个线程之间共享,不存在线程不安全的情况,可以减少线程数量;NIO...

彼岸舞的博客 293

Redis之SpringBoot整合

目录概述源码分析整合测试 本文摘录自狂神说 公众号:狂神说 概述 SpringBoot操作数据:spring-data jpa jdbc mongodb redis! SpringData也是和SpringBoot齐名的项目! 说明:在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce? jedis: 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool连接池!更像BIO模式! lettuce: 采用netty,实例可以再多个线程中进行共享

北冥煮鲲 360

Redis整合Springboot(RedisConfig和 RedisUtils)

Springboot 整合Redis,基本使用以及 RedisConfig 配置类和 RedisUtils 工具类

传播是知识存在的意义 1141
上一篇: springboot2.x +redis使用和源码分析一(springboot自动装配源码分析)
下一篇: springboot2.x +redis使用和源码分析三(序列化器)
F_Hello_World
博客等级 码龄11年 38粉丝 58原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值