不点蓝字,我们哪来故事?

上一章我们入门了,安装了Redis,简单的使用了一下,这一章准备开始讲Redis的一些基本命令以及它的几种数据类型。
性能测试redis-benchmark
在开始之前,先给大家介绍Redis官方自带的一个性能测试工具:

和redis-server、redis-cli命令一样,redis-benchmark命令也有一些参数:
| 序号 | 选项 | 描述 | 默认值 |
|---|---|---|---|
| 1 | -h | 指定服务器主机名 | 127.0.0.1 |
| 2 | -p | 指定服务器端口 | 6379 |
| 3 | -s | 指定服务器 socket | |
| 4 | -c | 指定并发连接数 | 50 |
| 5 | -n | 指定请求数 | 10000 |
| 6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
| 7 | -k | 1=keep alive 0=reconnect | 1 |
| 8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
| 9 | -P | 通过管道传输 <numreq> 请求 | 1 |
| 10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
| 11 | --csv | 以 CSV 格式输出 | |
| 12 | -l | 生成循环,永久执行测试 | |
| 13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
| 14 | -I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
我们来简单测试一下:
# 测试:100个并发连接 每个并发10w个请求redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000...====== SET ======100000 requests completed in 2.17 seconds # 对我们的10万个请求进行写入测试100 parallel clients # 100个并发客户端3 bytes payload # 每次写入3个字节keep alive: 1 # 只有1台服务器来处理这些请求,也就是说这是单机性能38.83% <= 1 milliseconds # 第1毫秒处理了38.83%的请求99.89% <= 2 milliseconds # 第2毫秒处理了99.89%的请求99.90% <= 3 milliseconds99.90% <= 4 milliseconds99.90% <= 5 milliseconds99.93% <= 6 milliseconds99.96% <= 7 milliseconds99.98% <= 8 milliseconds100.00% <= 8 milliseconds # 8毫秒内处理了所有请求46104.20 requests per second # 每秒钟处理46104.20个请求====== GET ======100000 requests completed in 2.19 seconds100 parallel clients3 bytes payloadkeep alive: 135.06% <= 1 milliseconds99.99% <= 2 milliseconds100.00% <= 2 milliseconds45578.85 requests per second...
这数据的分析可以参考备注,它其实打印出了很多的指标(SET、GET、PING_BULK、PING_INLINE、INCR等等),但是分析的方式都是一样的。
这个性能测试可以帮助我们理解,Redis到底有多快。
官方的测试数据是:读的速度是 110000次/s,写的速度是 81000次/s 。
Redis为什么是单线程的?
官方表示:Redis是基于内存操作的,CPU并不是Redis的内存瓶颈。Redis的瓶颈是机器的内存和网络带宽。既然可以使用单线程实现,为什么要使用多线程?
我们通常会陷进这样的误区:
高性能的服务器一定是多线程的
多线程一定比单线程效率高
而Redis就是对这两个误区最狠的打脸,因为Redis的核心就是:
Redis将所有的数据放在内存中,对于内存系统来说,没有上下文切换(多线程之间会产生上下文的切换,这是一个耗时的操作),效率就是最高的。
Redis的一些基本命令
在Redis中默认有16个数据库
在Redis.conf中
...# Set the number of databases. The default database is DB 0, you can select# a different one on a per-connection basis using SELECT <dbid> where# dbid is a number between 0 and 'databases'-1databases 16...
默认使用的是第0个数据库。
我们可以使用
127.0.0.1:6379> select 3 # 切换数据库OK
命令来切换数据库。
除此之外,Redis还有以下一些基本的命令
127.0.0.1:6379[3]> dbsize # 查看DB大小(integer) 0127.0.0.1:6379[3]> set name age # set一个值OK127.0.0.1:6379[3]> dbsize(integer) 1127.0.0.1:6379[3]> keys * # 查看所有的key1) "name"127.0.0.1:6379[3]> flushdb # 清空当前数据库;flushall清空全部数据库OK127.0.0.1:6379[3]> keys *(empty list or set)127.0.0.1:6379> exists name # 判断某个key是否存在,返回0说明不存在(integer) 0127.0.0.1:6379> set name ageOK127.0.0.1:6379> exists name # 判断某个key是否存在,返回1说明存在(integer) 1127.0.0.1:6379> move name 1 # 将name这个key移动到1号数据库(integer) 1127.0.0.1:6379> set name kevinOK127.0.0.1:6379> EXPIRE name 10 # 设置key的过期时间(integer) 1127.0.0.1:6379> ttl name # 查看key还剩多长时间,单位秒(integer) 7127.0.0.1:6379> ttl name(integer) 4127.0.0.1:6379> ttl name # -2代表key已过期(integer) -2127.0.0.1:6379> get name # 再去get这个key就返回了空(nil)127.0.0.1:6379> set name ageOK127.0.0.1:6379> type name # 查看key的value的类型string
更多的命令,我可以查阅Redis的官网

Redis支持的数据类型
String(字符串)
string是Redis中最基本的数据类型,可以理解为与Memcached一模一样的类型,一个key对应一个value。string类型是二进制安全的,意思是Redis的string可以包含任何数据,比如图片或者序列化的对象,一个redis中字符串value最多可以是512M。
上面讲过的一些基本命令,基本上都是关于String的命令,除了上面那些,关于String类型的命令还有以下这些:
127.0.0.1:6379> set key1 v1OK127.0.0.1:6379> get key1"v1"127.0.0.1:6379> append key1 age # [append key value]:追加字符串,在字符串末尾追加(integer) 5127.0.0.1:6379> get key1"v1age"127.0.0.1:6379> strlen key1 # 获取字符串长度(integer) 5127.0.0.1:6379> append name zhangsan # append一个不存在的key,相当于set(integer) 8127.0.0.1:6379> keys *1) "name"2) "key1"# *****************************************************127.0.0.1:6379> set view 0OK127.0.0.1:6379> incr view # [incr key]:自增(一般一些文章的浏览量都是用这种方式实现的)(integer) 1127.0.0.1:6379> get view"1"127.0.0.1:6379> decr view # [decr key]:自减(integer) 0127.0.0.1:6379> get view"0"127.0.0.1:6379> incrby view 10 # [incrby key increment]:根据步长自增(integer) 10127.0.0.1:6379> get view"10"127.0.0.1:6379> decrby view 5 # [decrby key increment]:根据步长自减(integer) 5127.0.0.1:6379> get view"5"# *****************************************************127.0.0.1:6379> flushdbOK127.0.0.1:6379> set key1 "age,hello world"OK127.0.0.1:6379> get key1"age,hello world"127.0.0.1:6379> getrange key1 0 3 # [getrange key start end]:获取一定范围内的字符串(截取的开始和结束是闭区间)"age,"127.0.0.1:6379> getrange key1 0 -1 # 从0到-1表示获取整个字符串"age,hello world"127.0.0.1:6379> set key2 abcdefgOK127.0.0.1:6379> get key2"abcdefg"127.0.0.1:6379> setrange key2 1 xx # [setrange key offset value]:从字符串中的某个位置开始替换字符串(integer) 7127.0.0.1:6379> get key2"axxdefg"# *****************************************************127.0.0.1:6379> setex key3 30 hello # [setex key second value]:带有过期时间的设置一个keyOK127.0.0.1:6379> ttl key3(integer) 26127.0.0.1:6379> ttl key3(integer) 21127.0.0.1:6379> setnx mykey redis # [setnx key value]:如果一个key不存在,则设置,如果已经存在,不做任何操作(integer) 1127.0.0.1:6379> get mykey"redis"127.0.0.1:6379> setnx mykey mongodb(integer) 0127.0.0.1:6379> get mykey"redis"# *****************************************************127.0.0.1:6379> flushdbOK127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # [mset key value [key value ...]]:批量设置值OK127.0.0.1:6379> mget k1 k2 k3 # [mget key [key ...]]:批量获取值1) "v1"2) "v2"3) "v3"# *****************************************************127.0.0.1:6379> msetnx k1 v1 k4 v4 # [msetnx key value [key value ...]]:批量的setnx操作,批量设置的key只能一起成功,如果有一个失败,则全部失败。(原子性的操作)(integer) 0127.0.0.1:6379> get key4(nil)# *****************************************************127.0.0.1:6379> getset db redis # [getset key value]:先获取值再设置值,先将获取的值返回,再把value设置进去(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"
List(列表)
在Redis中,我们可以把List当做栈、队列、阻塞队列来玩。
127.0.0.1:6379> flushdbOK127.0.0.1:6379> keys *(empty list or set)127.0.0.1:6379> lpush list one # [lpush key value [value ...]]将一个值或者多个值插入到list的头部(左)(integer) 1127.0.0.1:6379> lpush list two(integer) 2127.0.0.1:6379> lpush list three(integer) 3127.0.0.1:6379> lrange list 0 -1 # [lrange key start stop]通过区间来获取具体的值,-1代表获取全部1) "three"2) "two"3) "one"127.0.0.1:6379> lrange list 0 1 # 可以发现的索引是倒着赋值的,后放进去的元素索引为01) "three"2) "two"127.0.0.1:6379> rpush list right # 将一个值或者多个值插入到list的尾部(右)(integer) 4127.0.0.1:6379> lrange list 0 -11) "three"2) "two"3) "one"4) "right"127.0.0.1:6379> lpop list # [lpop key]移除list左边的第一个元素"three"127.0.0.1:6379> rpop list # 移除list右边的第一个元素"right"127.0.0.1:6379> lrange list 0 -11) "two"2) "one"127.0.0.1:6379> lindex list 1 # [lindex key index]根据索引获取某一个值"one"127.0.0.1:6379> lindex list 0"two"127.0.0.1:6379> llen list # [llen key]获取list长度(integer) 2127.0.0.1:6379> lpush list three(integer) 3127.0.0.1:6379> lpush list three(integer) 4127.0.0.1:6379> lrange list 0 -11) "three"2) "three"3) "two"4) "one"127.0.0.1:6379> lrem list 1 one # [lrem key count value]移除指定的一个值,精确匹配(integer) 1127.0.0.1:6379> lrem list 2 three # 移除指定的两个值(integer) 2127.0.0.1:6379> lrange list 0 -11) "two"127.0.0.1:6379> lpush list hello(integer) 2127.0.0.1:6379> lpush list hello1(integer) 3127.0.0.1:6379> lpush list hello2(integer) 4127.0.0.1:6379> lpush list hello3(integer) 5127.0.0.1:6379> ltrim list 1 2 # [ltrim key start stop]通过下标截取指定长度的listOK127.0.0.1:6379> lrange list 0 -11) "hello2"2) "hello1"127.0.0.1:6379> rpoplpush list mylist # [rpoplpush source destination]移除列表中最后一个元素,并将它移动到新的列表中,如果新的列表不存在,则创建"hello1"127.0.0.1:6379> lrange list 0 -11) "hello2"127.0.0.1:6379> lrange mylist 0 -11) "hello1"127.0.0.1:6379> lrange list 0 -11) "hello2"127.0.0.1:6379> lset list 0 value1 # [lset key index value]修改列表中某一个key对应的值OK127.0.0.1:6379> lrange list 0 -11) "value1"127.0.0.1:6379> lset list 1 value2 # 如果修改列表中某一个不存在的key对应的值,会报错(列表不存在也会报错)(error) ERR index out of range127.0.0.1:6379> lrange list 0 -11) "value1"127.0.0.1:6379> linsert list before value1 value2 # [linsert key before|after pivot value]往列表中某个值的前面或后面插入某个值(integer) 2127.0.0.1:6379> lrange list 0 -11) "value2"2) "value1"127.0.0.1:6379> linsert list after value2 value3(integer) 3127.0.0.1:6379> lrange list 0 -11) "value2"2) "value3"3) "value1"
List数据类型可以实现消息队列,它本质上是一个双向的链表。从左边插入lpush,从右边取rpop。
List数据类型还可以实现栈,从左边插入lpush,从左边取lpop。
Set(集合)
Set是无序且不重复集合。
127.0.0.1:6379> sadd myset "hello" # sadd key member [member...] 往set中添加值(integer) 1127.0.0.1:6379> sadd myset "world"(integer) 1127.0.0.1:6379> smembers myset # smembers key 查看set中的元素1) "hello"2) "world"127.0.0.1:6379> SISMEMBER myset hello # SISMEMBER key member 查看set中是否有某个元素,存在返回1,不存在返回0(integer) 1127.0.0.1:6379> SISMEMBER myset nihao(integer) 0127.0.0.1:6379> SCARD myset # SCARD key 查看set中的元素个数(integer) 2127.0.0.1:6379> SREM myset hello # SREM key member 从set中移除某个元素(integer) 1127.0.0.1:6379> smembers myset1) "world"127.0.0.1:6379> sadd myset hahahaha(integer) 1127.0.0.1:6379> sadd myset hehehehe(integer) 1127.0.0.1:6379> SRANDMEMBER myset # SRANDMEMBER key [count] 随机从set中取count个元素,默认取一个"hehehehe"127.0.0.1:6379> SRANDMEMBER myset"world"127.0.0.1:6379> SPOP myset # SPOP key [count] 随机移除一个元素"world"127.0.0.1:6379> SMEMBERS myset1) "hahahaha"2) "hehehehe"127.0.0.1:6379> sadd myset2 "hello2"(integer) 1127.0.0.1:6379> SMOVE myset myset2 "hahahaha" # SMOVE source destination member 将指定元素从一个set中移动到另一个set中(integer) 1127.0.0.1:6379> SMEMBERS myset21) "hahahaha"2) "hello2"127.0.0.1:6379> SMEMBERS myset1) "hehehehe"127.0.0.1:6379> sadd key1 a(integer) 1127.0.0.1:6379> sadd key1 b(integer) 1127.0.0.1:6379> sadd key1 c(integer) 1127.0.0.1:6379> sadd key2 c(integer) 1127.0.0.1:6379> sadd key2 e(integer) 1127.0.0.1:6379> SDIFF key1 key2 # SDIFF key [key...] 查看两个set的差集1) "a"2) "b"127.0.0.1:6379> SINTER key1 key2 # SINTER key [key...] 查看两个set的交集1) "c"127.0.0.1:6379> SUNION key1 key2 # SUNION key [key...] 查看两个set的并集1) "e"2) "a"3) "b"4) "c"
set这种数据结构,常用于存储类似微博,B站等社交网站的用户关注数,粉丝数等数据,例如交集可以实现共同关注。
Hash(哈希)
在Java中我们都用过Map,Redis中的Hash和那个也是大同小异的,我们都知道Redis是key - value的形式来存储值,而Hash这种数据结构也是key - value来存储值的,所以现在就变成了key - <key - value>这种结构。
127.0.0.1:6379> hset myhash map1 hello # hset key field value 往hash中set一个值(integer) 1127.0.0.1:6379> hget myhash map1 # hget key field 从hash中取出field对应的值"hello"127.0.0.1:6379> hmset myhash map2 world map3 hahahaha # hmset key field value [field value...] 批量设置值OK127.0.0.1:6379> hmget myhash map1 map2 map3 # hmset key field [field...] 批量获取值1) "hello"2) "world"3) "hahahaha"127.0.0.1:6379> HGETALL myhash # HGETALL key 把一个hash中所有的值都获取出来1) "map1"2) "hello"3) "map2"4) "world"5) "map3"6) "hahahaha"127.0.0.1:6379> HDEL myhash map1 # HDEL key field 删除hash中指定的field(integer) 1127.0.0.1:6379> HGETALL myhash1) "map2"2) "world"3) "map3"4) "hahahaha"127.0.0.1:6379> HLEN myhash # HLEN key 获取hash的长度(integer) 2127.0.0.1:6379> HEXISTS myhash map1 # HEXISTS key field 判断hash中某个field是否存在,0-不存在;1-存在(integer) 0127.0.0.1:6379> HEXISTS myhash map2(integer) 1127.0.0.1:6379> HKEYS myhash # HKEYS key 获取key中所有的field1) "map2"2) "map3"127.0.0.1:6379> HVALS myhash # HVALS key 获取key中所有的value1) "world"2) "hahahaha"127.0.0.1:6379> HINCRBY myhash map1 1 # HINCRBY key field increment 给key中的value自增1(integer) 7127.0.0.1:6379> HSETNX myhash map4 age # HSETNX key field value 如果值不存在则创建,存在则不做任何操作(integer) 1127.0.0.1:6379> HSETNX myhash map4 hehehe(integer) 0
Hash这种数据结构更适合用于对象的存储,例如用户的信息。
Zset(有序集合)
在Set基础上,增加了一个值:score,称之为分数,用于排序。
127.0.0.1:6379> ZADD myzset 1 one # ZADD key [NX|XX] [CH] [INCR] score member [score member...] 往zset中添加值(integer) 1127.0.0.1:6379> ZADD myzset 2 two(integer) 1127.0.0.1:6379> ZADD myzset 3 three 4 four(integer) 2127.0.0.1:6379> ZRANGE myzset 0 -1 # ZRANGE key start stop [WITHSCORE] 从zset中获取值,start,stop表示索引1) "one"2) "two"3) "three"4) "four"127.0.0.1:6379> ZADD salary 2500 xiaoming(integer) 1127.0.0.1:6379> ZADD salary 5000 zhangsan(integer) 1127.0.0.1:6379> ZADD salary 3000 lisi(integer) 1127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # ZRANGEBYSCORE key min max [WITHSCORE] [LIMIT offset count] 获取全部的值且按升序排序(-inf表示负无穷,+inf表示正无穷)1) "xiaoming"2) "lisi"3) "zhangsan"127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf WITHSCORES # 获取全部的值并且显示分数1) "xiaoming"2) "2500"3) "lisi"4) "3000"5) "zhangsan"6) "5000"127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES # 显示工资小于或等于2500的员工1) "xiaoming"2) "2500"127.0.0.1:6379> ZREM salary xiaoming # ZREM key member [member] 移除元素(integer) 1127.0.0.1:6379> ZRANGE salary 0 -11) "lisi"2) "zhangsan"127.0.0.1:6379> ZCARD salary # ZCARD key 获取zset中的个数(integer) 2127.0.0.1:6379> ZADD myzset 1 hello(integer) 1127.0.0.1:6379> ZADD myzset 2 world(integer) 1127.0.0.1:6379> ZADD myzset 3 age(integer) 1127.0.0.1:6379> ZCOUNT myzset 1 3 # ZCOUNT key min max 获取min到max区间元素的个数(integer) 3127.0.0.1:6379> ZCOUNT myzset 1 2(integer) 2
Zset这种数据结构通常用于存储班级成绩,工资表之类的数据,方便排序。也可以用于实现权重。
到这里,我们的五种基本数据类型就讲完了。下面我们将介绍三种特殊的数据类型。
三种特殊的数据类型
geospatial(地理位置)
该种数据类型通常用于朋友圈的定位,附近的人,打车距离的计算等场景。下面我就具体来看看它怎么使用。
既然它是用于存储地理位置,那么我们需要去网上巴拉一些地理位置的数据。
查询地理位置经纬度地址(在线):https://jingweidu.bmcx.com/
它只有六个命令:
# GEOADD:添加地理位置# 通常我们会直接下载城市数据,通过Java或者别的程序一次性导入# 有效的经度从-180度到180度# 有效的纬度从-85.05112878度到85.05112878度# 当坐标位置超出上述指定范围时,该命令会报错:# (error) ERR invalid longitude, latitude pair ...127.0.0.1:6379> GEOADD china:city 116.23128 40.22077 beijing # GEOADD key longitude latitude member [longitude latitude member...] 将longitude经度、latitude经纬度、member名称添加到key中(integer) 1127.0.0.1:6379> GEOADD china:city 121.48941 31.40527 shanghai(integer) 1127.0.0.1:6379> GEOADD china:city 104.10194 30.65984 chengdu(integer) 1# GEOPOS:从key中返回所有给定位置元素的位置(经度和纬度)127.0.0.1:6379> GEOPOS china:city beijing1) 1) "116.23128265142440796"2) "40.22076905438526495"127.0.0.1:6379> GEOPOS china:city shanghai chengdu1) 1) "121.48941010236740112"2) "31.40526993848380499"2) 1) "104.10194188356399536"2) "30.65983886217613019"# GEODIST:返回两个给定位置之间的距离# 如果两个位置中有其中一个不存在,那么该命令返回空值# 指定单位的参数unit必须是以下单位中的一个:# - m表示单位为米(默认)# - km表示单位为千米# - mi表示单位为英里# - ft表示单位为英尺127.0.0.1:6379> GEODIST china:city beijing shanghai"1088644.3544"127.0.0.1:6379> GEODIST china:city beijing shanghai km"1088.6444"# GEORADIUS:以给定的经纬度为中心,找出某一半径内的元素# 范围可以使用以下其中一个单位:# - m表示单位为米# - km表示单位为千米# - mi表示单位为英里# - ft表示单位为英尺# 在给定以下选项时,命令会返回额外的信息:# - WITHDIST:在返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致# - WITHCOORD:将位置元素的经度和纬度也一并返回# - WITHHASH:以52位有符号整数的形式,返回位置元素经过原始GEOHASH编码的有序集合分值。这个选项主要用于底层应用或者调试,实际中作用不大。# GEORADIUS key longitude latitude radius m|km|ft|mi [WITHDIST] [WITHCOORD] [WITHHASH] [COUNT count]127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km1) "chengdu"127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km WITHDIST # 显示距离1) 1) "chengdu"2) "570.8980"127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km WITHCOORD # 显示具体的经纬度信息1) 1) "chengdu"2) 1) "104.10194188356399536"2) "30.65983886217613019"127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km WITHCOORD count 1 # 限制显示几个1) 1) "chengdu"2) 1) "104.10194188356399536"2) "30.65983886217613019"# GEORADIUSBYMEMBER:找出位于指定范围内的元素,中心点是由给定的位置元素决定# 这个命令和GEORADIUS一样,都可以找出位于指定范围内的元素,但是GEORADIUSBYMEMBER的中心点是由给定的位置元素决定的,而不像是GEORADIUS那样,使用输入的经度和纬度来决定中心点127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1500 km # 以北京为中心,1500km内的元素1) "shanghai"2) "beijing"# GEOHASH:返回一个或多个位置元素的GEOHASH表示# 通常使用表示位置的元素使用不同的技术,使用Geohash位置52点整数编码。由于编码和解码过程中所使用的初始最小和最大坐标不同,编码的编码也不同于标准。此命令返回一个标准的Geohash# 该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示。返回的geohashes具有以下特性:# - 他们可以缩短从右边的字符。它将失去精度,但仍将指向同一地区。# - 它可以在 geohash.org 网站使用,网址 http://geohash.org/<geohash-string>。查询例子:http://geohash.org/sqdtr74hyu0.# - 与类似的前缀字符串是附近,但相反的是不正确的,这是可能的,用不同的前缀字符串附近。# 这个命令目前没啥用,了解即可。127.0.0.1:6379> GEOHASH china:city beijing # 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,说明两个距离越近1) "wx4sucvncn0"
GEO底层实现原理其实是Zset,我们可以使用Zset来操作GEO。
通过上面的命令我们发现,官方给出的GEO命令中,并没有删除地理位置的命令,我们就可以用Zset来实现删除:
127.0.0.1:6379> ZRANGE china:city 0 -11) "chengdu"2) "shanghai"3) "beijing"127.0.0.1:6379> ZREM china:city chengdu(integer) 1127.0.0.1:6379> ZRANGE china:city 0 -11) "shanghai"2) "beijing"
Hyperloglog
要了解这个数据类型,我们得先了解什么是基数。
假如我们有一个数据集:
A = {1, 3, 5, 7, 9, 7, 8}
基数就是指这个数据集中不重复元素的个数 = 6(1, 3, 5, 7, 9, 8)
Hyperloglog是一种数据结构,它是用于做基数统计的算法。
应用场景
一般用于统计网页的用户访问量(一个人访问同一个网站多次,还是算作一个人)。
以前传统的方式是用Set集合去保存用户的id,由于Set集合是不允许重复的,然后就可以统计Set集合中元素的数量来统计用户访问量。但是如果用户的id是uuid或者一些比较长的字符串时,数据就会比较大。
这时候我们就可以用Hyperloglog来实现。
优点:
占用的内存是固定的。存储2^64不同元素的基数,只需要占用12KB的内存。
缺点:
存在0.81%的错误率,只能用于对数据统计精度要求不高的场景。
127.0.0.1:6379> PFADD mykey1 a b c d e f g h i j # PFADD key element [element...] 创建元素key(integer) 1127.0.0.1:6379> PFCOUNT mykey1 # PFCOUNT key [key...] 统计key中基数(integer) 10127.0.0.1:6379> PFADD mykey2 i j z x c v y b n m(integer) 1127.0.0.1:6379> PFCOUNT mykey2(integer) 10127.0.0.1:6379> PFMERGE mykey3 mykey1 mykey2 # PFMERGE destkey sourcekey [sourcekey...] 将多个key的并集赋给一个destkey(a b c d e f g h i j z x v y n m)OK127.0.0.1:6379> PFCOUNT mykey3(integer) 16
Bitmaps
Bitmaps称之为位图,也是一种数据结构。
它都是操作二进制位来进行记录,就只有0和1两个状态。
# 比如使用我们的Bitmaps记录周一到周日的打卡情况# 0-代表未打卡# 1-代表已打卡127.0.0.1:6379> SETBIT sign 0 0(integer) 0127.0.0.1:6379> SETBIT sign 1 1(integer) 0127.0.0.1:6379> SETBIT sign 2 1(integer) 0127.0.0.1:6379> SETBIT sign 3 0(integer) 0127.0.0.1:6379> SETBIT sign 4 1(integer) 0127.0.0.1:6379> SETBIT sign 5 0(integer) 0127.0.0.1:6379> SETBIT sign 6 0(integer) 0127.0.0.1:6379> GETBIT sign 3 # 查看周四是否有打卡,0-未打卡(integer) 0127.0.0.1:6379> GETBIT sign 4 # 查看周五是否有打卡,1-已打卡(integer) 1127.0.0.1:6379> BITCOUNT sign # BITCOUNT key [start end] 统计key中1的数量,这里就可以用来统计周一到周日打卡的天数(integer) 3
总结
本章介绍了Redis中一些常用的命令以及几种数据类型。希望看完这章,大家在开发中使用Redis时,不要永远都只是使用String这种数据类型。要学会灵活运用这些数据类型,才能让程序媛们刮目相看不是吗?


Hi
感谢你的到来
我不想错过你
编程那些烦心事







