文章目录
1 发布&订阅
1.1 发布订阅模型
发布/订阅:一种消息通信模式
发布者(pub)发送消息 订阅者(sub)接收消息
Redis客户端可以订阅任意数量的频道
1.2 命令实现发布/订阅
1,打开redis-server
2,打开redis-cli 称为cli1
subscribe channel1
订阅channel1
3,打开redis-cli 称为cli2
publish channel1 hello
向所有订阅了channel1的机器发布消息
关于发布/订阅模型,类似于观察者模式,一个Speaker保存一个List,该List中存的是Listener
当Speaker发布消息时,遍历这个List实现一个方法即可
2 新数据类型
2.1 Bitmap
一个字节有8位,合理的操作位能有效提高内存使用率和开发效率
Redis中提供了Bitmap这个数据类型,可以实现对位的操作
1,Bitmap本身不算是一种数据结构 实际上就是字符串KV
但它可以对字符串的位进行操作
2,Bitmap单独提供了一套命令,所以在Redis中使用Bitmap和使用字符串的方法不太相同
可以把Bitmaps看作一个以位为单位的数组,数组的每个单元只能存储0或1 数组的下标在Bitmap中称为偏移量
2.1.1 命令
1,setbit key offset value
设置Bitmap中某个偏移量的值(0或1) 偏移量从0开始
可以将每个独立用户是否访问网站的记录存放在bitmap中 访问记1 未访问记0
用偏移量作为用户的id 假设现在有20个用户 偏移量从0-19 其中1,6,11,15用户访问了网站
key记为users:20211215
设置bitmap位则是 setbit users:20211215 1 1
很多用户的id可能以一个数字如1000开头,直接将offset和用户id映射会造成不必要的浪费
通常做法是每次setbit时将用户id减去这个id,在第一次初始化bitmap时,假如偏移量非常大可能造成Redis阻塞
2,getbit key offset
获取bitmap中某个偏移量的值
如getbit user:20211215 14
3,bitcount key [start end]
获取key对应的bitmap中值为1的个数
加上start和end表指定偏移量区间
2.1.2 Bitmap和set的比较
假设某网站有1亿用户,每天访问量5千万,用set和bitmap记录活跃用户对比如下:
在这种场景下使用bitmap能大大节约内存,且随着时间推移bitmap更加节省内存
但bitmap的使用也要结合场景,假如该网站每天只有10万访问量,那么此时set就更加合适
2.2 HyperLogLog
2.2.1 简介
在工作中有统计网站访问量等问题,可以用Redis的incr,incrby来解决
但是像求独立访问者,独立ip数,搜索记录数等需要去重和计数的问题应当如何解决
这种问题称为基数问题
什么是基数
数据集{1,3,5,7,5,7,8} 其基数集为{1,3,5,7,8}
基数(不重复元素)为5
解决基数问题有多种方案如使用Redis的hash或set来存储
或者存储到MySQL中使用distinct count来去重
但这些解决方案都需要存储数据,对非常大的数据集不切实际,而我们的目的也只是"不重复的有多少"
Redis的HyperLogLog是用来做基数统计的算法
在输入数据集非常大时,计算基数所需的空间是固定且很小的
在Redis中每个HyperLogLog键只需要12KB内存 就可以计算接近2^64个不同元素基数 非常节省空间
2.2.2 命令
1,pfadd key element [element...]
添加指定元素到HyperLogLog中
2,pfcount key
统计key中基数个数
2.3 Geospatial
2.3.1 简介
在Redis3.2中增加了对GEO类型的支持 GEO即Geospatial地理信息的缩写
该类型就是元素的二维坐标,地图上就是经纬
Redis基于该类型,提供了经纬度查询,范围查询,距离查询,经纬度Hash等常见操作
2.3.2 命令
1,geoadd key 经度 纬度 member
添加member的经纬到key中
2,geopos key member
查询key中member的经纬
3 Jedis使用
3.1 Jedis使用测试
导入依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.0</version>
</dependency>
测试能否连接成功:
3.2 API的简单使用
1,使用String相关API:
@Test
public void demo1() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 添加kv
jedis.set("name", "lucky");
// 根据k获取v 获取过期时间
System.out.println(jedis.get("name") + " TTL:" + jedis.ttl("name"));
// 设置多个kv
jedis.mset("k1", "v1", "k2", "v2");
List<String> mget = jedis.mget("k1", "k2");
System.out.println(mget.get(0) + ", " + mget.get(1));
//
// 获取当前库中所有key
Set<String> set = jedis.keys("*");
for (String key : set) {
System.out.println(key);
}
}
2,使用其他数据结构API
@Test
public void demo2() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 使用list
jedis.lpush("list1", "l1", "l2", "l3", "l4");
List<String> values = jedis.lrange("list1", 0, -1);
System.out.println(values);
// 使用Set
jedis.sadd("name", "lucy", "lucy", "jack", "jack", "lucy");
Set<String> set = jedis.smembers("name");
System.out.println(set);
// 使用hash
jedis.hset("users", "field1", "20");
System.out.println(jedis.hget("users", "field1"));
// 使用zset
jedis.zadd("class1", 100, "tom");
jedis.zadd("class1", 95, "jack");
jedis.zadd("class1", 120, "lucy");
System.out.println(jedis.zrange("class1", 0, -1));
}
3.3 Jedis模拟验证码发送
要求:
1,输入手机号,点击生成6位验证码,2分钟有效(SE的RandomAPI+RedisTTL)
2,输入验证码,点击验证,返回成功/失败(根据K取V判断)
3,每个手机号每天只能输入3次(Redis的incr)
伪代码:
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
verifyCode("1111111");
System.out.println(checkCode("1111111", jedis.get("verify" + "1111111" + "code")));
}
private static String creatRandomCode() {
Random random = new Random();
String code = "";
for(int i = 0; i < 6; i++) {
code += random.nextInt(10);
}
return code;
}
private static void verifyCode(String phoneNum) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String countKey = "verify" + phoneNum + "count";
String codeKey = "verify" + phoneNum + "code";
String count = jedis.get(codeKey);
if(count == null) {
jedis.setex(countKey, 24*60*60, "1");
} else if(Integer.valueOf(count) <= 2) {
jedis.incr(countKey);
} else if(Integer.parseInt(count) > 2) {
System.out.println("次数超过3次");
jedis.close();
return;
}
jedis.setex(codeKey, 120, creatRandomCode());
jedis.close();
}
private static boolean checkCode(String phoneNum, String code) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String codeKey = "verify" + phoneNum + "code";
String redisCode = jedis.get(codeKey);
if(code.equals(redisCode)) {
return true;
}
return false;
}
3.4 SpringBoot整合Redis
1,导入依赖
<!--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>
2,application.properties配置redis配置
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=192.168.0.24
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000
3,添加Redis配置类
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
4,测试
@RestController
@RequestMapping("/redisTest")
public class TestRedis {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis() {
redisTemplate.opsForValue().set("name", "tom");
return (String) redisTemplate.opsForValue().get("name");
}
}