redis 相关知识的再梳理总结。
redis是什么
简单说,Redis 是一种高性能的KEY-VALUE结构的内存数据库。
1、解决高并发场景(性能高10W左右的读写速度)
2、解决高性能
redis在什么场景下需要使用
缓存、秒杀、计数器、排行榜、热点、分布式锁、分布式ID、消息系统等相关场景
redis都需要了解哪些知识
redis 安装:
1、Downloads - Redis 官网下载地址
2、下载完成后,make && make install 编译安装
3、启动:redis-cil、redis-server、redis-sentinel(哨兵)
4、vim redis.conf,修改守护线程 (daemonize)属性 为 true 这样保障可以在后台运行
注:守护线程---特殊线程区别独立于业务线程,一般是用来做日志记录,防止业务线程死锁等场景。
5、其他的可以百度查一下,比如如何开启自启动(如何配置)
redis 使用:
可视化:可以安装 Redis Desktop Manager 通过输入 ip、端口、账号、密码登录,redis默认分16个存储数据库,我们可以选择0-16个中的任意一个。
客户端:如果从客户端访问,可以百度参考(各类命令,实际应用中暂时还没有用到相关内容)
redis 数据存储格式类型:
唯一的一点,无论是哪种格式类型,redis的存储方式 都是(key,value )键值对
1、String
就是普通的存储格式,字符串,比如 userId:1 userName:张三,存储信息就是(userId:1,张三)
示例:
//省略Jedis相关
//添加 一条数据
jedis.set("userId:1","张三");
//获取 一条数据
jedis.get("userId:1");
//删除 一条数据
jedis.del("userId:1");
2、List
字符串列表, 比如班级:3班,成员:['张三','李四','王五','王五'],(class:3,['张三','李四','王五','王五'])
// 使用 lpush 方法将元素添加到 List 的左侧
jedis.lpush("mylist", "李四");
jedis.lpush("mylist", "王五");
// 使用 rpush 方法将元素添加到 List 的右侧
jedis.rpush("mylist", "李四");
jedis.rpush("mylist", "王五");
3、set
无需集合,比如班级:3班,成员:['张三','李四','王五','王五'],(class:3,['李四','张三','王五']),注意了,这个是无序的,不重复。
// 添加元素到Set
jedis.sadd("myset", "element1");
jedis.sadd("myset", "element2");
jedis.sadd("myset", "element3");
4、hash
Hash 这个个人理解,存储的key是一个字符串,Value就是一个Hash(也是字符串),在Java示例代码可以看下区别(我们用jedis)
示例:
Hash
//省略Jedis相关
//添加 一条数据
String userHashKey = "userId:1"
jedis.hset(userHashKey,"age","12");
jedis.hset(userHashKey,"name","张三");
//获取 一条数据
jedis.hget(userHashKey,"age");
jedis.hget(userHashKey,"name");
//删除某一个属性
jedis.hdel(userHashKey, "age");
//删除
jedis.del(userHashKey);
//获取 全部值
Map<String, String> userInfo = jedis.hgetAll(userHashKey,"age");
5、ZSET
是一种有序的结构,但是插入的时候,会有一个 score的参数
//示例:
jedis.zadd(zsetKey,1,"张一")
jedis.zadd(zsetKey,3,"张三")
jedis.zadd(zsetKey,2,"张二")
//输出全部
Set<Tuple> studentsWithScores = jedis.zrangeWithScores(zsetKey, 0, -1);
输出结果:1,2,3
//指定范围
Set<String> studentsInRange = jedis.zrangeByScore(zsetKey, 1, 2);
输出结果:1,2
Redis 持久化:
因为redis 是内存数据库,基于内存来运行的,当我们电脑宕机,重启等情况下数据就会出现丢失,所以我们也要关注Redis持久化,现实中,很大多数企业都会购买云RDS产品,一般不需要我们来进行维护和管理,但是我们也要了解知晓相关的知识,有可能只有ECS产品,我们要自己搭建(没有运维)的情况下,只能自行就行管理咯。
那持久化,对于redis来说,就是要把内存中的数据存储到磁盘中。
简单来说就是说,Redis 运行的时候----》持久化机制-----》数据持久化到磁盘
Redis 挂掉的时候----》是否有持久化---》有----启动的时候从磁盘把数据加载到内存中
否---》完蛋,全都没了
所以在多数情况下,我们使用redis 的场景需要多多考虑容灾情况,或者业务场景。比如短信验证码(有效期)这类的,存储用户信息这类的场景,是否可以使用,为什么?
做持久化,绕不开两个内容,RDB/AOP,一句话简单说,一个是做备份,一个备份操作日志。
RDB
RDB相当于,一个存档数据,存档数据那就要考虑,什么时候备份,所以就会引出,save配置的概念,比如说3600秒保存一次,300秒一次,在config配置中,参数为 save 秒 次数,意思是说,多少秒,有多少key发生变化,我们就备份一次。
AOF
AOF相当于,操作日志记录,全日志记录,你每次操作都会被记录下来,那这个就很好理解,恢复的时候,直接重新执行一遍就OK,那相对的,记录就会很大。
所以RDB和AOF,各有优缺,RDB,无法保障数据丢失问题,AOF无法保障存储大小问题,恢复起来,RDB就很简单,我们可以理解成,直接把备份文件copy回去,AOF就是一行一行重新执行一遍,所以AOF就能保障数据丢失问题。
Redis 事务:
redis 的事务开启很简单,通过MULTI 就可以开启事务,但是事务跟我们java WEB开发中的事务有一些区别,无法进行回滚操作,即执行到哪一步就停在哪一步,redis 是顺序执行命令的,所以会导致数据在执行完毕之前可能导致出现脏数据的可能性,这一点我们要在开发过程中考虑清楚具体的业务情况来使用。
在spring 中,我们一般使用jedis来调用redis(springboot2.X以上开始使用lettuce),操作redis 一般我们会使用redistemplate,提供的api来操作redis
//示例1:不带序列化
redisTemplate.execute((RedisCallback<Long>) connection -> {
connection.set(key, value);
connection.expire(key, time);
LOGGER.debug("put key [{}] ttl [{}]s", key, time);
return 1L;
});
//示例2:带序列化
redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = serializer.serialize(value);
connection.set(keys, values);
connection.expire(keys, time);
LOGGER.debug("put key [{}] ttl [{}]s", key, time);
return 1L;
});
示例3:查询 key类似的值(在0-》2*n 时间快过期的keys)
final List<String> keysList = new ArrayList<>();
redisTemplate.execute((RedisCallback<List<String>>) connection -> {
Set<String> keys = redisTemplate.keys(key + "*");
for (String key1 : keys) {
Long ttl = connection.ttl(key1.getBytes(DEFAULT_CHARSET));
if (0 <= ttl && ttl <= 2 * time) {
keysList.add(key1);
}
}
return keysList;
});
return keysList;
Redis 订阅与发布:
简单来说,Redis服务端(A)发送消息---BCD客户端订阅消息(监听器),完成整个闭环。
示例:构建一个发布端
@Configuration class
public RedisConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("chat"));
return container;
}
@Bean
MessageListenerAdapter listenerAdapter(RedisSubscriber redisSubscriber) {
return new MessageListenerAdapter(redisSubscriber, "receiveMessage");
}
@Component
public static class RedisSubscriber {
public void receiveMessage(String message) {
System.out.println("Received <" + message + ">");
}
}
}
示例:构建一个接收端
@Service
public class RedisPublisherService {
private final RedisTemplate<String, String> redisTemplate;
@Autowired
public RedisPublisherService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void publishMessage(String message) {
redisTemplate.convertAndSend("chat", message);
}
}
Redis 锁:
redis分布式锁,这个是一个取巧的操作,redis 是单线程的,所以我们可以在我们业务开始的时候设置一个我们自己初始化的一个值到redis中,完成加锁操作,在业务结束的时候,删除掉这个值,完成解锁操作,若我们重新进入当前业务,首先判断我们设置的值是否存在,存在表示锁定状态,否则表示已经解锁可以继续操作。
Redis 验证码:
设置一个有效期的reids值,在当前有效期内都有效的话,表示验证码有效,否则,重新发送验证码
key: capcat:phone:13211111111, value : 123456 timeout:60s
示例代码:
public class test(){
String final static LOGIN_CODE = "captcha:phone:";
public void setCaptcha(String key,String value){
redisTemplate.execute((RedisCallback<Long>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = serializer.serialize(value);
connection.set(keys, values);
connection.expire(keys, time);
LOGGER.debug("put key [{}] ttl [{}]s", key, time);
return 1L;
});
}
public void getCaptcha(key){
String resultStr = redisTemplate.execute((RedisCallback<String>) connection -> {
RedisSerializer<String> serializer = getRedisSerializer();
byte[] keys = serializer.serialize(key);
byte[] values = connection.get(keys);
return serializer.deserialize(values);
});
LOGGER.debug("get key [{}]", key);
return resultStr;
}
public void testCaptcha(){
String phone = '13211111111'
String key = LOGIN_CODE + phone;
int captchaCode= (int) ((Math.random() * 9 + 1) * 100000);
setCaptcha(key,captchaCode);
String redisResultCode= getCaptcha(key);
}
}