Redis

目录

获取list和hash类型的键的值

获取List类型键值

获取Hash类型键值 

删除以指定内容开头的键

设置最大占用内存config set maxmemory xxx

redis由单线程到多线程、多路复用

惰性删除 

统计以某个关键字为前缀的键的数量

Keyspace notifications功能

redis的分布式锁setNx()心得

场景1

场景2--复杂场景

使用简单案例

为什么锁键要设置过期时间,以及设置过期时间后的自动续期问题 

Redisson

前言

Redisson

Lettuce

Jedis

实战之分布式锁

JedisPool查看客户端链接数目

Redis拓展set为setnx工具类

RedisConfig.java配置


获取list和hash类型的键的值

获取List类型键值

获取LifeCycleAutoCall_1键的全部值(List类型)0下标代表第一个元素,-1代表最后一个元素。

lrange LifeCycleAutoCall_1 0 -1


获取Hash类型键值 

获取LifeCycleAutoCall_1键的值(Hash类型)

HGETALL  LifeCycleAutoCall_1


删除以指定内容开头的键

Redis 中有删除单个 Key 的指令 DEL,但好像没有批量删除 Key 的指令,不过我们可以借助 Linux 的 xargs 指令来完成这个动作:

redis-cli -a 你的密码 keys 'product*' | xargs redis-cli -a 你的密码 del
//如果redis-cli没有设置成系统变量,需要指定redis-cli的完整路径  
//如:/opt/redis/redis-cli keys "*" | xargs /opt/redis/redis-cli del  

设置最大占用内存config set maxmemory xxx

Redis默认无限使用服务器内存, 为防止极端情况下导致系统内存耗尽, 建议所有的Redis进程都要配置maxmemory。在保证物理内存可用的情况下, 系统中所有Redis实例可以调整maxmemory参数来达到自由伸缩内存的目的。

如下命令进行调整:
Redis-1>config set maxmemory 6GB
Redis-2>config set maxmemory 2GB

注意,在64bit系统下,maxmemory设置为0表示不限制Redis内存使用,在32bit系统下,maxmemory不能超过3GB


redis由单线程到多线程、多路复用

在Redis4.0之前,Redis是单线程运行的,但单线程并不代表效率低,像Nginx(Nginx采用的是多进程(多个worker进程)单线程多路IO复用模型)、Node.js也是单线程程序,但是它们的效率并不低。
原因是Redis是基于内存的,它的瓶颈在于机器的内存、网络带宽,而不是CPU,在CPU还没达到瓶颈时机器内存可能就满了、或者带宽达到瓶颈了。因此CPU不是主要原因,那么自然就采用单线程了,况且使用多线程比较麻烦。
但是在Redis4.0的时候,已经开始支持多线程了,比如后台删除等功能。
简单来说,Redis在4.0之前使用单线程的模式是因为以下三个原因:
1.使用单线程模式的Redis,其开发和维护更简单,因为单线程模式方便开发和调试。
2.即使使用单线程模型也能够并发地处理多客户端的请求,主要是因为Redis内部使用了基于epoll(
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。)的多路复用
3.对于Redis来说,主要的性能瓶颈是内存或网络带宽,而非CPU。

惰性删除 

但Redis在4.0以及之后的版本中引入了惰性删除(也叫异步删除),意思就是我们可以使用异步的方式对Redis中的数据进行删除操作,例如:
unlink key:和del key类似,删除指定的key,若key不存在则key被跳过。但是del会产生阻塞,而unlink命令会在另一个线程中回收内存,即它是非阻塞的【http://www.redis.cn/commands/unlink.html】;
flushdb async:删除当前数据库的所有数据【http://www.redis.cn/commands/flushdb.html】;
flushall async:删除所有库中的数据【http://www.redis.cn/commands/flushall.html】。
这样处理的好处是不会使Redis的主线程卡顿,会把这些操作交给后台线程来执行。
通常情况下使用del指令可以很快的删除数据,但是当被删除的key是一个非常大的对象时,例如:删除的时包含成千上万个元素的hash集合时,那么del指令就会造成Redis主线程卡顿,因此使用惰性删除可以有效避免Redis卡顿问题。】 


统计以某个关键字为前缀的键的数量

通过rdm工具的命令窗口执行:

eval "return #redis.call('keys', 'your_keys_prefix_*')" 0


Keyspace notifications功能

使用springsession时需要开启redis的Keyspace notifications功能(默认不开启):

在redis.config中配置,windows是redis.window.config,增加一行

notify-keyspace-events Egx

redis的分布式锁setNx()心得

场景1

当多线程共享的数据一经设置到redis后,如果其值不变,即多线程写入同一个键(只允许写入一次),写入后多线程只取不改,此时只需要保证写入操作的原子性(setnx已实现)就行了[不需要保证对写入的键的值的操作的原子性,和减库存、减允许登录失败次数的场景不同]。

如:系统启动时生成并保存rsa密钥对到redis,随后由程序定期更新。

场景2--复杂场景

使用简单案例

减库存、减允许的登录失败次数。需要保证多线程对键值操作的原子性。

下图中的while循环含义:作为锁的键被某个线程锁定中..,其它线程循环等待以获取锁。

为什么锁键要设置过期时间,以及设置过期时间后的自动续期问题 

setNx的key必须设置一个超时时间以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。可以使用expire命令设置锁超时时间。

即便设置了超时时间,原子逻辑执行完也需要手动删除作为锁的键,让其它线程尽快获取锁。但是设置了过期时间就会出现上面说的问题

* 锁过期:客户端 1 操作共享资源耗时太久,导致锁被自动释放,之后被客户端 2 持有

* 释放别人的锁:客户端 1 操作共享资源完成后,却又释放了客户端 2 的锁

办法是借助Redisson给锁自动续期(Redisson是一个在Redis的基础上实现的Java驻内存数据网格。 就是在Redis的基础上封装了很多功能,以便于我们更方便的使用

为什么一定要给锁键设置超时时间呢?避免死锁。

为什么不设置超时会死锁(为什么写了原子逻辑执行完后立马删除/释放锁的代码,不设置超时还会死锁)?

1.网络抖动
进程A中的一个线程获取到了锁,然后执行在finally中释放锁的代码时,由程序到Redis的网络不好了,所以释放锁失败。此时对于redis服务端来说,它可不知道客户端曾经试图释放过锁,它会一直把锁给A,如此一来,其他进程的线程再也不能获取到这个锁了。
如果用设置过期时间的方式,即使客户端和服务端的网络不通了,服务端依然在进行时间的计算,时间到了直接把锁释放掉,等网络通了,不影响获取锁。
2.服务端宕机
进程A获取到了锁,Redis服务器宕机了,所以锁没有释放。等到Redis再次恢复的时候,Redis服务端还会保持这这个锁给到A,就会锁死。
如果是设置了过期时间的话,服务器恢复后就会继续倒计时,时间到了服务器自动把锁释放。
说白了,分布式锁用的是第三方的东西,所以要在第三方设置,不能只在客户端保证所的释放。

那么设置了过期时间,怎么在逻辑未执行完时自动续期,就涉及到Redisson了。


Redisson

前言

Redisson

Redisson是架设在Redis基础上的一个Java驻内存数据网格。Redisson在基于NIO的Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力。兼容 Redis 2.6+ and JDK 1.6+

Lettuce

Lettuce是一个可伸缩线程安全的Redis客户端。多个线程可以共享同一个RedisConnection。它利用优秀netty NIO框架来高效地管理多个连接。

Jedis

Jedis是redis官方推荐的java连接开发工具,使用java操作redis的中间件。

Redisson、Jedis、Lettuce 是三个不同的操作Redis的客户端,Jedis、Lettuce 的 API 更侧重对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。

实战之分布式锁

案例待补充...


JedisPool查看客户端链接数目

使用的时候需要进行连接池的设置,用户在超过MaxTotal连接数的时候也会出现获取不到连接池的情况,这个时候可以在访问客户端上通过netstat -an | grep 6379 | grep EST | wc -l


Redis拓展set为setnx工具类

package com.genertech.plm.aia.login.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Protocol;
import redis.clients.util.SafeEncoder;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.concurrent.Semaphore;

/**
 * https://dandelioncloud.cn/article/details/1486003750564384769/
 * Redis拓展set为setnx
 **/
@Component
public class RedisStringOps {
  /**
   * RedisTemplate 装饰器
   *
   * @date 2019/6/11 14:45
   **/
  private static class RedisTemplateHolder {
    /**
     * 最大有20个redis连接被使用,其他的连接要等待令牌释放
     * 令牌数量自己定义,这个令牌是为了避免高并发下,获取redis连接数时,抛出的异常
     * 在压力测试下,性能也很可观
     */
    private static Semaphore semaphore = new Semaphore(20);

    private RedisTemplateHolder() {
    }

    public static RedisTemplate getRedisTemplate(RedisTemplate redisTemplate) {
      try {
        semaphore.acquire();
        return redisTemplate;
      } catch (Exception e) {
        throw new IllegalStateException(e);
      }
    }

    public static void release() {
      semaphore.release();
    }

    public static Object execute(Statement statement, RedisTemplate<String, Object> redisTemplate) {
      try {
        return statement.prepare(getRedisTemplate(redisTemplate));
      } finally {
        RedisTemplateHolder.release();
      }
    }
  }

  private interface Statement {
    Object prepare(final RedisTemplate redisTemplate);
  }

  @Resource
  private RedisTemplate redisTemplate;
  private static RedisSerializer<String> stringSerializer = new StringRedisSerializer();
  private static RedisSerializer<Object> blobSerializer = new JdkSerializationRedisSerializer();

  /**
   * 如果key不存在,set key and expire key
   *
   * @param key
   * @param value
   * @param expire
   * @return
   */
  public boolean setAndExpireIfAbsent(final String key, final Serializable value, final long expire) {
    Boolean result = (Boolean) RedisTemplateHolder.execute(new Statement() {
      @Override
      public Object prepare(RedisTemplate redisTemplate) {
        return redisTemplate.execute(new RedisCallback<Boolean>() {
          @Override
          public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
            Object obj = connection.execute("set", serialize(key), serialize("\"" + value + "\""), SafeEncoder.encode("NX"), SafeEncoder.encode("EX"), Protocol.toByteArray(expire));
            return obj != null;
          }
        });
      }
    }, redisTemplate);
    return result;
  }

  public boolean setIfAbsent(final String key, final Serializable value) {
    Boolean result = (Boolean) RedisTemplateHolder.execute(new Statement() {
      @Override
      public Object prepare(RedisTemplate redisTemplate) {
        return redisTemplate.execute(new RedisCallback<Boolean>() {
          @Override
          public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
            Object obj = connection.execute("set", serialize(key), serialize(value), SafeEncoder.encode("NX"));
            return obj != null;
          }
        });
      }
    }, redisTemplate);
    return result;
  }

  public void delete(final String key) {
    RedisTemplateHolder.execute(new Statement() {
      public Object prepare(RedisTemplate redisTemplate) {
        redisTemplate.delete(serialize(key));
        return null;
      }
    }, redisTemplate);
  }

  private <T> Jackson2JsonRedisSerializer<T> configuredJackson2JsonRedisSerializer(Class<T> clazz) {
    Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<T>(clazz);
    ObjectMapper objectMapper = new ObjectMapper();
    // json转实体忽略未知属性
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    // 实体转json忽略null
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    serializer.setObjectMapper(objectMapper);
    return serializer;
  }

  private byte[] serialize(Object object) {
    return serialize(object, SerializeFormat.STRING);
  }

  private byte[] serialize(Object object, SerializeFormat sf) {
    if (object == null) {
      return new byte[0];
    }
    if (sf == SerializeFormat.BLOB) {
      return blobSerializer.serialize(object);
    }
    if (object instanceof String || isPrimitive(object.getClass())) {
      return stringSerializer.serialize(String.valueOf(object));
    } else {
      return configuredJackson2JsonRedisSerializer(object.getClass()).serialize(object);
    }
  }

  /**
   * 工具方法
   * 判定指定的 Class 对象是否表示一个基本类型或者包装器类型
   *
   * @param clazz
   * @return
   */
  @SuppressWarnings("rawtypes")
  public static boolean isPrimitive(Class clazz) {
    if (clazz.isPrimitive()) {
      return true;
    } else
      try {
        if (clazz.getField("TYPE") != null &&
                ((Class) (clazz.getField("TYPE").get(null))).isPrimitive()) {
          return true;
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    return false;
  }
}
@Autowired
private RedisStringOps redisStringOps;
boolean absent = redisStringOps.setAndExpireIfAbsent("RSASecretKey", str, 3600 * 24 * 30);//30天
if (absent) {
  // 键不存在添加成功
  log.info("CAS PublicKey => " + publicKey + "\n");
} else {
  // 如果添加的键已经存在导致添加失败,直接从redis重新获取键即可
  casSecretKey = (String) redisTools.get("RSASecretKey");
  String dec = desUtil.getDec(casSecretKey);
  jsonObject = JSONObject.parseObject(dec, JSONObject.class);
}

RedisConfig.java配置

package com.genertech.plm.aia.accesscontrol.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;

/**
 * <p>功能描述: Redis配置</p>
 *
 * @author : wu.ch
 * @version V1.0
 * @date 2023/1/5 14:07
 */
@Configuration
public class RedisConfig {

  /**
   * redisTemplate 初始化
   * 当前配置类不是必须的,因为Spring Boot框架会自动装配RedisTemplate对象,但是默认的key序列化器为JdkSerializationRedisSerializer,
   * 导致我们存到Redis中后的数据和原始数据有差别
   *
   * @param redisConnectionFactory 连接工厂
   * @return redisTemplate
   */
  @Bean
  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

    redisTemplate.setConnectionFactory(redisConnectionFactory);

    // 使用Jackson2JsonRedisSerialize 替换默认序列化
    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

    // 设置value的序列化规则 和 key的序列化规则
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    //jackson2JsonRedisSerializer就是JSON序列化规则,
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    // 设置HashValue的序列化规则 和 HashKey的序列化规则
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

    redisTemplate.afterPropertiesSet();
    return redisTemplate;
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值