学习redis的开发与实践①

使用“万金油”string,数据量大,占用内存大

刚启动redis客户端,通过info memory 命令查看内存开销,仅为728416,711.34kb
在这里插入图片描述

做了测试案例,查询出100万条数据,将id与内容通过string,set到内存中,占用内存为:105036784,100Mb,100w条数据增加了100Mb。
如果有几亿条数据呢?那内存占用量是相当恐怖的。
在这里插入图片描述
这时因为,string的数据结构,包含了两块,一块是内容,一块是元数据。
所以通过string将会保存许多我们用不到的数据。若有100M,大约有几十兆都是元数据信息。

那我们可以通过存储hash来进行优化。
hash的数据结构是压缩列表与hash表。
要注意的是,hash在压缩列表中保存了两个阈值,一旦超过了阈值,hash就会使用哈希表来保存数据了。

两个阈值分别为:
hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。

设置方式示例:

127.0.0.1:6379> config set hash-max-ziplist-entries 1000
OK
127.0.0.1:6379> config set hash-max-ziplist-entries 10
OK

若压缩列表中有单个值大于hash-max-ziplist-value或者每个键的所有值的集合的长度大于hash-max-ziplist-entries,hash就会转为哈希表存储,之后存储都不会再转成压缩列表了。在节省内存空间上,哈希表时不如压缩列表的。

使用hash方式存储,内存节约了20M左右。
由于没有好的数据,没有将内存节省得更多。

在这里插入图片描述

Java代码如下:
使用的是Jpa加载Mysql数据库,Spring Boot管理配置。

package com.redisroot.redisdemo.service;

import com.redisroot.redisdemo.entity.ProductComment;
import com.redisroot.redisdemo.repository.ProductCommentRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;

@Service
@Slf4j
public class RedisService {
    @Autowired
    private ProductCommentRepository productCommentRepository;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    public void findDataLoadInRedisByString(){
        ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
        HashOperations<String, Object, Object> hashOperations = stringRedisTemplate.opsForHash();
        log.info("开始加载所有数据");
        List<ProductComment> comments =  productCommentRepository.findAll();//O(n)时间复杂度
        log.info("开始循环数据加载进redis");
        long time = System.currentTimeMillis();
        comments.forEach(productComment->{//O(n^m)时间复杂度
            String id = productComment.getId();
            String commentText = productComment.getCommentText();
            //判断id合并数据使用hash列表,节省内存资源
            //比如pc10000111
            //会拆分成pc10000,111
            //存储为pc10000,111,commentText
            //就说明每个key底下都只有最多10000条的数据
            //因为条数多,所以hash的底层数据结构就由压缩列表转成了哈希表
            //提升效率,但是节省内存上,不如压缩列表
            String ids = "pc";
            String suffix = "";
            for(int i=2;i<id.length();i++){
                char chars = id.charAt(i);
                String charStr = String.valueOf(chars);
                if(!"0".equals(charStr)&&!"1".equals(charStr)){
                    suffix = id.substring(i);
                    break;
                }else{
                    ids += chars;
                }
            }
            if(StringUtils.isEmpty(suffix)){
                suffix = "None";
            }
            hashOperations.put(ids,suffix,commentText);
	        //valueOperations.set(key,value);
        });
        log.info("redis数据加载完成,耗时:"+(System.currentTimeMillis()-time)/(1000*60));
    }

}

若数据量大,要如何统计keys?

在不同的业务场景我们可能需要保存一种信息:一个key对应一个集合。

  • 某APP统计每天的访问用户ID
  • 某个商品对应N条评论
  • 某APP统计用户每天打卡信息
  • 某网站统计用户的唯一访问

以上这种场景都有可能有很大的数据量的存储与统计。面对这种场景,我们就要选择合适的集合,我们就要了解常用的集合统计模式,包括了:聚合统计、排序统计、二值状态统计、基数统计。

聚合统计指的是多个集合元素的聚合统计。

返回多个集合的共有集合(交集),返回多个集合的全部集合(并集),返回多个集合的独有元素(差集)。
交集:统计多个集合的共有集合,可以使用以下命令

SINTERSTORE DESTINATION KEY1 KEY2 KEY3

并集:统计多个集合的全部集合,可以使用以下命令

SUNIONSTORE DESTINATION KEY1 KEY2 KEY3

差集:统计多个集合的独有集合,可以使用以下命令

SDIFFSTORE DESTINATION KEY1 KEY2 KEY3

以上的意思就是统计KEY1 KEY2 KEY3三个集合,并将三个集合的统计返回的集合放在DESTINATION中并返回,多DESTINATION存在则覆盖。

注意:这三个命令都是SET的使用方法

排序统计
商品评论表,可能会需要统计最新的评论。

在redis中,List是进行顺序排序统计,Sorted set是进行权重排序统计。

list是链表的数据结构,最新的数据始终是在链表头,最旧的数据始终都在链表尾。
也就是说,若我们在list存0-9
然后我们分3页取出

LRANGE key 0 2

返回的结果就是0 1 2
再第二次分页的时候,此时有新的数据A进来,此时list的数据为A 0 1 2 3 … 9

LRANGE key 3 5

那么此时查询出来的是2 3 4,就会发现之前已经查询的2又被查询出来了。
造成这个问题的原因就是因为list是通过位置来排序的,若有新数据进入,那么所有元素都会往后移动一位。
sorted set就不会存在这个问题。

ZADD key score member [score member]

我们可以将评论时间作为权重score,并按score排序。

ZRANGEBYSCORE KEY MIN MAX [WITHSCORES] [LIMIT OFFSET COUNT]

若你不知道集合的最大值与最小值,那么MIN和MAX就可以使用-INF、+INF。就可以获取到集合中所有的数据,而如加上WITHSCORES,就会在member之后显示score值。LIMIT是将返回的集合从offset开始,取count值。

若知道score的值,那么可以使用闭区间、开区间 来查询。
1 < score < 2

(1 ( 2

1< score <=2

(1 2

1<= score <2

1 (2

二值状态统计
这种统计方式常用的是,比如:用户签到(1|0),用户是否在线(1|0)等
我们只用1表示存在,0表示不存在。

每个用户每天签到使用一个bit位,一年签到也就使用了365个bit。
我们可以使用:

SETBIT key offset 1

比如用户1111在2020年10月11日签到,可以写为

SETBIT userid:1111:20201011 10 1

offset可以记为签到的时间的天,这样就可以比较准确的记录每个月的签到天数了
由于offset偏移量是从0开始的,所以11天就为10。

若想取得某个人某一天是否有签到记录就可以使用

GETBIT userid:1111:20201010 9

这就会取得userid为1111在10月10号的签到记录,若有记录则显示记录,若没有记录则显示0

若想要知道最近10天内连续签到的人员的话,可以使用BITOP,具体写法如下:

BITOP operaters result key key …

operaters可以为AND|OR|XOR|,意思是:逻辑与、逻辑或、逻辑异或
result 就是 将多个bit集合进行逻辑运算得出的结果通过result返回

使用”逻辑与“,逻辑与的意思就是若有两个集合对比
第一个集合为1 0 0 1
第二个集合为1 0 1 1
若不相等,就为0,若都为0也为0,相等就为1。
那么得出来的结果就为 1 0 0 1
例如:

setbit bits-1 0 1
setbit bits-1 3 1
setbit bits-2 0 1
setbit bits-2 2 1
setbit bits-2 3 1

按上面命令,可以得知bits-1为 1 0 0 1,因为第0位为1,第1位没有数据所以为0,第2位没有数据所以为0,第3位为1。bits-2位1 0 1 1

bitop AND result bits-1 bits-2

通过bitop逻辑与,可以得到result结果为1 0 0 1,那么使用bitcount result就能得到结果(2),这个结果就是10天的连续签到的人数。

基数统计
需要统计网站一天访问的人员的基数(不重复数)UV的业务场景。

以看到不重复数,我们就想到可以使用Set集合,Set集合默认支持去重。
我们只需要使用

SADD key member [member]…

无论member是否存在N个重复,在set中都只有一个数据。
但是若有非常大数据量的场景,需要统计千万级别的人数,大型电商网站,可能有千百个网页,每个网页都要有set支持,那么内存消耗是非常大的。
当然我们也可以使用hash

HADD page:uv userid 1

这样就算有用户重复访问了,也只是将userid的值赋值为1。但是也存在以上set存在的问题。

那么若要又快,有节省内存
就可以使用redis 2.8.9后新增的HyperLogLog一种统计基数的数据集合类型
每个HyperLogLog都之占用为12kb的内存,支持264个元素基数。
若有一亿个基数,那么为108/8/1024/1024,大概为12M,就算统计10天的数据,也只需120M,占用内存不大。

往集合内添加数据

PFADD key element [element]…

统计集合的个数

PFCOUNT key key[…]

也可以将多个HyperLogLog合并为一个HyperLogLog

PFMERGE targetkey sourcekey [sourcekey]

例如:

127.0.0.1:6379> PFADD KEY username1 username2 usernam1 username2 username3 username2 username3 username1 usernam4

将重复的数据,不重复的数据,加入多条,然后使用PFCOUNT统计

127.0.0.1:6379> PFCOUNT KEY
(integer) 5

显示为5条,可以去重。
因为HyperLogLog是基于概率统计的,所以会有一定的误差,标准误差大概为(0.81%)。若不允许有误差的话,还是使用set、hash吧。

在不同的场景,使用不同的数据集合,可能会更好、更快的提升效率。
空间效率、时间效率都可以得到提升。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值