redis常见的场景设计

一、String场景

1.计数

如浏览次数,每浏览一次,就调用incr keyName.

或者是编号,如每下一个单,自增

2.session共享

用SpringSession和redis完成session共享

二、分布式锁

锁场景:

多任务环境-------多对一操作

有状态的资源-------会不一样(有状态类)

基础要点

缓存有效期

可以给key设置过期时间,key过期会自动删除

SETNX

当仅当key不存在时设置value,key存在啥都不做。

lua脚本

支持redis操作序列的原子性

案例:

开5个线程售票:

1.Controller
@RestController
public class LockController {
    //共20张票
    private static long count = 20;
    private CountDownLatch countDownLatch = new CountDownLatch(5);

    @Resource(name="redisLock")
    private Lock lock;

    @RequestMapping(value = "/sale", method = RequestMethod.GET)
    public Long sale() throws InterruptedException {
        count = 20;
        countDownLatch = new CountDownLatch(5);

        System.out.println("-------共20张票,分五个窗口开售-------");
        new PlusThread().start();
        new PlusThread().start();
        new PlusThread().start();
        new PlusThread().start();
        new PlusThread().start();
        return count;
    }

    // 线程类模拟一个窗口买火车票
    public class PlusThread extends Thread {
        private int amount = 0;//抢多少张票

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "开始售票");
            countDownLatch.countDown();
            if (countDownLatch.getCount()==0){
                System.out.println("----------售票结果------------------------------");
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            while (count > 0) {
                lock.lock();
                try {
                    if (count > 0) {
                        //模拟卖票业务处理
                        amount++;
                        count--;
                    }
                }finally{
                    lock.unlock();
                }

                try {
                    Thread.sleep(10);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "售出"+ (amount) + "张票");
        }
    }
}
2.Redis锁
@Service
public class RedisLock implements Lock {

    private static final String KEY = "LOCK_KEY";
    private static final String OK = "OK";

    @Resource
    private JedisConnectionFactory factory;

    private ThreadLocal<String> local = new ThreadLocal<>();


    @Override
    //阻塞式的加锁
    public void lock() {
        //1.尝试加锁
        if (tryLock()) {
            return;
        }
        //2.加锁失败,当前任务休眠一段时间
        try {
            Thread.sleep(10);//性能浪费
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //3.递归调用,再次去抢锁
        lock();
    }


    @Override
    //阻塞式加锁,使用setNx命令返回OK的加锁成功,并生产随机值
    public boolean tryLock() {
        //产生随机值,标识本次锁编号
        String uuid = UUID.randomUUID().toString();
        Jedis jedis = (Jedis) factory.getConnection().getNativeConnection();

        /**
         * key:我们使用key来当锁
         * uuid:唯一标识,这个锁是我加的,属于我
         * NX:设入模式【SET_IF_NOT_EXIST】--仅当key不存在时,本语句的值才设入
         * PX:给key加有效期
         * 1000:有效时间为 1 秒
         */
        String ret = jedis.set(KEY, uuid, "NX", "PX", 1000);

        //设值成功--抢到了锁
        if (OK.equals(ret)) {
            local.set(uuid);//抢锁成功,把锁标识号记录入本线程--- Threadlocal
            return true;
        }

        //key值里面有了,我的uuid未能设入进去,抢锁失败
        return false;
    }

    //正确解锁方式
    public void unlock() {
        //读取lua脚本(原子性)
		/*
			if redis.call("get",KEYS[1]) == ARGV[1] then
                return redis.call("del",KEYS[1])
            else
                return 0
            end
		 */
        String script = FileUtils.getScript("D:\\unlock.lua");
        //获取redis的原始连接
        Jedis jedis = (Jedis) factory.getConnection().getNativeConnection();
        //通过原始连接连接redis执行lua脚本
        jedis.eval(script, Arrays.asList(KEY), Arrays.asList(local.get()));
    }

    //-----------------------------------------------

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit)
            throws InterruptedException {
        return false;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    }

}

三、Hash存储

如存储我们的购物车:

127.0.0.1:6379> hmset cart01 pid01 2 pid02 1
OK
127.0.0.1:6379> hgetall cart01
1) "pid01"
2) "2"
3) "pid02"
4) "1"

购物车01中两个商品pid01和pid02,查询使用hgetall cart01,拿到id号后台就可以进行查询其他信息。

查询购物车物品数:

hlen cart01
2

四、List

实现阻塞队列(BlockingQueue)

BLPOP或BRPOP(以及BPOPLPUSH、BRPOPLPUSH等)可以实现阻塞操作

127.0.0.1:6379> lpush request r1 r2 r3
(integer) 3
127.0.0.1:6379> lrange request 0 -1
1) "r3"
2) "r2"
3) "r1"

接着使用BLPOP命令:

127.0.0.1:6379> blpop request 5
1) "request"
2) "r3"
127.0.0.1:6379> blpop request 5
1) "request"
2) "r2"
127.0.0.1:6379> blpop request 5
1) "request"
2) "r1"
127.0.0.1:6379> blpop request 5
(nil)
(5.09s)

5是阻塞时长,如果队列中没有数据了,就等待5s,如果还没有元素可以弹出,就结束阻塞。

如订阅号消息:

> lpush mes:u1 01 02 03 04 05
(integer) 5
> lpop mes:u1
"05"
> lpop mes:u1
"04"
> lpop mes:u1
"03"
> lpop mes:u1
"02"

公众号发送一个文章,就lpush到list中,然后用户打开时,再lpop按先进后出顺序显示消息。

五、集合set

投票

可以用集合设计一个投票的场景。

如有几十号人需要投票。

用户A给1号投了票,就可以使用:

> sadd userA u01
(integer) 1

用户A又给2号投票, :

> sadd userA u02
(integer) 1

这样就可以得到用户A投票的集合:

> smembers userA
1) "u01"
2) "u02"

如果他再给01用户投票:

> sadd userA u01
(integer) 0

因为已经存在了就返回0。

这样就可以根据返回值判断是否重复投票,给出相应的响应。

抽奖

> sadd user u1 u2 u3
(integer) 3
> smembers user
1) "u3"
2) "u2"
3) "u1"

向user中添加三个用户u1 u2 u3

使用SRANDMEMBER:

> SRANDMEMBER user
"u2"
> SRANDMEMBER user 2
1) "u1"
2) "u2"
> smembers user
1) "u3"
2) "u2"
3) "u1"

随机返回集合中指定个数的元素,不会移除该元素。

使用SPOP:

127.0.0.1:6379> spop user 2
1) "u2"
2) "u1"
127.0.0.1:6379> smembers user
1) "u3"

SPOP会随机移除并返回指定个数的元素。

因此我们可以使用SPOP做一个抽奖的活动:

将所有参与活动的人的id加入一个集合中,然后每次随机返回指定个数进行抽奖。

或者使用SRANDMEMBER用于随机抽人回答问题

关注/粉丝的设计

各种社交网站或是论坛,都有关注/粉丝的设计。

如果用redis的话,就可以使用集合来实现。

维护每个用户的关注列表如:

> sadd userA userB userC userD
(integer) 3
> sadd userB userA userC userE
(integer) 3
> sadd userC userB userE
(integer) 2
> sadd userD userA user B
(integer) 3

现在有以下几个问题:

  1. 某个用户的关注

    > smembers userA
    1) "userC"
    2) "userB"
    3) "userD"
    
  2. 两个用户的共同关注

    > sinter userA userB
    1) "userC"
    

六、有序集合

可用于点赞。因为点赞记录展示一般需要按顺序,同时需要保证不重复,因此选择有序结合。

而使用列表会出现重复,使用集合的话又不能保证有序。

当然不需要有序可以直接使用集合。

另外,有序集合最典型的应用就是排行榜。

如我们热门排行榜。

> zrange hot2020424 0 -1
1) "tag1"
2) "tag5"
3) "tag3"
4) "tag9"
> zrange hot2020423 0 -1
1) "tag2"
2) "tag7"
3) "tag3"
4) "tag8"

某两天的热门标签如上。

每使用一次某标签,就可以使用

> zincrby hot2020424 1 tag1
2.0

增加一个分数。

可以获取topN的排名:

> zrange hot2020424 0 2
1) "tag1"
2) "tag5"
3) "tag3"

获取两天中的前5的排名:

127.0.0.1:6379> zunionstore hot2020424/2020423 2 hot2020424 hot2020423 
(integer) 7

先合并到另一个集合中,这时,同样的元素,其分数等于两个集合中该元素的分数和。

127.0.0.1:6379> zrange hot2020424/2020423 0 -1 withscores
 1) "tag1"
 2) "1"
 3) "tag2"
 4) "1"
 5) "tag5"
 6) "2"
 7) "tag7"
 8) "2"
 9) "tag8"
10) "4"
11) "tag9"
12) "4"
13) "tag3"
14) "6"

如tag3,两天一共6次。

然后就可以获取前5的数据:

127.0.0.1:6379> zrange hot2020424/2020423 0 4 withscores
 1) "tag1"
 2) "1"
 3) "tag2"
 4) "1"
 5) "tag5"
 6) "2"
 7) "tag7"
 8) "2"
 9) "tag8"
10) "4"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值