Redis使用总结

235 阅读6分钟

redis基本类型以及对应命令介绍

redis基本类型有五种,分别是String(字符串),Hash(散列),List(列表),Set(集合),Zset(有序集合)。

其实这五种类型,经常使用的是String这种类型,这种类型是比较简单的也比较粗暴的,不管存储的是数值还是对象抑或是集合,都直接序列化进行存储,然后使用的时候,反序列化就可以了,这种用法不能说有什么问题,但是序列化和反序列化也是需要开销的,而且如果你存储的是对象,需要对 对象属性值进行修改的时候,就需要把整个对象都读取出来进行修改,所以我们用redis的时候最好选择对应的数据结构。

下面我说一下五种数据类型常用的命令:

  • String

    set key value
    get key
    getrange key start end
    getset key value
    
  • Hash

    hset key field value
    hget key field
    hgetall key
    hdel key field1
    hmset key field1 value1
    hmget key field1
    
  • List

    lpush key value1 value2 (将一个或者多个值插入到列表头部)
    rpush key value1 value2 (将一个或者多个值插入到列表尾部)
    lrange key start stop (获取列表指定范围的元素)
    lset key index value (通过索引设置列表元素的值)
    rpop key (移除列表的最后一个元素,返回值为移除的元素)
    lpop key(移除并获取列表的第一个元素)
    lindex key index (通过索引获取列表中的元素)
    
  • Set

    sadd key member1 member2 (向集合添加一个或者多个元素)
    scard key (获取集合的成员数)
    sdiff key1 key2 (返回给定所有集合的差集)
    smembers key (返回集合中的所有成员)
    spop key (移除并返回集合中的一个随机元素)  
    srem key member1 member2 (移除集合中一个或者多个元素)
    
  • ZSet

    zadd key score1 member1 score2 member2
    (向有序集合添加一个或者多个成员,或者更新已经存在的分数)
    zcard key (获取有序集合的成员数)
    zincrby key increment member (有序集合对指定成员的分数加上增量increment)
    zrange key start stop [withscores] 通过索引区间返回有序集合成指定区间内的成员
    zrem key member member1 移除有序集合中的一个或者多个成员
    zrevrange key start stop [withscores] (返回有序集中指定区间内的成员,通过索引,分数从高到底)  
    

redis分布式锁的实现

假设我们开发的程序部署在多台服务器上并且有更新数据的业务需求,那么我们就要考虑是否添加分布式锁来保证数据的准确性。一般分布式锁有两种实现方案,一个是利用redis,另一个是利用zookeeper。

下面我们来说一下利用redis如何实现:

  • 底层命令:

    setnx key value 
    expire key time_in_seconds
    
  • show your code:

    public class RedisLock implements Lock {
     private static final Logger logger = Logger.getLogger(RedisLock.class);
    
     private Jedis jedisClient;
    
     /**
      * 锁Id
      */
     private final String lockId;
    
     /**
      * 锁的命名空间
      */
     private final String lockNamespace;
    
     /**
      * 锁key值
      */
     private final String lockKey;
    
     /**
      * 锁超时时间,防止线程在入锁以后,无限的执行等待,默认5秒
      */
     private static final int EXPIRE_SECS = 5;
     
     /**
      * 是否持有锁
      */
     private boolean locked = false;
    
     public RedisLock(Jedis jedisClient, String lockNamespace, String lockKey) {
         this.lockId = UUID.randomUUID().toString();
         this.jedisClient = jedisClient;
         this.lockNamespace = lockNamespace;
         this.lockKey = lockKey;
     }
    
     @Override
     public void lock() {
         if (this.tryLock()) {
             return;
         }
    
         throw new RuntimeException("can not acquire lock");
     }
    
     @Override
     public void lockInterruptibly() {
         this.lock();
     }
    
     @Override
     public boolean tryLock() {
         return false;
     }
    
     private boolean setExpireAndNx(final String key, final String value) {
         return false;
     }
    
     /**
      * 分布式锁
      *
      * @param time 时间
      * @param unit 单位
      * @return 是否获取到锁
      */
     @Override
     public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
         String lock = lockNamespace + ":" + lockKey;
         long timeout = unit.toMillis(time);
         synchronized (RedisLock.class) {
             while (timeout >= 0) {
                 if (setExpireAndNx(lock, lockId)) {
                     // 获得锁
                     locked = true;
                     return locked;
                 }
                 // 防止饥饿
                 Thread.sleep(10000);
             }
         }
    
         return locked;
     }
    
    
     @Override
     public void unlock() {
         if (locked) {
             String lock = lockNamespace + ":" + lockKey;
             if (lockId.equals(jedisClient.get(lock))) {
                 jedisClient.del(lock);
             }
             locked = false;
         }
     }
    
     @Override
     public Condition newCondition() {
         return null;
     }
     
     private boolean setExpireAndNx(final String key, final String value) {
           final Long result = jedisClient.setnx(key, value);
           boolean res = 1 == result;
           if (res) {
               jedisClient.expire(key, EXPIRE_SECS);
           }
    
           return res;
       }
    

redis事务

redis事务和其他数据库的事务概念有些不一样,redis事务是为了隔离操作;什么意思?就是事务中所有的操作都会序列化,按顺序执行,在执行的过程中,不会被其他客户端执行的操作打断。

  • 涉及的命令:
multi 开启事务
exec  执行事务
discard 取消事务
watch 监控事务执行的时候,是否有其他客户端对 对应的key进行操作,如果有,那么事务执行之后,返回的值是nil,相当于不执行
unwatch 取消所有key的监控,慎用

下面用一个简单的小例子来演示一下:

假设有一个需求,我想更新一个数据,然后立即得到这个值的数据。ps:大家不用细较真为啥会有这样一个脑残的需求,我们达到演示的目的就行。

按照我们的需求,其实我们应该得到的值是2,但是为什么会是3,因为在执行INCR k1命令和get k1命令之间,有其他客户端执行了INCR k1的命令,所以导致我们获得的结果和预期的不符,这时候我们的事务就闪亮亮登场了。

这样我们就可以得到想要的结果了。

redis持久化(RDB, AOF)

众所周知,redis与memcached比较重要的区别就是redis可以数据持久化,那么redis有几种持久化的方式呢?两种,一种是RDB(Redis DataBase),另一种是AOF(Append Of File)。

下面分别说一下这两种持久化方式的特点:

  • RDB

    RDB在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读到内存中。所以RDB的缺点时有可能最后一次持久化的数据丢失。优点是节省磁盘空间,恢复速度快。

    我们可以通过配置文件来设置持久化策略:找到redis.conf文件

可以看图片上,通过改变 save 后面的数值可以调整持久化策略。当然,为了演示方便,图上显示的值是我已经改变过的了。save 10 3 这条配置是说的是 10s内有三次改变就进行数据持久化。如图

上面图片中的命令我是在10s执行的,所以会将数据持久化到dump.rdb文件中去。

  • AOF

    AOF以日志的形式来记录每个写操作,将redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。AOF默认是不开启,appendonly no,这个同样可以根据配置文件进行更改。这个比较好理解,就不演示了。

那么这里会有一个问题,如果我们同时开启了RDB,AOF,那么redis重启的时候,会读取那个文件中的数据?

答案是AOF文件,因为AOF文件中的数据是要比RDB文件中的数据全。

redis常见问题

  • 删除策略
  • 缓存更新策略
  • 雪崩
  • 击穿

上面的问题是使用redis经常遇到的问题,大家可以尝试分析一下。 如果大家对redis有兴趣还可以自行研究一下redis主从复制以及集群。