title: Redis-05Redis应用场景
keywords: Redis
cover: [https://z1.ax1x.com/2023/10/01/pPLPggO.png]
banner:    type: img  bgurl: https://z1.ax1x.com/2023/10/01/pPLPggO.png  bannerText: Redis应用场景
categories: Redis
tags:   - Redis
toc: false # 无需显示目录
1、缓存
Redis作为key-value形式的内存数据库,最先想到的应用场景就是作为数据缓存。
使用Redis来缓存数据的好处如下:
-  减少对数据库的访问 
-  提高系统响应速度 
String类型:
-  热点数据缓存 
-  对象缓存 -   把相应的热点对象进行缓存到Redis -  用户对象 
-  商品对象 
-  订单对象 
 
-  
 
-  
-  页面缓存 - 通过在手动渲染得到的html页面缓存到redis,下次访问相同页面时直接从redis中获取进行返回,减少服务端处理的压力
 
2、数据共享分布式
String类型:
-  分布式Session - Redis是分布式的独立服务,可以在多个应用之间共享
 
3、分布式锁
由于Redis单线程的特性,可以避免分布式部署之后的数据污染问题
Redisson:java分布式锁终极解决方案之 redisson_java redisson-CSDN博客
实现一个简易的锁:
    public String acquireLock(Jedis conn, String lockName) {
        return acquireLock(conn, lockName, 10000);
    }
    /**
     * 简易锁
     *
     * 如果程序在尝试获取锁的时候失败,那么它将不断地进行重试,知道成功地取得锁或者超过给定的时间限制为止
     * @param conn
     * @param lockName
     * @param acquireTimeout
     * @return
     */
    public String acquireLock(Jedis conn, String lockName, long acquireTimeout) {
        String identifier = UUID.randomUUID().toString();
        // 持有锁时间
        long end = System.currentTimeMillis() + acquireTimeout;
        while (System.currentTimeMillis() < end) {
            // 尝试获得锁
            if (conn.setnx("lock:" + lockName, identifier) == 1) {
                return identifier;
            }
            try {
                Thread.sleep(1);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
        return null;
    }
    /**
     * 释放锁操作
     *
     * @param conn
     * @param lockName
     * @param identifier
     * @return
     */
    public boolean releaseLock(Jedis conn, String lockName, String identifier) {
        String lockKey = "lock:" + lockName;
        while (true) {
            // 检查进程是否仍然持有锁
            conn.watch(lockKey);
            if (identifier.equals(conn.get(lockKey))) {
                // 释放锁
                Transaction trans = conn.multi();
                trans.del(lockKey);
                List<Object> results = trans.exec();
                if (results == null) {
                    continue;
                }
                return true;
            }
            conn.unwatch();
            break;
        }
        return false;
    }
带有超时限制特性的锁:
/**
     * 带有超时限制特性的锁
     *
     * @param conn
     * @param lockName
     * @param acquireTimeout
     * @param lockTimeout
     * @return
     */
    public String acquireLockWithTimeout(
            Jedis conn, String lockName, long acquireTimeout, long lockTimeout) {
        String identifier = UUID.randomUUID().toString();
        String lockKey = "lock:" + lockName;
        // 过期时间必须是整数
        int lockExpire = (int) (lockTimeout / 1000);
        long end = System.currentTimeMillis() + acquireTimeout;
        while (System.currentTimeMillis() < end) {
            // 获取锁并设置过期时间
            if (conn.setnx(lockKey, identifier) == 1) {
                // 检查过期时间
                conn.expire(lockKey, lockExpire);
                return identifier;
            }
            if (conn.ttl(lockKey) == -1) {
                conn.expire(lockKey, lockExpire);
            }
            try {
                Thread.sleep(1);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
        // null indicates that the lock was not acquired
        return null;
    }
    /**
     * 释放锁操作
     *
     * @param conn
     * @param lockName
     * @param identifier
     * @return
     */
    public boolean releaseLock(Jedis conn, String lockName, String identifier) {
        String lockKey = "lock:" + lockName;
        while (true) {
            // 检查进程是否仍然持有锁
            conn.watch(lockKey);
            if (identifier.equals(conn.get(lockKey))) {
                // 释放锁
                Transaction trans = conn.multi();
                trans.del(lockKey);
                List<Object> results = trans.exec();
                if (results == null) {
                    continue;
                }
                return true;
            }
            conn.unwatch();
            break;
        }
        return false;
    }
String 类型setnx方法,只有不存在时才能添加成功,返回true
# 加锁操作
public static boolean getLock(String key) {
    Long flag = jedis.setnx(key, "1");
    if (flag == 1) {
        jedis.expire(key, 10);
    }
    return flag == 1;
}
# 释放
public static void releaseLock(String key) {
    jedis.del(key);
}
4、计数器
int类型,incr方法
使用场景:
-  记录各个页面的被访问次数 
-  时间序列计数器(time series counter) 
时间序列计数器实现如下:
    // 以秒为单位的计数器精度,分别为1秒、5秒、60秒、300秒、3600秒、18000秒、86400秒。
    public static final int[] PRECISION = new int[]{1, 5, 60, 300, 3600, 18000, 86400};
    /**
     * 更新计数器信息
     *
     * @param conn
     * @param name
     * @param count
     * @param now
     */
    public void updateCounter(Jedis conn, String name, int count, long now) {
        Transaction trans = conn.multi();
        // 为我们记录的每种精度都创建一个计数器
        for (int prec : PRECISION) {
            long pnow = (now / prec) * prec;
            // 创建负责存储计数信息的散列
            String hash = String.valueOf(prec) + ':' + name;
            // 将计数器的引用信息添加到有序集合里面,并将其分值设置为0,以便在之后执行清理操作
            trans.zadd("known:", 0, hash);
            // 对给定名字和精度的计数器进行更新
            trans.hincrBy("count:" + hash, String.valueOf(pnow), count);
        }
        trans.exec();
    }
    /**
     * 获取计数器内容
     *
     * @param conn
     * @param name
     * @param precision
     * @return
     */
    public List<Pair<Integer, Integer>> getCounter(
            Jedis conn, String name, int precision) {
        // 获取存储计数器数据的键名
        String hash = String.valueOf(precision) + ':' + name;
        // 从Redis里面取出计数器数据
        Map<String, String> data = conn.hgetAll("count:" + hash);
        ArrayList<Pair<Integer, Integer>> results =
                new ArrayList<Pair<Integer, Integer>>();
        // 将计数器数据转换成指定的格式
        for (Map.Entry<String, String> entry : data.entrySet()) {
            results.add(new Pair<Integer, Integer>(
                    Integer.parseInt(entry.getKey()),
                    Integer.parseInt(entry.getValue())));
        }
        // 对数据进行排序,把旧的数据样本排在前面
        Collections.sort(results);
        return results;
    }
5、限流
int类型,incr方法
Rate Limit实现案例:
- Spring AOP + Redis + Lua 实现自定义限流注解
6、购物车
String或者Hash
7、时间轴(Timeline)
list作为双向链表,不光可以作为队列使用。
如果将它用作栈便可以成为一个公用的时间轴。
当用户发完微博后,都通过lpush将它存放在一个 key 为LATEST_WEIBO的list中,之后便可以通过lrange取出当前最新的微博。
8、消息队列
List提供了两个阻塞的弹出操作:blpop/brpop,可以设置超时时间
- blpop:blpop key1 timeout 移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
- brpop:brpop key1 timeout 移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
9、点赞
文章ID:t1001
用户ID:u3001
用 like:t1001 来维护 t1001 这条文章的所有点赞用户
- 点赞了这条文章:sadd like:t1001 u3001
- 取消点赞:srem like:t1001 u3001
- 是否点赞:sismember like:t1001 u3001
- 点赞的所有用户:smembers like:t1001
- 点赞数:scard like:t1001
10、排行榜
ZSET:有序set
-  zrevrangebyscore:获得以分数倒序排列的序列 
-  zrank:获取成员在该排行榜的位置 
id 为6001 的新闻点击数加1:
zincrby hotNews:20190926 1 n6001
获取今天点击最多的15条:
zrevrange hotNews:20190926 0 15 withscores
11、共同好友
通过Set的交集、并集、差集操作来实现查找两个人共同的好友
12、秒杀
流程如下:
- 提前预热数据,放入Redis
- 商品列表放入Redis List
- 商品的详情数据 Redis hash保存,设置过期时间
- 商品的库存数据Redis sorted set保存
- 用户的地址信息Redis set保存
- 订单产生扣库存通过Redis制造分布式锁,库存同步扣除
- 订单产生后发货的数据,产生Redis list,通过消息队列处理
- 秒杀结束后,再把Redis数据和数据库进行同步
实现Demo:
- SecKillProduct: 基于SpringBoot+RabbitMQ+Redis开发的秒杀系统(异步下单、热点数据缓存、解决超卖)










