算法

Redis(二):实战场景及实现方式

2019-08-14  本文已影响0人  雪飘千里

1、String 类型使用场景

incr key && decr key  递增/递减
incrby key increment && decrby key decrement  增加/减少指定值

set goods_id 10; 设置id为good_id的商品的库存初始值为10;
decr goods_id ; 当商品被购买时,库存数据减1

依次类推同样的场景,商品的浏览次数,问题或者回复的点赞次数等,这种计数的场景都可以考虑利用Redis 来实现

2、List类型使用场景

List是按照插入数据排序的字符串链表,可以在头部和尾部插入新的元素(双向链表实现,两端添加元素的时间复杂度为O(1))

这时候使用Redis的list数据结构,来进行 TOP 100新上架产品的存储,下面以伪代码演示:

// 1、把新上架的商品添加到链表中
ret = r.lpush("new:goods",goodsId)
// 2、通过ltrim裁减list链表,使之包含 指定范围内的指定元素 
r.ltrim("new:goods",0,99)
// 3、获取前100个最新上架的商品 id 列表
new_goods_list = r.lrange("new:goods",0,99)

List 存储就是一个队列的存储形式:
1、lpush key value;在key对应list的头部添加字符串元素;
2、rpop key;移除key对应list列表的最后一个元素,返回值为移除的元素

3、set类型使用场景

Set也是存储了一个集合列表的功能,但是,和list不同,set具备去重功能。当需要存储一个列表信息,同时要求列表内的元素不能有重复,这时候使用set比较适合。与此同时,set还可以实现交集、并集、差集。

例如,在交易网站,我们会存储用户感兴趣的商品信息,在进行相似性用户分析的时候,可以通过计算两个不同用户之间感兴趣商品的数量来提供一些依据。

下面以伪代码演示:

//userId为用户id,goodid为感兴趣的商品id
sadd  "user:userId"  goodId;

sadd  "user:101"  1;
sadd  "user:101"  2;
sadd  "user:102"  1;
sadd  "user:102"  3;

interResult = sinter "user:101"  "user:102"  返回直接定集合的交集;1
unionResult = sinter "user:101"  "user:102"  返回直接定集合的并集;1,2,3
diffResult = sinter "user:101"  "user:102"  返回直接定集合的差集;2,3

获取到两个用户相似的产品,然后确定相似产品的类目就可以进行用户分析。

类似的场景还有,社交场景下共同关注好友,相似兴趣tag 等场景的支持。

4、Hash类型使用场景

Redis在存储对象(例如,用户信息)的时候,需要对对象进行系列化转换然后进行存储。

还有一种形式,就是将对象数据转换为JSON结构数据,然后存储json字符串到Redis。

其实,对于一些对象类型,还有一种比较方便的类型,那就是按照Redis的Hash类型进行存储

例如,我们存储一些网站用户的基本信息,我们可以使用

hset key field value

hset user101 name "小明"
hset user101 sex "男"
hset user101 phone "123456"

这样我们就存储了一个用户的基本信息,存储信息有{name : 小明,sex : 男,phone : 123456}
当然这种类似场景还非常多,比如存储订单的数据,产品的数据,商家基本信息等。

5、Sorted Set 类型使用场景

Redis sorted set的使用场景与set类似,区别是set不是自动有序,而sorted set可以通过提供一个score参数来为存储数据排序,并且是自动排序,插入即有序。

业务中,如果需要一个有序且不重复的集合列表,就可以选择sorted set这种数据结构

比如,商品的购买热度可以将购买总量num当做商品列表的score,这样获取最热门的商品时,就可以自动按售卖总量排好序。

sorted set适合有排序需求的集合存储场景。

有序集合对象使用ziplist编码需要满足两个条件:一是所有元素长度小于64字节;二是元素个数小于128个;不满足任意一条件将使用skiplist编码。
以上两个条件可以在Redis配置文件中修改zset-max-ziplist-entries选项和zset-max-ziplist-value选项。ziplist编码结构如下

image.png

skiplist编码的有序集合对象底层实现是跳跃表和字典两种:
一个是 dict(字典),key是成员,value是分值,用于支持 O(1) 复杂度的按成员取分值操作
一个是 skiplist(跳跃表),按分值排序成员,用于支持平均复杂度为O(log N)的按分值定位成员的操作,以及范围操作;

备注:上面提到的ziplist quicklist skiplist数据结构详解见Redis(七):Redis底层数据类型

6、分布式环境下常见的应用场景之分布式锁

当多个进程不在同一个系统中时,用分布式锁控制多个进程对资源的操作或者访问。

分布式锁可以避免不同进程重复相同的工作,减少资源浪费。同时分布式锁可以避免破坏数据正确性的发生,例如多个进程对同一个订单操作,可以导致订单状态错误覆盖。。。

image.png

上面的这种业务场景,也可以使用分布式任务调度框架xxjob来实现。

分布式锁的实现要保证几个基本点:
1、互斥性:任意时刻,只有一个资源能够获取到锁
2、容灾性:能够在未成功释放锁的情况下,一定时限内能够恢复锁的正常功能
3、统一性:加锁和解锁保证同一资源来进行操作

image.png image.png

7、分布式环境下常见的应用场景之分布式自增id

随着用户以及交易量的增加,我们可能会针对用户数据,商品数据,以及订单数据进行分库发表的操作,这时候由于进行了分库分表的行为,所以mysql自增id的形式来唯一表示一行数据的方案不可行了,因此需要一个分布式id生成器,来提供唯一id的信息。

通常对于分布式自增id的实现方式有下面几种:
1、利用数据库自增id的属性
2、通过uuid来实现唯一id生产
3、Twitter的SnowFlake算法
4、利用Redis生成唯一id

我们使用redis的incr命令来实现唯一id,因为Redis是单进程单线程架构,不会因为多个取号方的incr命令导致取号重复,因此,基于Redis的incr命令实现序列号的生成基本能满足全局唯一与单调自增的特性

8、Redis发布订阅使用应用场景

Redis有一个发布订阅的通信方式,发送者publish发送消息,订阅者subscribe接收消息。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

image.png

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

image.png

监听

/**
 * 订阅者
 */
public class RedisSubTest {
    @Test
    public void subjava() {
        System.out.println("订阅者 ");
        Jedis jr = null;
        try {
            jr = new Jedis("127.0.0.1", 6379, 0);// redis服务地址和端口号
// redis发布订阅消息监听器 需要继承JedisPubSub;当然也可以像下面这种写法
JedisPubSub jedisPubSub = new JedisPubSub(){
            @Override  //收到消息会调用
            public void onMessage(String channel, String message) {      
                System.out.println(String.format("receive redis published message, channel %s, message %s", channel, message));
            }
            @Override //订阅了频道会调用
            public void onSubscribe(String channel, int subscribedChannels) {    
                System.out.println(String.format("subscribe redis channel success, channel %s, subscribedChannels %d",
                        channel, subscribedChannels));
            }
            @Override //取消订阅 会调用
            public void onUnsubscribe(String channel, int subscribedChannels) {   
                System.out.println(String.format("unsubscribe redis channel, channel %s, subscribedChannels %d",
                        channel, subscribedChannels));

            }
        };

            // jr客户端配置监听两个channel
            jr.subscribe(jedisPubSub, "news.share", "news.blog");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jr != null) {
                jr.disconnect();
            }
        }
    }



}
/**
 * 发布者
 */
public class RedisPubTest {
    @Test
    public void pubjava() {
        System.out.println("发布者 ");
        Jedis jr = null;
        try {
            jr = new Jedis("127.0.0.1", 6379, 0);// redis服务地址和端口号
            // jr客户端配置监听两个channel
            jr.publish( "news.share", "新闻分享");
            jr.publish( "news.blog", "新闻博客");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jr != null) {
                jr.disconnect();
            }
        }
    }
}
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

9、key设计原则

9.1 key名设计

ugc:video:1

用冒号作为分割是设计key的一种不成文的原则,遵循这种格式设计出的key在某些redis客户端下可以有效的识别

user:{uid}:friends:messages:{mid}
简化为
u:{uid}:fr:m:{mid}。

9.2 value设计

//1. Hash删除: hscan + hdel
public void delBigHash(String host, int port, String password, String bigHashKey) {
    Jedis jedis = new Jedis(host, port);
    if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
    ScanParams scanParams = new ScanParams().count(100);
    String cursor = "0";
    do {
        ScanResult<Entry<String, String>> scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
        List<Entry<String, String>> entryList = scanResult.getResult();
        if (entryList != null && !entryList.isEmpty()) {
            for (Entry<String, String> entry : entryList) {
                jedis.hdel(bigHashKey, entry.getKey());
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));
    
    //删除bigkey
    jedis.del(bigHashKey);
}


//2. List删除: ltrim
public void delBigList(String host, int port, String password, String bigListKey) {
    Jedis jedis = new Jedis(host, port);
    if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
    long llen = jedis.llen(bigListKey);
    int counter = 0;
    int left = 100;
    while (counter < llen) {
        //每次从左侧截掉100个
        jedis.ltrim(bigListKey, left, llen);
        counter += left;
    }
    //最终删除key
    jedis.del(bigListKey);
}


//3. Set删除: sscan + srem
public void delBigSet(String host, int port, String password, String bigSetKey) {
    Jedis jedis = new Jedis(host, port);
    if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
    ScanParams scanParams = new ScanParams().count(100);
    String cursor = "0";
    do {
        ScanResult<String> scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
        List<String> memberList = scanResult.getResult();
        if (memberList != null && !memberList.isEmpty()) {
            for (String member : memberList) {
                jedis.srem(bigSetKey, member);
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));
    
    //删除bigkey
    jedis.del(bigSetKey);
}


//4. SortedSet删除: zscan + zrem
public void delBigZset(String host, int port, String password, String bigZsetKey) {
    Jedis jedis = new Jedis(host, port);
    if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
    ScanParams scanParams = new ScanParams().count(100);
    String cursor = "0";
    do {
        ScanResult<Tuple> scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
        List<Tuple> tupleList = scanResult.getResult();
        if (tupleList != null && !tupleList.isEmpty()) {
            for (Tuple tuple : tupleList) {
                jedis.zrem(bigZsetKey, tuple.getElement());
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));
    
    //删除bigkey
    jedis.del(bigZsetKey);
}
//反例:
set user:1:name tom
set user:1:age 19
set user:1:favor football


//正例:
hmset user:1 name tom age 19 favor football
上一篇 下一篇

猜你喜欢

热点阅读