Java阶段四06
第4章-第6节
一、知识点
geospatial、hyperloglog、bitmap、事务、Jedis、SpringBoot集成Redis
二、目标
-
了解三种特殊数据类型的使用
-
理解什么是Redis事务
-
学会使用Redis事务
-
掌握使用JAVA代码操作Redis
三、内容分析
-
重点
-
理解什么是Redis事务
-
学会使用Redis事务
-
掌握使用JAVA代码操作Redis
-
-
难点
-
理解什么是Redis事务
-
掌握使用JAVA代码操作Redis
-
四、内容
1、三种特殊数据类型
1.1 geospatial 地理位置
用来记录地理位置信息,两地之间的距离,范围等
-
GEOADD
将指定的地理空间位置(纬度、经度、名称)添加到指定的
key
中经纬度不能错,错了就加不进去了
GEOADD china:city 119.27345 26.04769 fuzhou # 添加地址 GEOADD china:city 118.613 24.88946 quanzhou # 添加地址 GEOADD china:city 118.03394 24.48405 xiamen 113.27324 23.15792 guangzhou # 添加多条
-
GEOPOS
从
key
里返回所有给定位置元素的位置(经度和纬度)。GEOPOS china:city fuzhou # 查询 GEOPOS china:city fuzhou quanzhou # 查询多条
-
GEODIST
返回两个给定位置之间的距离。
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
-
m 表示单位为米。
-
km 表示单位为千米。
-
mi 表示单位为英里。
-
ft 表示单位为英尺。
GEODIST china:city fuzhou guangzhou # 查询两地的直线距离 GEODIST china:city fuzhou guangzhou km # 自定义单位
-
-
GEOHASH
该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示。返回的geohashes具有以下特性:
-
他们可以缩短从右边的字符。它将失去精度,但仍将指向同一地区。
-
它可以在
geohash.org
网站使用,网址http://geohash.org/<geohash-string>
。查询例子:Geohash - geohash.org/sqdtr74hyu0. -
与类似的前缀字符串是附近,但相反的是不正确的,这是可能的,用不同的前缀字符串附近。
GEODIST china:city fuzhou guangzhou # 查询两地的直线距离 GEODIST china:city fuzhou guangzhou km # 自定义单位
-
-
GEORADIUS
以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
范围可以使用以下其中一个单位:
-
m 表示单位为米。
-
km 表示单位为千米。
-
mi 表示单位为英里。
-
ft 表示单位为英尺。
GEORADIUS china:city 119 26 500 km # 119 26这个经纬度为中心,方圆500KM的城市 GEORADIUS china:city 119 26 500 km WITHDIST # 显示到中心的距离 GEORADIUS china:city 119 26 500 km WITHDIST WITHCOORD # 显示自身的经纬度 GEORADIUS china:city 119 26 500 km WITHDIST WITHCOORD COUNT 2 # 查询指定数量
-
-
GEORADIUSBYMEMBER
这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是
GEORADIUSBYMEMBER
的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点指定成员的位置被用作查询的中心。
GEORADIUSBYMEMBER china:city fuzhou 500 km # 查询福州附近500km的城市
-
其他使用
ZRANGE china:city 0 -1 # 查看所有数据 ZREM china:city fuzhou # 删除
1.2 hyperloglog
HyperLogLog是一种概率数据结构,用于计数唯一的东西(技术上这指的是估计集合的基数)。通常计数唯一项需要使用与您想要计数的项数量成比例的内存,因为您需要记住过去已经见过的元素,以避免多次计数。然而,有一组算法是用内存来换取精度的:在Redis实现的情况下,你以一个标准误差的估计度量结束,这个误差小于1%。这个算法的神奇之处在于,您不再需要使用与计算项数量成比例的内存量,而是可以使用固定数量的内存量!在最坏的情况下是12k字节,或者如果你的HyperLogLog(从现在开始我们就叫它HLL)包含很少的元素,那么就会少得多。
基数
A(1,2,3,4,5)
B(1,2,3,4,5,1)
这个时候 基数 = 5
应用场景
QQ空间访问量(一个人访问十次,还是算一个访问量)
-
传统模式:使用set保存用户的id,利用set的去重特性,当用户量很大的时候会比较麻烦,而且占用内存比较大
-
Hyperlolos:内存固定,只需要用12KB的内存就可以进行实现(有错误率,但是是在允许范围内的)
PFADD mykey 1 2 3 4 5 # 添加数据
PFCOUNT mykey # 5
PFADD mykey2 1 2 3 1 2 3 1 2 3 # 添加数据
PFCOUNT mykey2 # 3
PFMERGE mykey3 mykey mykey2 # 合并mykey和mykey2为mykey3
PFCOUNT mykey3 # 5
1.3 bitmap
位图不是实际的数据类型,而是在String类型上定义的一组面向位的操作。因为字符串是二进制安全blob,它们的最大长度是512 MB,所以它们适合设置2^32个不同的位。
位图的最大优点之一是,在存储信息时,它们通常可以极大地节省空间。例如,在一个用增量用户id表示不同用户的系统中,仅使用512 MB内存就可以记住40亿用户的单个比特信息(例如,知道用户是否想要接收新闻通讯)。
位存储
主要用于存储用的状态 登录/未登录 打卡/未打卡 两个状态都可以使用Bitmaps
一个用户 365天打卡 = 365bit 差不多46B
SETBIT sign 0 1 # 设置用户sign的第一天 为1
GETBIT sign 0 # 获取第一天是否打卡
BITCOUNT sign # 统计打卡记录,统计为1的总数
2、事务
2.1 什么是Redis事务
一组命令的集合。事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
-
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
-
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
Redis的单条命令是原子性的,但是事务不保证原子性
所有的命令在事务中,没有执行,只有发起执行命令以后才会执行
2.2 使用流程
2.2.1 开启事务(MULTI)
127.0.0.1:6379> MULTI # 开启事务
OK
2.2.2 命令入队(写命令)
# 命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
2.2.3 执行事务(EXEC)
127.0.0.1:6379(TX)> EXEC # 执行事务
1) OK
2) OK
3) "v1"
4) OK
2.2.4 放弃事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> get k4
QUEUED
127.0.0.1:6379(TX)> DISCARD # 放弃事务,这个时候事务不会执行
OK
2.2.5 编译型异常
代码写错了,所有命令都不执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> set k6 v6
QUEUED
127.0.0.1:6379(TX)> get k5 v5
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379(TX)> EXEC # 异常,所有命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5
(nil)
2.2.6 运行时异常
其他命令正常运行,错误命令抛出错误
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k7 v7
QUEUED
127.0.0.1:6379(TX)> set k8 v8
QUEUED
127.0.0.1:6379(TX)> incr k7
QUEUED
127.0.0.1:6379(TX)> EXEC # 运行的时候异常,其他命令可以正常执行
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
127.0.0.1:6379> get k8
"v8"
127.0.0.1:6379>
3、Jedis
Redis官方推荐的JAVA连接开发工具
3.1 依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
3.2 使用
@Test
void redisTest01() {
// 创建Jedis连接对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 直接调用相关语法即可
// 检测是否连接成功
System.out.println(jedis.ping());
System.out.println(jedis.get("k1"));
jedis.set("k1", "V1");
// 关闭连接
jedis.close();
}
3.3 事务
@Test
void redisTest02() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 获取一个Redis事务对象
Transaction multi = jedis.multi();
try {
// 往事务中插入语句
multi.set("k2", "v2");
multi.set("k3", "v3");
// 执行事务
multi.exec();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
multi.discard();
}
jedis.close();
}
4、 SpringBoot整合Redis
创建的时候选择NoSql的Redis或者手动导入依赖
4.1 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.2 配置Redis
spring:
redis:
host: 127.0.0.1
port: 6379
4.3 使用
-
使用默认配置
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private String id; private String username; private String password; private String name; } @Resource RedisTemplate<String, Object> redisTemplate; @Test void redisTest03() throws JsonProcessingException { // RedisTemplate用来操作不同的数据类型 ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue(); // opsForValue操作字符串 opsForValue.set("t1", "t1"); // 操作对象 User user = new User("1", "admin", "admin", "管理员"); ObjectMapper mapper = new ObjectMapper(); // 转成字符串 opsForValue.set("user:" + user.getId(), mapper.writeValueAsString(user)); System.out.println(opsForValue.get("user:" + user.getId())); }
-
自己修改(推荐)
发现存在Redis中的数据长这样 \xac\xed\x00\x05t\x00\x06user:1
需要自定义一个Template
添加依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.5</version> </dependency> @Configuration public class RedisConfig { // 定义了一个RedisTemplate @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { // RedisTemplate 为了自己方便一般直接使用<String,Object> RedisTemplate<String, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); // 序列化配置 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper om = new ObjectMapper(); // 设置可见度 om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 启动默认的类型 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 序列化类,对象映射设置 jackson2JsonRedisSerializer.setObjectMapper(om); // String的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key采用String的学历恶化 template.setHashKeySerializer(stringRedisSerializer); // value采用jackson的序列化 template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value采用jackson的序列化 template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } }
-
工具类
/** * spring redis 工具类 * * @author ruoyi **/ @SuppressWarnings(value = {"unchecked", "rawtypes"}) @Component public class RedisUtil { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 */ public <T> void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @param unit 时间单位 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public <T> T getCacheObject(final String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection 多个对象 * @return */ public long deleteObject(final Collection collection) { return redisTemplate.delete(collection); } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public <T> long setCacheList(final String key, final List<T> dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public <T> List<T> getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 缓存Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 获得缓存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public <T> void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 获取Hash中的数据 * * @param key Redis键 * @param hKey Hash键 * @return Hash中的对象 */ public <T> T getCacheMapValue(final String key, final String hKey) { HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 删除Hash中的数据 * * @param key * @param hKey */ public void delCacheMapValue(final String key, final String hKey) { HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.delete(key, hKey); } /** * 获取多个Hash中的数据 * * @param key Redis键 * @param hKeys Hash键集合 * @return Hash对象集合 */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection<String> keys(final String pattern) { return redisTemplate.keys(pattern); } }
-
使用
@Autowired RedisUtil redisUtil; @Test void redisTest04() throws JsonProcessingException { User user = new User("1", "admin", "admin", "管理员"); redisUtil.setCacheObject("user:"+user.getId(),user); System.out.println(redisUtil.getCacheObject("user:"+user.getId()).toString()); }
5、小结
本章节中我们学习了三种特殊数据类型的使用、理解了什么是Redis事务、学会使用Redis事务、在JAVA中使用了Redis依赖来操作Redis数据 ,同时使用了SpringBoot和工具类对Redis进行整合。
下一节中我们将会学习Redis的持久化、主从赋值等操作,加深对Redis的使用的理解。