Redis——整合springboot
redis6新的数据类型——HyperLogLog
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
解决基数问题有很多种方案:
-
数据存储在MySQL表中,使用distinct count计算不重复个数
-
使用Redis提供的hash、set、bitmaps等数据结构来处理
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。
什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
HyperLogLog常用方法
- pfadd
:可以添加一个或多个的元素。这些元素如果重复则不会添加成功。如果一个添加不成功,剩下的也会添加成功。
pfadd k1 java c++ java
# 输出结果为1,表示添加成功
pfcount k1
# 输出结果为2,第二次java没有添加进去
- pfcount
- pfmerge
[sourcekey...]:将一个或多个HLL合并后的结果存储在另一个HLL中,比如每月活跃用户可以使用每天的活跃用户来合并计算可得
pfadd k1 java c++ mysql
pfadd k2 c python mysql
pfmerge k3 k1 k2
pfcount k3
# 输出结果为5,重复的mysql被去重。
redis6新的数据类型——Geospatial
Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。
- geoadd
<longitude经度><latitude纬度> :添加一个或多个经纬度组成的……城市?
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing
#也可以写在一起
geoadd china:city 114.05 22.52 shenzhen 116.38 39.90 beijing
- geopos
[member...]: 获得指定地区的坐标值
geopos china:city shanghai
# 输出经纬度
- geodist
:获取两个位置之间的直线距离。可以指定单位,默认为m
geodist china:city shenzhen beijing km
# 输出结果为1945.5740
- georadius
:给定的经纬度为中心,找出某一半径内的元素
georadius china:city 110 30 1000 km
# 找出经度110 纬度30 为中心1000km为半径在china:city中的元素
# 输出结果为shenzhen
我觉得了解一哈就行了。。
Jedis
使用java代码操作redis数据库
- 引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
- 编写代码
/**
* 1.需要将redis仅本机访问关闭
* 2.需要将保护模式关闭
*/
public static void main(String[] args) {
//创建jedis对象
Jedis jedis = new Jedis("192.168.1.2",6379);
String value= jedis.ping();
System.out.println(value);//输出结果为PONG
}
此时的jedis可以使用以上所有的命令。
Jedis操作字符串
@Test
public void testDemo1(){
jedis.mset("k1","v1","k2","v2");
List<String> mget = jedis.mget("k1", "k2");
for (String s : mget) {
System.out.println(s);
}
}
//输出结果为 v1 v2
Jedis操作列表
@Test
public void testDemo2(){
jedis.flushDB();
jedis.lpush("name","boerk","lyly","cookie");
List<String> name = jedis.lrange("name", 0, -1);
System.out.println(name);
}
//输出结果为[cookie, lyly, boerk]
Jedis操作set
/**
* set是一个自动排重的无序集合
*/
@Test
public void testDemo3(){
jedis.flushDB();
jedis.sadd("name","boerk","lyly","cookie");
Set<String> name = jedis.smembers("name");
System.out.println(name);
}
//输出结果为[lyly, cookie, boerk]
Jedis操作hash
/**
* hash存储的是一个个键值对
*/
@Test
public void testDemo4(){
jedis.flushDB();
jedis.hset("user","age","20");
String hget = jedis.hget("user", "age");
System.out.println(hget);
}
/**
* 如果需要使用hmset一次性存储多个值,则需要传入HashMap
*/
@Test
public void testDemo4(){
jedis.flushDB();
HashMap hashMap = new HashMap();
hashMap.put("jiangsu","nanjing");
hashMap.put("anhui","hefei");
jedis.hmset("provincial_capital",hashMap);
Map<String, String> provincial_capital = jedis.hgetAll("provincial_capital");
System.out.println(provincial_capital);
}
Jedis操作Zset
/**
* Zset是一个关联了评分的简单字符串集合
*/
@Test
public void testDemo6(){
jedis.flushDB();
//添加一个
jedis.zadd("name",500d,"java");
//添加多个使用hashmap<String,Double>,添加顺序是反过来的…
HashMap hashMap = new HashMap();
hashMap.put("c++",600d);
hashMap.put("mysql",700d);
jedis.zadd("name",hashMap);
Set<String> name = jedis.zrange("name", 0, -1);
System.out.println(name);
}
如果需要使用WITHSCORES,则使用
Set<Tuple> name = jedis.zrangeWithScores("name", 0, -1);
System.out.println(name);
//输出结果为[[java,500.0], [c++,600.0], [mysql,700.0]]
Redis案例:
-
输入手机号,点击发动后随机生成6位数字码,2分钟有效
-
输入验证码,点击验证,返回成功或失败
-
每个手机,每天只能输入三次
public class demo2 {
/**
* 1.输入手机号,点击发动后随机生成6位数字码,2分钟有效
* 2.输入验证码,点击验证,返回成功或失败
* 3.每个手机,每天只能输入三次
*/
static Jedis jedis = new Jedis("192.168.1.2",6379);
public static void main(String[] args) {
String tel="11111111111";
// //存储
// verify(tel);
//验证
isSame(tel,"111111");
}
public static String getCode(){
Random random = new Random();
String code = "";
for (int i=0;i<=5;i++){
int randomCode = random.nextInt(10);
code+=randomCode;
}
return code;
}
//每个手机号,每天只能发送三次
public static void verify(String tel){
String s = jedis.get(tel);
if (s==null){
jedis.setex(tel,24*60*60,"1");
}else if (Integer.parseInt(s)<=2){
jedis.incr(tel);
}else {
System.out.println("今天已经获取三次了!请明天再试");
jedis.close();
return;
}
//2.没有超过三次就获取验证码。
String code = getCode();
//存储验证码
String telCode=tel+"code";
save(telCode,code);
}
//3.储存
public static void save(String tel,String code){
jedis.setex(tel,120,code);
jedis.close();
}
//4.检验验证码是否相同
public static void isSame(String tel,String code){
String telCode=tel+"code";
String s = jedis.get(telCode);
if (code.equals(s)){
System.out.println("正确!");
}else {
System.out.println("错误!");
}
}
}
SpringBoot整合redis
- 引入依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<!--这个依赖不引入,PropertyAccessor.ALL会报错-->
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-base</artifactId>
<version>2.13.2</version>
<type>pom</type>
</dependency>
- 编写springboot配置类
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
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);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
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);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
- 测试
@RestController
public class RedisController {
@Autowired
RedisTemplate redisTemplate;
@GetMapping("/testRedis")
public String testRedis(){
redisTemplate.opsForValue().set("name","kitty");
return (String)redisTemplate.opsForValue().get("name");
}
}
RedisTemplate中定义了对5种数据结构操作
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
Redis事务操作
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
- Multi:开启事务,进入组队阶段。
- Exec:执行命令,进入执行阶段。
- Discard:弃用
使用multi后,所有的命令不会立即执行,而是等待exec。使用discard后,命令就被弃用了。
- 在组队阶段,命令出现错误,而执行(exec),则所有的命令都会失效
- 在执行阶段,命令出现错误,则该命令失效,其他命令正常进行。
事务冲突

-
悲观锁
![image]()
-
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
但是效率会很低!!!
- 乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
Redis模拟乐观锁
- watch
set balance 100
watch balance
multi
incrby balance 50
---此时另一窗口
incrby balance 500
#返回值为600
---
exer
#返回值为nil
Redis事务的三特性
- 单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
- 不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
案例:秒杀。
//秒杀过程
public static boolean doSecKill(String uid,String prodid) throws IOException {
//1 uid和prodid非空判断
if(uid == null || prodid == null) {
return false;
}
//2 连接redis
//Jedis jedis = new Jedis("192.168.44.168",6379);
//通过连接池得到jedis对象
JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = jedisPoolInstance.getResource();
//3 拼接key
// 3.1 库存key
String kcKey = "sk:"+prodid+":qt";
// 3.2 秒杀成功用户key
String userKey = "sk:"+prodid+":user";
//监视库存
jedis.watch(kcKey);
//4 获取库存,如果库存null,秒杀还没有开始
String kc = jedis.get(kcKey);
if(kc == null) {
System.out.println("秒杀还没有开始,请等待");
jedis.close();
return false;
}
// 5 判断用户是否重复秒杀操作
if(jedis.sismember(userKey, uid)) {
System.out.println("已经秒杀成功了,不能重复秒杀");
jedis.close();
return false;
}
//6 判断如果商品数量,库存数量小于1,秒杀结束
if(Integer.parseInt(kc)<=0) {
System.out.println("秒杀已经结束了");
jedis.close();
return false;
}
//7 秒杀过程
//使用事务
Transaction multi = jedis.multi();
//组队操作
multi.decr(kcKey);
multi.sadd(userKey,uid);
//执行
List<Object> results = multi.exec();
if(results == null || results.size()==0) {
System.out.println("秒杀失败了....");
jedis.close();
return false;
}
//7.1 库存-1
//jedis.decr(kcKey);
//7.2 把秒杀成功用户添加清单里面
//jedis.sadd(userKey,uid);
System.out.println("秒杀成功了..");
jedis.close();
return true;
}
使用LUA脚本语言解决乐观锁的库存遗留问题


浙公网安备 33010602011771号