• Redis-05Redis应用场景


    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    带有超时限制特性的锁:

    /**
         * 带有超时限制特性的锁
         *
         * @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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    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
    
    • 1

    获取今天点击最多的15条:

    zrevrange hotNews:20190926 0 15 withscores
    
    • 1

    11、共同好友

    通过Set的交集、并集、差集操作来实现查找两个人共同的好友

    12、秒杀

    流程如下:

    • 提前预热数据,放入Redis
    • 商品列表放入Redis List
    • 商品的详情数据 Redis hash保存,设置过期时间
    • 商品的库存数据Redis sorted set保存
    • 用户的地址信息Redis set保存
    • 订单产生扣库存通过Redis制造分布式锁,库存同步扣除
    • 订单产生后发货的数据,产生Redis list,通过消息队列处理
    • 秒杀结束后,再把Redis数据和数据库进行同步

    实现Demo:

  • 相关阅读:
    快速拿下 AI Prompt 工程师证书攻略!
    30 个Python代码实现的常用功能(附案例源码)
    Unity点乘的实战案例1
    递归实现指数型枚举(DAY 91)
    五、04【Java IO模型】之字符流
    LeetCode 438. 找到字符串中所有字母异位词__滑动窗口
    UE5 C++ 斯坦福 1
    算法之斐波那契数列
    ANSYS中如何手动为装配体添加接触约束教程
    中医-通过舌象判断身体状况
  • 原文地址:https://blog.csdn.net/weixin_45688141/article/details/133711386