一, Redis 是什么?
首先数据库分为关系型数据库和非关系型数据库, 关系型数据库是采用关系模型来组织数据的数据库, 简单来说就是二维表格模型, 同时保证事务的一致性.
相反非关系型数据库采用 key-value 形式进行存储, 是一种数据结构化存储方法的集合, 具有分布式性质.
Redis 是当前比较热门的 NOSQL 系统之一, 它是一个开源的使用 ANSI c 语言编写的 key-value 存储系统 (区别于 MySQL 的二维表格的形式存储.) 遵守 BSD 协议, 支持网络, 可基于内存亦可持久化的日志型, Key-Value 数据库, 并提供多种语言的 API. 它通常被称为数据结构服务器, 因为值 (value) 可以是 字符串 (String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets) 等类型.
二, Redis 的优势
1, 性能快: Redis 读取的速度是 110000 次 / s, 写的速度是 81000 次 / s.
2, 丰富的数据类型: string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)等.
3, 原子性: Redis 的所有操作都是原子性的, 且多个客户端同时访问 Redis 客户端可获得更新后的值.
4, 持久化: 集群(主从复制, 分布式).
三, Redis 与 Memcached 区别(经典面试题)
1 ,Redis 不仅仅支持简单的 k/v 类型的数据, 同时还提供 list,set,zset,hash 等数据结构的存储类型. memcache 支持简单的数据类型, String, 同时还可以缓存图片, 视频.
2 ,Redis 支持数据的备份, 即 master-slave 模式的数据备份(主从复制).
3 ,Redis 支持数据的持久化, 可以将内存中的数据保持在磁盘中, 重启的时候可以再次加载进行使用.
4, Redis 的速度比 Memcached 快很多
5,Memcached 是多线程, 非阻塞 IO 复用的网络模型; Redis 使用单线程的 IO 复用模型.
6, 数据安全性: memcache 挂掉后, 数据便消失; Redis 可以定期保存到磁盘(持久化).
四, Redis 常用命令及 springboot 操作 Redis
4.1, 引入依赖包.
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-Redis</artifactId>
- </dependency>
4.2, 常用命令与代码整合操作.
注意: Redis 默认使用 JDK 序列化方式
更多详细命令, 请参考 Redis 中文网: https://www.redis.net.cn/
4.3, 字符串操作类型.
- /**
- * String - 字符串类型的操作方式
- * redisTemplate.opsForValue()
- */
- @Test
- public void stringType(){
- // 改为 String 序列化方式
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- redisTemplate.setValueSerializer(new StringRedisSerializer());
- // Redis 命令: set key value
- redisTemplate.opsForValue().set("age", "19");
- // Redis 命令: get key
- String age = (String) redisTemplate.opsForValue().get("age");
- System.out.println("-->" + age);
- // Redis 命令: mset key value key value ...
- Map<String, Object> map = new HashMap<>();
- map.put("key1", "value1");
- map.put("key2", "value2");
- map.put("key3", "value3");
- redisTemplate.opsForValue().multiSet(map);
- // Redis 命令: mget key key key...
- List<String> keys = new ArrayList<>();
- keys.add("key1");
- keys.add("key2");
- keys.add("key3");
- List values = redisTemplate.opsForValue().multiGet(keys);
- System.out.println("mget -->" + values);
- // Redis 命令: del key
- Boolean boo = redisTemplate.delete("key1");
- // Redis 命令: strlen key - 可能会因为序列化的原因造成长度不准
- Long resultLong = redisTemplate.opsForValue().size("age");
- System.out.println("strlen -->" + resultLong);
- // Redis 命令: getset key value
- String oldValue = (String) redisTemplate.opsForValue().getAndSet("age", "25");
- System.out.println("getset -->" + oldValue);
- // Redis 命令: getrange key start end - 可能会因为序列化的原因造成长度不准
- String age1 = redisTemplate.opsForValue().get("age", 0, 1);
- System.out.println("getrange -->" + age1);
- // Redis 命令: append - 可能会因为序列化的原因造成长度不准
- Integer age2 = redisTemplate.opsForValue().append("age", "26");
- System.out.println("append -->" + age2);
- // Redis 命令: incr key - 自增 - 可能会因为序列化的原因造成长度不准
- Long age3 = redisTemplate.opsForValue().increment("age", 10);
- System.out.println("incr -->" + age3);
- // Redis 命令: decr key - 自减
- redisTemplate.opsForValue().increment("age", -10);
- Long decr = redisTemplate.getConnectionFactory().getConnection().decr("age".getBytes());
- System.out.println("decr -->" + decr);
- }
4.4,Hash 操作类型.
- /**
- * Hash 数据类型的操作
- */
- @Test
- public void hashType(){
- // Redis 命令: mset key field value
- redisTemplate.opsForHash().put("person", "name", "张三");
- redisTemplate.opsForHash().put("person", "age", 19);
- // Redis 命令: mget key field
- String value = (String) redisTemplate.opsForHash().get("person", "name");
- System.out.println("mget-->" + value);
- // Redis 命令: hmset key field1 value1 field2 value2 ...
- Map<String, String> map = new HashMap<>();
- map.put("bookname", "Java 精通之路");
- map.put("price", "100.99");
- redisTemplate.opsForHash().putAll("book", map);
- // Redis 命令: hmget key field1 field2 ...
- List<String> list = new ArrayList<>();
- list.add("bookname");
- list.add("price");
- List books = redisTemplate.opsForHash().multiGet("book", list);
- System.out.println("hmget-->" + books);
- // Redis 命令: del key
- redisTemplate.delete("book");
- // Redis 命令: hdel key field1 field2...
- redisTemplate.opsForHash().delete("book", "bookname", "price");
- // Redis 命令: hexists key field
- Boolean bool = redisTemplate.opsForHash().hasKey("book", "bookname");
- System.out.println("hexists-->" + bool);
- // Redis 命令: hlen key
- Long length = redisTemplate.opsForHash().size("book");
- System.out.println("hlen-->" + length);
- // Redis 命令: hkeys key - 展示 key 对应的所有字段名称
- Set set = redisTemplate.opsForHash().keys("book");
- System.out.println("hkeys-->" + set);
- // Redis 命令: hvals key - 展示 key 对应的所有字段的值
- List values = redisTemplate.opsForHash().values("book");
- System.out.println("hvals-->" + values);
- // Redis 命令: hgetall key - field and value
- Map bookmap = redisTemplate.opsForHash().entries("book");
- System.out.println("hgetall-->" + bookmap);
- }
4.5, 链表数据结构.
- /**
- * 链表数据结构
- */
- @Test
- public void linkedType(){
- // Redis 命令: lpush key value1 value2...
- redisTemplate.opsForList().leftPush("book", "c++");
- redisTemplate.opsForList().leftPushAll("book", "c", "java");
- // Redis 命令: rpush key value1 value2
- redisTemplate.opsForList().rightPush("book", "mysql");
- redisTemplate.opsForList().rightPushAll("book", "oracle", "sqlserver");
- // Redis 命令: lindex key index
- String book0 = (String) redisTemplate.opsForList().index("book", 0);
- System.out.println("lindex-->" + book0);
- // Redis 命令: llen key
- Long bookLen = redisTemplate.opsForList().size("book");
- System.out.println("llen-->" + bookLen);
- // Redis 命令: lpop key
- String leftBook = (String) redisTemplate.opsForList().leftPop("book");
- System.out.println("lpop-->" + leftBook);
- // Redis 命令: rpop key
- String rightBook = (String) redisTemplate.opsForList().rightPop("book");
- System.out.println("rpop-->" + rightBook);
- // Redis 命令: linsert key before|after oldnode newnode
- redisTemplate.opsForList().leftPush("book", "java", "pythod");
- redisTemplate.opsForList().rightPush("book", "java", "jquery");
- // Redis 命令: lrange key start end
- List rangeList = redisTemplate.opsForList().range("book", 0, redisTemplate.opsForList().size("book") - 1);
- System.out.println("lrange-->" + rangeList);
- // Redis 命令: lset key index value
- redisTemplate.opsForList().set("book", 0, "db");
- // Redis 命令: ltrim key start end
- redisTemplate.opsForList().trim("book", 1, 3);
- // Redis 命令: lrange key start end
- List rangeList2 = redisTemplate.opsForList().range("book", 0, redisTemplate.opsForList().size("book") - 1);
- System.out.println("lrange-->" + rangeList2);
- }
4.6, 集合操作类型.
- /**
- * 集合操作
- */
- @Test
- public void setType(){
- // Redis 命令: sadd
- redisTemplate.opsForSet().add("person", "小明","小红","小刚");
- // Redis 命令: scard
- Long person = redisTemplate.opsForSet().size("person");
- System.out.println("scard-->" + person);
- // Redis 命令: smembers
- Set set = redisTemplate.opsForSet().members("person");
- System.out.println("smembers-->" + set);
- }
4.7, 有序集合操作类型
- /**
- * 有序集合
- */
- @Test
- public void zsetType(){
- redisTemplate.opsForZSet().add("book", "mysql", 1.5);
- redisTemplate.opsForZSet().add("book", "java", 8.5);
- redisTemplate.opsForZSet().add("book", "html", 10.5);
- Set set = redisTemplate.opsForZSet().range("book", 0, redisTemplate.opsForZSet().size("book") - 1);
- System.out.println(set);
- }
五, Redis 高级应用
5.1, 事务
与其他 NoSQL 不同, Redis 是存在事务的, 尽管没有数据库那么强大, 但是还是非常有用, 尤其是在高并发的情况中, 使用 Redis 的事务可以保证数据一致性的同时, 大幅度提高数据读写的响应速度.
Redis 的事务是使用 multi-exec 的命令组合, 使用它可以提供两个重要保证:
1, 事务是一个被隔离的操作, 事务中的方法都会被 Redis 进行序列化并按顺序执行, 事务在执行的过程中不会被其他客户端的发出的命令所打断.
2, 事务是一个原子性操作, 它要么全部执行, 要么全部不执行.
== 事务的常用命令:==
multi: 开启事务, 之后的命令就会进入队列, 而不是马上执行.
watch key1 [key2]...: 监听某些键, 当被监听的键在提交事务前被修改, 则事务会回滚 (基于乐观锁机制).
unwatch key1 [key2]...: 取消监听.
exec: 执行事务, 如果被监听的键没有被修改, 则采用提交命令, 否则就执行回滚命令.
discard: 回滚事务.
事务的开启及提交如下图:
从上图中看出, 当开始事务时进行操作, 命令并不会马上执行, 而是放在队列中, 只有在事务提交后才会执行.
== 但是, 要注意注意: Redis 中, 如果遇到格式正确而数据类型不符合的情况时, 不会进行事务回滚. 这是什么意思, 如下图操作所示:==
问题描述: 比如我要保存 1000 金额到内存中, 但是我不小心将金额输入成 1000a, 后面多了一个 a. 但是同样保存到了队列中. 最后当提交事务的时候便会报错, 可是 1000a 还是保存到了内存, 证明事务并没有回滚.
Redis 中存在监听机制, 可以监听某一个 key.
- /**
- * Redis 的事务管理, 要保证事务的开启和提交是同一条连接.
- */
- @Test
- public void transcation(){
- List results = (List) redisTemplate.execute(new SessionCallback() {
- @Override
- public Object execute(RedisOperations redisOperations) throws DataAccessException {
- // 开启事务
- redisOperations.multi();
- // 进行操作
- redisOperations.opsForValue().set("name", "张三");
- redisOperations.opsForValue().get("name");
- // 提交事务
- List result = redisOperations.exec();
- return result;
- }
- });
- System.out.println("-->" + results);
- }
5.2, 流水线
在现实情况中, Redis 的读写速度十分快, 而系统的瓶颈往往是在网络通信中的延迟. Redis 可能会再很多时候处于空闲状态而等待命令的到达. 为了解决这个问题, 可以使用 Redis 的流水线, 流水线是一种通讯协议, 类似一个队列批量执行一组命令.
由于这种情况在实际工作中较少使用, 所以就简短介绍一下.
- /**
- * 流水线
- */
- @Test
- public void pipelined(){
- // 流水线
- long begin = System.currentTimeMillis();
- redisTemplate.executePipelined(new SessionCallback() {
- @Override
- public Object execute(RedisOperations redisOperations) throws DataAccessException {
- for (int i = 0; i <100000; i++) {
- redisOperations.opsForValue().set("key" + i, "value" + i);
- redisOperations.opsForValue().get("key" + i);
- }
- return null;
- }
- });
- long end = System.currentTimeMillis();
- System.out.println("耗时:" + (end - begin));
- }
5.3, 发布订阅
说到发布订阅是否会想到 RabbitMQ 等消息中间件?
但是 Redis 的发布订阅具有实时性, 当发布者改变数据时, 订阅者便会接收到更改后的消息.
== 使用命令:==
subscribe chat: 订阅 chat 渠道.
publish chat "message: 发布消息到 chat 渠道.
5.3.1, 定义监听类:
- /**
- * Redis 消息监听器
- */
- public class RedisMessageListener implements MessageListener{
- private RedisTemplate template;
- @Override
- public void onMessage(Message message, byte[] pattern) {
- // 获取渠道名称
- System.out.println("渠道名称:" + new String(pattern));
- // 获得消息
- byte[] body = message.getBody();
- // 获得值序列化转换器
- String msg = (String) template.getValueSerializer().deserialize(body);
- System.out.println("消息为:" + msg);
- }
- public RedisTemplate getTemplate() {
- return template;
- }
- public void setTemplate(RedisTemplate template) {
- this.template = template;
- }
- }
5.3.2, 配置文件配置监听
- <!-- 配置监听器 -->
- <bean id="redisMsgListener" class="com.yx.redis.RedisMessageListener">
- <!-- 注入 redis 模板 -->
- <property name="template" ref="template"></property>
- </bean>
- <!-- 配置监听容器 -->
- <bean id="topicContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer" destroy-method="destroy">
- <!-- redis 连接工厂 -->
- <property name="connectionFactory" ref="connectionFactory"/>
- <!-- 配置连接池, 连接池生存才能持续监听 -->
- <property name="taskExecutor">
- <bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
- <property name="poolSize" value="3"/>
- </bean>
- </property>
- <!-- 配置消息监听 -->
- <property name="messageListeners">
- <map>
- <!-- key-ref 和监听器的 id 保持一致 -->
- <entry key-ref="redisMsgListener">
- <bean class="org.springframework.data.redis.listener.ChannelTopic">
- <!-- 定义渠道 -->
- <constructor-arg value="yx"/>
- </bean>
- </entry>
- </map>
- </property>
- </bean>
5.3.3, 发布消息
- ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-*.xml");
- RedisTemplate redistemp = context.getBean(RedisTemplate.class);
- redistemp.convertAndSend("yx", "Hello");
5.4, 超时时间
在 Redis 中的超时时间时非常重要的, 因为我们的内存时有限的, 在一段时间内如果没有对一些数据进行处理. 那便会产生很多的垃圾数据, 因此对数据进行时间 上的设置是一种较好的习惯.
这里先暂时不讲述过期时间的原理, 后面会与大家分享, 还请关注哦~~~
== 超时时间的命令:==
persist key: 持久化 key, 即得永生(移除 key 的超时时间).
expire key seconds: 设置超时时间, 单位为秒.
ttl key: 查看 key 的超时时间, 单位为秒, 返回 - 1 表示没有超时时间, 如果 key 不存在或者已经超时, 则返回 - 2.
pttl key: 查看 key 的超时时间, 单位为毫秒.
pexpire key milliseconds: 设置 key 的超时时间, 以毫秒为单位.
== 关于超时时间需要有几点注意: 当一个 key 过了超时时间以后, 并不会立刻从内存中移除. 在以下情况下数据会被清除.==
1, 当要获得 key 的值的时候, 比如执行了 get key 命令.
2, 系统自己会有一个定时器, 每隔 1 秒, 扫描一次内存. 清除超时的 key(不会完全扫描所有的 key, 不会完全的移除所有超时的 key).
3, 内存已满, 就会根据配置文件进行内存数据的清理.
- @Test
- public void expire() throws ParseException {
- redisTemplate.opsForValue().set("name", "小明");
- // 设置超时时间 - 5 ~ 10 分钟
- redisTemplate.expire("name", 10, TimeUnit.SECONDS);
- // 设置超时时间到指定的时间
- String time = "2019-08-23 12:00:00";
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- Date date = sdf.parse(time);
- redisTemplate.expireAt("name", date);
- // 移除超时时间
- redisTemplate.persist("name");
- // 获得还能活多久
- redisTemplate.getExpire("name");
- String name = (String) redisTemplate.opsForValue().get("name");
- System.out.println(name);
- try {
- Thread.sleep(10000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(name);
- }
六, 总结
写到这里 Redis 的基本使用也差不多了, 但是仍有很多技术点没有记录到. 比如与 Lua 语言的结合使用, Redis 的持久化, 内存的淘汰策略, 读写分离(哨兵模式), 集群等.
如果你看到这篇博客, 以上没有分享的内容会在后续发布的, 还请关注~
最后, 以上内容均是自主学习的总结, 如有错误或者不合适的地方欢迎留言 (或者邮箱) 指教.
感谢观看!
来源: https://www.cnblogs.com/fenjyang/p/11402242.html