四、三种特殊数据类型

Geospatial(地理位置)

使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用

  • geoadd key longitud(经度) latitude(纬度) member [..] 将具体经纬度的坐标存入一个有序集合
  • geopos key member [member..] 获取集合中的一个/多个成员坐标
  • geodist key member1 member2 [unit] 返回两个给定位置之间的距离。默认以米作为单位。
  • georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count] 以给定的经纬度为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
  • GEORADIUSBYMEMBER key member radius... 功能与GEORADIUS相同,只是中心位置不是具体的经纬度,而是使用结合中已有的成员作为中心点。
  • geohash key member1 [member2..] 返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。

有效经纬度

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。

指定单位的参数 unit 必须是以下单位的其中一个:

m 表示单位为米。

km 表示单位为千米。

mi 表示单位为英里。

ft 表示单位为英尺。

关于GEORADIUS的参数

通过georadius就可以完成 附近的人功能

withcoord:带上坐标

withdist:带上距离,单位与半径单位相同

COUNT n : 只显示前n个(按距离递增排序)

----------------georadius---------------------
127.0.0.1:6379> GEORADIUS china:city 120 30 500 km withcoord withdist # 查询经纬度(120,30)坐标500km半径内的成员
1) 1) "hangzhou"
2) "29.4151"
3) 1) "120.20000249147415"
2) "30.199999888333501"
2) 1) "shanghai"
2) "205.3611"
3) 1) "121.40000134706497"
2) "31.400000253193539" ------------geohash---------------------------

127.0.0.1:6379> geohash china:city yichang shanghai # 获取成员经纬坐标的geohash表示
  1. "wmrjwbr5250"
  2. "wtw6ds0y300"

Hyperloglog(基数统计)

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。

因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

其底层使用string数据类型。

什么是基数?

数据集中不重复的元素的个数。

应用场景:

网页的访问量(UV):一个用户多次访问,也只能算作一个人。

传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。

  • PFADD key element1 [elememt2..] 添加指定元素到 HyperLogLog中
  • PFCOUNT key [key] 返回给定 HyperLogLog 的基数估算值。
  • PFMERGE destkey sourcekey [sourcekey..] 将多个 HyperLogLog 合并为一个 HyperLogLog

代码示例

----------PFADD--PFCOUNT---------------------
127.0.0.1:6379> PFADD myelemx a b c d e f g h i j k # 添加元素
(integer) 1
127.0.0.1:6379> type myelemx # hyperloglog底层使用String
string
127.0.0.1:6379> PFCOUNT myelemx # 估算myelemx的基数
(integer) 11
127.0.0.1:6379> PFADD myelemy i j k z m c b v p q s
(integer) 1
127.0.0.1:6379> PFCOUNT myelemy
(integer) 11
----------------PFMERGE-----------------------

127.0.0.1:6379> PFMERGE myelemz myelemx myelemy # 合并myelemx和myelemy 成为myelemz

OK

127.0.0.1:6379> PFCOUNT myelemz # 估算基数

(integer) 17

BitMaps(位图)

使用位存储,信息状态只有 0 和 1

Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。

应用场景: 签到统计、状态统计

  • setbit key offset value 为指定key的offset位设置值
  • getbit key offset 获取offset位的值
  • bitcount key [start end] 统计字符串被设置为1的bit数,也可以指定统计范围按字节
  • bitop operration destkey key[key..] 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
  • BITPOS key bit [start] [end] 返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位

代码示例

------------setbit--getbit--------------
127.0.0.1:6379> setbit sign 0 1 # 设置sign的第0位为 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1 # 设置sign的第2位为 1 不设置默认 是0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign
string 127.0.0.1:6379> getbit sign 2 # 获取第2位的数值

(integer) 1

127.0.0.1:6379> getbit sign 3

(integer) 1

127.0.0.1:6379> getbit sign 4 # 未设置默认是0

(integer) 0
-----------bitcount----------------------------

127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数

(integer) 4

五、事务

Redis的单条命令是保证原子性的,但是redis事务不能保证原子性

Redis事务本质:一组命令的集合。

----------------- 队列 set set set 执行 -------------------

事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。



一次性

顺序性

排他性

Redis事务没有隔离级别的概念

Redis单条命令是保证原子性的,但是事务不保证原子性!

Redis事务操作过程

  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec)

所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(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 k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec # 事务执行
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
2) "k2"
3) "k1"

取消事务(discurd)

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> DISCARD # 放弃事务
OK
127.0.0.1:6379> EXEC
(error) ERR EXEC without MULTI # 当前未开启事务
127.0.0.1:6379> get k1 # 被放弃事务中命令并未执行
(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> error k1 # 这是一条语法错误命令
(error) ERR unknown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 执行报错
127.0.0.1:6379> get k1
(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> INCR k1 # 这条命令逻辑错误(对字符串进行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 运行时报错
4) "v2" # 其他命令正常执行

虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了。

所以说Redis单条指令保证原子性,但是Redis事务不能保证原子性。

监控

悲观锁:

  • 很悲观,认为什么时候都会出现问题,无论做什么都会加锁

乐观锁:

  • 很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
  • 获取version
  • 更新的时候比较version

使用watch key监控指定数据,相当于乐观锁加锁。

正常执行

127.0.0.1:6379> set money 100 # 设置余额:100
OK
127.0.0.1:6379> set use 0 # 支出使用: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 use 20
QUEUED
127.0.0.1:6379> exec # 监视值没有被中途修改,事务正常执行
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做redis的乐观锁操作(相当于getversion)

我们启动另外一个客户端模拟插队线程。

线程1:

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 use 20
QUEUED
127.0.0.1:6379> # 此时事务并没有执行

模拟线程插队,线程2:

127.0.0.1:6379> INCRBY money 500 # 修改了线程一中监视的money
(integer) 600

回到线程1,执行事务:

127.0.0.1:6379> EXEC # 执行之前,另一个线程修改了我们的值,这个时候就会导致事务执行失败
(nil) # 没有结果,说明事务执行失败
127.0.0.1:6379> get money # 线程2 修改生效

"600"

127.0.0.1:6379> get use # 线程1事务执行失败,数值没有被修改

"0"

解锁获取最新值,然后再加锁进行事务。

unwatch进行解锁。

注意:每次提交执行exec后都会自动释放锁,不管是否成功

六、Jedis

使用Java来操作Redis,Jedis是Redis官方推荐使用的Java连接redis的客户端。

1.导入依赖

<!--导入jredis的包-->
<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.70</version>
</dependency>

2.编码测试

连接数据库

操作命令

断开连接

代码示例

public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.xx.xxx", 6379);
String response = jedis.ping();
System.out.println(response); // PONG
}
}

输出PONG

常用的API

string、list、set、hash、zset

所有的api命令,就是我们对应的上面学习的指令,一个都没有变化!

public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","kuangshen");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
// jedis.watch(result)
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("user2"));
jedis.close(); // 关闭连接
}
}
}

七、SpringBoot整合

SpringBoot 操作数据:spring-data jpa jdbc mongodb redis!

SpringData 也是和 SpringBoot 齐名的项目!

说明: 在 SpringBoot2.x 之后,原来使用的jedis 被替换为了 lettuce?

jedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式

lettuce : 采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式

源码分析:

@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
// 我们可以自己定义一个redisTemplate来替换这个默认的!
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化!
// 两个泛型都是 Object, Object 的类型,我们后使用需要强制转换 <String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean // 由于 String 是redis中最常使用的类型,所以说单独提出来了一个bean!
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

整合测试

1.导入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换。

jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式

lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式

我们在学习SpringBoot自动配置的原理时,整合一个组件并进行配置一定会有一个自动配置类xxxAutoConfiguration,并且在spring.factories中也一定能找到这个类的完全限定名。Redis也不例外。

那么就一定还存在一个RedisProperties类

@ConditionalOnClass注解中有两个类是默认不存在的,所以Jedis是无法生效的

然后再看Lettuce:

完美生效。

现在我们回到RedisAutoConfiguratio

只有两个简单的Bean

  • RedisTemplate
  • StringRedisTemplate

当看到xxTemplate时可以对比RestTemplat、SqlSessionTemplate,通过使用这些Template来间接操作组件。那么这俩也不会例外。分别用于操作Redis和Redis中的String数据类型。

在RedisTemplate上也有一个条件注解,说明我们是可以对其进行定制化的

说完这些,我们需要知道如何编写配置文件然后连接Redis,就需要阅读RedisProperties

这是一些基本的配置属性。

还有一些连接池相关的配置。注意使用时一定使用Lettuce的连接池。

2.编写配置文件

# 配置redis
spring.redis.host=39.99.xxx.xx
spring.redis.port=6379

3.使用RedisTemplate

@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate; @Test
void contextLoads() { // redisTemplate 操作不同的数据类型,api和我们的指令是一样的
// opsForValue 操作字符串 类似String
// opsForList 操作List 类似List
// opsForSet
// opsForHash
// opsForZSet
// opsForGeo
// opsForHyperLog // 除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD // 获取连接对象
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//connection.flushDb();
//connection.flushAll(); redisTemplate.opsForValue().set(&quot;mykey&quot;,&quot;kuangshen&quot;);
System.out.println(redisTemplate.opsForValue().get(&quot;mykey&quot;));
}

}

4.测试结果

此时我们回到Redis查看数据时候,惊奇发现全是乱码,可是程序中可以正常输出。这时候就关系到存储对象的序列化问题,在网络中传输的对象也是一样需要序列化,否者就全是乱码。

RedisTemplate内部的序列化配置是这样的

默认的序列化器是采用JDK序列化器

后续我们定制RedisTemplate就可以对其进行修改。

RedisSerializer提供了多种序列化方案:

我们来编写一个自己的 RedisTemplete

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
// 这是我给大家写好的一个固定模板,大家在企业中,拿去就可以直接使用!
// 自己定义了一个 RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 我们为了自己开发方便,一般直接使用 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
template.setConnectionFactory(factory);
    // Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(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);
template.afterPropertiesSet(); return template;
}

}

所有的redis操作,其实对于java开发人员来说,十分的简单,更重要是要去理解redis的思想和每一种数据结构的用处和作用场景!

狂神说redis笔记(二)的更多相关文章

  1. 狂神说redis笔记(四)

    十二.Redis主从复制 概念 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器.前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), ...

  2. 狂神说redis笔记(三)

    八.Redis.conf 容量单位不区分大小写,G和GB有区别 可以使用 include 组合多个配置问题 网络配置 日志 # 日志 # Specify the server verbosity le ...

  3. 狂神说redis笔记(一)

    一.Nosql概述 1.单机Mysql时代 90年代,一个网站的访问量一般不会太大,单个数据库完全够用.随着用户增多,网站出现以下问题: 数据量增加到一定程度,单机数据库就放不下了 数据的索引(B+ ...

  4. redis 笔记(二)

    在上一篇中提到了数据类型 ,在本章中就具体说说这几种数据类型: sting :set /get / del / append /strlen 简单的对key-->value 写入读取删除增减 i ...

  5. redis相关笔记(二.集群配置及使用)

    redis笔记一 redis笔记二 redis笔记三 1.配置:在原redis-sentinel文件夹中添加{8337,8338,8339,8340}文件夹,且复制原8333中的配置 在上述8333配 ...

  6. Redis学习笔记二 (BitMap算法分析与BitCount语法)

    Redis学习笔记二 一.BitMap是什么 就是通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身.我们知道8个bit可以组成一个Byte,所以bitmap本身会极大的节省 ...

  7. python3.4学习笔记(二十五) Python 调用mysql redis实例代码

    python3.4学习笔记(二十五) Python 调用mysql redis实例代码 #coding: utf-8 __author__ = 'zdz8207' #python2.7 import ...

  8. python3.4学习笔记(二十四) Python pycharm window安装redis MySQL-python相关方法

    python3.4学习笔记(二十四) Python pycharm window安装redis MySQL-python相关方法window安装redis,下载Redis的压缩包https://git ...

  9. Elasticsearch笔记二之Curl工具基本操作

    Elasticsearch笔记二之Curl工具基本操作 简介: Curl工具是一种可以在命令行访问url的工具,支持get和post请求方式.-X指定http请求的方法,-d指定要传输的数据. 创建索 ...

随机推荐

  1. Tomcat管理弱口令页面Getshell

    目录 弱口令Getshell 利用Burpsuite对tomcat账号密码进行爆破 弱口令Getshell Tomcat安装完成后会有如下页面,点击该页面的 Manager App 处会弹出输入用户名 ...

  2. (Py练习)数组元素调换

    # 输入数组,最大的元素与第一个元素调换,最小的与最后一个元素调换,输出数组 # 数组的输入输出.元素交换使用不同的函数来实现 import math p = 0 def inp(numbers): ...

  3. php单列模式和工厂模式

    一.单例模式又称为职责模式,它用来在程序中创建一个单一功能的访问点,通俗地说就是实例化出来的对象是唯一的.所有的单例模式至少拥有以下三种公共元素: 1. 它们必须拥有一个构造函数,并且必须被标记为pr ...

  4. web&HTML

    内容索引 1. web概念概述 2. HTML web概念概述 * JavaWeb: * 使用Java语言开发基于互联网的项目 * 软件架构: 1. C/S: Client/Server 客户端/服务 ...

  5. .NET Core 服务诊断工具

    前言: 前一篇文中介绍了.NET Core-全局性能诊断工具 的使用方法,那么接下来自己实现一个简单.NET Core的诊断工具. 该工具主要包括:.NET Core 程序进程信息查看.性能计数器结果 ...

  6. JDBC核心技术(获取数据库链接、数据库事务、数据库链接池)

    @ 目录 前言 数据的持久化 Java数据存储技术 JDBC介绍 JDBC体系结构 获取数据库链接 Driver接口 加载注册JDBC驱动 获取数据库链接 数据库链接方式(实例) 方式一:代码中显示出 ...

  7. Rust 泛型

    Rust 泛型 泛型程序设计(generic programming)是程序设计语言的一种风格或范式.泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些 ...

  8. 030. Python装饰器

    一  装饰器 1.1 装饰器介绍 扩展函数新功能的@定义:替换旧函数,返回新函数,在不改变原有代码的前提下,为该函数扩展新功能;语法:@ (语法糖) 1.2 装饰器的原型 def show(func) ...

  9. 分布式存储ceph---ceph常用命令(3)

    1.查看ceph集群配置信息 ceph daemon /var/run/ceph/ceph-mon.$(hostname -s).asok config show 2.在部署节点修改了ceph.con ...

  10. 云计算OpenStack环境搭建(4)

    准备工作: 准备3台机器,确保yum源是可用的,分别为控制节点(192.168.11.3).计算节点(192.168.11.4)和存储节点(192.168.11.5) 控制节点:OpenStack日常 ...