Redis是一个用C语言开发的,开源的高性能非关系型的键值对数据库。
Redis可以存储 键 和 不同类型数据结构值 之间的映射关系。键的类型只能是字符串,而值除了支持最 基础的五种数据类型 外,还支持一些 高级数据类型
基础数据类型:String字符串、List列表、hash字典、set集合,zset有序列表
高级数据类型:bitMap位图、Hyperloglog、布隆过滤器、GeoHash、Pub/Sub、Stream
与传统数据库不同的是 Redis 的数据是 存在内存 中的,所以 读写速度 非常 快,因此 Redis 被广泛应用于 缓存 方向,每秒可以处理超过 10
万次读写操作,是已知性能最快的 Key-Value 数据库。另外,Redis 也经常用来做 分布式锁。
官方表示,Redis是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了。
redis是c语言写的,官方提供的数据为100000+的QPS(服务器每秒可以查询的最多次数),完全不比Memcache差。
Redis为什么单线程还那么快?
- 误区:高性能的服务器一定是多线程的?多线程一定比单线程效率高?
- 速度:CPU > 内存 > 硬盘
- 核心:
- Redis是将所有的数据都放在内存中,所以使用单线程的效率是最高的
- 对于多线程,上下文切换是一种十分耗时的操作,对于内存系统来说,单线程效率就是最高的。
- 采用多路复用I/O技术可以让单个线程高效的处理多个网络请求。
- 上下文切换:当前任务在执行完CPU时间片后,切换到另一个任务需要保存自己的状态,以便下次切回这个任务时,可以再加载这个任务的状态,任务从保存到再加载的过程就是一次上下文切换,上下文切换是计算密集型的,需要相当可观的处理器时间,可能是操作系统中时间消耗最大的操作。
127.0.0.1:6379> set key value1
OK
127.0.0.1:6379> APPEND key value2 #动态的修改存取的字符串
(integer) 12
127.0.0.1:6379> get key
"value1value2"
127.0.0.1:6379> STRLEN key #当前key的长度
(integer) 12
127.0.0.1:6379> SET views 0
OK
127.0.0.1:6379> INCR views #加1操作,用作浏览量
(integer) 1
127.0.0.1:6379> DECR views #减1操作
(integer) 0
127.0.0.1:6379> INCRBY views 10 #增加指定数字
(integer) 10
127.0.0.1:6379> DECRBY views 5
(integer) 5
127.0.0.1:6379> GETRANGE key 0 3 #字符串的截取[0,3]闭区间
"valu"
#setex (set with expire) 设置过期时间
#setnx (set if not exist) 如果不存在的时候设置
127.0.0.1:6379> SETEX key seconds value
127.0.0.1:6379> SETNX key value
#mset 批量插入键值对
#批量获取mget
#MSETNX 原子性操作,要么一起成功,要么一起失败
127.0.0.1:6379> mset key value [key value ...]
127.0.0.1:6379> MGET key [key ...]
127.0.0.1:6379> MSETNX key value [key value ...]
127.0.0.1:6379> MSET k1 v1 k2 v2
OK
127.0.0.1:6379> MSETNX k1 v1 k4 v4
(integer) 0
127.0.0.1:6379> KEYS *
1) "k1"
2) "k2"
127.0.0.1:6379>
#存取对象的写法
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 20 #推荐写法,拿出不用解析
OK
127.0.0.1:6379> keys *
1) "user:1:age"
2) "user:1:name"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "20"
127.0.0.1:6379> set user:1 {name:zhangsan,age:20}
OK
127.0.0.1:6379> get user:1
"{name:zhangsan,age:20}"
#组合方法,如果没有该键,返回nil,如果该键曾经有值,返回该键的上一个值
127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> getset db mongodb
"redis"
String类型的使用场景:
双端队列,可以进行栈,队列,阻塞队列的操作
所有的list操作都是l
开头的
127.0.0.1:6379> LPUSH list one two three #将一个值或者多个值插入列表的头部(左)
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> RPUSH list four ##将一个值或者多个值插入列表的尾部(右侧)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
#移除操作,同样分左pop和右pop
127.0.0.1:6379> LPOP list
"three"
127.0.0.1:6379> RPOP list
"four"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
#通过下标获取lindex
127.0.0.1:6379> LINDEX list 1
"one"
#返回列表的长度
127.0.0.1:6379> LLEN list
(integer) 2
#移除操作 需要输入key 数目(list中的value可以重复,移除从上到下依次移除) 具体的值
127.0.0.1:6379> LREM key count element
#截取操作
127.0.0.1:6379> LRANGE list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> LTRIM list 0 2 #截取范围 [0,2)
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "five"
2) "four"
3) "three"
127.0.0.1:6379>
#移除源列表的最后一个元素并放置在一个新列表中
127.0.0.1:6379> RPOPLPUSH list newList
"three"
127.0.0.1:6379> LRANGE list 0 -1
1) "five"
2) "four"
127.0.0.1:6379> LRANGE newList 0 -1
1) "three"
#lset主要用于list的更新操作
#如果下标存在,更新,如果不存在,报错
127.0.0.1:6379> LSET key index element
#LINSERT 插入操作 在某个值的前面或者后面 插入数据
127.0.0.1:6379> LINSERT key BEFORE|AFTER pivot element
127.0.0.1:6379> LRANGE list 0 -1
1) "4"
2) "four"
127.0.0.1:6379> LINSERT list before four 40
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "4"
2) "40"
3) "four"
List小结:
set中的值是不能重复的
127.0.0.1:6379> SADD myset set1 set2 set3 #set集合中添加元素
(integer) 3
127.0.0.1:6379> SMEMBERS myset #查看set的所有值
1) "set3"
2) "set2"
3) "set1"
127.0.0.1:6379> SISMEMBER myset set #判断某一个值是否在set集合中
(integer) 0
127.0.0.1:6379> SISMEMBER myset set1
(integer) 1
127.0.0.1:6379> SADD myset set3 #不支持添加重复的值 与java相同
(integer) 0
127.0.0.1:6379> SCARD myset #当前set的数量
(integer) 3
127.0.0.1:6379> SREM myset set2 # 移除set中的元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "set3"
2) "set1"
127.0.0.1:6379> SRANDMEMBER myset 1 #从集合中随机抽取
1) "set1"
127.0.0.1:6379> SRANDMEMBER myset 1
1) "set3"
127.0.0.1:6379> SPOP key [count] #随机移除元素
127.0.0.1:6379> SMOVE source destination member #移动元素到另一个集合中
127.0.0.1:6379> SMEMBERS myset
1) "set4"
2) "set3"
3) "set5"
4) "set1"
5) "set6"
127.0.0.1:6379> SMEMBERS myset3
1) "set9"
2) "set6"
3) "set3"
4) "set12"
127.0.0.1:6379> SDIFF myset myset3 #sdiff 查看两个集合中的不同元素
1) "set4"
2) "set1"
3) "set5"
127.0.0.1:6379> SUNION myset myset3 #并集
1) "set4"
2) "set3"
3) "set6"
4) "set1"
5) "set9"
6) "set12"
7) "set5"
127.0.0.1:6379> SINTER myset myset3 #交集
1) "set6"
2) "set3"
Map集合,key-map的本质和String类型没有太大区别
hash更适用于对象存储,String更适合字符串的存储
#格式
127.0.0.1:6379> hset key field value [field value ...]
127.0.0.1:6379> HGET key field
127.0.0.1:6379> HSET hash name zhangsan age 20 #存数据
(integer) 2
127.0.0.1:6379> hget hash name #取数据
"zhangsan"
127.0.0.1:6379> HMGET hash name age #取多个数据
1) "zhangsan"
2) "20"
127.0.0.1:6379> HGETALL hash #获取所有
1) "name"
2) "zhangsan"
3) "age"
4) "20"
127.0.0.1:6379> HDEL hash age #删除操作
(integer) 1
127.0.0.1:6379> HGETALL hash
1) "name"
127.0.0.1:6379> HLEN hash #获取hash的内容长度
(integer) 1
127.0.0.1:6379> HEXISTS hash age #判断key是否存在
(integer) 0
127.0.0.1:6379> HEXISTS hash name
(integer) 1
127.0.0.1:6379> HVALS hash #只获取hash中的值
1) "zhangsan"
2) "20"
3) "male"
127.0.0.1:6379> HKEYS hash #只获取hash中的键
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> HINCRBY hash age 1 #使用hincrby进行增操作
(integer) 21
在set的基础上增加一个值score
,表示权重,通过权重将元素从小到大排序,没有修改操作。内部实现依赖跳跃列表的数据结构。
#示例
127.0.0.1:6379> ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
127.0.0.1:6379> ZADD zset 2500 zhangsan 5000 lisi 2650 wangxiaoer 4300 zhaoxiaosan
(integer) 4
#排序
127.0.0.1:6379> ZRANGE zset 0 -1 #获取值
1) "zhangsan"
2) "wangxiaoer"
3) "zhaoxiaosan"
4) "lisi"
#
127.0.0.1:6379> ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
#根据分值区间遍历zset
127.0.0.1:6379> ZRANGEBYSCORE zset 4000 5000
1) "zhaoxiaosan"
2) "lisi"
#根据分值区间遍历zset,同时遍历分值
127.0.0.1:6379> ZRANGEBYSCORE zset 4000 5000 withscores
1) "zhaoxiaosan"
2) "4300"
3) "lisi"
4) "5000"
#-inf 负无穷 +inf 正无穷
127.0.0.1:6379> ZRANGEBYSCORE zset -inf +inf withscores
#移除操作
127.0.0.1:6379> zrem zset zhaoxiaosan
(integer) 1
#获取zset的元素数量
127.0.0.1:6379> ZCARD zset
(integer) 3
#获取指定区间的人员数量
127.0.0.1:6379> ZCOUNT zset 3000 5000
(integer) 1
zset使用场景:
#插入
127.0.0.1:6379> GEOADD key longitude latitude member [longitude latitude member ...]
#例如
#两级无法直接添加,我们一般通过java程序一次性导入数据
127.0.0.1:6379> GEOADD address 116 39 beijing 112 28 hunan 121 31 shanghai
(integer) 3
#获取城市经纬度 获取当前定位,获得的定位一定是个坐标值
127.0.0.1:6379> GEOPOS address beijing hunan
1) 1) "116.00000113248825073"
2) "38.99999918434559731"
2) 1) "112.00000137090682983"
2) "27.99999879696989069"
#返回两个给定位置的距离 表示直线距离
- m 米
- km 千米
- ft 英里
- mi 英尺
127.0.0.1:6379> GEODIST key member1 member2 [m|km|ft|mi]
127.0.0.1:6379> GEODIST address beijing shanghai
"999207.7044"
#GEORADIUS 附近的人的位置 给定经纬度,给定半径
- WITHCOORD 经纬度
- WITHDIST 距离
- WITHHASH 位置元素的 geohash 值
- count 指定数量
127.0.0.1:6379> GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]
#某个元素位置的hash值
127.0.0.1:6379> GEOHASH address beijing shanghai
1) "wwfmzesx7y0"
2) "wtw037ms070"
#geo属于zset的扩展
对于geo的获取和移除,使用zrange zrem
127.0.0.1:6379> ZRANGE address 0 -1
1) "hunan"
2) "shanghai"
3) "beijing"
什么是基数?
例如A{1,3,5,7,8,7}
,B{1,3,5,7,8}
其基数(不重复的元素)等于5
可以接受一定的误差
Hyperloglog可以用在网站的UV上(一个人访问一个网站多次,但是还是算作一个人)
传统的方式:set 保存用户的id,然后就可以统计set中的元素数量作为标准判断,这个方式如果保存大量的用户id,就会比较麻烦,我们的目的是为了基数,还不是保存用户的id
Hyperloglog的优缺点
可以用来统计用户信息:活跃、不活跃!登录、未登录!打卡、365打卡!
#从打卡的角度记录
#打卡为1 未打卡为0
127.0.0.1:6379> SETBIT sign 0 1
(integer) 0
127.0.0.1:6379> SETBIT sign 1 0
(integer) 0
127.0.0.1:6379> SETBIT key offset value
#查看某一天是否打卡
127.0.0.1:6379> GETBIT key offset
#统计操作
127.0.0.1:6379> BITCOUNT key [start end]
日常针对数据库的访问,读的操作要远远大于写的操作,所以需要读的可能性比写大很多。Mysql这类数据库QPS大概是在1w左右,而基于内存的数据库Redis可以达到10w。所以直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的。
因此使用Redis,可以提高数据的访问速度**(高性能),并且极大减小数据库的压力(高并发)**
限于成本原因,一般我们只是使用Redis存储一些常用和主要数据,比如用户登录的信息。
一般而言在使用 Redis 进行存储的时候,我们需要从以下几个方面来考虑:
是什么-> 为什么 -> 怎么解决