Redis核心数据结构及其应用场景

Redis核心数据结构及其应用场景

首先附上redis官方文档: link.


String的应用场景

	redis缓存String信息可分为以下几点:
		1.) 单值缓存 
		  - SET  key value //键值对
		2.) 批量缓存
		  - MSET  key  value [key value ...]
		3.) 对象缓存   {"name":"zhangsan","height":"180cm"}
		  - SET  user:Id value(json)  
		  - MSET  user:Id:name  zhangsan user:Id:height 180cm
		  - MGET  user:Id:name user:Id:height  //获取对象信息
		4.) 分布式锁
		  - SETNX  biz:Id lock // 返回1 代表成功,只有第一次对key设值才会成功


5.) 计数器
点赞

使用redis的计数器实现点赞功能:
	INCR pyq:likeCount:{msgid}  	
	取消手滑点赞:
	DECR  pyq:likeCount:{msgid}  
2:0>INCR pyq:likeCount:111
"2"

2:0>INCR pyq:likeCount:111
"3"

2:0>INCR pyq:likeCount:111
"4"

2:0>DE2:0>CR pyq:likeCount:111
"3"

6.) 分布式序列号

	假设此时我们的数据库已经分表,此时再使用数据库的自增Id显然不对。在不考虑uuid,且主键类型为数字的情况
	下,假设不使用雪花算法。
	INCRBY  fbs_Id  1000	 		// 一次拉取一千个id,返回自增结果
	各个系统拉取一千个Id供本地使用 可以有效解决分布式情况下的Id自增

HashMap的应用场景

HSET  key  field  value 			//存储一个哈希表key的键值
HSETNX  key  field  value 		//存储一个不存在的哈希表key的键值
HMSET  key  field  value [field value ...] 	//在一个哈希表key中存储多个键值对
HGET  key  field 				//获取哈希表key对应的field键值
HMGET  key  field  [field ...] 		//批量获取哈希表key中多个field键值
HDEL  key  field  [field ...] 		//删除哈希表key中的field键值
HLEN  key				//返回哈希表key中field的数量
HGETALL  key				//返回哈希表key中所有的键值

在这里插入图片描述
以购物车为例,使用hashSet实现这一功能。

1)以用户id为key
2)商品id为field
3)商品数量为value

hset cart:1001 10088 1 // 将id为10088的商品添加至user(1001)的购物车中
hincrby cart:1001 10088 1 // user(1001)还想再买一个
hdel cart:1001 10088 // 1001删除了该商品
hlen cart:1001 //获取商品总数
hgetall cart:1001 // 全选 咬咬牙买了

List的应用场景

LPUSH  key  value [value ...] 		//将一个或多个值value插入到key列表的表头(最左边)
RPUSH  key  value [value ...]	 	//将一个或多个值value插入到key列表的表尾(最右边)
LPOP  key			//移除并返回key列表的头元素
RPOP  key			//移除并返回key列表的尾元素
BLPOP  key  [key ...]  timeout  // 从左侧弹出一个元素,如果size==0 等待timeout秒。
如果timeout==0 	一直等待


常用数据结构(分布式)
Stack(栈) = LPUSH + LPOP
Queue(队列)= LPUSH + RPOP
Blocking MQ(阻塞队列)= LPUSH + BRPOP

爬虫分布式使用redis队列案例:
分布式抓取1688店铺数据:
1688中词大概有接近5w个,每个词需要爬取三页,每一页需要分批请求三次。
这种场景下,单线程是真的不够看的,在多线程的情况下,考虑词是趋于无限增大的情况下,抓取时间也是相应增大。
在这种情况下,考虑服务器横向扩容。n个服务器从redis队列中pop数据,保证各个服务器抓取词不重复。这边还涉及
redis发布/订阅功能。所有服务器统一订阅一个"频道",同一时间爬取。
  @Override
    public void run() {

        while (!toStop) {
            try {
                running = false;

                spider.tryFinish(); // 如果队列==0 尝试终止

                final String url = spider.getRunData().getUrl(); // 分布式队列 pop
                if (url == null) continue;
                running = true;

                final RunConfig runConfig = spider.getRunConfig();

                final PageRequest pageRequest = makePageRequest(url);

                IpPool.Ip ip = null;
                if (runConfig.isProxy()) {
                    // ip 代理
                    ip = IpPool.proxyIp();  
                }
                    spider.getExecutorBiz().run();

                } catch (Throwable e) {
                    // 失败放回队列中 
                    // 其他的业务逻辑

            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }
 private ExecutorService crawlers = Executors.newCachedThreadPool();             // 爬虫线程池
 private List<SpiderExecutorThread> crawlerThreads = new CopyOnWriteArrayList<SpiderExecutorThread>();     // 爬虫线程引用镜像



    /**
     * 尝试终止
     */
    public  void tryFinish() {
        boolean isRunning = false;
        for (SpiderExecutorThread crawlerThread : crawlerThreads) {
            if (crawlerThread.isRunning()) {
                isRunning = true;
                break;
            }
        }
        synchronized (object){
            boolean isEnd = runData.getUrlNum() == 0 && !isRunning;
            if (isEnd) {
                SpiderConfig.getSpiderConfig().setRunning(false);
                stop();
            }

        }
    }



    /**
     * 终止
     */
    public void stop() {
        for (SpiderExecutorThread crawlerThread : crawlerThreads) {
            crawlerThread.toStop();
        }
        crawlers.shutdownNow();
        log.info("spider finish....");
    }
@Component
public class Redis1688RunData extends RunData {

    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 新增一个待采集的URL,接口需要做URL去重,爬虫线程将会获取到并进行处理;
     *
     * @param link
     */
    @Override
    public boolean addUrl(String link) {
        redisTemplate.opsForList().leftPush("unCrawlKey", link);
        return true;
    }

    /**
     * 获取一个待采集的URL,并且将它从"待采集URL池"中移除
     */
    @Override
    public String getUrl() {
        String link = (String) redisTemplate.opsForList().rightPop("unCrawlKey");
        return link;
    }

    /**
     * 获取待采集URL数量;
     */
    @Override
    public int getUrlNum() {
        Long unVisitedUrl = redisTemplate.opsForList().size("unCrawlKey");
        return Math.toIntExact(unVisitedUrl);
    }
}

Set常用操作

SADD  key  member  [member ...]			//往集合key中存入元素,元素存在则忽略,	若key不存在则新建
SREM  key  member  [member ...]			//从集合key中删除元素
SMEMBERS  key					//获取集合key中所有元素
SCARD  key					//获取集合key的元素个数
SISMEMBER  key  member			//判断member元素是否存在于集合key中
SRANDMEMBER  key  [count]			//从集合key中选出count个元素,元素不从key中删除
SPOP  key  [count]				//从集合key中选出count个元素,元素从key中删除

Set运算操作

SINTER  key  [key ...] 				//交集运算
SINTERSTORE  destination  key  [key ..]		//将交集结果存入新集合destination中
SUNION  key  [key ..] 				//并集运算
SUNIONSTORE  destination  key  [key ...]		//将并集结果存入新集合destination中
SDIFF  key  [key ...] 				//差集运算
SDIFFSTORE  destination  key  [key ...]		//将差集结果存入新集合destination中

使用场景 店铺案例:
在这里插入图片描述

		以店铺案例为例,一个客户详情页需要各种json大对象信息组合而成,使用mysql进行缓存持久化,而且需要支持
		各种搜索。搜索本客户自带属性或是不包含json对象内属性的搜索,mysql是非常简单可以做到的。表结构如下。
		为了考虑索引失效的情况下,拉出大数据,单独建表。


客户表—建索引 在这里插入图片描述
详情页键入客户id
在这里插入图片描述
此时可以发现这边搜索用mysql索引是十分方便的,除了第一个宝贝关键词搜索。
宝贝关键词需要解释一下,他是拉取detail中的keyword_rank中json的1688关键词作为搜索。
一个客户可以有多个关键词,一个关键词也可以对应多个客户。典型的多对多结构。
如果使用mysql搜索,需要建一张第三方表,关键词对照表。为了这一个关键词改变表结构,
实在是不值得。redis set 可以十分简单的解决这一难题。

	将关键词排名信息缓存到mysql时:
		1.)  SADD  sell_keyword:word  companyId		//key 关键词 value Set<Id>
		2.)  SADD  SELL_KEY_SET   关键词		// 为了做关键词模糊搜索
// 模糊搜索 所有相关的word
 final Set<String> keywordScanSet = keywordScan(keyword.trim(), 1000);
 Set<String> unionSet = new HashSet<>();
 keywordScanSet.forEach(c -> unionSet.add(SELL_KEY.concat(c)));
 final Set<String> idSet = redisTemplate.opsForSet().union("", unionSet);
 // 取其并集 获取到Set<id> 使用sql in id 
 if (!idSet.isEmpty()) {
     CriteriaBuilder.In<Long> in = cb.in(root.get("id"));
     idSet.forEach(c -> in.value(Long.valueOf(c)));
     predicates.add(in);
 } else {
     predicates.add(cb.equal(root.get("id").as(Long.class), -1L));
 }
@Override
   public Set<String> keywordScan(String keyword, int count) {
       if (StringUtils.isBlank(keyword)) return new HashSet<>();
       final ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions();
       scanOptionsBuilder.count(1000);
       scanOptionsBuilder.match("*" + keyword + "*");
       final Cursor sell_keyword_set = redisTemplate.opsForSet().scan("sell_keyword_set", scanOptionsBuilder.build());
       final Set<String> keywordSet = new HashSet<>();
       while (sell_keyword_set.hasNext()) {
           keywordSet.add(sell_keyword_set.next().toString());
           if (keywordSet.size() == count) return keywordSet;
       }
       return keywordSet;
   }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值