1、Redis能干什么?
- 内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量)
2、基础知识
- redis有16个数据库,默认使用第0个,可以使用select进行切换
127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> dbsize #查看DB大小
(integer) 0
127.0.0.1:6379> keys * #查看数据库所有的key
(empty list or set)
127.0.0.1:6379> flushdb #清空当前数据库
OK
127.0.0.1:6379> flushall #清空全部数据库的内容
OK
-
Redis是单线程的!
核心:redis是将所有的数据放在内存中的,所有使用单线程去操作效率就是最高的,多线程(CPU上下文切换:耗时的操作),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上,在内存情况下,这个就是最佳的方案。
3、关于Redis Key的基本命令
127.0.0.1:6379> set name zhangsan #set key
OK
127.0.0.1:6379> exists name #判断当前的key是否存在
(integer) 1
127.0.0.1:6379> move name 1 #移除当前数据库的key
(integer) 1
127.0.0.1:6379> expire age 10 #设置key的过期时间,单位是秒
(integer) 1
127.0.0.1:6379> ttl age #查看当前key的过期时间
(integer) 7
127.0.0.1:6379> type name #查看当前key的类型
string
3.1、String(字符串)
127.0.0.1:6379> append name hello #追加字符串,如果当前key不存在,就相当于set key
(integer) 18
127.0.0.1:6379> strlen name #获取字符串的长度
(integer) 18
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) 0
127.0.0.1:6379> incrby views 10 #可以设置步长,指定增量
(integer) 10
127.0.0.1:6379> getrange key 0 3 #截取字符串 [0,3]
"hell"
127.0.0.1:6379> getrange key 0 -1 #获取全部的字符串
"hello,zhangsan"
127.0.0.1:6379> setex key 30 hello #设置key的值,并在30s后过期
OK
127.0.0.1:6379> ttl key
(integer) 26
127.0.0.1:6379> setnx key world #如果key不存在则创建key,存在则创建失败返回0
(integer) 0
3.2、List(列表)
127.0.0.1:6379> lpush list one #类似一个双端队列,左边是首部,右边是尾部
(integer) 1
127.0.0.1:6379> lpush list two #从左边插入数据
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #通过区间获取list中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> rpush list right #从右边插入数据
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list #从左边移除第一个元素
"three"
127.0.0.1:6379> rpop list #从右边移除第一个元素
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0 #通过下表获得list中的某一个值
"two"
127.0.0.1:6379> llen list #返回list的长度
(integer) 2
127.0.0.1:6379> lset list 0 hello #根据下标更新值,如果下标不存在则会报错
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "one"
127.0.0.1:6379> linsert list before hello world #将值插入到某个元素的前面或者后面
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "world"
2) "hello"
3) "one"
可以将List作为:消息队列(Lpush Rpop)、栈(Lpush Lpop)
3.3、Set(集合)
127.0.0.1:6379> sadd myset hello #往set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> smembers myset #查看set集合中的所有元素
1) "hello"
2) "world"
127.0.0.1:6379> sismember myset hello #判断某个值是否在set集合中
(integer) 1
127.0.0.1:6379> sismember myset other
(integer) 0
127.0.0.1:6379> scard myset #判断某个set中元素的个数
(integer) 2
127.0.0.1:6379> srem myset hello #移除set中某个元素
(integer) 1
127.0.0.1:6379> sdiff key1 key2 #两个集合的差集
1) "a"
127.0.0.1:6379> sinter key1 key2 #交集
1) "c"
2) "b"
127.0.0.1:6379> sunion key1 key2 #并集
1) "b"
2) "a"
3) "e"
4) "c"
3.4、Hash(哈希)
Map集合,key–map,值为一个map集合
127.0.0.1:6379> hset myhash 1 hello #存入一个map(key--value)
(integer) 1
127.0.0.1:6379> hset myhash 2 world
(integer) 1
127.0.0.1:6379> hget myhash 1 #根据key取出value
"hello"
127.0.0.1:6379> hmget myhash 1 2 #获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取全部的数据
1) "1"
2) "hello"
3) "2"
4) "world"
127.0.0.1:6379> hdel myhash 1 #根据key删除指定的key
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "2"
2) "world"
127.0.0.1:6379> hlen myhash #查看hash表的字段数量
(integer) 1
127.0.0.1:6379> hexists myhash hello #判断hash中指定字段是否存在
(integer) 0
127.0.0.1:6379> hkeys myhash #获取所有的key
1) "2"
127.0.0.1:6379> hvals myhash #获取所有的value
1) "world"
127.0.0.1:6379> hset myhash hello 1
(integer) 1
127.0.0.1:6379> hincrby myhash hello 2 #根据增量增加
(integer) 3
127.0.0.1:6379> hincrby myhash hello -4
(integer) -1
3.5、Zset(有序集合)
在set的基础上,增加了一个数值用来将元素进行排序
127.0.0.1:6379> zadd myset 1 hello #添加元素
(integer) 1
127.0.0.1:6379> zadd myset 2 world
(integer) 1
127.0.0.1:6379> zrange myset 0 -1 #查看所有的元素,默认从小到大
1) "hello"
2) "world"
127.0.0.1:6379> zrange myset 0 -1 withscores #查看所有元素及分数
1) "hello"
2) "1"
3) "world"
4) "2"
127.0.0.1:6379> zrem myset hello #移除元素
(integer) 1
127.0.0.1:6379> zcard myset #获取有序集合中的个数
(integer) 1
127.0.0.1:6379> zcount myset 0 11 #获取指定区间的成员数量
(integer) 2
案列思路:set排序 存储班级成绩单 工资单排序
普通消息 1,重要消息 2;带权重进行判断
4、三种特殊数据类型
4.1、geospatial地理位置
朋友的定位,附近的人,打车的距离计算?
Geo可以推算地理位置的信息,两地之间的距离,方圆几里的人。
可以查询一些测试数据:http://www.jsons.cn/lngcode/
127.0.0.1:6379> geoadd china:city 106.504962 29.533155 chongqi
(integer) 1 #添加地理位置(经度 纬度 名称)
127.0.0.1:6379> geoadd china:city 116.405285 39.904989 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.472644 31.231706 shanghai
(integer) 1
127.0.0.1:6379> geopos china:city beijin #获取指定城市的经度和纬度
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
127.0.0.1:6379> geopos china:city chongqi shanghai
1) 1) "106.50495976209640503"
2) "29.53315530684997015"
2) 1) "121.47264629602432251"
2) "31.23170490709807012"
127.0.0.1:6379> geodist china:city chongqi shanghai km #查看城市之间的直线距离
"1447.3973"
127.0.0.1:6379> georadius china:city 110 30 500 km #以110,30为中心。寻找方圆500km内的城市
1) "chongqi"
127.0.0.1:6379> georadiusbymember china:city chongqi 1450 km #找出位于指定元素周围的元素
1) "chongqi"
2) "shanghai"
- m 表示单位为米。
- km 表示单位为千米。
- mi 表示单位为英里。
- ft 表示单位为英尺。
GEO底层是实现原理其实就是Zset,我们可以使用Zset命令来操作GEO
127.0.0.1:6379> zrange china:city 0 -1 #查看地图中的全部元素
1) "chongqi"
2) "shanghai"
3) "beijing"
127.0.0.1:6379> zrem china:city chongqi #移除指定元素
(integer) 1
4.2、Hyperloglog
统计不同元素的个数,264数据只需要12KB内存
0.81%的错误率,统计UV任务可以忽略不计
127.0.0.1:6379> pfadd key1 a b c #创建第一组元素
(integer) 1
127.0.0.1:6379> pfadd key2 1 2 3 #创建第二组元素
(integer) 1
127.0.0.1:6379> pfmerge key key1 key2 #合并key1和key2到可以中去
OK
127.0.0.1:6379> pfcount key #统计不同元素的个数
(integer) 6
4.3、Bitmap
位运算:统计用户信息,活跃,不活跃;登录,未登录;打卡,未打卡;都是两个状态的,都可以使用Bitmap
使用bitmap来记录:周一到周日的打卡
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(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> setbit sign 7 0
(integer) 0
127.0.0.1:6379> getbit sign 4 #查看某一天是否打卡
(integer) 1
127.0.0.1:6379> bitcount sign #查看这周的打卡记录,也就是1的个数
(integer) 4
5、事务
redis单条命令是保证原子性的,但是事务不保证原子性!也就是一条命令出错,后面命令将继续执行
redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行!Exec
redis的事务
- 开启事务(multi)
- 命令入队
- 执行事务(exec)
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> get v2
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) (nil)
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard #取消事务
OK
127.0.0.1:6379> get k3 #事务队列中的命令都不会被执行
(nil)
6、乐观锁
悲观锁:
- 很悲观,认为什么时候都会出问题,无论做什么都会加锁!(影响性能)
乐观锁:
- 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据。
6.1、Redis监视测试
正常执行成功:
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> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec #事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用watch可以当作redis的乐观锁操作:
127.0.0.1:6379> watch money #监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> exec #执行之前,另一个线程修改了money的值,就会导致事务执行失败!
(nil)
如果修改失败,获取最新的值就好
127.0.0.1:6379> unwatch #如果事务执行失败,就先解锁
OK
127.0.0.1:6379> watch money #获取最新的值,再次监视,select version
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
127.0.0.1:6379> exec #对比监视的值是否发生了变化,如果没有变化,那么可以执行成功,如果改变就执行失败。
1) (integer) 90
2) (integer) 30