Redis(Remote Dictionary Server),是远程字典服务。本文主要介绍Redis,五大基础数据结构,三大特殊数据结构以及Redis的事务特性

Redis

Reids是什么

Redis(Remote Dictionary Server),是远程字典服务。

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步

免费和开源,是当下最热门的NoSQL技术之一,也被称为结构化数据库

Redis能干嘛

1、内存存储、持久化,因为内存是断电即失,所以说持久化很重要(rdb,aof)

2、效率高,可以用于高速缓存

3、发布订阅系统

4、地图信息分析

5、计时器、计数器

Reids的特性

1、多样的数据类型

2、持久化

3、集群

4、事务

学习地址以及下载

1、官网:https://redis.io/

2、中文网:http://www.redis.cn/

3、下载地址:官网下载

注意:Window在Github上下载,Redis推荐在Linux服务器上搭建

Linux安装

1、下载安装包

2、放入Linux系统下的/opt目录,进行解压

3、进入解压后的目录可以看到redis.conf配置文件

sl4j2redis信息 rds redis_sl4j2redis信息

4、基本的环境安装

yum install gcc-
make
make install

5、redis的默认安装路径/usr/local/bin

sl4j2redis信息 rds redis_数据库_02

6、将redis配置文件复制到我们当前目录下

sl4j2redis信息 rds redis_redis_03

7、redis默认不是后台启动的,修改配置文件

vim redis.conf
将daemonize no -> daemonize yes

8、启动Redis服务

redis-server bconfig/redis.conf

通过redis-cli来判断是否启动成功

redis-cli -p 6379
>ping
返回pong说明连接成功

9、查看redis的进程是否开启

ps -ef|grep redis

10、关闭Redis服务

shutdown

测试性能

redis-benchmark是一个压力测试用具,并且是官方自带的

图片来自于菜鸟教程:

sl4j2redis信息 rds redis_redis_04

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

sl4j2redis信息 rds redis_sl4j2redis信息_05

Redis基础的知识

Redis默认有16个数据库

select:进行切换数据库

127.0.0.1:6379> select 3	#切换数据库
OK
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379>

DBSIZE:查看数据库的现在的数据量

127.0.0.1:6379> dbsize(integer) 3

keys *:查看数据库所有的键的名称

127.0.0.1:6379> keys *1) "myset"2) "counter:__rand_int__"3) "key:__rand_int__"

flushdb:清空当前数据库内容

flushall:清空全部数据库内容

127.0.0.1:6379> flushdbOK127.0.0.1:6379> flushallOK

EXPIRE:设置过期时间

expire name 10

move:移除某个键值对

127.0.0.1:6379> move name 1(integer) 1

Redis5是单线程的,Rdis6是多线程的(只是IO多线程,实际上还是单线程操作)

官方表示,Redis是基于内存操作,CPU不是Redis的瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,既可以使用单线程了。

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

1、高性能的服务器不一定是多线程的

2、多线程(CPU上下文切换)不一定比单线程效率高

访问速度:CPU>内存>硬盘

核心:Redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文切换:耗时的操作),对于内存系统来说,如果没有上下文切换,效率就是最高的。Redis多次读写都是在一个CPU上的,在内存情况下,速度自然就很快了。

五大数据类型

Redis-key

127.0.0.1:6379> set name bobo	#设置key value
OK
127.0.0.1:6379> keys *	#查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> exists name	#判断当前的key是否存在
(integer) 1
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists name 1
(integer) 1
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> move name 1	#移除当前的key
(integer) 0
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> expire name 10	#设置key的过期时间,时间是秒
(integer) 1
127.0.0.1:6379> ttl name	#查看key的剩余时间
(integer) 5
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> type name	#查看键值对是什么类型
string

String(字符串)

#基础命令
127.0.0.1:6379> set key1 v1	#设置值
OK
127.0.0.1:6379> get key1	#获得值
"v1"
127.0.0.1:6379> exists key1	#判断某一个key是否存在
(integer) 1
127.0.0.1:6379> append key1 bobo	#在key1后追加字符串,如果key不存在就相当于set key value
(integer) 6	#返回字符串长度
127.0.0.1:6379> strlen key1	#返回key保存的值的长度
(integer) 6
################################################################
# incr	i++
# decr	i--
# incrby	i+n
# decrby	i-n

127.0.0.1:6379> set views 0	#初始量为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views	#自增1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> decr views	#自减1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10	#设置指定增量
(integer) 11
127.0.0.1:6379> decrby views 5	#设置指定减量
(integer) 16
################################################################
# getrange	字符串范围
127.0.0.1:6379> set key1 hello,bobo	#设置key1的值
OK
127.0.0.1:6379> get key1
"hello,bobo"
127.0.0.1:6379> getrange key1 0 3	#截取字符串0-3
"hell"
127.0.0.1:6379> getrange key1 2 6	#截取字符串2-6
"llo,b"
127.0.0.1:6379> getrange key1 0 -1	#获取全部的字符串
"hello,bobo"

# setrange	替换
127.0.0.1:6379> set key2 hello.rourou
OK
127.0.0.1:6379> get key2
"hello.rourou"
127.0.0.1:6379> setrange key2 6 bobo	#替换指定位置开始的字符串
(integer) 12
################################################################
#setex(set with expire)	#设置过期时间
#setnx(set if not exist)#不存在的时候创建新的键值对,如果存在则无效(分布式锁中常常使用)

127.0.0.1:6379> setex keys3 30 "hello"	#设置key3的值为hello,30后过期
OK
127.0.0.1:6379> ttl keys3
(integer) 22
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> setnx mykey 123	#由于mykey存在,所以无法修改值
(integer) 0
127.0.0.1:6379> get mykey
"redis"
################################################################
# mset	批量插入
# mget	批量获得

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3	#批量插入
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3	#批量获得
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4	#msetnx是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)

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

# 这里的key是一个巧妙的设计:user:{id}:{filed}
127.0.0.1:6379> set user:1 {name:zhangsan,age:3}
OK
127.0.0.1:6379> get user:1
"{name:zhangsan,age:3}"
################################################################
# getset	先get然后set

127.0.0.1:6379> getset db redis	#如何不存在值,则返回null
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb	#如果存在值,获取原来的值,然后设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"

这些数据的结构都是一样的!

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

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

List

基本的数据类型,列表

在redis里面,可以将list转化成栈、队列、阻塞队列

所有的list命令都是用l开头的,Redis不区分大小命令

# lpush	将一个值或者多个值,插入到列表头部(左)
# rpush	讲一个值或者多个值,插入到列表尾部(右)
# lpop	移除list的第一个元素
# rpop	移除list的最后一个元素
# lrange	获取list的中指定范围的值

127.0.0.1:6379> lpush list one two three	#将一个值或者多个值,插入到列表头部(左)
(integer) 3
127.0.0.1:6379> lrange list 0 -1	#获取list的中值,0 -1代表全部
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> rpush lists one two three	#讲一个值或者多个值,插入到列表尾部(右)
(integer) 3
127.0.0.1:6379> lrange lists 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> lpop list 1	#移除list的第一个元素
1) "three"
27.0.0.1:6379> rpop list 1	#移除list的最后一个元素
1) "one"
127.0.0.1:6379> lrange list 0 -1
1) "two"
################################################################
# lindex	通过下标获得list的值
# llen	返回列表的长度

127.0.0.1:6379> lrange lists 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> lindex lists 1	#通过下标获得list的值
"two"
127.0.0.1:6379> llen lists	#返回列表的长度
(integer) 3
################################################################
# lrem 移除list集合中指定个数的value,精确匹配

127.0.0.1:6379> lrange lists 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> lrem lists 1 three	#移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrange lists 0 -1
1) "one"
2) "two"
################################################################
# trim	通过下标截取指定的长度,list会被修改成截取后的长度

127.0.0.1:6379> rpush list h1 h2 h3 h4
(integer) 4
127.0.0.1:6379> ltrim list 1 2	#通过下标截取指定的长度,list会被修改成截取后的长度
OK
127.0.0.1:6379> lrange list 0 -1
1) "h2"
2) "h3"
################################################################
# rpoplpush	移除原列表的最后一个元素,并且将其移动到新列表中

127.0.0.1:6379> rpoplpush list mylist	#移除原列表的最后一个元素,并且将其移动到新列表中
"h3"
127.0.0.1:6379> rpoplpush list mylist
"h2"
127.0.0.1:6379> lrange list 0 -1	#查看原列表	
(empty array)
127.0.0.1:6379> lrange mylist 0 -1	#查看新列表
1) "h2"
2) "h3"
################################################################
# lset	将列表中的指定下标的值替换成另一个,更新操作,如果下标不存在则报错err

127.0.0.1:6379> exists list	#判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item	#如果不存在,更新操作会报err
(error) ERR no such key
127.0.0.1:6379> lpush list value
(integer) 1
127.0.0.1:6379> lset list 0 item	#如果存在,更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
################################################################

# linsert 将某个具体的value插入到列表中某个元素的前面或者后面

127.0.0.1:6379> rpush list hello world
(integer) 2
127.0.0.1:6379> linsert list before world new	#在world前面插入new
(integer) 3
127.0.0.1:6379> linsert list after world new	#在world后面插入new
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "new"
3) "world"
4) "new"

小结

  • 他实际上是一个链表,before Node after,left,right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有的值,此时链表为空的话,会自动释放,用exists会返回不存在
  • 在两边插入或者改动值,效率最高。中间元素,相对来说效率会低一点

消息队列:Lpush Rpop(队列),Lpush Lpop(栈)

Set(集合)

set中的值是不能重复的

# sadd	添加元素
# smembers 查看指定set的所有值
# sismember 判断某个值是否在set集合中
# scard 获得set集合中的元素个数

127.0.0.1:6379> sadd mysey hello	#set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset hello bobo lovebobo
(integer) 3
127.0.0.1:6379> smembers myset	#查看指定set的所有值
1) "bobo"
2) "lovebobo"
3) "hello"
127.0.0.1:6379> SISMEMBER myset hello	#判断某个值是否在set集合中
(integer) 1
127.0.0.1:6379> SISMEMBER myset word
(integer) 0
127.0.0.1:6379> scard myset	#获得set集合中的元素个数
(integer) 3
################################################################
# srem	移除set集合中的指定元素
# spop	随机移除指定个数的元素

127.0.0.1:6379> srem myset lovebobo	#移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "bobo"
2) "hello"
127.0.0.1:6379> spop myset	#随机移除指定个数的元素
"hello"
127.0.0.1:6379> spop myset
"h1"
127.0.0.1:6379> SMEMBERS myset
1) "h2"
2) "bobo"
3) "h3"
################################################################
# srandmember 随机抽出指定的一个元素

127.0.0.1:6379> SRANDMEMBER myset 2	#随机抽出指定个数的元素
"hello"
"bobo"
127.0.0.1:6379> SRANDMEMBER myset	#随机抽出一个元素
"bobo"
################################################################
# smove 将一个指定的值移到另一个set集合中

127.0.0.1:6379> sadd myset hello world bobo
(integer) 3
127.0.0.1:6379> sadd myset2 h1
(integer) 1
127.0.0.1:6379> smove myset myset2 bobo	#将一个指定的值移到另一个set集合中
(integer) 1
127.0.0.1:6379> SMEMBERS myset2
1) "bobo"
2) "h1"
################################################################
# SDIFF		差集
# SINTER	交集
# SUNION	并集

127.0.0.1:6379> sadd key1 a b c
(integer) 3
127.0.0.1:6379> sadd key2 c d e
(integer) 3
127.0.0.1:6379> SDIFF key1 key2		#差集
1) "b"
2) "a"
127.0.0.1:6379> SINTER key1 key2	#交集	实际应用中公共好友可以这么实现
1) "c"
127.0.0.1:6379> SUNION key1 key2	#并集
1) "a"
2) "c"
3) "b"
4) "e"
5) "d"

应用场景

常见用于共同好友以及推荐好友中的使用,将A用户所有关注的人放在一个set集合,粉丝放在另一个set集合,B用户一样。然后可以通过redis的差集,并集,交集来得到一些不同的结果

Hash(哈希)

map集合,key-map(<key,value>)集合,key-value中的value是map集合。

本质和String类型没有太大区别,还是一个简单的key-value

# hset	添加一个hash类型的key-value
# hget	获取一个hash类型的key-value
# hmset	添加多个字段值
# hmget	获得多个字段值
# hgetall	获取全部数据
# hdel	删除hash指定的字段值
# hlen	获取hash列表的长度

127.0.0.1:6379> hset myhash field bobo	#添加一个hash类型的key-value
(integer) 1
127.0.0.1:6379> hget myhash field	#获取一个hash类型的key-value
"bobo"
127.0.0.1:6379> hmset myhash field hello field1 world	#添加多个字段值
OK
127.0.0.1:6379> hmget myhash field field1	#获得多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash	#获取全部数据
1) "field"
2) "hello"
3) "field1"
4) "world"
127.0.0.1:6379> hdel myhash field1	#删除hash指定的字段值,对应的value也会消失
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field"
2) "hello"
127.0.0.1:6379> hlen myhash	#获取hash列表的长度
(integer) 1
################################################################
# hexists	判断hash中指定字段是否存在
# hkeys	获取所有的field
# hvals	获取所有的value

127.0.0.1:6379> HEXISTS myhash field	#判断hash中指定字段是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash field2
(integer) 0
127.0.0.1:6379> hkeys myhash	#获取所有的field
1) "field"
127.0.0.1:6379> hvals myhash	#获取所有的value
1) "hello"
################################################################
# hincrby	指定增量	
# hsetnx	如果不存在添加,否则不动

127.0.0.1:6379> hset myhash field3 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1	#指定增量
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello	#如果不存在添加
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world	#如果不存在则不设置
(integer) 0

应用场景

常用于变更信息的保存以及用户信息的保存,例如用户信息就可以通过一个hash来保存各种不同的字段,以及修改。

hash适合对象的存储,String适合字符串存储

127.0.0.1:6379> hset user:1 name bobo age 3
(integer) 2
127.0.0.1:6379> hget user:1 name
"bobo"
127.0.0.1:6379> hget user:1 age
"3"

Zset(有序集合)

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

# zadd	添加一个值或者多个值
# zrange	返回myset中所有的值

127.0.0.1:6379> zadd myset 1 one	#添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three	#添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1	#返回myset中所有的值
1) "one"
2) "two"
3) "three"
################################################################
# zrangebyscore	显示所有用户,从小到大
# zrevrange	显示所有用户,从大到小

127.0.0.1:6379> zadd salary 2500 xiaohong	#添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 bobo
(integer) 1
127.0.0.1:6379> clear
127.0.0.1:6379> zrange salary 0 -1
1) "bobo"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf	#显示所有用户,从小到大
1) "bobo"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores	#显示所有用户,附带成绩
1) "bobo"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores	#显示员工工资小于等于2500的升序排序
1) "bobo"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> zrevrange salary 0 -1 withscores	#显示所有员工的降序排序
1) "zhangsan"
2) "5000"
3) "xiaohong"
4) "2500"
5) "bobo"
6) "500"
################################################################
# zrem	移除zset中的元素
# zcard	返回有序序列的长度

127.0.0.1:6379> zrange salary 0 -1
1) "bobo"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong	#移除有序序列中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "bobo"
2) "zhangsan"
127.0.0.1:6379> zcard salary	#返回有序序列的长度
(integer) 2
################################################################
# zcount	获取指定区间的元素个数

127.0.0.1:6379> zadd myset 1 hello 2 world 3 bobo
(integer) 3
127.0.0.1:6379> zcount myset 1 3	#获取1-3的元素个数
(integer) 3
127.0.0.1:6379> zcount myset 1 2	#获取1-2的元素个数
(integer) 2

应用场景

存储班级成绩表,工资表排序

加权重的消息:1.普通消息 2.重要消息

排行榜应用场景

三种特殊数据类型

geospatial(地理位置)

常用与个人定位,附近的人,打车距离的计算

Redis的Geo在Redis3.2版本就推出了,这个功能可以推算地理位置的信息,两个人的距离,方圆几里的人

GEOADD 添加地理位置

# getadd 添加地理位置
# 规则:两极是无法直接添加的,我们一般会下载城市数据,然后通过java程序一次性导入
# 参数 key 值(经度,纬度,名称)
# 有效的经度从-180到180
# 有效的纬度从-85.05112878到85.05112878
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(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 160.50 29.53 chongqing 114.05 22.54 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 12.15 30.28 hangzhou 108.96 34.26 xian
(integer) 2

GEOPOS 获取指定城市的经度和纬度

#获取指定城市的经度和纬度

127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing shanghai
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "121.47000163793563843"
   2) "31.22999903975783553"

GEODIST 两地之间的距离

  • m 为米。
  • km 为千米。
  • mi 为英里。
  • ft 为英尺。
127.0.0.1:6379> GEODIST china:city shanghai beijing km	#从北京到上海的直线距离
"1067.3788"

GEORADIUS 以给定的经纬度为中心,找出某一半径内的元素

实现查找附近的人,通过半径来查询

可以查询限定人数的个数,利用count

所有的数据都应该录入到china:city中

127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km	#以110 30为中心,找出1000km内的元素的key
1) "xian"
2) "shenz"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist	#以110 30为中心查找元素,附带直线距离
1) 1) "xian"
   2) "483.8340"
2) 1) "shenz"
   2) "922.6257"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord	#以110 30为中心查找元素,附带定位信息
1) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"
2) 1) "shenz"
   2) 1) "114.04999762773513794"
      2) "22.53999903789756587"
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km count 1	#以110 30为中心,找出1000km内的指定个数的元素个数
1) "xian"

GEORADIUSBYMEMBER 找出位于指定范围内的元素,中心点是由给定的位置元素决定(和上面的API类型,只是中心点可以是元素不一定是实际坐标了

#找出位于指定元素周围的其他元素

127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km	#查找北京附近1000km的元素
1) "beijin"
2) "beijing"
3) "xian"

GEOHASH 返回一个或者多个位置元素的Geohash表示

该命令返回11个字符的 Geohash 字符串,因此与 Redis 内部52位表示相比,没有任何精度损失。

#将二维的经纬度转化成一纬的字符串,如果两个字符串越近,则距离越近

127.0.0.1:6379> geohash china:city beijing shanghai	#返回11个字符串的geohash表示的经纬度
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"

GEO的底层实现原理其实就是Zset,所以可以使用Zset操作geo

127.0.0.1:6379> zrem china:city beijing	#利用zset的移除命令移除geo数据类型的元素用于证明geo的底层是zset
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1	#查看china:city中的全部元素
1) "hangzhou"
2) "xian"
3) "shenz"
4) "shanghai"
5) "beijin"
6) "chongqing"

Hyperloglog(基数存储)

简介

Redis 2.8.9版本就更新了Hyperloglog数据结构

Redis Hyperloglog 基数统计的算法

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

传统的方式,set保存用户的id,统计set中的元素作为标准判断

这个方式如果保存了大量的用户id,会比较麻烦,因为我们实际上解决的问题是计数而并不是id

优点:占用的内存是固定的,2^64不同元素的基数,只需要12KB的内存

测试使用

# pfadd	创建一个或多个元素
# pfcount	统计元素的基数个数
# pfmerge	合并两个元素的并集到新列表中

127.0.0.1:6379> pfadd mykey a b c d e f g h i j	#创建第一组元素,存储不重复的基数
(integer) 1
127.0.0.1:6379> PFCOUNT mykey	#统计 mykey 元素的基数数量
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j z x c v b n m	#创建第二组元素
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2	#合并mykey mykey2的并集到mykey3中
OK
127.0.0.1:6379> pfcount mykey3
(integer) 15

如果允许容错,用Hyperloglog首选

如果不允许容错,使用set或者自己的数据类型

Bitmap(位存储)

位存储

统计xx情况人数: 0 1 0 1 0

统计用户信息(活跃、不活跃),登录信息(登录,未登录),打卡信息(打卡,未打卡),一般两个状态的切换的话就可以使用Bitmap,因为Bitmap用于操作二进制来进行记录,只有0和1

测试使用

例如:使用bitmap来记录一周的打卡情况

setbit sign 0 1	#周一打卡
setbit sign 1 0	#周二未打卡
setbit sign 2 0	#周三未打卡
setbit sign 3 1	#周四打卡	
setbit sign 4 1	#周五打卡
setbit sign 5 0	#周六未打卡
setbit sign 6 0	#周某未打卡

现在我们查看某一天是否有打卡:

127.0.0.1:6379> getbit sign 3	#判断星期四是否打卡,返回1是打卡,0是未打卡
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0

统计操作,统计一星期打卡的天数:

127.0.0.1:6379>  bitcount sign 0 6	#统计星期一到星期天的打卡情况
(integer) 3

事务

Redis事务本质:一组命令的集合。

一个事务中的所有命令都会被序列化,在事务的执行过程中,会按照顺序执行

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

----- 队列 set set set ... 执行 ------

Redis事务没有隔离级别的概念

所有的命令在事务中,并没有直接被执行。只有发起执行命令的时候才会执行。Eexc

Reids单条命令是保证原子性的,但是整个事务不保证原子性,下面会具体说明

reids的事务:

  • 开启事务(命令:MULIT)
  • 命令入队(命令:数据结构中的命令)
  • 执行事务(命令;EXEC)

正常执行事务

127.0.0.1:6379> multi	# 开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec	# 执行事务
1) OK
2) OK
3) "v2"
4) OK

放弃事务

127.0.0.1:6379> multi	# 开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard	# 放弃事务
OK
127.0.0.1:6379> get k4	# 事务队列中命令都不会被执行
(nil)

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3	# 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> exec	# 执行事务的时候报错,所有命令都不会被执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5	# 由于所有命令没有执行,所以k5的值为空
(nil)

运行时异常(1/0,数组下标,语法型错误),事务队列中存在语法型错误,其他命令可以执行,错误命令抛出异常

127.0.0.1:6379> set k1 "v1"	# 设置k1的值为"v1"字符串
OK
127.0.0.1:6379> MULTI	# 开启事务
OK
127.0.0.1:6379(TX)> incr k1	# k1自增,但是k1是字符串,所以exec的时候这里会出错
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range	# 虽然第一条命令出错了,但是不影响后面的命令执行
2) OK
3) OK
4) "v3"

监控 Watch(一旦事务执行成功后,监控就会自动取消)

悲观锁:

  • 很悲观,认为什么时候都可能会出现问题,所以做什么操作都会加上锁

乐观锁:

  • 很乐观,认为什么时候都不会出现问题,所以不会上锁。更新数据的时候会拿version去判断有人是否修改过数据
  • 获得version
  • 更新的时候比较version

Reids的监视测试

第一次正常执行

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money	# 监视 money 对象
OK
127.0.0.1:6379> MULTI	# 事务正常结束,数据期间没有发生变动,这个时候就正常执行
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20

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

127.0.0.1:6379> watch money	# 监视money
OK	
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> exec	# 执行之前,另一个线程将money修改为1000,此时返回就是null,代表事务执行失败。因为wtach监视到了money的数据发生了变化,使用的乐观锁的操作
(nil)

如果正在watch的元素遇到了事务执行失败的情况,就需要用unwatch进行解锁,然后重新加锁再操作