springBoot使用redis获取自增序号

spring boot 集成 redis client

pom文件

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.6.RELEASE</version>
        <relativePath/>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.1.6.RELEASE</spring-boot.version>
    </properties>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
        <version>${spring-boot.version}</version>
    </dependency>
    <dependency>
        <groupId>io.lettuce</groupId>
        <artifactId>lettuce-core</artifactId>
        <version>${lettuce.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.5.0</version>
    </dependency>

参考代码

配置

spring:
  redis:
    cluster:
      nodes: ip:port
      max-redirects: 3
redis:
  timeout: 10000 #客户端超时时间单位是毫秒 默认是2000
  maxIdle: 300 #最大空闲数
  maxTotal: 1000 #控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性
  maxWaitMillis: 1000 #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
  minEvictableIdleTimeMillis: 300000 #连接的最小空闲时间 默认1800000毫秒(30分钟)
  numTestsPerEvictionRun: 1024 #每次释放连接的最大数目,默认3
  timeBetweenEvictionRunsMillis: 30000 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
  testOnBorrow: true #是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
  testWhileIdle: true #在空闲时检查有效性, 默认false
  #password: 123456 #密码
  password: 
  dataBase: 0

配置类代码

package cn.项目路径.config;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

import java.time.Duration;
import java.util.HashSet;
import java.util.Set;

/** redis 配置
 * @author tk
 * @date 2020/11/3 9:58
 */
@Configuration
public class RedisConfig {
    @Value("${spring.redis.cluster.nodes}")
    private String clusterNodes;
    @Value("${spring.redis.cluster.max-redirects}")
    private Integer maxRedirects;
    @Value("${redis.password}")
    private String password;
    @Value("${redis.timeout}")
    private Integer timeout;
    @Value("${redis.maxIdle}")
    private Integer maxIdle;
    @Value("${redis.maxTotal}")
    private Integer maxTotal;
    @Value("${redis.maxWaitMillis}")
    private Integer maxWaitMillis;
    @Value("${redis.minEvictableIdleTimeMillis}")
    private Integer minEvictableIdleTimeMillis;
    @Value("${redis.numTestsPerEvictionRun}")
    private Integer numTestsPerEvictionRun;
    @Value("${redis.timeBetweenEvictionRunsMillis}")
    private Integer timeBetweenEvictionRunsMillis;
    @Value("${redis.testOnBorrow}")
    private boolean testOnBorrow;
    @Value("${redis.testWhileIdle}")
    private boolean testWhileIdle;
    @Value("${redis.dataBase}")
    private Integer dataBase;

    /** Redis集群的配置 */
    @Bean
    public RedisClusterConfiguration redisClusterConfiguration(){
        if(!clusterNodes.contains(",")) {
            return null;
        }
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
        //Set<RedisNode> clusterNodes
        String[] serverArray = clusterNodes.split(",");
        Set<RedisNode> nodes = new HashSet<RedisNode>();
        for (String ipPort : serverArray) {
            String[] ipAndPort = ipPort.split(":");
            nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.valueOf(ipAndPort[1])));
        }
        redisClusterConfiguration.setClusterNodes(nodes);
        redisClusterConfiguration.setMaxRedirects(maxRedirects);
        redisClusterConfiguration.setPassword(RedisPassword.of(password));
        return redisClusterConfiguration;
    }
    /** 集群redis 使用Lettuce集群模式 */
    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory() {
        // 连接池配置
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxWaitMillis(maxWaitMillis);
        LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(timeout))
                .shutdownTimeout(Duration.ofMillis(timeout))
                .poolConfig(poolConfig)
                .build();
        LettuceConnectionFactory lettuceConnectionFactory = null;
        RedisClusterConfiguration redisClusterConfiguration = redisClusterConfiguration();
        /** 集群redis */
        if (redisClusterConfiguration != null) {
            lettuceConnectionFactory = new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
        }else{
            /* 单机redis模式 */
            RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(clusterNodes.split(":")[0],Integer.valueOf(clusterNodes.split(":")[1]));
            standaloneConfiguration.setPassword(password);
            lettuceConnectionFactory = new LettuceConnectionFactory(standaloneConfiguration, lettuceClientConfiguration);
        }
        lettuceConnectionFactory.setDatabase(dataBase);
        return lettuceConnectionFactory;
    }
    /** 实例化 RedisTemplate 对象 */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        initDomainRedisTemplate(redisTemplate);
        return redisTemplate;
    }
    /** 设置数据存入 redis 的序列化方式,并开启事务
     * 使用默认的序列化会导致key乱码 */
    private void initDomainRedisTemplate(RedisTemplate <String, Object> redisTemplate) {
        //如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //这个地方有一个问题,这种序列化器会将value序列化成对象存储进redis中,如果
        //你想取出value,然后进行自增的话,这种序列化器是不可以的,因为对象不能自增;
        //需要改成StringRedisSerializer序列化器。
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setEnableTransactionSupport(false);
        /** lettuce 集群模式 */
        redisTemplate.setConnectionFactory(lettuceConnectionFactory());
        redisTemplate.afterPropertiesSet();
    }

    @Bean
    public static ConfigureRedisAction configureRedisAction() {
        return ConfigureRedisAction.NO_OP;
    }

    @Bean
    public CookieSerializer httpSessionIdResolver(){
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setUseHttpOnlyCookie(false);
        cookieSerializer.setSameSite(null);
        return cookieSerializer;
    }
}

使用代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class IDUtil {
    private final RedisTemplate<String, String> redisTemplate;
    @Autowired
    public IDUtil(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    public String genNo(){
		DefaultRedisScript script = new DefaultRedisScript<>();
		script.setResultType(Long.class);
		script.setScriptSource(new ResourceScriptSource(new ClassPathResource("id.lua")));
		List<String> key = new ArrayList<>(1);
		key.add("genid");
		Object result = redisTemplate.execute(script, key, "1", "9999");
		return result.toString();
    }
}

lua脚本

local curr = redis.call('GET',KEYS[1]);
if( curr == false) then
	redis.call('SET',KEYS[1],0)
elseif curr and tonumber(curr) >= tonumber(ARGV[2]) then
	redis.call('SET',KEYS[1],0)
	return 0
else
	redis.call('incrby',keys[1],ARGV[1])
	return curr + ARGV[1]
end

测试代码

配置

spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 10000ms
    lettuce:
      pool:
        max-active: 50
        max-wait: -1
        max-idle: 8

配置类代码

package cn.**.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * RedisConfig 类说明:
 *
 * @author z.y
 * @version v1.0
 * @date 2021/10/26
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // key 使用 字符串序列化
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        template.setEnableTransactionSupport(false);
        template.setHashKeySerializer(new StringRedisSerializer());
        valueSerializer(template);
        // 其余 属性 使用默认设置
        template.afterPropertiesSet();
        return template;
    }
    private void valueSerializer(RedisTemplate<String, Object> template){
        // 对象 使用 json 序列化
        Jackson2JsonRedisSerializer<?> jrs = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jrs.setObjectMapper(mapper);
        template.setHashValueSerializer(jrs);
    }
}

使用代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class RedisCommon{
    private final RedisTemplate<String, Object> redisTemplate;
    private ValueOperations<String, Object> ops;
    private DefaultRedisScript<Long> script;
    @Autowired
    public RedisCommon(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
        ops = redisTemplate.opsForValue();
        script = new DefaultRedisScript<>();
		script.setResultType(Long.class);
		script.setScriptSource(new ResourceScriptSource(new ClassPathResource("id.lua")));
    }
    /** 使用 redis 生成 序号
    * @param endKey 后缀key
    * @param max 序号最大值
    * @param expire 存活时长
    * @return 序号
    */
    public Long genNo(String endKey,String max,String expire){
		List<String> key = new ArrayList<>(1);
		key.add("CN:ZY:GEN:"+endKey);
		return redisTemplate.execute(script, key, "1", max ,expire);
    }
    public Long genNo(String key){
		return genNo(key,"999999","86400");
    }
}

lua脚本

local curr = redis.call('GET',KEYS[1]);
if( curr == false) then
	redis.call('SET',KEYS[1],0)
	if ( ARGV[3] ~= nil) then
		redis.call('EXPIRE',KEYS[1],ARGV[3])
	end
	return 0
elseif curr and tonumber(curr) >= tonumber(ARGV[2]) then
	redis.call('SET',KEYS[1],0)
	if ( ARGV[3] ~= nil) then
		redis.call('EXPIRE',KEYS[1],ARGV[3])
	end
	return 0
else
	redis.call('INCRBY',keys[1],ARGV[1])
	return curr + ARGV[1]
end

lua脚本2

local curr = redis.call('GET',KEYS[1]);
if( curr == false or (curr and tonumber(curr) >= tonumber(ARGV[2])) ) then
	redis.call('SET',KEYS[1],0)
	if ( ARGV[3] ~= nil) then
		redis.call('EXPIRE',KEYS[1],ARGV[3])
	end
	return 0
else
	return redis.call('INCRBY',keys[1],ARGV[1])
end

输出

在这里插入图片描述

另一种方式实现

这种方式 不用写 lua脚本,更简洁
spring boot整合redisTemplate获取自增主键incr

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;


@Component
public class RedisCommon{
    private final RedisTemplate<String, Object> redisTemplate;
    @Autowired
    public RedisCommon(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    public Long genNo2(String endKey, long liveTime){
        RedisAtomicLong ral = new RedisAtomicLong("CN:ZY:GEN:"+endKey, redisTemplate.getConnectionFactory());
        long l = ral.getAndIncrement();

        if ( ral.longValue() <= 1 && liveTime > 0) {
            ral.expire(liveTime, TimeUnit.SECONDS);
        }
        return l;
    }
}

输出

0
1
2

end

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐