java-redis
前言
掌握noSql
linux系统安装
-
下载安装包 ! redis-5.0.8.tar.gz
-
解压redis的安装包 ! 程序放在 /opt 目录下
-
进入解压后的文件,并执行命令:
yum install gcc-c++
make
make install
- redis的默认安装路径
/usr/local/bin
- 将redis的配置文件放到/usr/local/bin 目录下 我们之后使用redis.conf进行启动
-
redis默认不是后台启动的,修改配置文件! redis.conf
daemonize no ->yes 改为yes 后台启动
-
启动redis服务 redis-server kconfig/redis.conf
-
启动redis客户端 redis-cli -p 6379
`
- 如何关闭redis服务
redis-benchmark性能测试
它是redis官方自带的性能测试工具(压力测试工具)
命令参数如下:
- -h 指定服务主机名 默认值127.0.0.1
- -p 指定服务器端口 6379
- -s 指定服务器 socket
- -c 指定并发连接数 50
- -n 指定请求数 100000
- -d 以字节的形式指定SET/GET值的数据大小
- -k 1=keep alive 0=reconnect
- -r SET/GET/INCR 使用随机key SADD 使用随机值
- -p 通过管道传输请求
- -q 强制退出redis 仅显示query/sec值
- –csv 以CSV格式输出
- -I 生成循环,永久执行测试
- -t 仅运行以逗号分隔的测试命令列表
# 测试并发连接 100个并发连接 100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
redis的基本知识说明
-
redis默认有16个数据库,配置文件redis.conf中有说明,默认使用第0个数据库,可以使用select 进行切换数据库
-
dbsize 命令查看数据的大小
-
清除数据库内容命令 flushdb,清除所有数据库的内容命令flushall
-
思考redis是单线程的!
明白redis是很快的,官方表示,redis是基于内存操作,CPU不是redis的性能瓶颈,redis的性能瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!所以就使用了单线程了!
redis 是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-value 的Memecache差! -
Redis 为什么单线程还这么快?
误区1: 高性能的服务器一定是多线程的?
误区2: 多线程(CPU上下文会切换)一定比单线程效率高?
核心: redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案.
redis的基本命令
-
keys * 查看所有的key
-
flushdb 清空当前数据库
-
flushall 清空所有的数据库
-
select 0 切换redis为0的数据库(有16个数据库)
-
dbsize 查看当前数据库的key的数量
-
exists key 查看key是否存在 存在返回1不存在返回0
-
move key 把这个key 移除当前redis数据库
-
expire key 10 设置key的过期时间为10秒
-
ttl key 查看key还有多久过期
-
type key 查看key是什么类型的
-
后面遇到不懂的命令可以去redis中文官方文档中查看
-
redis英文网站
- redis中文网站
String字符串类型详解
-
append key 在原有的key上追加字符串,若存在就追加,不存在就相当于set key
-
strlen key 查看key值得长度返回Integer类型
-
incr key 自动加一每执行一次就加一
-
decr key 自动减一每执行一次就减一
-
incrby key increment 自动加increment值,每执行一次就加increment值(步长)
-
decrby key increment 自动减increment值,每执行一次就自动减increment值
-
getrange key start end 截取字符串的长度 start开始位置,end结束位置
区间是从第零个字符串开始的 闭区间 【1,3】 -
getrange key 0 -1 获取字符串 相当于get key
-
setex (set with expire) 设置过期时间
-
setnx (set if not exists) 不存在再设置(在分布式锁中会常常使用!)
-
setex key 30 value #设置key值30秒后过期
-
ttl key 查看过期时间
-
setnx key value 设置key如果不存在则设置成功并返回1如果存在则设置不成功并返回0
-
mset k1 v1 k2 v2 k3 v3 批量设置值
-
mget k1 k2 k3 批量获取值
-
msetnx k1 v1 k4 v4(批量set if not exists)
注意!由于msetnx redis命令是原子操作的,上面msetnx k1 v1 k4 v4这个命令返回值为0设置失败,由于当前的k1已经存在k4不存在,redis的原子性操作导致k4 设置失败 -
set user:1 {name:zhangsan,age:3} 设置用户id=1的值
-
mset user:1:name zhangsan user:1:age 4 也可以批量设置用户为1的值实战中基本上使用这个设置方式
-
getset 先get后set getset db redis 如果不存在,则返回nil
get db 返回redis , getset db mongodb 如果存在值,获取原来的值,并设置新的值 -
String数据结构使用的场景:value除了是字符串还可以是数字
-
计数器 incr
-
统计多单位的数量 mget
-
粉丝数
-
对象缓存存储 uid:name ly uid:age 28
List列表类型
在redis里面,我们可以把list玩成,栈、队列、阻塞队列!
所有的命令都是用“L” 开头。
常用命令如下:
- lpush k v 将一个值或者多个值,插入到列表头部(左)
- lrange k 0 -1 遍历list列表的值
- rpush k v 将一个值或者多个值,插入到列表的尾部(右)
- lpop k 从左边移除列表的第一个元素
- rpop k 从右边移除列表的第一个元素
- lindex k index 通过下标获得list中的某一个值
- llen k 返回列表的长度
- lrem k count v 移除list集合中指定个数的value,精确匹配
- ltrim 修剪列表 ltrim k index1 index2 表示只要list集合中 下表index1 到index2的元素,这个list已经被改变了,截断了只剩下截取的元素!
- rpoplpush 移除列表的最后一个元素,并将移除的元素添加到新指定的集合中(rpoplpush mylist myotherlist)
- lset list index v 更新集合中指定的值(若不存在,则报错,只是更新原有的值为新值,若不存在则并不能添加新的),将列表中指定下标的值替换为另一个值,更新操作。
- exists list 判断 list集合列表是否存在 不存在返回0 存在返回1
- lset list 0 item 如果存在,更新当前下标的值
- lset list 1 other 如果不存在,则会报错
- linsert k [before|after] pivot value
linsert mylist before “word” “hello” 在集合中‘word’ 元素前插入‘hello’ 元素
将某个具体的value插入到列表中某个元素的前面或者后面。
小结:
list集合他实际上是一个链表,before node after ,left ,right 都可以插入值;
如果key不存在,创建新的列表;
如果key存在,新增内容;
如果移除了所有值,空链表,也代表不存在;
在两边插入或者改动值,效率最高,中间元素,相对来说效率会低一点;
消息队列(lpush rpop),栈(lpush lpop)
set集合
set中的值是不能重复的,set集合的命令都是s开头的
- sadd key value 往set集合中添加值
- smembers key 遍历set集合元素
- sismember key value 判断该值是否存在在key集合中,存在返回1不存在返回0
- scard key 获取key集合中的元素的个数
- srem key v1 移除set集合中某一个元素
- set 无序不重复集合,抽随机数
- srandmember key 随机抽集合中的元素
- srandmember key count 随机抽集合中的count个数元素
- 删除指定的key,随机删除指定的key
- spop key 随机删除key集合中的某个元素
- smove key1 value key2 将一个指定集合中的元素移动到另一个key2集合中
- sdiff key1 key2 取两个集合中的差集
- sunion key1 key2 取两个集合中并集
- sinter key1 key2 取两个集合中的交集
微博,A用户将所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中!共同关注,共同爱好,二度好友(六度分隔理论)
hash(哈希)
Map集合,key-<key,value> key-Map ! 值是map集合
set myhash field1 value1 field2 value2
hash 变更的数据 user name age 尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,string更适合于字符串的存储。
- hset k field1 value1 set一个具体的key-value
- hget k field1 获取一个字段的值
- hmset k field1 value1 field2 value2 set多个key-value
- hmget k field1 field2 获取多个字段值
- hgetall k 获取全部的数据
- hdel k field1 删除hash指定的key字段,对应的value值也就消失了
- hlen k 获取hash表的字段数量
- hexists k field1 判断hash中指定字段是否存在
- hkeys k 查看k中所有的key值
- hvals k 查看k中所有的values值
- hincrby k field 1 设置k中某个field自增1
- hincrby k field -1 设置k中某个field自减1
- hsetnx k field value 如果不存在则可以设置,若存在则不能设置
zset(有序集合)
在set的基础上增加一个值 set k1 v1 zset k1 score v1
-
zadd key score value
-
zrangebyscore key min max 显示全部的用户从小到大
zrangebyscore key -inf +inf -
zrevrange key 0 -1 从大到小进行降序
-
zrangebyscore key -inf +inf withscores 显示全部用户并且附带成绩
-
zrangebyscore key -inf 2500 显示成绩小于2500的升序排序
-
zrem key value 移除有序集合中的指定元素
-
zrange key 0 -1 遍历集合中的元素
-
zcard key 获取有序集合中的个数
-
zcount key 1 3 获取指定空间的成员数量
案例思路:
set排序 存储班级成绩表,工资排序表
普通消息 1 重要消息2 带权重进行判断,排行榜应用实现取Top N
geospatial 地理位置
朋友的定位,附近的人,打车距离计算?
redis的geo在redis3.2版本就推出了这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人
可以查询一些测试数据:
http://www.json.cn/lngcodeinfo/0706D99C19A781A3/
只有六个命令
#geoadd
#添加地理位置
#规则:两极无法直接添加,我们一般会下载城市数据,直接通过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 106.50 29.53 chongqing 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
(integer) 1
127.0.0.1:6379>
#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 shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379>
#getdist
#两人之间的距离
#单位:m=米 km=千米 mi=英里 ft=英尺
# 查看两地之间的距离默认是米
127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
#查看两地之间的距离可以指定单位 km
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788"
#georadius 以给定的经纬度为中心,找出某一半径内的元素
#例如:我附近的人?(获得所有附近的人的地址,定位!)通过半径来查询!
#获得指定数量的人如 200 所有城市数据都应该录入,才会让结果更加清楚
#以100 30 这个经纬度为中心,寻找方圆1000km内的城市
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"
2) "xian"
#显示到中间直线距离的位置
127.0.0.1:6379> georadius china:city 110 30 500 km withdist
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
#显示他人的定位信息
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
#帅选出指定的结果
127.0.0.1:6379> georadius chian:city 110 30 500 km withdist withcoord count 1
(empty list or set)
127.0.0.1:6379>
#georadiusbymember
#找出位于指定元素周围的其他元素
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> georadiusbymember china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379>
#geohash 命令返回一个或多个位置元素的geohash
#该命令将返回11个字符串的geohash字符串
#将二维的经纬度转换为一维的字符串,如果两个字符串越近,那么则距离越近
127.0.0.1:6379> geohash china:city beijing shanghai
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"
127.0.0.1:6379>
#geo底层实现原理其实就是zset!我们可以使用zset命令来操作geo!
#使用zrange 遍历地图中的全部元素
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
#使用zrem 删除key中的一个成员
127.0.0.1:6379> zrem china:city chongqing
(integer) 1
127.0.0.1:6379>
Hyperloglog
- 简介
redis在2.8.9版本添加了Hyperloglog结构,是用来做基数统计的算法,在redis里面,每个Hyperloglog 键只需要花费12kb内存,就可以计算接近2的64次方个不同的基数。因为Hyperloglog 只会根据输入元素来计算基数,而不会存储输入元素本身,所以Hyperloglog不能像集合那样,返回输入的值。
小知识:什么是基数?
比如数据集{1,3,5,7,7,8},那么这个数据集的基数集为{1,3,5,7,8 },基数(不重复元素)为5,基数估计是误差可节省的范围内。
为什么需要Hyperloglog?
如果要统计1亿个数据的基数值,大约需要内存 1亿/8/1024/1024 约12m,内存减少占用的效果显著。 - 应用场景
统计注册ip数
统计每日访问ip数
统计页面实时UV数(一个人访问一个网站多次,但还是算作一个人!)
统计在线用户数
统计每天搜索不同词条的个数
统计真实文章阅读数
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断!
这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id;
0.81%错误率!统计UV任务,可以忽略不计的!
优点:占用的内存是固定的,2^64不同的元素的技术,只需要废12kb内存!如果要从内存角度来比较的话Hyperloglog首选! - 命令如下:
#创建第一组元素mykey
127.0.0.1:6379> pfadd mykey a b c d e f g h i j
(integer) 1
#统计mykey元素的基数数量
127.0.0.1:6379> pfcount mykey
(integer) 10
#创建第二组元素mykey2
127.0.0.1:6379> pfadd mykey i j
(integer) 0
127.0.0.1:6379> pfcount
(error) ERR wrong number of arguments for 'pfcount' command
127.0.0.1:6379> pfcount mykey
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j i j a b n f v g k
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 9
# 合并两组mykey mykey2 =>mykey3并集
127.0.0.1:6379> pfmerge mykey3 mykey myke2
OK
#查看并集的数量
127.0.0.1:6379> pfcount mykey3
(integer) 10
- 总结
Hyperloglog 是一种算法,并非redis独有,目的是做基数统计,故不是集合,不会保存元数据,知识记录数量而不是数值。
耗费空间小,支持输入非常大体积的数据量。
如果允许容错,那么一定可以使用Hyperloglog!
如果不允许容错,就使用set或者自己的数据类型
Bitmaps
-
简介
位存储
场景:
1.统计疫情感染人数:0 1 0 1 0感染的是1 没有感染的是0
2.统计用户信息,活跃,不活跃,登录,未登录 ,打卡 365天打卡
两个状态的,都可以使用Bitmaps!
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0 和1 两个状态。
365天 = 365bit 1字节=8bit 46个字节左右! -
使用场景
使用bitmaps 来记录周一到周日的打卡!!!
周一 :1 周二:0
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(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
#查看第五天是否打卡 1=打卡
127.0.0.1:6379> getbit sign 5
(integer) 1
#查看第六天是否打卡 0=未打卡
127.0.0.1:6379> getbit sign 6
(integer) 0
# 统计这周打卡的次数
127.0.0.1:6379> bitcount sign
(integer) 4
redis基本的事务操作
redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
一次性、顺序性、排它性!执行一些列的命令
--------- 队列 set set set 执行 ------------
redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!
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 k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
#执行事务
127.0.0.1:6379> exec
1) OK
2) OK
3) "v2"
4) OK
放弃事务!
#开启事务
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> set k4 v4
QUEUED
#取消事务
127.0.0.1:6379> discard
OK
#set k4 v4 命令失效
127.0.0.1:6379> get k4 #事务队列中的命令都不会被执行!
(nil)
编译性异常(代码有问题!命令有错!),事务中所有的命令都不会被执行!
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> getv1 k1 #错误命令
(error) ERR unknown command `getv1`, with args beginning with: `k1`,
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2 v2 #错误命令
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> exec #执行事务报错,所有命令都不会被执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 #执行事务报错,所有命令都不会被执行
(nil)
运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!
127.0.0.1:6379> set k1 v1 #先设置一个key为字符串类型的
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> incr k1 #对一个字符串进行加一操作
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #执行事务,
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
127.0.0.1:6379> get k2 #事务没有报错,正常执行
"v2"
127.0.0.1:6379> get k3 #虽然第一条命令报错了,但是依旧正常执行成功了!
"v3"
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> get money
QUEUED
127.0.0.1:6379> get out
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
3) "80"
4) "20"
测试多线程修改值,监视失败!使用watch可以当作redis的乐观锁操作!
#第一个线程执行命令如下(当第二个线程正准备执行exec命令时,第一个线程执行)
127.0.0.1:6379> set money 1000
OK
#第二个线程执行命令如下
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
(nil)
127.0.0.1:6379> unwatch #1.如果发现事务执行失败,就先解锁
OK
127.0.0.1:6379> watch money #2.获取最新的值,再次监视, 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) 990
2) (integer) 10
127.0.0.1:6379>