Redis

1、Nosql概述

1.1、什么是Nosql

image-20200814121249432

NoSQL = Not Only SQL(不只是SQL)

NoSQL泛指非关系型的数据库

关系型数据库:表格、行、列

随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。

NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

很多数据类型用户的个人信息,社区网络,地理位置。这些数据的存储不需要一个固定的格式!不需要过多的操作就可以横向扩展。

NoSQL特点

  1. 方便扩展(数据之间没有关系,很好扩展)

  2. 大数据量高性能(Redis一秒写8万次,读取11万,NoSQL的缓存记录,是一种细粒度的缓存,性能会比较高)

  3. 数据类型是多样型的(不需要事先设计数据库)

  4. 传统RDBMS 和 NoSQL

    传统的RDBMS
    - 结构化组织
    - SQL
    - 数据和关系都存在单独的表中
    - 严格的事务
    - ……
    Nosql
    - 不仅仅是数据
    - 没有固定的查询语言
    - 键值对存储,列存储,文档存储,图形数据库
    - 最终一致性
    - CAP定理和BASE
    - 高性能,高可用,高可扩
    - ……

了解: 3V + 3高

大数据时代的3V:主要描述的问题

  1. 海量Velume
  2. 多样Variety
  3. 实时Velocity

大数据时代的3高:主要是对程序的要求

  1. 高并发
  2. 高可扩
  3. 高性能

真正在公司的实践:NoSQL + RDBMS 一起使用

1.2、为什么需要Nosql

单机MySQL时代

在单机MySQL时代(90年代)中,一个基本的网站访问量不会太大,单个数据库完全足够,那时候更多的是使用静态网页HTML,服务器没有那么大的压力!

而随着网站的发展出现了以下问题:

  1. 数据量太大了,一个机器放不下
  2. 数据的索引(B + Tree),一个机器内存也放不下
  3. 访问量(读写混合),一个服务器承受不了

只要一个网站出现以上的三种情况之一的时候,就需要对网站进行升级!

Memcached(缓存) + MySQL + 垂直拆分(读写分离)

Memcached:是一套分布式高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,但被许多网站使用。这是一套开放源代码软件,以BSD license授权发布。

因为网站80%的情况都是在读,每次都要查询数据库的话就十分麻烦,并占用过多资源!所以可以使用缓存来提高效率!

image-20200814122038262

分库分表 + 水平拆分 + MySQL 集群

image-20200814125149609

如今情况

MySQL等关系型数据库就不够用了!数据量很多,变化很快!

image-20200814132519056

用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发增长!

这时候我们就需要使用NoSQL数据库,NoSQL可以很多的处理以上问题!

1.3、NoSQL的四大分类

KV键值对

  • Redis

文档型数据库(bson格式和json一样)

  • MongoDB
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • MongoDB是一个介于关系型数据库和非关系型数据库的中间产品,最像关系型数据库
  • CouchDB

列存储数据库

  • HBase
  • 分布式文件系统

图形关系数据库

image-20200826213411681

  • 不是存图形的,是存放关系的,例如:朋友圈社交网络,广告推荐!
  • Neo4j,InfoGrid

2、Redis入门

2.1、简介

Redis官网:https://redis.io/

Redis中文网:http://www.redis.cn/

​ Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

image-20200826214821249

​ Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。

2.2、Windows下安装

  1. 下载安装包:https://github.com/dmajkic/redis

  2. 下载完毕得到压缩包

    image-20200826215231236

  3. 解压到电脑上的目录下即可

    image-20200826215432790

  4. 开启Redis,双击redis-server.exe运行服务即可

    image-20200826215552558

    默认端口为:6379

  5. 使用redis客户端来连接,打开redis-cli.exe,必须先开着服务端

    image-20200826215708869

  6. 测试使用

    image-20200826215926421

在Windows下使用比较简单,但Redis推荐我们使用Linux

image-20200826220209011

2.3、Linux下安装

  1. 下载安装包:https://redis.io/

    image-20200826220454502

  2. 上传到Linux服务器上的/opt目录下

  3. 进行解压

    tar -zxvf redis-6.0.6.tar.gz

    image-20200826221422703

  4. 进入解压后的文件,可以看到redis的配置文件

    image-20200826221559966

  5. 基本的环境安装

    yum install gcc-c++

    image-20200826221802275

    make    # 安装配置环境,需要一点时间

    如果报了如下错误

    image-20200826222314336

    参考网站:https://www.freesion.com/article/8060652360/

    #查看gcc的版本是否在 5.3以上,centos7默认是4.8.5.我这里的就是4.8.5
    gcc -v
    
    #升级到 5.3及以上版本
    yum -y install centos-release-scl
    yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
    
    scl enable devtoolset-9 bash
    
    #scl命令启用只是临时的,推出xshell或者重启就会恢复到原来的gcc版本。如果要长期生效的话,执行如下
    echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
    
    # 重新安装环境配置,进入redis目录下
    make

    image-20200826223145364

    make install

    image-20200826223204836

  6. redis默认安装路径usr/local/bin

    cd /usr/local/bin

    image-20200826223446206

  7. 创建文件夹,将redis配置文件复制到目录下

    mkdir myconfig
    
    cp /opt/redis-6.0.6/redis.conf myconfig

    image-20200826223820154

  8. 修改配置文件

    默认不是后台启动的,我们需要修改配置文件

    vim redis.conf 

    找到daemonize,然后按insert键,将no改为yes(最后按Esc : wq保存退出)

    image-20200826224217895

  9. 启动服务

    redis-server myconfig/redis.conf

    image-20200826224633203

  10. 测试连接

    # 连接本机的指定端口号
    redis-cli -p 6379
    
    # 获取所有键
    keys *

    image-20200826225007772

  11. 查看redis进程是否开启

    ps -ef|grep redis

    image-20200826225222539

  12. 如何关闭Redis服务shutdown

    # 关闭redis
    shutdown
    # 退出
    exit

    image-20200826225414446

    再次查看进程

    image-20200826225444079

2.4、测试性能

redis-benchmark是一个压力测试工具,官方自带的性能测试工具!

redis 性能测试工具可选参数如下所示:

序号 选项 描述 默认值
1 -h 指定服务器主机名 127.0.0.1
2 -p 指定服务器端口 6379
3 -s 指定服务器 socket
4 -c 指定并发连接数 50
5 -n 指定请求数 10000
6 -d 以字节的形式指定 SET/GET 值的数据大小 3
7 -k 1=keep alive 0=reconnect 1
8 -r SET/GET/INCR 使用随机 key, SADD 使用随机值
9 -P 通过管道传输 请求 1
10 -q 强制退出 redis。仅显示 query/sec 值
11 –csv 以 CSV 格式输出
12 -l 生成循环,永久执行测试
13 -t 仅运行以逗号分隔的测试命令列表。
14 -I Idle 模式。仅打开 N 个 idle 连接并等待。

测试

# 开启redis
redis-server myconfig/redis.conf

# 测试100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

image-20200826231111771

每毫秒处理45578.85个请求

2.5、基础知识

redis默认有16个数据库

image-20200826231424672

默认使用的是第0个,可以使用select进行切换

# 选择第三个数据库
SELECT 3

# 查看数据大小
DBSIZE

# 判断是否存在
EXISTS name

# 查看所有的key
keys *

# 设置值
set name xiaojiang

# 获取值
get name

# 移除值,1代表从当前数据库移除
move name 1

# 设置10秒后过期,设置单点登录时间
EXPIRE name 10

# 查看剩余时间
ttl name

# 查看数据类型
type name

image-20200826232006738

不同数据库的内容不会相互影响

清空当前数据库

FLUSHDB

image-20200826232451733

清空所有数据库

FLUSHALL

Redis 是单线程的

Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽。

Redis为什么单线程还这么快?

  • 多线程(单CPU上下文进行切换)不一定比单线程效率高!

  • redis是将所有的数据全部放在内存中的,所以使用单线程去操作效率就是最高的,多线程(CPU上下文切换:耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的。多次读写都是在一个CPU上,在内存情况下,这就是最佳的方案!

3、五大数据类型

Redis可用的类型如下

image-20200827130209111

翻译:

​ Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库缓存消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

可以查看Redis命令介绍:http://www.redis.cn/

image-20200827175006013

3.1、String(字符串)

90% 的java程序员使用redis只会使用String类型

设置值

# 设置值
set name xiaojiang

# 获取值
get name

# 查看所有key
keys *

# 追加字符串,如果name不存在,那会自动创建
APPEND name "hello"

# 获取字符串长度
STRLEN name

自动的浏览量incr

# 设置自动的浏览量
set views 0

# 加一,会输出1
incr views

# 减一
decr views

# 设置步长,每次加10
INCRBY views 10

# 设置步长,每次减10
DECRBY views 10

获取字范围字符串GETRANGE

# 获取字范围字符串GETRANGE
# 设置长字符串
set name "hello, xiaojiang"

# 截取从0到3的字符[0,3]
GETRANGE name 0 3

# 截取全部字符,和get key一样
GETRANGE name 0 -1

替换指定位置字符SETRANGE

# 替换指定位置字符SETRANGE

# 设置长字符串
set name "hello, xiaojiang"

# 将第一个字符替换为XX
SETRANGE name 0 XX

设置过期时间setex 和 条件设置setnx

# setex(set with expire)    # 设置过期时间
# setnx(set if not exist)    # 如果不存在就设置返回1,存在就不会返回0,从而不会覆盖原来的值

# 设置30秒后过期
setex name 30 "xiaojiang"

# 查看剩余时间
ttl name

# 设置值,如果key2不存在就设置成功,返回1
setnx key2 "hello"

批量设置mset和获取mget

# 批量设置值,这样就设置了k1, k2, k3的值
mset k1 v1 k2 v2 k3 v3 

# 批量获取
mget k1 k2 k3

# 设置msetnx,如果有一个已经存在就会都创建失败
msetnx k1 v1 k4 v4 

设置对象

# 设置user:1对象,1为id,值为json字符串来保存一个对象!
set user:1 { name:zhangsan, age:3} 

# 这个方式也一样
mset user:1:name zhangsan user:1:age 3

获取之后再设置

# 先设置值
set name zhangsan

# 获取之后再设置,这时候获取zhangsan,然后再设置name为redis,如果为空时,返回为空但会设置新的值
getset name redis

String类似的使用场景:value除了是字符串还可以是数字

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储

3.2、List(列表)

在Redis中,可以把List当成栈、队列、阻塞队列,所有的list密令都是LR开头的

倒序存值LPUSH、取值LRANGE(先放的后出)

# 存放值,放到列表的头部
LPUSH list one

LPUSH list two

LPUSH list three

# 取值,先放的后出
LRANGE list 0 -1
"three"
"two"
"one"

正序存值RPUSH(即最后输出)

# 追加值,放到列表的尾部
RPUSH list right

# 取值
LRANGE list 0 -1
"three"
"two"
"one"
"right"

移除元素

# 移除头个元素,即"three"
LPOP list

# 移除最后一个元素,即"right"
RPOP list

LINDEX 通过坐标获取值

# 查看当前的值
"two"
"one"

# 获取第二个值
lindex list 1
"one"

# 获取第一个值
lindex list 0
"two"

查看长度LLEN

# 返回列表的长度
LLEN list 

移除指定的值LREM

# 移除值为 one 的第一个元素,因为值可以重复
lrem list 1 one

# 添加重复的值
LPUSH list "four"
LPUSH list "four"

# 移除两个值
lrem list 2 four

截取指定位置ltrim

# 截取第二第三个出来
ltrim list 1 2

移除列表的最后一个元素,并将值添加到另一个列表中

# 插入值
rpush mylist "hello"
rpush mylist "hello1"
rpush mylist "hello2"

# 移除最后一个元素,并添加到myohterlist
rpoplpush mylist myotherlist

# 此时查询mylist
lrange mylist 0 -1
"hello"
"hello1"

# 查看myohterlist,自动创建,并添加了值"hello2"
lrange myohterlist 0 -1
"hello2"

更新列表指定下标的值lset

# 设置值
lpush list value

# 更新值,将value改为item,0为下标,如果不存在就会报错
lset list 0 item

插入一个值linsert

# 创建一个新列表
RPUSH mylist "hello"

# 往列表的前面插入值
linsert mylist before "hello" "before"

# 往后面插入值
linsert mylist after "hello" "after"

# 查询所有值
RANGE mylist 0 -1
"before"
"hello"
"after"

小结

List实际上就是一个链表,在两边插入或者改动值效率最高!中间元素,相对来说效率会低一点~

可用作:

  • 消息队列(Lpush、Rpop)
  • 栈(ltrim)可以截断队列

3.3、Set(集合)

与List最大的区别是,Set中的值是不会重复的,以S开头

存值sadd,取值smembers

# 设置集合的值,设置成功返回1,失败(重复值)则返回0
sadd myset "hello"
sadd myset "world"

# 查看集合所有值
smembers myset 
"hello"
"world"

# 判读值是否存在,存在则返回1,不存在返回0
sismenber myset hello 

获取集合中元素的个数scard

# 查看个数
scard myset

移除srem

# 移除值为hello的元素
srem myset hello

抽取随机的值srandmenber

# 随机取出集合中的一个元素
srandmenber myset

# 随机取出集合中的2个元素
srandmenber myset 2

随机删除一个key

# 随机删除一个元素
spop myset

将一个指定的值,移动到另一个集合中

# 设置集合1
sadd myset "hello"
sadd myset "world"
# 设置集合2
sadd myset2 "set2"

# 移动元素,将集合1的"world"元素移动到集合2中,所以myset就只剩"hello"
smove myset myset2 "world"

数字集合类

微博,A用户将所有关注的人放在一个set集合中!将他的粉丝也放在一个集合中!实现共同关注,共同爱好!

# 设置集合1
sadd key1 a
sadd key1 b
sadd key1 c
# 设置集合2
sadd key1 c
sadd key1 d
sadd key1 e

# 获取差集
SDIFF key1 key2
1)"b"
2)"a"

# 获取交集,可用作共同关注
SINTER key1 key2
1)"c"

# 获取并集
SUNION key1 key2
1)"b"
2)"c"
3)"e"
4)"a"
5)"d"

3.4、Hash(哈希)

Map集合,key-map,这时候这个值是一个map集合!(跟字符串类似,只是将值变为map)

创建和获取

# 设置一个hash(key-value)
hset myhash field1 hello

# 获取值
hget myhash field1

# 同时设置多个hash(key-value)
hmset myhash field2 world filed3 redis

# 同时获取多个字段值
hmget myhash field1 field2 filed3
1)"hello"
1)"world"
1)"redis"

# 获取所有的键值对
hgetall myhash
1)"field1"    # 输出键
2)"hello"    # 输出值
3)"field2"
4)"world"
5)"field3"
6)"redis"

删除指定键值

# 删除field1键值对
hdel myhash field1 

# 查询所有的键值对
hgetall myhash
1)"field2"
2)"world"
3)"field3"
4)"redis"

获取总键值对数量

# 查看有多少键值对
hlen myhaset
1)2

判断某个键值是否存在

# 判读field1该键值对是否存在,存在返回1
hexists myhash field1

只获取所有key,或value

# 获取所有的keys
hkeys myhash
1)"field2"
2)"field3"

# 获取所有的value
hvals myhash
1)"world"
2)"redis"

指定自增、自减

# 创建数字值
hset myhash field4 5

# 自增一
hincrby myhash field4 1

# 自减一
hincrby myhash field4 -1

判断是否存在后再创建,如果存在则创建失败返回0

# 如果存在field5,就创建失败,返回0
hsetnx myhash field5 hello
1)0

hash变更的数据user name age,用户信息类的保存,使用hash比String存储更快

# 设置用户1的信息
hset user:1 name xiaojiang

# 获取用户信息
hget user:1 name
"xiaojiang"

所以字符串使用String,对象使用hash

3.5、Zset(有序集合)

在set的基础上,增加一个值,set k1 v1 , zset k1 score1 v1

存值、取值

# 设置值,数字是为了区分和排序
zadd myset 1 one 

# 设置多个值
zadd myset 2 two 3 three

# 获取所有的值
zrange myset 0 -1
1)"one"
2)"two"
3)"three"

升序排序

# 设置工资集合
zadd salary 2500 xiaohong
zadd salary 2000 xiaoming
zadd salary 3500 zhangsan

# 按照工资排序(升序),从负无穷到正无穷的范围内查询
zrangebyscore salary -inf +inf
1)"xiaoming"
2)"xiaohong"
3)"zhangsan"

# 将工资数也输出出来
zrangebyscore salary -inf +inf withscores
1)"xiaoming"
2)"2000"
3)"xiaohong"
4)"2500"
5)"zhangsan"
6)"3500"

# 查询工资在2500以内的
zrangebyscore salary -inf 2500 withscores
1)"xiaoming"
2)"2000"
3)"xiaohong"
4)"2500"

降序排列

# 按照工资排序(降序)
zrevrange salary 0 -1 
1)"zhangsan"
2)"xiaohong"
3)"xiaoming"

移除元素

# 移除xiaohong这个元素
zrem salary xiaohong

查看有序集合中元素数量

# 查看总数量
zcard salary
2

获取指定区间的数量

# 获取指定工资区间的数量
zcount salary 2000 3500 
2

案例:

  • 存储班级成绩
  • 工资表排序
  • 加权排序:普通消息1,重要消息2
  • 排行榜应用

其余的一些API,可以通过查看官方文档:http://www.redis.cn/commands.html

4、三种特殊数据类型

4.1、Geospatial地理位置

可用作朋友的定位,附近的人,打车距离计算

geospatial可以推算地理位置的信息,两地之间的距离,方圆几里的人!

查询测试经度纬度数据网站:http://www.toolzl.com/tools/gps.html

只有六个命令:

geoadd添加地理位置

# geoadd 添加地理位置
# 规则:南北极无法直接添加,一般会下载城市数据,通过java程序一次性导入
# 参数:key value(经度、纬度、名称)
# 有效的经度从-180度到180度
# 有效的纬度从-85.05112878度到85.05112878度,不然报下面错误
# 127.0.0.1:6379> geoadd china:city 39.90 116.40 beijin
# (error) ERR invalid longitude,latitude pair 39.900000,116.400000

127.0.0.1:6379> geoadd china:city 116.40 39.90 beijin
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2

geopos获取地理位置

获得当前定位:一定是一个坐标值!

# 获取一个指定城市的经度和纬度
127.0.0.1:6379> geopos china:city beijin
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"

# 获取多个城市的经度纬度
127.0.0.1:6379> geopos china:city beijin
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"

geodist获取直线距离

返回两个给定位置之间的距离。

如果两个位置之间的其中一个不存在, 那么命令返回空值。

指定单位的参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。
# 获取北京到上海的距离,单位默认为m
127.0.0.1:6379> GEODIST china:city beijin shanghai
"1067378.7564"

# 获取北京到上海的距离,单位设置为km
127.0.0.1:6379> GEODIST china:city beijin shanghai km
"1067.3788"

georedius获取附近信息

查看附近的人(先获取所有附近的人的地址,定位!)通过半径来查询

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

范围可以使用以下其中一个单位:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。
# 所有的数据应该都录入到china:city才会有结果出来
# 查询在110 30位置(可以替换为自己的定位)方圆500km的城市
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"

# 获取附近城市的经纬度
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"

# 获取附近城市的距离
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"

# 上面两个也可以同时使用
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord withdist
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"

# 也可以指定获取最近的几个
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km count 1
1) "chongqing"

georandusbymember指定城市附近的信息

# 获取北京附近1000km的城市,可以添加上方一样的参数
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijin 1000 km
1) "beijin"
2) "xian"

# 获取伤害附近400km的城市
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"

geohash命令,返回一个或多个位置元素的Geohash表示

该命令将返回11个字符的Geohash字符串

# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,则距离越近
127.0.0.1:6379> GEOHASH china:city beijin shanghai
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"

GEO底层的实现原理其实就是Zset!

位置查看和删除

# 查看所有位置信息
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijin"

# 删除位置信息
127.0.0.1:6379> ZREM china:city beijin
(integer) 1

# 再次查看
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"

4.2、Hyperloglog

简介

Redis Hyperloglog 基数统计的算法!

优点:占用的内存十固定,2^64不同的元素的技术,只需要用12KB内存!

例如:网页的UV(一个人访问一个网站多次,但还是算作一个人)

传统的方式,set保存用户的id,然后可以统计set中的元素数量作为标准判断这个方式如果保存了大量的用户id,就会比较麻烦!

0.81%的错误率!统计UV任务可以忽略不计!

测试使用

计算并集数量,可用作统计网站访问量,允许容错就可以使用该方法!

# 设置第一组hyperloglog
127.0.0.1:6379> PFADD mykey a b c d e f
(integer) 1
# 获取该组的数量
127.0.0.1:6379> PFCOUNT mykey
(integer) 6

# 设置第二组hyperloglog
127.0.0.1:6379> PFADD mykey2 e f g h i j
(integer) 1
# 获取该组的数量
127.0.0.1:6379> PFCOUNT mykey2
(integer) 6

# 讲第一组和第二组做并集操作,生成key3
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
# 获取key3的数量
127.0.0.1:6379> PFCOUNT mykey3
(integer) 10、

4.3、Bitmap

为存储

统计用户信息,活跃、不活跃、登录、未登录、打卡!(两个状态的都可以使用Bitmaps位图,都是操作二进制来进行记录,只有0和1两个状态)

测试

使用bitmap来记录周一到周日的打卡!

# 周一用0表示,1表示已打卡
127.0.0.1:6379> SETBIT sign 0 1
(integer) 0
127.0.0.1:6379> SETBIT sign 1 1
(integer) 0
127.0.0.1:6379> SETBIT sign 2 0
(integer) 0
127.0.0.1:6379> SETBIT sign 3 1
(integer) 0
127.0.0.1:6379> SETBIT sign 4 0
(integer) 0
127.0.0.1:6379> SETBIT sign 5 1
(integer) 0
127.0.0.1:6379> SETBIT sign 6 0
(integer) 0

# 查看某一天是否有打卡
127.0.0.1:6379> GETBIT sign 3
(integer) 1
127.0.0.1:6379> GETBIT sign 6
(integer) 0

# 查看打卡的天数
127.0.0.1:6379> BITCOUNT sign
(integer) 4

5、事务

Redis 事物本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!

一次性、顺序性、排他性!执行一些列的命令!

Redis 事务没有隔离级别的概念!所有的命令在事务中并没有直接被执行!只有发起执行命令的时候才会执行!

Redis 单条命令是保存原子性的,但是事物不保存原子性!

redis事务:

  • 开启事务(mult)
  • 命令入队(……)
  • 执行事务(exec)

正常执行事务

# 开启事务
127.0.0.1:6379> MULTI
OK

# 以下命令都会进入到事务中
127.0.0.1:6379> set key1 v1 
QUEUED
127.0.0.1:6379> set key2 v2
QUEUED
127.0.0.1:6379> set key3 v3
QUEUED
127.0.0.1:6379> get key1
QUEUED

# 执行事务,并输出上面的命令结果,所以事务结束
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
4) "v1"

放弃事务

# 开启事务
127.0.0.1:6379> multi
OK

# 命令入队
127.0.0.1:6379> set key4 v4 
QUEUED
127.0.0.1:6379> set key5 v5
QUEUED

# 放弃事务
127.0.0.1:6379> DISCARD
OK

# 由于事务被放弃了,下列将获取不到
127.0.0.1:6379> get key4
(nil)
127.0.0.1:6379> get key5
(nil)

编译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行

# 开启事务
127.0.0.1:6379> multi
OK

# 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
# 执行错误命令getset
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED

# 提交事务
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

# 发现事务中的所有命令都没有被执行
127.0.0.1:6379> get k2
(nil)
127.0.0.1:6379> get k4
(nil)

运行时错误(1/0),如果事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!

# 先设置一个字符串
127.0.0.1:6379> set k1 "v1"
OK

# 开启事务
127.0.0.1:6379> multi
OK
# 让字符串加1,语法没错但运行会报错
127.0.0.1:6379> INCR k1
QUEUED
# 设置k2、k3的值
127.0.0.1:6379> set k2 v2 
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED

# 提交事务,第一条命令的语法报错了
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK

# 查询设置的值,发现语法报错还是成功提交正确的命令
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

6、Watch监控

加锁

悲观锁:

  • 无论做什么都会加锁!
  • 效率低下

乐观锁:

  • 无论做什么都不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
  • 获取version
  • 更新的时候比较version

Redis监视测试

# 设置余额和消费
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK

# 监视money对象
127.0.0.1:6379> watch money
OK

# 开启事务
127.0.0.1:6379> multi
OK
# 余额自减20
127.0.0.1:6379> DECRBY money 20
QUEUED
# 消费自增20
127.0.0.1:6379> INCRBY out 20
QUEUED

# 提交事务,输出结果,数据期间没有发生变动就正常执行成功!
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当作redis的乐观锁

# 监视money
127.0.0.1:6379> watch money
OK

# 开启事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED

# 在提交事务之前在另一个线程中执行修改money的操作,此时检测到数据被修改(不是上面监视的时候的值),所以数据会提交失败
127.0.0.1:6379> exec
(nil)

# 此时获取money的值是被修改之后的
127.0.0.1:6379> get money
"1000"

image-20200901145552120

加锁和解锁

# 监视money是否为当前的值
127.0.0.1:6379> watch money
OK

# 故意修改了money的值
127.0.0.1:6379> set money 100
OK

# 发现不加事务的时候还是可以正常执行命令
127.0.0.1:6379> DECRBY money 10
(integer) 90

# 开启事务则会发现提交失败
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> exec
(nil)

# 取消监视
127.0.0.1:6379> unwatch
OK
# 重新设置监视的值
127.0.0.1:6379> watch money
OK

# 开启事务,这次可以正常执行事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 80

所以,如果执行失败就获取新的监视值