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、缓存

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开发的秒杀系统(异步下单、热点数据缓存、解决超卖)

你可能感兴趣的:(Redis,redis,数据库,缓存)