今天是:
带着程序的旅程,每一行代码都是你前进的一步,每个错误都是你成长的机会,最终,你将抵达你的目的地。
title

spring boot 整合redis

  

Redis是什么

Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。 Redis提供数据结构有字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流。 Redis具有内置的复制,Lua脚本,LRU过期策略,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供高可用性。

为什么使用Redis

  1. 速度非常快
  2. 支持的数据结构比其他缓存更多
  3. 大多数语言都支持redis
  4. 它是开源且稳定的

springboot中使用Redis

引入依赖

compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.4.2'

配置缓存属性

spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=2
spring.redis.timeout=6000

 下面以对用户的增删改查说明redis的使用

用户实体对象

@Entity
@Table(name = "user")
@Data
@Accessors(chain = true)
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Integer id;

    @Column(name = "name")
    private String name;

    @Column(name = "age")
    private Integer age;

}

 

在调用的方法上加上缓存注解以便可以缓存@CachePut,@Cacheable,@CacheEvict

 

@Service
@CacheConfig(cacheNames = "users")
public class UserServiceImpl implements UserService {

    Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    UserRepository userRepository;

    @CachePut(key ="#result.id")
    @Override
    public User addUser(User user) {
        logger.debug("添加用户 ==>{}",user.toString());
        return userRepository.save(user);
    }

    @Override
    @CachePut(key ="#result.id")
    public User updateUser(User user) {
        logger.debug("更新用户 ==>{}",user.toString());
        return userRepository.save(user);
    }

    @Override
    public List<User> findAll() {
        logger.debug("查询所有用户");
        return userRepository.findAll();
    }

    @Override
    @Cacheable(key="#id")
    public User findById(Integer id) {
        logger.debug("查询所有用户");
        return userRepository.findById(id).get();
    }

    @Override
    @CacheEvict(key = "#id")
    public boolean deleteUser(Integer id) {
        logger.debug("删除用户==>{}",id);
        userRepository.deleteById(id);
        return true;
    }
}

 

rest api 调用 测试缓存

 

@RestController
@RequestMapping("/api/user")
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/findById/{id}")
    private User findById(@PathVariable("id") Integer id){
       return  userService.findById(id);
    }

    @GetMapping("/findAll")
    private List<User> findAllUser(Model model){
        return  userService.findAll();
    }

    @PostMapping(value="/save")
    private ResponseEntity<User> save(@RequestBody User user){
        User add= userService.addUser(user);
        return new ResponseEntity<User>(add, HttpStatus.CREATED);
    }

    @PutMapping("/update")
    private ResponseEntity<User> update(@RequestBody User user){
        User update= userService.updateUser(user);
        return new ResponseEntity<User>(update, HttpStatus.OK);
    }

    @DeleteMapping("/delete/{id}")
    private ResponseEntity<HttpStatus> delete(@PathVariable("id") Integer id){
        userService.deleteUser(id);
        return new ResponseEntity<HttpStatus>(HttpStatus.ACCEPTED);
    }

启动redis,启动类将@EnableCaching注解,启动spring boot 应用。当我们调用增删改查时,将会有缓存存入到redis中,或者从redis中删除。通过控制台的sql输出语句,和redis-cli和验证缓存的情况。

使用RedisTemplate

@Configuration
@EnableCaching
@EnableRedisRepositories
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @Primary
    public RedisProperties redisProperties() {
        return new RedisProperties();
    }
    @Bean
    JedisConnectionFactory jedisConnectionFactory()
    {
        RedisProperties properties = redisProperties();
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(properties.getHost());
        configuration.setPort(properties.getPort());
        configuration.setPassword(properties.getPassword());
        configuration.setDatabase(properties.getDatabase());
        return new JedisConnectionFactory(configuration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        RedisSerializer stringSerializer = new StringRedisSerializer();//序列化为String

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);

        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(serializer);
        return redisTemplate;
    }
}

配置redis哨兵模式

 配置哨兵模式网上介绍的文章很多,这里不做过多的解释. redis 配置好主从模式,哨兵之后在spring boot中配置也比较简单。

spring boot 配置文件

spring:
  redis:
    host: localhost
    port: 6379
    jedis:
      pool:
        max-active: 10
        max-idle: 5
        max-wait: 8
        min-idle: 2
    timeout: 6000
    password: admin
    sentinel:
      master: mymaster
      nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381

 

连接工厂配置

 

    @Bean
    @Primary
    public RedisProperties redisProperties() {
        return new RedisProperties();
    }
    @Bean
    JedisConnectionFactory jedisConnectionFactory(RedisSentinelConfiguration sentinelConfig)
    {
        return new JedisConnectionFactory(sentinelConfig);
    }


    @Bean
    public RedisSentinelConfiguration sentinelConfiguration(){
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
        RedisProperties properties = redisProperties();
        List<String> nodes=properties.getSentinel().getNodes();
        redisSentinelConfiguration.master(properties.getSentinel().getMaster());
        //配置redis的哨兵sentinel
        Set<RedisNode> redisNodeSet = new HashSet<>();
        nodes.forEach(x->{
            redisNodeSet.add(new RedisNode(x.split(":")[0],Integer.parseInt(x.split(":")[1])));
        });
        redisSentinelConfiguration.setSentinels(redisNodeSet);

        properties.getSentinel();
        redisSentinelConfiguration.setPassword(RedisPassword.of(properties.getPassword()));
        return redisSentinelConfiguration;
    }

配置redis集群

window redis集群推荐将多个redis注册为window服务,方便管理。

spring boot 配置文件

 spring:
    cluster:
      max-redirects: 3
      nodes: 127.0.0.1:6379,127.0.0.1:6380,127.0.0.1:6381

 

连接工厂配置及redistemplate配置

 

    @Bean
    JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPool,
                                                  RedisClusterConfiguration jedisClusterConfig)
    {

        return new JedisConnectionFactory(jedisClusterConfig,jedisPool);
    }


    @Bean
    public JedisPoolConfig jedisPool(RedisProperties properties) {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(properties.getJedis().getPool().getMaxIdle());
        jedisPoolConfig.setMaxWaitMillis(properties.getJedis().getPool().getMaxWait().toMillis());
        jedisPoolConfig.setMaxTotal(properties.getJedis().getPool().getMaxActive());
        jedisPoolConfig.setMinIdle(properties.getJedis().getPool().getMinIdle());
        return jedisPoolConfig;
    }

    @Bean
    public RedisClusterConfiguration jedisConfig() {
        RedisClusterConfiguration config = new RedisClusterConfiguration();
        RedisProperties properties = redisProperties();
        List<String> nodes=properties.getCluster().getNodes();
        Set<RedisNode> redisNodeSet = new HashSet<>();
        nodes.forEach(x->{
            redisNodeSet.add(new RedisNode(x.split(":")[0],Integer.parseInt(x.split(":")[1])));
        });
        config.setClusterNodes(redisNodeSet);
        config.setMaxRedirects(properties.getCluster().getMaxRedirects());
        config.setPassword(RedisPassword.of(properties.getPassword()));
        return config;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        RedisSerializer stringSerializer = new StringRedisSerializer();//序列化为String

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);

        redisTemplate.setKeySerializer(stringSerializer);
        //redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
       // redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
        return redisTemplate;
    }

如果配置的集群正常,就可以使用redistemplate操作数据了。

spring boot Redis 消息订阅与发部

配置文件

spring
  reids
  channel:
    site: site-visit

 

使用redis 消息监听容器

 

    @Bean
    public RedisMessageListenerContainer listenerContainer(MessageListenerAdapter listenerAdapter,
                                                           RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, ChannelTopic.of(siteVisitChannel));
        return container;
    }

    @Bean
    public MessageListenerAdapter listenerAdapter(SiteVisitConsumer consumer) {
        MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(consumer);
        messageListenerAdapter.setSerializer(new JdkSerializationRedisSerializer());
        return messageListenerAdapter;
    }

 

设置消息生产者 

 

@Component
public class SiteVisitProducer {
    Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Value("${spring.redis.channel.site}")
    private String siteVisitChannel;

    public void sendMessage(SiteVisitInfo pv) {
        logger.info("新的访问信息==>>{}",pv);
        redisTemplate.convertAndSend(siteVisitChannel, pv);
    }
}

 

消费者端

 

public interface MessageDelegate {
    void handleMessage(SiteVisitInfo message);
    void handleMessage(String message);
    void handleMessage(Map message);
    void handleMessage(byte[] message);
    void handleMessage(Serializable message);
    // pass the channel/pattern as well
    void handleMessage(Serializable message, String channel);
}


@Component
public class SiteVisitConsumer implements MessageDelegate{
    @Autowired
    SiteVisitInfoService siteVisitInfoService;
    Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void handleMessage(SiteVisitInfo message) {
        logger.info("接受站点访问信息==>>>{}",message);
        siteVisitInfoService.saveOrUpdate(message);
    }

    @Override
    public void handleMessage(String message) {
        logger.info("接受站点访问信息==>>>{}",message);
    }

    @Override
    public void handleMessage(Map message) {
        logger.info("接受站点访问信息==>>>{}",message);
    }

    @Override
    public void handleMessage(byte[] message) {
        logger.info("接受站点访问信息==>>>{}",message);
    }

    @Override
    public void handleMessage(Serializable message) {

        logger.info("接受站点访问信息==>>>{}",message);
    }

    @Override
    public void handleMessage(Serializable message, String channel) {
        logger.info("接受站点访问信息==>>>{}",message);
    }

接下来,当我们在程序中发消息时siteVisitProducer.sendMessage(); 消费端将会接受到消息,处理接受到的消息即可。

使用Redis分布式锁

为什么要使用分布式锁

在单机情况下我们通常使用Java的内置锁来实现高并发情况,但是在分布式的情况下,如果有多个服务对外提供服务,即使使用java的锁也会造成数据不一致的情况,因为进入单个服务的请求是同步访问的,但该服务中的锁不能对其他服务起作用,最终导致数据出现错误。

springboot使用分布式锁

 redis分布式锁实现的集中方式

  • setnx+expire ,又有这两个命令不是原子的,可能会引发并发问题

  • lua脚本 或set key value [EX seconds][PX milliseconds][NX|XX] 命令 ,具备原子性
        @Autowired
        RedisTemplate redisTemplate;
        @Autowired
        JedisPool jedisPool;
    
        private static final String REDIS_LOCK="covid";
    
       public void saveDailyData() {
            Jedis jedis=jedisPool.getResource();
            String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
            Boolean exist=redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK,value);
            redisTemplate.expire(REDIS_LOCK,10, TimeUnit.SECONDS);
    
    /*        String lua_scripts_lock = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
                    "redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
            Object result = jedis.eval(lua_scripts_lock, Collections.singletonList(REDIS_LOCK), Arrays.asList(value,"10"));*/
    
            try {
                if(exist) {
                    Covid[] dailyList = restTemplate.getForObject(covidDaliyDataApi, Covid[].class);
                    logger.info("COVID daily data size ===>>>>>[{}]", dailyList.length);
                    covidRepository.saveAll(Arrays.asList(dailyList));
                }
            } catch (RestClientException e) {
                e.printStackTrace();
            } finally {
    
                String lua_script_unlock = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                Object obj=jedis.eval(lua_script_unlock , Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
                if(obj.toString().equals(1)){
                    logger.info("分布式锁{}删除成功",REDIS_LOCK);
                }
                jedisPool.close();
    /*            String currLockValue= (String) redisTemplate.opsForValue().get(REDIS_LOCK);
                if(currLockValue.equals(value))
                    redisTemplate.delete(REDIS_LOCK);*/
            }
        }

     

  • redis官方推荐 Redisson
    //引入依赖
    implementation group: 'org.redisson', name: 'redisson', version: '3.15.0'
    
    //在集群环境下Redisson
        @Bean
        public Redisson redisson() {
            RedisProperties properties = redisProperties();
            List<String> clusterNodes = new ArrayList<>();
            for (int i = 0; i < properties.getCluster().getNodes().size(); i++) {
                clusterNodes.add("redis://" + properties.getCluster().getNodes().get(i));
            }
            Config config = new Config();
            ClusterServersConfig clusterServersConfig = config.useClusterServers()
                    .addNodeAddress(clusterNodes.toArray(new String[clusterNodes.size()]));
            clusterServersConfig.setPassword(properties.getPassword());//设置密码
            return (Redisson) Redisson.create(config);
        }
    //RLock使用
            RLock rLock = redisson.getLock(REDIS_LOCK);
            rLock.lock();
            try {
               //业务代码
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(rLock.isLocked()&&rLock.isHeldByCurrentThread()){
                    rLock.unlock();
                }
            }

     

 

分享到:

专栏

类型标签

网站访问总量