【Redis】Redis中的五种数据结构及位图、HyperLogLog、GEO等

Redis

redis API

通用命令

keys

​ 计算数据库所有的键 时间复杂度O(n)【一般在生产环境中使用】

>  keys *  #遍历所有key
>
>  ```mysql
>  127.0.0.1:6379 > set hello word
>  127.0.0.1:6379 > set java good
>  127.0.0.1:6379 > set php best
>  127.0.0.1:6379 > keys *
>  1)"java"
>  2)"hello"
>  3)"php"
>  127.0.0.1:6379 > dbsize
>  <Integer> 3
>  ```
>
>  keys [pattern]  #遍历所有key
>
>  ```mysql
>  127.0.0.1:6379 > mset hello world hehe haha php good phe his
>  127.0.0.1:6379 > keys he*
>  1)"hehe"
>  2)"hello"
>  127.0.0.1:6379 > keys he[h-l]*
>  1)"hehe"
>  2)"hello"
>  127.0.0.1:6379 > keys ph?
>  1)"phe"
>  2)"php"
>  ```
>
>  

应用:1. 热备从节点 2. scan

dbsize

​ 算出数据库的大小,10个key,大小就是10

dbsize #计算key的总数

127.0.0.1:6379 > mset k1 v1 k2 v2 k3 v3 k4 v4
127.0.0.1:6379 > dbsize
(integer) 4
127.0.0.1:6379 > sadd myset a b c d e
(integer) 5
127.0.0.1:6379 > dbsize
(integer) 5

exists key

​ 判断key是否存在

exits key #检查key是否存在 ——存在则返回1,不存在则返回0

127.0.0.1:6379 > set a b
127.0.0.1:6379 > exists a
(integer) 1
127.0.0.1:6379 > del a
(integer) 1
127.0.0.1:6379 > exists a
(integer) 0

del key [key …]**

​ 删除key,可以删除多个key

del key #删除指定key-value ——删除成功则返回1,key不存在则返回0

127.0.0.1:6379 > set a b
127.0.0.1:6379 >  get a
"b"
127.0.0.1:6379 > del a
(integer) 1
127.0.0.1:6379 > get a
(nil)

expire key seconds

​ 设置key的过期时间,在一段时间内自动删除

expire key seconds #key在seconds秒后过期

ttl key # 查看key剩余的过期时间

persist key # 去掉 key的过期时间

127.0.0.1:6379 > set hello world
127.0.0.1:6379 > expire hello 20 
(integer) 1
127.0.0.1:6379 > ttl hello
(integer) 16
127.0.0.1:6379 > get hello
"world"
127.0.0.1:6379 > ttl hello
(integer) 7
127.0.0.1:6379 > ttl hello
(integer) -2 (-2代表key已经不存在了)
127.0.0.1:6379 > get hello
(nil)
127.0.0.1:6379 > set hello world
127.0.0.1:6379 > expire hello 20
(integer) 1
127.0.0.1:6379 > ttl hello
(integer) 16(还有16秒过期)
127.0.0.1:6379 > persist hello
(integer) 1
127.0.0.1:6379 > ttl hello
(integer) -1 (-1代表key存在,并没有过期时间)
127.0.0.1:6379 > get hello
"world"

type key

​ 查看key的数据类型

type key # 返回key的类型——string、hash、list、set、zset、none

127.0.0.1:6379 > set a b
127.0.0.1:6379 >  type a
string
127.0.0.1:6379 > sadd myset 1 2 3
(integer) 3
127.0.0.1:6379 > type myset
set
命令时间复杂度
keysO(n)
dbsizeO(1)
delO(1)
existsO(1)
expireO(1)
typeO(1)

数据结构和内部编码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LWbVFAej-1618412848334)(D:\chencan\img\redis\redis_10.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iVx8PLgQ-1618412848339)(D:\chencan\img\redis\redis_11.png)]

单线程架构

redis在一瞬间只会执行一条命令

  • 为什么redis使用的是单线程还这么快呢?

    1. 纯内存

      内存的响应速度是很快的,比硬盘运行速度快很多

    2. 非阻塞IO

      将连接,读写,关闭转换为自身的一个事件,不在IO上浪费过多的时间

    3. 避免线程切换和竞争状态的消耗

    多线程需要线程切换,但是单线程不需要涉及线程切换和竞争状态,避免了消耗

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5Os57bd-1618412848341)(D:\chencan\img\redis\redis_19.png)]

    1. redis 服务器启动时,会把 AE_READABLE 向 eventLoop(IO 多路复用)注册。

    2. 客户端请求与服务器建立连接,服务器生成 Scoket(s1)通道并绑定 AE_READABLE 事件。

    3. 客户端(s1)请求执行 set key value(写命令),s1 触发 AE_READABLE 由命令请求器将 key 和 value 读取到内存并对数据修改

    4. 设值结束后将 s1 与 AE_WRITABLE 事件关联绑定,从而触发写操作,成功后由命令回复器输出结果“OK”

    5. 返回结果将 s1 的 AE_WRITABLE 事件与命令回复处理器解除绑定。

  • 使用单线程要注意什么?

    1. 一次只运行一条命令

    2. 拒绝长(慢)命令

      keys,flushall,flushdb,slow lua script,mutil/exec,operate big value(collection)

    3. 其实不是单线程

      fysnc file descriptor

      close file descriptor

redis 数据结构

string

redis所有的key都是一个字符串,字符串的上限是512M,建议几百K

命令:

setnx 分布式锁

  1. get set del # 获取键 设置键 删除键

incr key # key自增1,如果key不存在,自增后get(key) = 1

decr key # key自减1,如果key不存在,自减后get(key)= -1

incrby key k # key自增k,如果key不存在,自增后get(key) = k

decrby key k # key自减k,如果key不存在,自减后get(key) = -k

  1. set setnx setxx

## ex seconds:为键设置秒级过期时间。px milliseconds:为键设置毫秒级过期时间。 ## nx:键必须不存在,才可以设置成功,用于添加。xx:与nx相反,键必须存在,才可以设置成功,用于更新。

et key value [ex seconds] [px milliseconds] [nx|xx]

set key value # 不管key是否存在,都设置

setnx key value # key不存在,才设置

set key value xx # key存在,才设置

  1. mget mset

    n次get = n次网络时间 + n次命令时间

    1次mget = 1次网络时间 + n次命令时间

  2. getset append strlen

  3. incrbyfloat getrange setrange

    decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)、incrbyfloat(自增浮点数)

应用:

  1. 缓存

  2. 计数器

  3. 分布式锁

举例:

  1. 记录网站每个用户个人主页的访问量?

    incr key value

    | | |

    incr userid:pageview count (单线程:无竞争)

    因为是天生单线程的,在并发执行incr的时候,不会有竞争问题,在执行计数的时候每个key的自增都是独立去执行的,不会记错数

  2. 缓存视频的基本信息(数据源在MySQL)中,伪代码

    解释:很多网站的基本信息存储在mysql中,但是为了提高接口的访问性能或者说是并发量,会把视频的基本信息存在redis中

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhkAnVYm-1618412848344)(D:\chencan\img\redis\redis_13.png)]

    实现思路:

    1. 通过视频的vid,获取视频的信息

    2. 从redis来获取

    3. 如果redis中存在,说明redis缓存中有这样的信息,不需要从后端的数据库中查,减小后端数据库的访问压力,则返回给app server;如果不存在,则通过redis从服务器中查找数据

    4. 查找到数据,将数据写入到redis中,方便下次查找

    5. 将查找的数据返回给app server

      伪代码实现:

public VedioInfo get(long id){
	String redisKey = redisPrefix + id; //可能是id + vedio的前缀
	VideoInfo videoInfo = redis.get(redisKey); 
	if(videoInfo == null){
		videoInfo = mydql.get(id);//从数据库中取
		if(videoInfo != null){ //如果数据库中取到的值不是空
			//序列化
			redis.set(redisKey,serialize(videoInfo)); //序列化 
		}
	}
	return videoInfo;
}
  1. 实现分布式id生成器

    incr id

hash

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXUqKi2q-1618412848345)(D:\chencan\img\redis\redis_14.png)]

特点:map-map、small redis、field不能相同,value可以相同

命令:

hget key field # 获取hash key对应的field的value

hsetkey field # 设置hash key对应的field的value

hdel key field # 删除hash key对应的field的value

hexists key field # 判断hash key是否有field

hlen key # 获取hash key field的数量

hmget hmset

举例:

  1. 记录网站每个用户个人主页的访问量?

    hincrby key field value

    hincrby user:1:info pageview count ——相当于给key:user:1:info添加了一个属性,pageview count进行每个用户个人主页的访问量

  2. 缓存视频的基本信息(数据源在MySQL)中,伪代码

public VedioInfo get(long id){
	String redisKey = redisPrefix + id; //可能是id + vedio的前缀
	Map<String,String> hashMap = redis.hgetAll(redisKey);
    VideoInfo videoInfo = transferMapToVideo(hashMap);
	if(videoInfo == null){
		videoInfo = mydql.get(id);//从数据库中取
		if(videoInfo != null){ //如果数据库中取到的值不是空
			redis.hmset(redisKey,transferMapToVideo(videoInfo)); 
		}
	}
	return videoInfo;
}

list

key elements

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eEGzddDn-1618412848347)(D:\chencan\img\redis\redis_list.png)]

左边是key,右边是value(这里的value是一个有序的队列)

特点:有序、可以重复、左右两边插入弹出

一个列表最多可以存储 2 的 32 次方减 1 个元素,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素

命令:

插入:rpush、lpushlinsert

删除:lpop、rpop、lrem、ltrim

查:lrange、lindex、llen

改:lset、

blpop brpop

lpush + lpop = stack

lpush + rpop = queue

lpush + ltrim = capped collection

lpush + brpop = message queue

举例:

​ 微博的TimeLine

​ 关注用户的时间轴来排序,一定范围内来按照每十页进行分页lrange

​ 你关注的人更新微博,lpush,把更新的微博放到队头来显示

​ 最新列表,List 类型的 lpush 命令和 lrange 命令能实现最新列表的功能,每次通过 lpush 命令往列表里插入新的元素,然后通过 lrange 命令读取最新的元素列表,如朋友圈的点赞列表、评论列表。

​ 排行榜, List 类型的 lrange 命令可以分页查看队列中的数据, 但是只有定时计算的排行榜才适合使用 list 类型存储(实时不行)。

set

集合:集合不允许添加重复元素——无序、无重复、集合间操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UA1VkOy9-1618412848348)(D:\chencan\img\redis\redis_set.png)]

sinter:取出两个集合中相同的元素

sdiff:取出与另一个集合不同的元素

sunion:把两个集合的∪并集收集起来

sdiff | sinter | suion + store destkey …将差集、交集、并集结果保存在destkey中

sadd 添加元素

srem 删除元素

scard 算集合中元素的个数

sismember 元素是否在集合中

srandmember 从集合中随机取出一个元素——不破坏集合的元素

smembers 取出集合中所有的元素

spop 从集合中随机弹出一个元素——弹出之后就没有了

举例:

​ 抽奖系统:用spop进行弹出,因为用户量非常大,如果用srandmember会阻塞队列

​ like、赞、踩:存在集合中

​ 标签(tag):(下面两点是同个事务)

  1. 用集合给用户添加标签

    sadd user:1:tags tag1 tag2 tag5

    sadd user:2:tags tag2 tag3 tag5

    sadd user:k:tags tag1 tag2 tag4

  2. 给标签添加用户

    sadd tag1:users user:1 user:3

    sadd tag2:users user:1 user:2 user:3

    sadd tagk:users user:1 user:2

​ 共同关注:共同关注的好友、共同关注的兴趣都可以用集合来实现

​ 黑白名单,有业务出于安全性方面的考虑,需要设置用户黑名单、ip黑名单、设备黑名单等,set类型适合存储这些黑名单数据,sismember命令可用于判断用户、ip、设备是否处于黑名单之中。

​ TIPS:SADD 命令做一些标签的使用;spop / srandmember 命令做一些随机数的使用;sadd + sinter 命令做一些社会关系图,如共同关注的好友

zset

有序集合,其实"值”中存储的是一个集合,但是集合中每个元素是有排序的,score - value 通过分数(优先级)进行排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EuE8rDty-1618412848349)(D:\chencan\img\redis\redis_zset.png)]

集合 VS 有序集合

集合有序集合
无重复元素无重复元素
无序有序
elementelement + score

有序集合 VS 列表

有序集合列表
无重复元素可以有重复元素
有序有序
element + scoreelement

命令:

zadd 添加操作 # zadd key score element (分数可以重复,但是element不可以重复,即班级人员的不可重复,但是考试成绩的分数可以重复)

zrem 删除元素 zrem key element

zscore 返回元素的分数 zscore key element

zincrby 增加或减少元素的分数 zincrby key increScore element # zincrby user:1:ranking 9 mike(给mike的分数增加9分)

zcard 返回集合中元素的个数

zrank 获取某个元素的排名,从小到大的排名

zrange 返回指定索引范围内的升序元素[分值] zrange key start end [分值] O(log(n) + m)

zrangebyscore 返回按照分数范围内的升序元素

zcount 返回有序集合内在指定分数范围内的个数

zremrangebyrank 删除指定排名内的升序元素

zremrangebyscore 删除指定分数内的升序元素

举例:

​ 标签:比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并。

​ 共同好友功能,共同喜好,或者可以引申到二度好友之类的扩展应用。

​ 统计网站的独立 IP。利用 set 集合当中元素不唯一性,可以快速实时统计访问网站的独立 IP。

​ 统计用户的点赞/取消点赞

​ 排行榜功能,比如展示获取赞数最多的十个用户

操作类型命令
基本操作zadd、zrem、zcard、zincrby、zscore
范围操作zrange、zrangebyscore、zcount、zremrangebyrank
集合操作zunionstore、zinterstore

位图

对redis来说,可以直接去操作数据的位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AzZHLTz8-1618412848350)(D:\chencan\img\redis\bitmap.png)]

set hello big

setbit key offset value # 给位图指定索引设置值

gitbit key offset # 获取位图指定索引的值 getbit hello 1 (获取hello键中的值big的位图的第二位)

bitcount key [start end] #获取位图指定范围(start到end,单位为字节,如果不指定就是获取全部)位值为1的个数

bitop op destkey key [key…] #做多个bitmap的and(交集)、or(并集)、not(非)、xor(异或),操作并将结果保存在destkey中

bitpos key targetBit [start] [end] #计算位图指定范围

举例:

  1. 独立用户统计:

    • 使用set和bitmap进行对比,当一个网站有1亿用户,并且有5千万用户每天在独立访问

      当用set进行操作是,需要存储的用户量是50,000,000,因为userid可能是长整型的,则占32位,则全部内存量为32位 * 50,000,000 = 200MB

      而,bitmap,位图每个userid占用1位,当需要存储的的用户量是100,000,000时,需要的全部内存量为1位 * 100,000,000 = 12.5MB

      占用内存对比:

    一天一个月一年
    set200M6G72G
    bitmap12.5M375M4.5G
    • 只有十万独立用户呢

      数据类型每个userid占用空间需要存储的用户量全部内存量
      set32位(假设userid用的是整型,很多网站用的是长整型)1,000,00032 * 1,000,000 = 4MB
      bitmap1位100,000,0001 *100,000,000 = 12.5M

HyperLogLog

HyperLogLog算法,是利用极小空间完成独立数量统计。本质结构还是字符串

三个命令:

  • pfadd key element [element] :向HyperLogLog添加元素

  • pfcount key [key …] 计算HyperLogLog的独立总数

  • pfmerge destkey sourcekey [sourcekey…] 合并多个HyperLogLog

    内存消耗(百万独立用户)???

GEO

地理信息定位:存储经纬度,计算两地距离,范围计算

redis一些知识

Jedis

  1. 生成一个Jedis对象,这个对象负责和指定redis节点通信

    Jedis jedis = new Jedis(“127.0.0.1”,6379);

  2. jedis执行set操作

    jedis.set(“hello”,“world”);

  3. jedis执行get操作,value = “world”

    String value = jedis.get(“hello”)

Jedis(String host,int port,int connectionTimeout,int soTimeout)

  • host :redis节点的所在的机器的IP

  • port :redis节点的端口

  • connectionTimeout:客户端连接超时

  • soTimeout:客户端读写超时

    jedis直连:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zeXTMKFk-1618412848351)(D:\chencan\img\redis\jedis.png)]

jedis连接池:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RrP27KWd-1618412848352)(D:\chencan\img\redis\jedisPool.png)]

优点缺点
直连- 简单方便
适用于少量长期连接的场景
- 存在每次新建/关闭TCP开销
资源无法控制,存在连接泄露的可能
Jedis对象线程不安全
连接池Jedis预先生成,降低开销使用
连接池的形式保护和控制资源的使用
相对于直连,使用相对麻烦,尤其在资源的管理上需要很多参数来保证,一旦规划不合理也会出现问题

慢查询

  1. 生命周期

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7skdY5Lv-1618412848353)(D:\chencan\img\redis\redis_16.png)]

    • 慢查询一般发生在第3阶段
    • 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素
  2. 两个配置

    slowlog-max-len:

    ​ 1. 先进先出队列

    ​ 2. 固定长度

    ​ 3. 保存在内存内

    slowlog-log-slower-than:

     1. 慢查询阈值(单位:微秒)
     2. slow-log-slower-than=0,记录所有命令
     3. slowlog-log-slower-than<0,不记录任何命令
    

    配置方法:

    1. 默认值

      config get slowlog-max-len = 128

      config get slowlog-log-slower-than = 10000

    2. 修改配置文件重启

    3. 动态配置

      config set slowlog-max-len 1000

      config set slowlog-log-slower-than 1000

  3. 三个命令

    慢查询命令

    ​ slowlog get [n] :获取慢查询队列

    ​ slowlog len:获取慢查询队列长度

    ​ slowlog reset :清空慢查询队列

  4. 运维经验

     1. slowlog-max-len 不要设置过大,默认10ms,通常设置1ms
     	2. slowlog-log-slower-than 不要设置过小,通常设置1000左右
         	3. 理解命令生命周期
             	4. 定期持久化慢查询
    

pipeline

它能将一组 Redis 命令进行组装,通过一次传输给 Redis 并返回结果集

  1. 什么流水线

    节省网络连接 时间的开销

    1 次pipeline(n条命令) = 1 次网络时间 + n 次命令时间

    命令n个命令操作1次pipeline(n个命令)
    时间n次网络+n次命令1次网络+n次命令
    数据量1条命令n条命令

    注意:

    1. redis的命令时间是微秒级别

    2. pipeline每次条数要控制(网络)

      流水线的作用

      ​ 当我们需要从北京发往上海的数据,光纤的速度是光速的2/3,则一次命令传输时间则大约是13毫秒,但是redis操作的时间可能是几微秒,但是如果不用流水线,每次操作命令都需要建立网络连接,则就影响用户的体验,如果可以使用一次连接,操作多条命令,则大大节省了网络的开销。

      应用场景Pipeline是 Redis 的一个提高吞吐量的机制,适用于多 key 读写场景,比如同时读取多个keyvalue,或者更新多个keyvalue,并且允许一定比例的写入失败实时性也没那么高,那么这种场景就可以使用了。比如 10000 条一下进入 redis,可能失败了 2 条无所谓,后期有补偿机制就行了,像短信群发这种场景,这时候用 pipeline 最好了。

      注意

      Pipeline是非原子的,在上面原理解析那里已经说了就是 Redis 实际上还是一条一条的执行的,而执行命令是需要排队执行的,所以就会出现原子性问题。

      Pipeline中包含的命令不要包含过多。

      Pipeline每次只能作用在一个 Redis 节点上。

      Pipeline 不支持事务,因为命令是一条一条执行的。

  2. 与原生M操作对比

    1. 原生批量命令是原子的,Pipeline 是非原子的。
    2. 原生批量命令是一个命令对应多个 key,Pipeline 支持多个命令。
    3. 原生批量命令是 Redis 服务端支持实现的,而 Pipeline 需要服务端和客户端的共同实现

    pipeline是将多条命令进行拆分,如有一万条命令,将其拆分成每100条进行发送,返回的结果是顺序的

    原生M操作,即mget,是原子性的操作

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5abUeFbi-1618412848355)(D:\chencan\img\redis\redis_18.png)]

  3. 客户端实现

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gqjDZG0n-1618412848356)(D:\chencan\img\redis\redis_pipeline_jedis.png)]

  4. 使用建议

    1. 每次pipeline携带数据量
    2. pipeline每次只能作用在一个redis节点上
    3. M操作和pipeline区别

发布订阅

  1. 角色

    发布者(publisher)

    频道(channel)

    订阅者(subscriber)

  2. 模型

    发布订阅模式:订阅者都可以收到

    redis进行消息的发布和订阅功能,在发送消息之前,但是订阅者还没有订阅,此时订阅者接收不到之前发布者发布的消息。

    发布者:

    publish channel message

    redis > publish sohu:tv “hello world”
    (integer) 3 #订阅者个数
    redis> publish sohu:auto "taxi'
    (integer)
    

    订阅者

    subscribe [channel] #一个或多个

    redis> subscribe sohu:tv
    1) "subscribe"
    2) "sohu:tv"
    3) (integer) 1
    

    取消订阅

    unsubscribe [channel] #一个或多个

消息队列模式:只要一个消息订阅者可以收到,是抢的模式,使用的是队列阻塞的形式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值