以下内容摘自redis中文官网:http://www.redis.cn/
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作**数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询**, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)==>可以用来做集群
redis的key值是二进制安全带,任何二进制序列都可以作为key值。从string到jpeg文件内容都可以,甚至是一个空字符串。
127.0.0.1:6379> exists name #判断key是否存在,1表示存在,0表示不存在 (integer) 1 127.0.0.1:6379> exists name2 (integer) 0 127.0.0.1:6379> clear 127.0.0.1:6379> move name 1 #把这个key从当前数据库移动到第几个数据库,后面表示第几个数据库 (integer) 1 127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> keys * 1) "name" 127.0.0.1:6379[1]> expire name 10 #给这个key设置超时时间 (integer) 1 127.0.0.1:6379[1]> ttl name #获取这个key的有效时间,-2表示已经超时不存在了 (integer) -2 127.0.0.1:6379[1]> get name (nil) 127.0.0.1:6379[1]> set name 7 OK 127.0.0.1:6379[1]> type name #查看key的数据类型 string
docker run
-d以守护进程方式启动,守护进程:参考java的守护线程,即使ctrl+c退出,也不会停止这个容器的运行
–name 设置name,所有需要用id访问的命令都可以用name替代
这里报错是因为我之前跑过这个redis,如果使用docker images 就会看到myredis这个容器已经存在了
mirana@末小七:/mnt/c/Users/末小七$ docker run -d --name myredis redis docker: Error response from daemon: Conflict. The container name "/myredis" is already in use by container "3bfc98b3236f47bd46475bb3213bb0bef03782b01e2f3c1a9bde90899333af45". You have to remove (or rename) that container to be able to reuse that name. See 'docker run --help'.
所以:
使用restart命令直接跑容器就可
mirana@末小七:/mnt/c/Users/末小七$ docker restart myredis myredis
进入redis交互
mirana@末小七:/mnt/c/Users/末小七$ docker exec -it myredis redis-cli 127.0.0.1:6379>
这里歪个楼:
redis的端口为啥默认是6379?
拿出手机–敲下键盘–这其实是redis作者喜欢的一个明星名字,有兴趣可以去知乎搜下
查找redis的路径
mirana@末小七:/mnt/c/Users/末小七$ whereis redis redis: /etc/redis
进入对应路径
mirana@末小七:/mnt/c/Users/末小七$ cd /etc/redis
展示当前目录下内容,打开redis.conf配置文件
就可以看到这行,redis默认有16个数据库,可以修改配置文件来自己定义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OsqN49a2-1631719421521)(image-20210911120530196.png)]
切换数据库
redis默认是第0个数据库,可以通过select命令切换数据库
比如切换到第三个数据库
127.0.0.1:6379> select 3 OK
查看当前数据库多少个key
127.0.0.1:6379[3]> dbsize (integer) 0 127.0.0.1:6379[3]> set name 77 #set OK 127.0.0.1:6379[3]> set name2 88 OK 127.0.0.1:6379[3]> dbsize (integer) 2
查看当前数据库所有的key
127.0.0.1:6379[3]> keys * 1) "name2" 2) "name"
清空当前数据库
127.0.0.1:6379[3]> flushdb OK 127.0.0.1:6379[3]> dbsize (integer) 0
轻空所有的数据库
如下测试,先在第三个数据库塞值,再去第0个数据库塞值,然后flushdb
然后就会看到第0个数据库跟第3个数据库都空了
127.0.0.1:6379[3]> set name 777 OK 127.0.0.1:6379[3]> select 0 OK 127.0.0.1:6379> set name 77777 OK 127.0.0.1:6379> dbsize (integer) 1 127.0.0.1:6379> flushall OK 127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> select 3 OK 127.0.0.1:6379[3]> dbsize (integer) 0
redis共八种数据结构,其中五种常用数据结构,3种特殊
value的值可以是任何种类的字符串,包括二进制数据。最大不能超过512MB。除了是字符串也可以用数字,图片或者是序列化对象。
使用场景
计数器
统计多单位的数量
粉丝数
对象缓存、存储
分布式锁
常见命令:
127.0.0.1:6379> set key name OK **************set支持类似json的写法,如下**************** 127.0.0.1:6379> set user:1:name 77 OK 127.0.0.1:6379> set user:1:age 18 OK 127.0.0.1:6379> keys * 1) "user:1:age" 2) "user:1:name" ****************************** 127.0.0.1:6379> exists name #判断这个key是否存在 (integer) 0 127.0.0.1:6379> append key 77 #在key的value后面追加 (integer) 6 127.0.0.1:6379> get key "name77" 127.0.0.1:6379> strlen key #获取这个key的value的长度 (integer) 6 127.0.0.1:6379> append key ",today" (integer) 12 127.0.0.1:6379> strlen key (integer) 12 127.0.0.1:6379> append key1 zhangsan #append的时候,如果这个key不存在就set (integer) 8 127.0.0.1:6379> keys * 1) "key1" 2) "key"
incr、decr、incrby、decrby
原子递增/递减
127.0.0.1:6379> set viewcount 0 OK 127.0.0.1:6379> get viewcount "0" 127.0.0.1:6379> incr viewcount #递增这个key的值 (integer) 1 127.0.0.1:6379> get viewcount "1" 127.0.0.1:6379> incr viewcount (integer) 2 127.0.0.1:6379> get viewcount "2"
127.0.0.1:6379> flushdb OK 127.0.0.1:6379> set view 0 OK 127.0.0.1:6379> incrby view 2 #按照步长递增这个key的值 (integer) 2 127.0.0.1:6379> incrby view 2 (integer) 4 127.0.0.1:6379> get view "4"
127.0.0.1:6379> set total 100 OK 127.0.0.1:6379> decr total #递减这个key的值 (integer) 99 127.0.0.1:6379> decr total (integer) 98 127.0.0.1:6379> get total "98" 127.0.0.1:6379> decrby total 2 #按照步长递减这个key的值 (integer) 96 127.0.0.1:6379> decrby total 2 (integer) 94 127.0.0.1:6379> get total "94"
incr、decr都是原子操作
获取字符串
命令格式:GETRANGE key start end
当start end是0 -1 表示返回全部
127.0.0.1:6379> set return hello,77,fignting OK 127.0.0.1:6379> get return "hello,77,fignting" 127.0.0.1:6379> getrange return 0 2 "hel" 127.0.0.1:6379> getrange return 0 -1 "hello,77,fignting"
set with expire :给这个key设置值并设置过期时间
命令格式:SETEX key seconds value
127.0.0.1:6379> setex name 10 7 OK 127.0.0.1:6379> ttl name #获取key的有效时间 (integer) 8 127.0.0.1:6379> get name "7"
这个key如果不存在就设置
127.0.0.1:6379> setnx address shenzhen (integer) 1 127.0.0.1:6379> keys * 1) "return" 2) "address" 3) "age" 127.0.0.1:6379> setnx age 19 #如果这个key存在就会失败 (integer) 0
经常用于分布式锁,不过更好的是用zk来实现。因为zk有临时节点,机制类似于sql的for update,一旦链接挂掉,这个锁会自动释放,就不需要考虑redis中下面这么多场景
setnx实现分布式锁,有几个需要注意的点:
1)要设置超时时间
防止setnx…业务代码…释放锁,在执行业务代码的时候jvm挂掉了,那这个就死锁了
2)释放锁的时候要判断set的值是不是你设置的值
jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
try{
//业务代码
}catch{
//异常处理
}finally{
//释放 即del key
}
如果你setnx的时候设置了超时时间5s,业务代码执行了6s,在这超时的1s,锁释放了,这时另外一个线程重新set了值,这时你的代码finally里释放的就是别人的锁。所以你需要保证你释放的是自己的锁(可以通过redis,给这个key设置个uuid,然后在finally中先获取key的值,然后判断是否是你设置的uuid来实现)
3)还是上个问题
finally代码:
getkey
delkey
如果在getkey,刚获取到,另外一个线程正好在你获取到之后来了,你del的就又是别人的锁了。
所以需要保证get跟del是同一条指令,这时用java代码已经无法保证了,要用lua脚本
更多注意事项,参考博客:https://mp.weixin.qq.com/s/qJK61ew0kCExvXrqb7-RSg
批量设置、获取
msetnx 同setnx ,批量设置的原子操作
127.0.0.1:6379> mset name 77 age 17 OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> msetnx age 19 collage xidian (integer) 0
如果不存在,返回nil
存在,则返回原来的值并更新
127.0.0.1:6379> getset db redis (nil) 127.0.0.1:6379> get db "redis" 127.0.0.1:6379> getset db mysql "redis" 127.0.0.1:6379> get db "mysql"
时间复杂度为O(1)
命令格式 SCAN cursor [MATCH pattern] [COUNT count]
以下摘自redis中文官网,有部分改动
SSCAN 命令用于迭代SET集合中的元素。
HSCAN 命令用于迭代Hash类型中的键值对。
ZSCAN 命令用于迭代SortSet集合中的元素和元素对应的分值
以上列出的四个命令都支持增量式迭代,它们每次执行都只会返回少量元素,所以这些命令可以用于生产环境,而不会出现像 KEYS 或者 SMEMBERS 命令带来的可能会阻塞服务器的问题。
不过,SMEMBERS 命令可以返回集合键当前包含的所有元素, 但是对于SCAN这类增量式迭代命令来说,有可能在增量迭代过程中,集合元素被修改,对返回值无法提供完全准确的保证。
SCAN, SSCAN, HSCAN 和 ZSCAN 四个命令的工作方式都非常相似,需要注意的是SSCAN, HSCAN ,ZSCAN命令的第一个参数总是一个key; SCAN 命令则不需要在第一个参数提供任何key,因为它迭代的是当前数据库中的所有key。
SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程
当SCAN命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
SCAN增量式迭代命令并不保证每次执行都返回某个给定数量的元素,甚至可能会返回零个元素, 但只要命令返回的游标不是 0 , 应用程序就不应该将迭代视作结束。
不过命令返回的元素数量总是符合一定规则的, 对于一个大数据集来说, 增量式迭代命令每次最多可能会返回数十个元素;而对于一个足够小的数据集来说, 如果这个数据集的底层表示为编码数据结构(小的sets, hashes and sorted sets), 那么增量迭代命令将在一次调用中返回数据集中的所有元素。
如果需要的话,用户可以通过增量式迭代命令提供的COUNT选项来指定每次迭代返回元素的最大值。SCAN增量式迭代命令并不保证每次执行都返回某个给定数量的元素,甚至可能会返回零个元素, 但只要命令返回的游标不是 0 , 应用程序就不应该将迭代视作结束。
不过命令返回的元素数量总是符合一定规则的, 对于一个大数据集来说, 增量式迭代命令每次最多可能会返回数十个元素;而对于一个足够小的数据集来说, 如果这个数据集的底层表示为编码数据结构(小的sets, hashes and sorted sets), 那么增量迭代命令将在一次调用中返回数据集中的所有元素。
如果需要的话,用户可以通过增量式迭代命令提供的COUNT选项来指定每次迭代返回元素的最大值。
对于增量式迭代命令不保证每次迭代所返回的元素数量,我们可以使用COUNT选项, 对命令的行为进行一定程度上的调整。COUNT 选项的作用就是让用户告知迭代命令, 在每次迭代中应该从数据集里返回多少元素。使用COUNT 选项对于对增量式迭代命令相当于一种提示, 大多数情况下这种提示都比较有效的控制了返回值的数量。
注意: **并非每次迭代都要使用相同的 COUNT 值 **,用户可以在每次迭代中按自己的需要随意改变 COUNT 值, 只要记得将上次迭代返回的游标用到下次迭代里面就可以了。
所以最好不要使用scan命令来做分页之类的功能,类似踩坑经验请看博客:
https://blog.csdn.net/zjcsuct/article/details/108138876
应用场景:
可以用来实现栈(lpush、lpop)、队列、消息队列(lpush、rpop)、阻塞访问(lpush与rpop:可以利用阻塞式访问的brpop与blpop命令来实现)
可以想象成从左边(链表头)写入
127.0.0.1:6379> lpush list one two #可以看出,先进后出 (integer) 2 127.0.0.1:6379> lrange list 0 1 1) "two" 2) "one" 127.0.0.1:6379> lrange list 0 2 3) "two" 4) "one" 127.0.0.1:6379> lrange list 0 -1 #获取list的值,也可以根据index获取具体某个位置的值,如果 0 -1就是获取全部 1) "two" 2) "one"
从右边(链表尾部)写入
127.0.0.1:6379> rpush list zero (integer) 3 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 3) "zero"
127.0.0.1:6379> lpop list #移除list的第一个元素 "two" 127.0.0.1:6379> lrange list 0 -1 1) "one" 2) "zero" 127.0.0.1:6379> rpop list #移除list的最后一个值 "zero" 127.0.0.1:6379> lrange list 0 -1 1) "one"
获取指定元素的值
127.0.0.1:6379> lpush list zero one two three (integer) 4 127.0.0.1:6379> lindex list 1 "two" 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "zero"
获取list长度
127.0.0.1:6379> llen list (integer) 4
移除list中指定个数的value
命令格式:LREM key count value
127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "zero" 5) "zero" 6) "zero" 7) "1" 127.0.0.1:6379> lrem list 6 1 (integer) 1 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "zero" 5) "zero" 6) "zero" 127.0.0.1:6379> lrem list 2 zero (integer) 2 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "zero"
截取list
命令格式:LTRIM key start stop
127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "zero" 127.0.0.1:6379> ltrim list 0 1 OK 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two"
移除列表最后一个元素,并且lpush到一个新的list
127.0.0.1:6379> lrange list 0 -1 1) "one" 2) "two" 3) "three" 4) "four" 127.0.0.1:6379> rpoplpush list otherlist "four" 127.0.0.1:6379> lrange otherlist 0 -1 1) "four"
lset:
将列表中指定下标的值替换
先判断这个列表是否存在,如果不存在就会报错
再判断指定的下标是否存在,不存在也会报错
127.0.0.1:6379> exists list2 #判断这个list是否存在 (integer) 0 127.0.0.1:6379> lset list2 0 77 #这个list不存在就报错 (error) ERR no such key 127.0.0.1:6379> lpush list2 1 (integer) 1 127.0.0.1:6379> lset list2 1 77 #如果这个下标不存在也会报错 (error) ERR index out of range 127.0.0.1:6379> lset list2 0 77 #存在就会更新 OK 127.0.0.1:6379> lrange list2 0 -1 1) "77"
将某个具体的值插入到列表中的指定元素的前面或者后面
127.0.0.1:6379> lrange list2 0 -1 1) "77" 127.0.0.1:6379> linsert list2 before 77 88 (integer) 2 127.0.0.1:6379> lrange list2 0 -1 1) "88" 2) "77" 127.0.0.1:6379> linsert list2 after 77 66 (integer) 3 127.0.0.1:6379> lrange list2 0 -1 1) "88" 2) "77" 3) "66"
本质就是个k-map结构,可用于存放经常需要改变的对象。string虽然也可以,但是最好的做法是用hash
存放/读取 hash中的某个key
hset也可以在hash中存放多个key,在Redis 4.0.0之后的版本,hmset已被弃用,使用hset即可
127.0.0.1:6379> hset myhash name 77 age 17 address shenzhen (integer) 3 127.0.0.1:6379> hget myhash name "77"
批量set/get
As per Redis 4.0.0, HMSET is considered deprecated. Please prefer HSET in new code.------源自redis官网
127.0.0.1:6379> hmset myhash name 88 name2 99 OK 127.0.0.1:6379> hmget myhash name name2 1) "88" 2) "99" 127.0.0.1:6379> hsetnx myhash name 00 (integer) 0
当key不存在的时候,设置key
key存在则设置失败
类似于string的setnx,可以用来做分布式
127.0.0.1:6379> hsetnx myhash name 00 (integer) 0 127.0.0.1:6379> hsetnx myhash name3 11 (integer) 1
时间复杂度为O(N) ,当数据过多时,应使用hscan来查找
127.0.0.1:6379> hgetall myhash 1) "name" 2) "88" 3) "age" 4) "17" 5) "address" 6) "shenzhen" 7) "name2" 8) "99" 9) "name3" 10) "11"
参考scan命令
127.0.0.1:6379> hscan myhash 111 match name count 1 1) "0" 2) 1) "name" 2) "88"
127.0.0.1:6379> hdel myhash name2 name3 (integer) 2 127.0.0.1:6379> hgetall myhash 1) "name" 2) "88" 3) "age" 4) "17" 5) "address" 6) "shenzhen"
127.0.0.1:6379> hlen myhash (integer) 3
127.0.0.1:6379> hexists myhash name (integer) 1
127.0.0.1:6379> hkeys myhash 1) "name" 2) "age" 3) "address" 127.0.0.1:6379> hvals myhash 1) "88" 2) "17" 3) "shenzhen"
127.0.0.1:6379> hincrby myhash age 1 (integer) 18 127.0.0.1:6379> hget myhash age "18"
不重复的
应用场景
批量添加
127.0.0.1:6379> sadd name1 77 88 (integer) 1
查看set的所有元素,同hget命令,效率较低,推荐用sscan
127.0.0.1:6379> smembers name1 1) "77" 2) "88"
查看这个元素在set中是否存在
127.0.0.1:6379> sismember name1 99 (integer) 0
查看set中元素总数
127.0.0.1:6379> scard name1 (integer) 2
移除set中的指定元素
127.0.0.1:6379> srem name1 88(integer) 1
随机获取set中指定个数的元素
127.0.0.1:6379> srandmember name1 2 1) "77" 2) "88" 127.0.0.1:6379> srandmember name1 2 1) "99" 2) "88""
随机移除set中指定个数的元素
127.0.0.1:6379> spop name1 1 1) "77" 127.0.0.1:6379> smembers name1 1) "88" 2) "99"
把一个set中的指定元素移动到另一个set中
127.0.0.1:6379> sadd set1 11 22 33 (integer) 3 127.0.0.1:6379> sadd set2 99 (integer) 1 127.0.0.1:6379> smove set1 set2 33 (integer) 1 127.0.0.1:6379> smembers set2 1) "33" 2) "99" 127.0.0.1:6379> smembers set1 1) "11" 2) "22"
sdiff:获取两个set的差集
sinter:获取两个set的交集
sunion:获取两个set的并集
127.0.0.1:6379> smembers set1 1) "11" 2) "22" 3) "33" 127.0.0.1:6379> smembers set2 1) "33" 2) "99" 127.0.0.1:6379> sdiff set1 set2 1) "11" 2) "22" 127.0.0.1:6379> sinter set1 set2 1) "33" 127.0.0.1:6379> sunion set1 set2 1) "11" 2) "22" 3) "33" 4) "99"
有序的set
应用场景:
zadd:添加
zrange:查看set中元素,0 -1就是返回全部,zscan效率较高
127.0.0.1:6379> zadd myzset 0 77 1 88 (integer) 2 127.0.0.1:6379> zrange myzset 0 -1 1) "77" 2) "88" 127.0.0.1:6379> zadd myzset 5 99 4 10 (integer) 2 127.0.0.1:6379> zrange myzset 0 -1 1) "77" 2) "88" 3) "10" 4) "99"
命令格式
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
zrangebyscore :score按升序在指定区间(两边均可指定>或者≥,<或者≤)内返回,可做分页,可指定返回数量
zrevrangebyscore:score按降序返回
127.0.0.1:6379> zadd myzset 1 one 2 two 3 three (integer) 3 127.0.0.1:6379> zrange myzset 0 -1 1) "one" 2) "two" 3) "three" 127.0.0.1:6379> zrangebyscore myzset -inf +inf 1) "one" 2) "two" 3) "three" 127.0.0.1:6379> zrangebyscore myzset -inf +inf withscores 1) "one" 2) "1" 3) "two" 4) "2" 5) "three" 6) "3" 127.0.0.1:6379> zrevrangebyscore myzset +inf -inf 1) "three" 2) "two" 3) "one"
ZRANGEBYLEX key min max [LIMIT offset count]
ZREVRANGEBYLEX key max min [LIMIT offset count]命令说明,这两个命令的前提是zset中的score权重必须一样
zrangebylex:按字典顺序升序排列 (两边均可指定>或者≥,<或者≤),可指定区间,也可以做分页,指定返回条数
zrevrangebylex:降序
127.0.0.1:6379> zadd myzset 0 a 0 c 0 b (integer) 3 127.0.0.1:6379> zrangebylex myzset - + 1) "a" 2) "b" 3) "c" 127.0.0.1:6379> zadd names 0 Toumas 0 Jake 0 Bluetuo 0 Gaodeng 0 Aimini 0 Aidehua (integer) 6 127.0.0.1:6379> zrevrangebylex names + - 1) "Toumas" 2) "Jake" 3) "Gaodeng" 4) "Bluetuo" 5) "Aimini" 6) "Aidehua"
移除zset的指定元素
127.0.0.1:6379> zrem myzset zero (integer) 1
返回zset中元素总数
127.0.0.1:6379> zcard myzset (integer) 3
返回指定score范围的元素个数
127.0.0.1:6379> zcount myzset 1 3 (integer) 3
底层实现原理就是zset,因此zset的所有指令都可以使用,可以查看最后面论证
应用场景:
添加
127.0.0.1:6379> geoadd china 121.43333 34.50000 shanghai 117.20000 39.13333 tianjing (integer) 2
计算两地的距离,可以指定km、m、mi、ft
127.0.0.1:6379> geodist china beijing shanghai "748346.9287"
返回指定城市的经纬度
127.0.0.1:6379> geoadd china 116.41667 39.91667 beijing (integer) 1 127.0.0.1:6379> geopos china beijing 1) 1) "116.41667157411575317" 2) "39.91667095273589183"
以指定经纬度为圆心,指定半径来查找,withcoord可以返回结果的精度 ,withdist返回结果到圆心的距离
127.0.0.1:6379> geoadd china 106.45000 29.56667 chongqing (integer) 1 127.0.0.1:6379> georadius china 110 30 1000 km 1) "chongqing" 127.0.0.1:6379> georadius china 110 30 1000 km withcoord withdist 1) 1) "chongqing" 2) "346.0548" 3) 1) "106.4500012993812561" 2) "29.56666939001875249" 127.0.0.1:6379> georadius china 110 30 1000 km count 1 1) "chongqing"
同上个命令,圆心换做指定的城市
127.0.0.1:6379> georadiusbymember china beijing 1000 km 1) "shanghai" 2) "tianjing" 3) "beijing" 4) "xian"
返回指定元素的哈希值
127.0.0.1:6379> geohash china beijing 1) "wx4g14s53n0"
127.0.0.1:6379> zrange china 0 -1 1) "chongqing" 2) "xian" 3) "shanghai" 4) "tianjing" 5) "beijing" 127.0.0.1:6379> zrem china tianjing (integer) 1
基数统计算法的数据结构,虽然通过其他的数据类型也可以实现,但是Hyperloglog占用内存是固定的,用2^64不同元素的技术,只占用12kb内存。
应用场景:
需要统计不重复的数据,比如统计网站的UV
有0.81%错误率
添加元素
127.0.0.1:6379> pfadd test 0 1 1 2 3 3 (integer) 1
查看不重复的元素总数
127.0.0.1:6379> pfcount test (integer) 4
将test跟test2合并到test3
127.0.0.1:6379> pfmerge test3 test test2 OK 127.0.0.1:6379> pfcount test3 (integer) 6
位存储,位图,是种数据结构。都是二进制位,就只有0跟1两种状态,所以非常节省空间、高效率。
应用场景:
统计只有两种状态的场景:用户信息(活跃/不活跃),用户登陆/未登录
set
127.0.0.1:6379> setbit mybitmap 0 0 (integer) 0 127.0.0.1:6379> setbit mybitmap 1 0 (integer) 0 127.0.0.1:6379> setbit mybitmap 2 1 (integer) 0 127.0.0.1:6379> setbit mybitmap 3 1 (integer) 0 127.0.0.1:6379> setbit mybitmap 4 0 (integer) 0
获取bitmap中指定偏移量的值
127.0.0.1:6379> getbit mybitmap 3 (integer) 1
统计
127.0.0.1:6379> bitcount mybitmap (integer) 2
redis事务的本质:一组命令的集合,一个事务中的所有命令都会被序列化。在事务执行的过程中,按顺序执行,其他客户端提交的命令请求不会插入到事务执行命令序列中。
redis的事务没有mysql隔离级别的概念,也就不存在脏读、幻度之类
总结redis的事务:一次性、顺序性、排他性的执行一系列的命令
redis执行事务的流程:
开启事务:multi
一系列命令入队(这时,这些命令并没有被直接执行,要在发起执行命令的时候才会被执行)
执行事务/撤销事务 exec
redis的单条命令保证原子性,但是redis的事务并不保证原子性!(见运行时异常)
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set key1 77 QUEUED 127.0.0.1:6379(TX)> set key2 88 QUEUED 127.0.0.1:6379(TX)> set key3 99 QUEUED 127.0.0.1:6379(TX)> exec 1) OK 2) OK 3) OK 127.0.0.1:6379> keys * 1) "key2" 2) "key1" 3) "key3"
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set key1 11 QUEUED 127.0.0.1:6379(TX)> set key2 22 QUEUED 127.0.0.1:6379(TX)> set key3 33 QUEUED 127.0.0.1:6379(TX)> discard OK 127.0.0.1:6379> get key3 (nil)
悲观锁
很悲观,无论什么时候都加锁。比如java的synchronized
乐观锁
很乐观,认为什么时候都不会有问题,所以就不会加锁,在更新的时候判断下,是否有人在这个期间更改过数据
java:cas
mysql:存的时候比较下version
redis:watch
watch :
监视key ,如果在事务执行之前,该key 被其他命令所改动,那么事务将被打断。
可以被用来做乐观锁
unwatch:
取消watch对key的监视
==========================窗口1模拟线程1========================== 127.0.0.1:6379> set account1 1000 OK 127.0.0.1:6379> set account2 10 OK 127.0.0.1:6379> watch account1 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> decrby account1 200 QUEUED 127.0.0.1:6379(TX)> incrby account2 100 QUEUED 127.0.0.1:6379(TX)> exec #这里执行的时候,account1的值被修改了,事务就执行失败了 (nil) 127.0.0.1:6379> get account1 #account1的值还是线程2赋值的900 "900" ==========================窗口2模拟线程2========================== 127.0.0.1:6379> set account1 900(在执行事务中重新给account1赋值) OK
事务内的所有命令都不会被执行
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> setget k1 v2 #这条命令有误 (error) ERR unknown command `setget`, with args beginning with: `k1`, `v2`, 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> exec (error) EXECABORT Transaction discarded because of previous errors.
错误的那条命令不会被执行,并抛出异常
其余的命令依旧会被执行。所以说redis的事务不保证原子性
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set key1 value1 QUEUED 127.0.0.1:6379(TX)> set key2 value2 key3 value3 #这条语法有误 QUEUED 127.0.0.1:6379(TX)> set key4 value4 QUEUED 127.0.0.1:6379(TX)> exec 1) OK 2) (error) ERR syntax error 3) OK
测试:
pom文件引入jedis:
<!--jedis--> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
编写测试类
@Test public void connectTest(){ Jedis jedis=new Jedis("127.0.0.1",6379); System.out.println(jedis.ping()); }
运行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zMa2dtKK-1631719421534)(image-20210913155114126.png)]
api基本跟redis的一致,这里不再过多重复
@Test public void lock() { Jedis jedis = new Jedis("127.0.0.1", 6379); Transaction multi = jedis.multi(); try { //业务代码 multi.set("key1", "77"); multi.set("key2", "88"); int i = 1 / 0;//制造个bug multi.exec(); System.out.println(jedis.mget("key1", "key2")); } catch (Exception e) { //放弃事务 multi.discard(); //异常处理 e.printStackTrace(); } finally { //关闭链接 jedis.close(); } }
运行后查看redis:
127.0.0.1:6379> dbsize (integer) 0
SpringBoot操作数据库:spring-data、jpa、jdbc、mongodb、redis
springboot2.x以后的版本,使用的不再是jedis,而是lettuce
jedis:采用直连,多个线程操作是不安全的。如果想要避免,要使用jedis pool,类似BIO
lettuce:采用netty,实例key在多个线程中共享,不存在线程不安全的情况。减少线程数据,更像NIO
测试:
pom文件引入:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
在springboot自动配置jar包的spring.factories(springboot的工厂加载机制)中查找spring-boot-start-redis相关配置类:
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
接下来去查看RedisAutoConfiguration:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") @ConditionalOnSingleCandidate(RedisConnectionFactory.class) //从我们可以定制自己的RedisTemplate public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
查看RedisProperties配置文件属性类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uGuuxLl0-1631719421537)(image-20210913170417095.png)]
那么我们就知道了,配置文件中,这个jar包的配置属性前缀是spring.redis。如:
spring.redis.host=127.0.0.1 spring.redis.database=3
写测试代码:
@SpringBootTest public class RedisTest { @Autowired private RedisTemplate redisTemplate; @Test public void connectTest(){ redisTemplate.opsForValue().set("name","77"); redisTemplate.opsForValue().set("age","17"); System.out.println(redisTemplate.opsForValue().get("name")); } }
RestTemplate
序列化
命令测试:
消息订阅端:
127.0.0.1:6379> subscribe channel1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channel1" 3) (integer) 1 1) "message" 2) "channel1" 3) "hello"
消息发送端:
127.0.0.1:6379> publish channel1 hello (integer) 1
原理:
Redis是通过C实现的,Redis通过PUBLISH、SUBSCRIBE、PSUBSCRIBE等命令来实现发布与订阅功能。
通过SUBSCIBE命令订阅某个频道后,redis-service里会维护一个字典,字典的键就是一个个channel,而字典的值则是一个链表。链表中保存了所有订阅这个channel的客户端,SUBSCRIBE命令的关键,就是将客户端添加到给定的channel订阅链中。
通过PUBLISH命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中,查找记录了订阅这个频道的链表,遍历,将消息发布给所有的订阅者。
应用场景:
实时消息系统
订阅、关注系统