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

ArcGIS 10.2.2自带Python 2.7.5环境配置全攻略:从pip安装到gdal、arcpy调用

本文详细指导如何在ArcGIS 10.2.2自带的Python 2.7.5环境中进行高效配置。核心内容包括安装pip包管理器、部署gdal等关键地理数据处理库,并确保arcpy模块的无缝调用。通过解决路径配置与PyCharm集成问题,帮助用户在此经典环境中构建稳定、兼容的地理信息自动化工作流,有效连接ArcGIS桌面功能与Python脚本编程。

moon9的博客 768

玩转 SpringBoot 2.x 之 RedisTemplate 操作

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

程序员小奎的博客 3840

使用Java实现CRC16 MODBUS 格式校验

CRC16校验,测试如下 String str = "a5 98 03 00 00 5f c6 0f 7c 40 78 06 09 46 dc 6e db 00 16 3e 12 5c 98 00 19 98 0e 10 03 06 09 46 dc 6e db 03 0b bf 5f c6 0f 7c 43 bd b9 61 ee 96 76 99"; 校验规则: /** * CRC16_CCITT:多项式x16+x12+x5+1(0x1021),初始值0x0000,低位在前,高位在后,结

weixin_45063710的博客 1315

【COMSOL超材料和超表面仿真设计】

超材料和超表面的设计通常需要自定义材料属性。在COMSOL中,可以通过定义介电常数和磁导率的张量来模拟超材料的特性。对于超表面,通常需要定义表面阻抗或等效电路模型。

weixin_51101456的博客 1667

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

2025年人形机器人产业研究

人形机器人是一种模仿人类外形、动作和行为的机器人,旨在替代或辅助人类完成各种任务。其显著特征包括人类外形、动作模仿和智能交互能力。根据形态和功能,人形机器人可分为小型、中型、大型以及服务型、工业型、救援型和娱乐型等类别。发展历程经历了初始阶段、高度集成系统阶段和突破性进展阶段,技术不断进步。关键技术包括执行系统、控制系统和感知与交互系统,涉及机械结构、运动控制、平衡控制、任务执行控制以及感知与交互能力。主要研究方向包括外部仿人化和内部仿生,前者关注外观设计、动作控制和任务执行能力,后者致力于模拟人类核心机制

一名80后IT人的成长记录,记录自己在工作、学习中的点点滴滴,在成长的喜悦与忧心的故事里,铭记反脆弱的勇气。 1571

Spring Boot中RedisTemplate的使用

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

北辰 5025

【实用运维脚本】Windows 批量 TCP 端口检测(tcping 一键巡检)

摘要:本文介绍一款高效的Windows批量TCP端口检测脚本,适配tcpingv0.39工具,实现多服务器一键巡检。脚本具有进度可视化、双日志输出、自动容错等优势,支持注释行过滤和UTF-8编码。 使用仅需三步: 1)下载tcping工具 2)编辑目标列表 3)运行脚本即可自动检测并生成带编号的检测报告。 该方案解决了手动检测效率低、易遗漏的问题,特别适合日常运维中的端口连通性检查,实测稳定可靠,开箱即用。

蕲士|D365 & 服务器运维,心有东坡,行稳致远。 802

从VASP数据到LAMMPS模拟:手把手教你用DeePMD-kit搭建Cr-Si-Fe合金势函数

本文详细介绍了使用DeePMD-kit从VASP数据构建Cr-Si-Fe合金深度势能模型的全流程,包括数据准备、模型训练、优化及LAMMPS集成。通过实战教程,帮助研究者掌握DeePMD-kit训练技巧,实现高精度分子动力学模拟,提升材料计算效率。

weixin_33737134的博客 521

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

AMDGPU SVM VRAM Migration 实现详解

AMD正在基于DRM SVM框架重构SVM实现,该框架即将广泛应用。本文分析了AMD开发者提交的POC验证版本的技术方案。该架构分为三层:AMDGPU SVM映射层、DRM Pagemap通用框架层和AMDGPU迁移后端硬件驱动层。系统涉及三个地址空间转换:VRAM分配器空间、HPA/PFN空间和GPU PTE地址空间。关键数据结构包括设备级迁移基础设施amdgpu_pagemap、迁移临时BO包装器amdgpu_svm_bo等。该方案通过地址空间转换实现VRAM迁移功能,目前仍处于POC阶段,设计可能变更

图形/计算GPU的驱动与虚拟化技术分享园地 870

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

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

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

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

SpringBoot集成Redis

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

... 1310

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

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、付费专栏及课程。

余额充值