三大特殊数据类型
geospatial地理位置
附近的人,地理查询,打车距离计算等等
Redis的Geo在Redis3.2就推出了!推算地理位置,方圆几里的人
Redis规定有效的纬度经度值
经度值-180 到 180
纬度值-85.05112878 到 85.05112878
超出范围会爆出错误
GEOADD
############################################################
### GEOADD添加地理信息 两级无法直接添加 我们一般通过Java程序导入
# 参数 key(纬度 经度 名称)
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 sahnghai
(integer) 1
127.0.0.1:6379> GEOADD china:city 106.50 29.53 chongqing 114.05 22.54 shenzhen
(integer) 2
127.0.0.1:6379> GEOADD china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
############################################################
GEOPOS
获得当前定位:坐标值
############################################################
### GEOPOS 获取指定的经度纬度 可获取多个
127.0.0.1:6379> keys *
1) "china:city"
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city beijing chongqing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
############################################################
GEODIST
两人之间的距离
单位:
●m表示单位为米。
●km表示单位为千米。
●mi表示单位为英里。
●ft表示单位为英尺。
############################################################
#### GEODIST 两地之间的距离
#### 参数 地点1 地点二 单位
#不带单位
127.0.0.1:6379> GEODIST china:city beijing sahnghai
"1067378.7564"
#带单位
127.0.0.1:6379> GEODIST china:city beijing sahnghai km
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing chongqing km
"1464.0708"
############################################################
GEORADIUS
以给定的经纬度为中心,找出某半径内的元素
附近的人(获得所有附近的人的定位!!!)通过半径搜索周围几千米
############################################################
#### GEORADIUS 以给定的经纬度为中心,找出某半径内的元素
#### 为了精确 所有的数据应该都要存储在city里面
#### 以经纬度110 30 的地方搜索1000km以内的城市(都在city里)
#### 参数下面例子详解
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"
##### withdist 显示距离中心的直线距离
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"
##### withcoord 显示地点的经纬度
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"
##### withdist withcoord混合使用,即会显示距离中心的直线距离,又会显示地点的经纬度
###### count 限制查询出来的城市个数
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
############################################################
GEORADIUSBYMEMBER
找出位于指定范围内的元素,中心点是由给定的位置元素决定的
附近的人朋友
############################################################
#### GEORADIUSBYMEMBER 找出指定元素周围的元素,附近的人朋友
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
###我这里上面存储的时候上海拼错了
127.0.0.1:6379> GEORADIUSBYMEMBER china:city sahnghai 400 km
1) "hangzhou"
2) "sahnghai"
############################################################
GEOHASH
返回一个或多个位置元素的Geohash表示
该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示(缩短字符,丢失精度)
############################################################
#### GEOHASH 将二维的经纬度转换为一维字符串,即降维打击
#### 如果两个字符串越接近,就说明位置越接近
127.0.0.1:6379> GEOHASH china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
############################################################
GEO底层实现原理其实就是Zset!!
所以我们可以使用Zset来操作GEO
############################################################
##查看地图中所有元素
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "sahnghai"
6) "beijing"
####移除地图中的某个元素
127.0.0.1:6379> ZREM china:city sahnghai
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "beijing"
Hyperloglog
什么是基数
A{1,3,5,7,8,9,7} B{1,3,5,7,8}
基数: 不重复的元素
什么是Hyperloglog
Redis2.8.9版本就更新了Hyperloglog数据结构
Redis Hyperloglog 基数统计的算法
网页的UV(一个人访问一个网站多次,但是还是所做一个人)
传统的方式:set保存用户id,然后就可以统计set中的元素数量作为标准判断
但是有的网站采用UUID或者分布式ID来辨识用户,这样网站会存储大量的用户id,会比较麻烦!我们的目的是为了计数,而不是保存用户id!!!
Redis Hyperloglog
占用的内存是固定的,2^64不同的元素的技术(位计算),只需12KB内存!!如果要从内存角度比较,Redis Hyperloglog 是首选!!有一定的错误率
如果允许容错,那么一定可以使用Redis Hyperloglog
如果不允许容错,就是用set或者自己的数据类型
方法使用
############################################################
### PFADD 添加元素
### PFCOUNT 统计指定key中的基数
### PFMERGE 合并(并集) 相同的不会重复插入
127.0.0.1:6379> PFADD mykey a b c d e f g h i j
(integer) 1
127.0.0.1:6379> PFCOUNT mykey
(integer) 10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 15
############################################################
Bitmaps
位存储
采用01来存储状态
统计疫情感染人数 使用01来存储 365天打卡设计,打卡/不打卡
统计用户信息!!活跃,不活跃 登录信息!!登录/不登录
两个状态的都可以用Bitmaps
Bitmaps位图,数据结构!操作二进制来进行记录,就只有0和1两个状态
拿 365天打卡设计来说 365天=365bit 1字节=8bit ======>46个字节我们就能解决
方法测试
############################################################
### setbit存储数据
### sign表示一周内的打卡情况
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 sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
##### GETBIT 查看某一天是否打卡
127.0.0.1:6379> GETBIT sign 3 #查看周四
(integer) 1
127.0.0.1:6379> GETBIT sign 6 #查看周六
(integer) 0
###### BITCOUNT 统计打卡天数 扣全勤奖(xiao)
127.0.0.1:6379> BITCOUNT sign
(integer) 3
事务
MySQL:ACID原则,要么同时成功,要么同时失败,一个事务的所有的事务,都会被序列化,在事务的执行过程中,按照顺序执行,排他性
Redis:单条命令是保证原子性的,但是Redis的事务是不保证原子性
Redis事务没有隔离级别的概念,所有的命令在事务中,并没有直接执行!!只有发起执行命令的时候才会执行 Exec
Redis事务:
- 开启事务(MULTI)
- 命令入队(…)
- 执行事务(EXEC)
正常执行事务
############################################################
###### multi开启事务
##### 命令入队
#### exec命令执行
127.0.0.1:6379> FLUSHDB
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> 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
放弃事务
因为是事务,上次开启执行之后就没了,我们得重新开启新的事务
############################################################
##### DISCARD 放弃事务
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
127.0.0.1:6379> GET key4
(nil)
编译型异常(代码有问题!命令有错),事务中所有的命令都不会被执行
############################################################
####
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 k3 v3
QUEUED
127.0.0.1:6379> GETSET k3 ####错误命令,报错
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
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"
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> get k3
QUEUED
127.0.0.1:6379> exec ##第一条报错,但是其他的依然成功
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
监控 Watch
悲观锁:
认为什么时候都出错,无论做什么都会加锁
乐观锁:
- 认为什么时候都不出现问题,所以不会加锁,在更新数据的时候判断,再次期间,是否有人修改过这个数据**(mysql)version字段来处理**
- 获取version
- 更新的时候比较version
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
多线程测试
############################################################
## 第一个线程 开启监视 进行事务操作 但是还没有执行
127.0.0.1:6379> WATCH money
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
############################################################
#### 第二个线程,修改了我们的money
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
############################################################
#### 此时再去执行事务的命令 发现监视失败 相当于Redis的乐观锁操作
127.0.0.1:6379> WATCH money
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
(nil)
############################################################
##### 解决办法
##### 发现事务失败先 UNWATCH解锁
#### 再次监视(获取最新值,再次加锁)
#### 比对监视的值是否发生变化,如果没有,则执行成功
##### 如果变了,则会执行失败
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 1
QUEUED
127.0.0.1:6379> INCRBY out 1
QUEUED
127.0.0.1:6379> exec
1) (integer) 999
2) (integer) 21
Jedis
使用Java操作我们的Redis
什么是jedis
Redis是Redis官方推荐的Java连接的开发工具!!Java操作Reids的中间件
如果要使用Java操作Redis,一定要对Jedis十分熟悉
测试
1.导入对应的依赖
<!--导入jedis的包-->
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!-- fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
2.编码测试
- 连接数据库
- 操作命令
- 断开连接
这个地方我们会出现很多问题
这里我总结一下
docker exec -it myredis redis-cli
Error response from daemon: Container 36e472dc2ac121942cd621d85c8d54c0e8d650de2e5e16ed94cc04b06f89dee5 is not running
这里说的是我们的这个容器没有启动
[root@bogon docker]# docker run -p 6379:6379 --privileged=true --name myredis -d redis redis-server /usr/local/docker/redis.conf
docker: Error response from daemon: Conflict. The container name "/myredis" is already in use by container "36e472dc2ac121942cd621d85c8d54c0e8d650de2e5e16ed94cc04b06f89dee5". You have to remove (or rename) that container to be able to reuse that name.
这里说的是我们已经有了这个容器,容器名字冲突了
然后我们真正启动后
[root@bogon docker]# docker exec -it redis redis-cli
127.0.0.1:6379> clear
127.0.0.1:6379> keys *
1) "out"
2) "money"
public class TestPing {
public static void main(String[] args) {
//1.new jedis对象
Jedis jedis = new Jedis("192.168.1.99",6379);
//dedis所有的命令跟linux一样
System.out.println(jedis.ping());
}
}
//如果ping失败我们要看看我们的配置文件是不是修改了
//1.bind 127.0.0.1 绑定IP 注释掉,默认只接收本机访问
//2.protected-mode no 守护进程设置no
//3.daemonize yes 作为守护进程运行yes
//地址要填成我们虚拟机CentOS的IP地址
运行结果
PONG
进程已结束,退出代码 0
java操作Redis API
public class TestPing {
public static void main(String[] args) {
//1.new jedis对象
Jedis jedis = new Jedis("192.168.1.99",6379);
//dedis所有的命令跟linux一样
System.out.println(jedis.ping());
System.out.println(jedis.keys("*"));
System.out.println("清空数据"+jedis.flushDB());
System.out.println("判断某个键是否存在:"+jedis.exists("username"));
System.out.println("新增<'username','wang'>的键值对:"+jedis.set("username","wang"));
System.out.println("新增<'password','wa'>的键值对:"+jedis.set("password","wa"));
System.out.println("系统中所有的键:"+jedis.keys("*"));
}
}
PONG
[money, out]
清空数据OK
判断某个键是否存在:false
新增<'username','wang'>的键值对:OK
新增<'password','wa'>的键值对:OK
系统中所有的键:[password, username]
其他的命令跟我们的命令一模一样,这里就不一一赘述了
通过jedis再次理解事务
1.正常执行
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.1.99", 6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","wanghuahua");
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1",result);
multi.set("user2",result);
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
//输出数据
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user1"));
jedis.close();
}
}
}
{"name":"wanghuahua","hello":"world"}
{"name":"wanghuahua","hello":"world"}
2.出现异常
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.1.99", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","wanghuahua");
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1",result);
multi.set("user2",result);
int i=1/0;
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
//输出数据
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user1"));
jedis.close();
}
}
}
java.lang.ArithmeticException: / by zero
at com.wang.test.TestTX.main(TestTX.java:27)
null
null
3.监控对象
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.1.99", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","wanghuahua");
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
multi.watch(result);
//jedis.watch(result);
try {
multi.set("user1",result);
multi.set("user2",result);
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
//输出数据
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user1"));
jedis.close();
}
}
}
Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: Cannot use Jedis when in Multi. Please use Transaction or reset jedis state.
at redis.clients.jedis.BinaryJedis.checkIsInMultiOrPipeline(BinaryJedis.java:1885)
at redis.clients.jedis.Jedis.watch(Jedis.java:1709)
at com.wang.test.TestTX.main(TestTX.java:24)
原因是因为我们使用了事务监控,需要用事务的实例( Transaction实例 )来执行命令,最后调用exec执行事务模块中的命令。如果不小心使用了conn连接(Jedis实例)直接执行命令,则会抛出以上的异常。