【松思园】Redis 学习笔记(狂神教程)

一、NoSql

Not Only Sql (不仅仅是SQL)

很多数据类型如用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式,不需要多余的操作就可以横向扩展。

1. NoSQL 特点

  • 方便扩展(数据之间没有关系,很好扩展);

  • 大数据量高性能(Redis 一秒写8万次,读11万次);

  • 数据类型是多样型的(不需要事先设计数据库);

  • 传统的 RDBMS 和 NoSQL的区别:

    传统的 关系型数据库:
    	- 结构化组织;
        - SQL;
        - 数据和关系存在单独的表中;
        - 严格的一致性;
    NoSQL:
        - 不仅仅是数据;
        - 没有固定的查询语言;
        - 键值对存储,列存储,文档存储,图形数据库(社交关系);
        - CAP 定理 和 BASE (异地多活)
        - 高性能,高可用,可扩展
    

真正的实践:NoSQL + RDBMS 一起使用才是最强的。

2. NoSQL 的四大分类

  • KV键值对:Redis ;

  • 文档型数据库:(bson 格式 和 json 一样): MongoDB ;

    - MongoDB 是一个基于分布式文件存储的数据库,C++ 编写,主要用来存储大量的文档;
    - MongoDB 是一个介于关系型数据库和非关系型数据库之间的一个产品;
    - MongoDB 是非关系型数据库中功能最丰富的,最像关系型数据库的!3
    
  • 列存储数据库:HBase ;

  • 图关系数据库:Neo4j , InfoGrid ;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sgA4Gonk-1642057063675)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20211228110501095.png)]

二、Redis 基础

1. redis 是什么?

  • 即远程字典服务!免费、开源,当下最热门的的技术之一;
  • 可以内存存储、持久化;
  • 效率高,可以用于高速缓存;
  • 发布订阅系统 ;
  • 地图信息分析 ;
  • 计时器、计数器等;

在redis目录下,在命令窗口执行:redis-server.exe redis.windows.conf

中文官网:http://www.redis.cn/

2. windows 安装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zO0keNRi-1642057063676)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20211228141550178.png)]

测试连接: 通过 ping 如果输出 PONG 表示连接成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hb9R0DCV-1642057063677)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20211228152716514.png)]

3. reis-benchmark 性能测试

可选参数如下:

image-20211229094120631

举例:本地 redis 100个并发连接,1000次的请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LT3yUlTz-1642057063678)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20211229095213732.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aTUiiFys-1642057063679)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20211229094224333.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9VUYf15i-1642057063680)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20211229095127583.png)]

4. 基础知识

  • redis 有16个数据库,默认使用的是第 1 个;
  • redis 是单线程的,基于内存操作的,是将多有的数据放在内存中的;
  • redis 的瓶颈是机器的内存和网络带宽;

5. 数据类型

(1)redis-key

  • select 【数据库索引】:切换到对应索引值的数据库 ;
  • flushdb :清空当前数据库 ;
  • flushall :清空所有数据库;
  • keys * : 查询所有的key ;
  • exists 【key】:判断当前 key 是否存在 1表示存在 0表示不存在 ;
  • set 【key】 【values】 :设置键值对,如果已经存在对应 key 会覆盖 ;
  • setnx 【key】【values】:不存在在设置,如果已经存在,不会设置成功 ;
  • mset 【key1】【values1】【keys2】【values2】……: 批量设置值,会覆盖 ;
  • msetnx ……: 批量设置,如果存在则不会设置成功 ;
  • get 【key】:获取对应 key 的值 ;
  • mget 【k1】【k2】【k3】……: 批量获取对应 key 的值 ;
  • getset 【key】【value】:先 get 然后再 set ;
  • move 【key】 【数据库】:从某个数据库移除 key 值 ;
  • expire 【key】 【times】:设置 key 的过期时间,单位是 秒 ;
  • ttl 【key】:查看当前 key 的剩余时间 ;
  • type 【key】:查看当前 key 的类型 ;

(2)类型1:String类型

  • append 【key】【values】:为字符串追加内容,如果不存在,则新建 ;
  • strlen 【key】:获取指定 key 值的长度 ;
  • getrange 【key】【start】【end】:获取 key 上值起始和结束长度的一个字符串 ;
  • setrange 【key】【start】【values】:替换 key 指定位置开始的字符串 ;

(3)类型2:List 类型

  • lpush 【list】【value】:将一个或多个值插入到列表的头部(头);
  • rpush 【list】【value】:将一个或多个值插入到列表的尾部(尾);
  • lrange 【list】【start】【end】:从列表中获取指定返回的元素 0 到 -1 是取所有;
  • lpop 【list】:移除列表中 头部的 第一个值 ;
  • rpop【list】:移除列表中 尾部的 第一个值 ;
  • lindex 【list】【下标】:根据下标获取元素 ;
  • llen 【list】:返回列表的长度 ;
  • lrem 【list】【count】【value】:移除指定的数据 ;
  • ltrim 【list】【start】【end】:通过下标截取指定的长度操作,原 list 被修改;
  • rpoplpush 【原list】【目标 list】:移除尾部元素,并赋值给新的列表 ;
  • lset 【list】【index】【value】:为指定下标赋值 ,下标不存在会报错;
  • linsert 【list】【before / after】【目标值】【value】:在指定目标值的前或后插入内容 ;

(4)类型3 :Set 类型

  • sadd 【set】【value】:set中添加一个值 ;
  • smembers 【set】 : 查看set中的值列表 ;
  • sismember 【set】 【value】:判断某个值是否在set中 ;
  • scard 【set】:获取 set 集合中的元素个数 ;
  • srem 【set】【value】:移除 set 集合中的元素 ;
  • spop 【set】:随机移除 set 集合中的元素 ;
  • srandmember 【set】【count】:随机取出 set 集合中的 几个 元素;
  • smove 【原set】【目标set】【value】:移动元素;
  • sdiff 【set1】【set2】:筛选出 set1 和 set2 不一样的内容;
  • sinter 【set1】【set2】:筛选出 set1 和 set2 中交集的内容;
  • sunion 【set1】【set2】:筛选出 set1 和 set2 的并集;

(5)类型 4:Hash 类型:

<key,Map>

  • hset 【Hash 】【field字段名】【value字段值】:设置 hash 的值 ,同一个 key 相同的 name 会覆盖器内容;
  • hget 【Hash 】【field 字段名】:取出值;
  • hgetall 【Hash 】:获取 key 中所有的 值,包括了 map 的key和value ;
  • hkeys 【Hash 】:获取 hash 中所有的 keys ;
  • hvals 【Hash】:获取 hash 中所有的 values ;
  • hmset :设置多个值 ;
  • hmget : 获取多个值 ;
  • hdel 【Hash 】【field】:删除 hash 指定的字段;
  • hlen 【Hash 】:获取 hash 表的字段数量 ;
  • hexists 【Hash 】【field】:判断 hash 中指定的 字段 是否存在;

(6)类型 5 :Zset 类型

在 set 的基础上,可以进行排序;

  • zadd 【set】【序号】【value】:set 中添加一个有序的值,可以添加多个 ;
  • zrange 【set】【start】【end】:获取 zset 中的值,0 ,-1 是获取所有的 ;
  • zrangebyscore 【set】【-inf 无穷小】【+inf 无穷大】:显示全部的数据,从小到大 ;
  • zrevrange 【set】【0】【-1】:显示全部数据,从大到小 ;
  • zrem 【set】【value】:移除元素 ;
  • zcard 【set】:查看元素数量 ;
  • zcount 【set】【start】【end】:统计指定区间的成员数量 ;

(6)类型 6: Integer 类型

  • incr 【key】:key 值自增 1 ;
  • decr 【key】:key 值自减 1 ;
  • incrby 【key】 【量】:key 值按照自增量进行自增 ;
  • decrby 【key】 【量】:key 值按照自减量进行自减 ;

6. 三种特殊数据类型

(1)geospatial 地理位置

有效的经度 -180度 到180度,有效的维度 -85.05112878度 到 85.05112878度

  • geoadd 【key】【维度】【经度】【名称】:添加城市地理位置;
  • geopos 【key】 【名称】:获取经纬度 ;
  • geodist 【key】【名称1】【名称2】【单位 km / m】:获取两个地点的直线距离;
  • georadius 【key】【经度】【维度】 【半径】【单位】【withdist经度】【withcoord维度】【count数量】:查找给你经纬度位置半径内的位置;
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hq4qRI4A-1642057063684)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220104151920113.png)]
  • georadiusbymember 【key】【名称】【距离】【单位】:查找以某个城市为中心半径为多少所覆盖的城市;

(2)hyperloglog 数据结构

用来做基数统计的算法。

优点:占用内存是固定的

  • pfadd 【key】【values】: 添加元素;
  • pfcount 【key】:统计元素个数;
  • pfmerge 【新key】【key1】【key2】:合并两个集合生成第三个集合,取的是并集;

(3)bitmap :位存储

都是操作二进制位来进行记录,只有 0 和 1 两个状态;

  • setbit 【key】【位置】【0/1】:设置值;
  • getbit 【key】【位置】:获取对应位置的值;
  • bitcount 【key】:统计里面为 1 的数量;

7. 事务

事务:一组命令的集合;

redis 单条命令是保持原子性的,但是redis事务不保证原子性,redis事务没有隔离级别的概念,所有的命令在事物中,并没有被直接执行,只有发起执行命令的时候才会执行;

  • 开启事务(multi )
  • 命令入队(其他命令……)
  • 执行事务(exec)
  • 取消事务(discard)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C8bly8XZ-1642057063686)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220104162947848.png)]

  • 错误,指令语法错误,虽然事务不会停止,提交事务时所有的命令都不会运行(编译时异常);
  • 错误,操作错误,事务不会停止,提交事务时,只有操作错误的指令报错,其他指令正常运行(运行时异常);

8. 乐观锁 - 监视 Watch

  • 第一步:获取 ; 第二步:更新 ;

  • 加锁: watch 【对象】

  • 解锁:unwatch

9. Jedis

是 redis 官方推荐的 java 连接开发工具,使用 java 操作redis 中间件。

  • 导入对应的依赖:

    <dependencies>
            <!--导入 jedis 的包-->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.2.0</version>
            </dependency>
            <!--json 包-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.62</version>
            </dependency>
        </dependencies>
    
  • 编码测试:

    • 连接数据库;
    • 操作命令;
    • 断开连接;
  • Jedis 常用的 API 和 redis 指令是一样的;

10. SpringBoot 整合

新建 springboot 项目,选择 nosql 中的 redis 组件 :

<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 模式;

  • 导入依赖;
  • 配置连接;
########## redis 配置
# ip地址
spring.redis.host=172.31.240.211
# 端口号
spring.redis.port=6379
# 默认的数据库
spring.redis.database=0
  • 代码测试;

(1) opsFor - 方法:

  • opsForValue() : 操作字符串 ;
  • opsForZSet():操作 set ;
  • opsForList() : 操作 list ;
  • opsForGeo() :操作 地图;
  • ……

除了上面的进入操作,可以之间通过 redisTemplate 进行操作;

(2)自定义redis配置类 RedisConfig

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1WNSpuC-1642057063687)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220106152017364.png)]

因为自带的redis配置类,编码方式会导致中文乱码的问题;

  • 自定义配置类 RedisConfig.java:

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    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;
    
    /**
     * Redis 配置
     */
    @Configuration
    public class RedisConfig {
    
    	@Autowired
    	private RedisConnectionFactory factory;
    
    	@Bean("myRedisTemplate")
    	@SuppressWarnings("all")
    	public RedisTemplate<String, Object> redisTemplate() {
    		RedisTemplate<String, Object> template = new RedisTemplate<>();
    		template.setConnectionFactory(factory);
    		// 序列化配置
    		// 1.所有的对象通过 json 进行序列化
    		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    		// 2.通过 ObjectMapper 进行转义
    		ObjectMapper om = new ObjectMapper();
    		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    		// om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
    		jackson2JsonRedisSerializer.setObjectMapper(om);
    		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;
    	}
    }
    

(3)自定义 redis 工具类 RedisUtils.java

/**
 * Redis工具类
 */
@Component
public class RedisUtils {

	@Autowired
	@Qualifier("myRedisTemplate")
	private RedisTemplate<String, Object> redisTemplate;

	/**
	 * 指定缓存失效时间
	 *
	 * @param key  键
	 * @param time 时间(秒)
	 */
	public boolean setExpireTime(String key, long time) {
		try {
			if (time > 0) {
				redisTemplate.expire(key, time, TimeUnit.SECONDS);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 根据 key 获取过期时间
	 *
	 * @param key 键 不能为null
	 * @return 时间(秒) 返回0代表为永久有效
	 */
	public long getExpireTime(String key) {
		return redisTemplate.getExpire(key, TimeUnit.SECONDS);
	}

	/**
	 * 判断key是否存在
	 *
	 * @param key 键
	 * @return true 存在 false不存在
	 */
	public boolean isHasKey(String key) {
		try {
			return redisTemplate.hasKey(key);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 删除缓存
	 *
	 * @param key 可以传一个值 或多个
	 */
	@SuppressWarnings("unchecked")
	public void delCache(String... key) {
		if (key != null && key.length > 0) {
			if (key.length == 1) {
				redisTemplate.delete(key[0]);
			} else {
				redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
			}
		}
	}
	// ============================String=============================

	/**
	 * 普通缓存获取
	 *
	 * @param key 键
	 * @return 值
	 */
	public Object getCacheString(String key) {
		return key == null ? null : redisTemplate.opsForValue().get(key);
	}

	/**
	 * 普通缓存放入
	 *
	 * @param key   键
	 * @param value 值
	 * @return true成功 false失败
	 */
	public boolean setCacheString(String key, Object value) {
		try {
			redisTemplate.opsForValue().set(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 普通缓存放入并设置时间
	 *
	 * @param key   键
	 * @param value 值
	 * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
	 * @return true成功 false 失败
	 */

	public boolean setCacheAndExpireTimeString(String key, Object value, long time) {
		try {
			if (time > 0) {
				redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
			} else {
				setCacheString(key, value);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 递增
	 *
	 * @param key   键
	 * @param delta 要增加几(大于0)
	 */
	public long incrCacheString(String key, long delta) {
		if (delta < 0) {
			throw new RuntimeException("递增因子必须大于0");
		}
		return redisTemplate.opsForValue().increment(key, delta);
	}

	/**
	 * 递减
	 *
	 * @param key   键
	 * @param delta 要减少几(小于0)
	 */
	public long decrCacheString(String key, long delta) {
		if (delta < 0) {
			throw new RuntimeException("递减因子必须大于0");
		}
		return redisTemplate.opsForValue().increment(key, -delta);
	}
	// ================================Map=================================

	/**
	 * HashGet
	 *
	 * @param key  键 不能为null
	 * @param item 项 不能为null
	 */
	public Object hgetCacheMap(String key, String item) {
		return redisTemplate.opsForHash().get(key, item);
	}

	/**
	 * 获取hashKey对应的所有键值
	 *
	 * @param key 键
	 * @return 对应的多个键值
	 */
	public Map<Object, Object> hmgetCacheMap(String key) {
		return redisTemplate.opsForHash().entries(key);
	}

	/**
	 * HashSet
	 *
	 * @param key 键
	 * @param map 对应多个键值
	 */
	public boolean hmsetCacheMap(String key, Map<String, Object> map) {
		try {
			redisTemplate.opsForHash().putAll(key, map);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * HashSet 并设置时间
	 *
	 * @param key  键
	 * @param map  对应多个键值
	 * @param time 时间(秒)
	 * @return true成功 false失败
	 */
	public boolean hmsetCacheAndExpireTimeMap(String key, Map<String, Object> map, long time) {
		try {
			redisTemplate.opsForHash().putAll(key, map);
			if (time > 0) {
				setExpireTime(key, time);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 向一张hash表中放入数据,如果不存在将创建
	 *
	 * @param key   键
	 * @param item  项
	 * @param value 值
	 * @return true 成功 false失败
	 */
	public boolean hsetCacheMap(String key, String item, Object value) {
		try {
			redisTemplate.opsForHash().put(key, item, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 向一张hash表中放入数据,如果不存在将创建
	 *
	 * @param key   键
	 * @param item  项
	 * @param value 值
	 * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
	 * @return true 成功 false失败
	 */
	public boolean hsetCacheAndExpireTimeMap(String key, String item, Object value, long time) {
		try {
			redisTemplate.opsForHash().put(key, item, value);
			if (time > 0) {
				setExpireTime(key, time);
			}
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 删除hash表中的值
	 *
	 * @param key  键 不能为null
	 * @param item 项 可以使多个 不能为null
	 */
	public void hdelCacheMap(String key, Object... item) {
		redisTemplate.opsForHash().delete(key, item);
	}

	/**
	 * 判断hash表中是否有该项的值
	 *
	 * @param key  键 不能为null
	 * @param item 项 不能为null
	 * @return true 存在 false不存在
	 */
	public boolean hHasKeyCahcheMap(String key, String item) {
		return redisTemplate.opsForHash().hasKey(key, item);
	}

	/**
	 * hash递增 如果不存在,就会创建一个 并把新增后的值返回
	 *
	 * @param key  键
	 * @param item 项
	 * @param by   要增加几(大于0)
	 */
	public double hincrCacheMap(String key, String item, double by) {
		return redisTemplate.opsForHash().increment(key, item, by);
	}

	/**
	 * hash递减
	 *
	 * @param key  键
	 * @param item 项
	 * @param by   要减少记(小于0)
	 */
	public double hdecrCacheMap(String key, String item, double by) {
		return redisTemplate.opsForHash().increment(key, item, -by);
	}
	// ============================set=============================

	/**
	 * 根据key获取Set中的所有值
	 *
	 * @param key 键
	 */
	public Set<Object> sGetCacheSet(String key) {
		try {
			return redisTemplate.opsForSet().members(key);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 根据value从一个set中查询,是否存在
	 *
	 * @param key   键
	 * @param value 值
	 * @return true 存在 false不存在
	 */
	public boolean sHasKeyCacheSet(String key, Object value) {
		try {
			return redisTemplate.opsForSet().isMember(key, value);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将数据放入set缓存
	 *
	 * @param key    键
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	public long sSetCacheSet(String key, Object... values) {
		try {
			return redisTemplate.opsForSet().add(key, values);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 将set数据放入缓存
	 *
	 * @param key    键
	 * @param time   时间(秒)
	 * @param values 值 可以是多个
	 * @return 成功个数
	 */
	public long sSetAndTime(String key, long time, Object... values) {
		try {
			Long count = redisTemplate.opsForSet().add(key, values);
			if (time > 0)
				setExpireTime(key, time);
			return count;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 获取set缓存的长度
	 *
	 * @param key 键
	 */
	public long sGetSetSize(String key) {
		try {
			return redisTemplate.opsForSet().size(key);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 移除值为value的
	 *
	 * @param key    键
	 * @param values 值 可以是多个
	 * @return 移除的个数
	 */

	public long setRemoveCacheSet(String key, Object... values) {
		try {
			Long count = redisTemplate.opsForSet().remove(key, values);
			return count;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}
	// ===============================list=================================

	/**
	 * 获取list缓存的内容
	 *
	 * @param key   键
	 * @param start 开始
	 * @param end   结束 0 到 -1代表所有值
	 */
	public List<Object> lGetCacheList(String key, long start, long end) {
		try {
			return redisTemplate.opsForList().range(key, start, end);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 获取list缓存的长度
	 *
	 * @param key 键
	 */
	public long lGetListSize(String key) {
		try {
			return redisTemplate.opsForList().size(key);
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	/**
	 * 通过索引 获取list中的值
	 *
	 * @param key   键
	 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
	 */
	public Object lGetIndexCacheList(String key, long index) {
		try {
			return redisTemplate.opsForList().index(key, index);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 */
	public boolean lSetCacheList(String key, Object value) {
		try {
			redisTemplate.opsForList().rightPush(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @param time  时间(秒)
	 */
	public boolean lSetCacheList(String key, Object value, long time) {
		try {
			redisTemplate.opsForList().rightPush(key, value);
			if (time > 0)
				setExpireTime(key, time);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @return
	 */
	public boolean lSetCacheList(String key, List<Object> value) {
		try {
			redisTemplate.opsForList().rightPushAll(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}

	}

	/**
	 * 将list放入缓存
	 *
	 * @param key   键
	 * @param value 值
	 * @param time  时间(秒)
	 * @return
	 */
	public boolean lSetCacheList(String key, List<Object> value, long time) {
		try {
			redisTemplate.opsForList().rightPushAll(key, value);
			if (time > 0)
				setExpireTime(key, time);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 根据索引修改list中的某条数据
	 *
	 * @param key   键
	 * @param index 索引
	 * @param value 值
	 * @return
	 */

	public boolean lUpdateIndexCacheList(String key, long index, Object value) {
		try {
			redisTemplate.opsForList().set(key, index, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 移除N个值为value
	 *
	 * @param key   键
	 * @param count 移除多少个
	 * @param value 值
	 * @return 移除的个数
	 */

	public long lRemoveCacheList(String key, long count, Object value) {
		try {
			Long remove = redisTemplate.opsForList().remove(key, count, value);
			return remove;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}

	}

}

三、Redis 进阶

1. redis.config 详解

  • 对大小写是不敏感的;
  • 可以把多个配置文件都包含进来 通过 include

网络

bing 127.0.0.1 # 绑定的 ip

protected-mode yes # 保护模式

port 6379 # 端口设置

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JLHibyPl-1642057063688)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220110110537732.png)]

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hR8HR7bM-1642057063689)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220110111339659.png)]

  • 设置 redis 的密码,默认是没有的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clHrrHyC-1642057063690)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220110110959548.png)]

设置空密码: config set requirepass ""        这样就把密码设置空了
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-70e6YpmA-1642057063692)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220110111717643.png)]

2. Redis 持久化

redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 redis 提供持久化的功能。

(1)RDB 文件

在指定的时间间隔内,将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里。

redis 会单独床阿金 一个子进程进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程,主进程是不进行任何的IO操作的,这就确保了性能,如果需要进行大规模的数据恢复,且对于数据护肤的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。RDB的缺点是最后一次持久化的数据可能会丢失。rdb 保存的文件是 dump.rdb 文件

触发机制:

  • save 的规则满足的情况下,会自动出发 rdb 规则;
  • 执行 flushall 命令,也会触发我们的 rdb 规则;
  • 退出 redis ,也会产生 rdb 文件 ;

恢复 rdb 文件:

  • 只要将 rdb 文件放在我们 redis 启动目录就可以,redis 启动的时候会自动检查 dump.rdb 恢复其中数据;
  • 优点:适合大规模的数据恢复;对数据的完整性要求不高;
  • 缺点:需要一定的时间间隔;如果意外宕机,最后一次修改数据就没有了;fork进程的时候会占用一定的内存空间;

(2)AOF 文件

将我们所有的命令都记录下来,history 文件,恢复的时候就把这个文件全部在执行一遍;

以日志的形式来记录每个写操作,将 redis 执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改文件,redis 启动之初会读取该文件重新构建数据,换言之,redis 重启的话就会根据日志文件的内容,将指令从前到后执行一次以完成数据的恢复工作。 AOF 保存的是 appendonly.aof 文件

默认是不开启的 ,需要将配置文件中的 appendonly 改为 yes 就开启了 aof ,重启 redis 即可生效 !

如果说 appendonly.aof 文件被破坏,那么将无法启动 redis,可以通过 redis-check-aof 工具进行自动修复

  • 有点:每一次修改都同步,文件的完整会更加好;默认开启的是每秒同步一次;
  • 缺点:相对于 rdb 来说,aof 远远大于 rdb,修复的速度也比较慢;运行效率也比较慢

3. 主从复制

是指将一台 redis 服务器的数据,复制到其他的 redis 服务器,前者称为 主节点 Master,后者称为 从节点 Slave,数据的复制是单向的,只能由主节点到从节点, 主节点以写为主,从节点以读为主

默认情况下,每天 redis 服务器都是主节点,且一个主节点可以有多个从节点,但一个从节点只能有一个主节点;

主从复制的作用主要包括:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式;
  • 故障恢复:当节点出现问题时,可以有从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余;
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,从节点提供读服务,分担服务器的负载;
  • 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 redis 高可用的基础;

(1)环境配置

只配置从库,不用配置主库 一主二从; 目前在同一台window下安装三个redis实例 其中主服务端口6379,从服务slave1为6380,从服务slave2为6381. 单机多集群

a). 三个服务情况如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SNIVOgQp-1642057063693)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220111095450588.png)]

b). 主服务进行配置 6379: redis.windows-service.conf 和 redis.windows.conf 都要做修改

  • redis.windows.conf

  • logfile :原默认没有名次,指定名次为 “6379.log” ,此处建议不动

    注意:如果这里指定了日志名,就会在目录下生产对应的日志文件,而不会在命令行窗口进行打印了
    
    image-20220111145506659
  • dbfilename dump.rdb :修改为 dbfilename dump6379.rdb

  • redis.windows-service.conf

    • logfile “server_log.txt” : 修改为 logfile “server_log6379.txt”
    • dbfilename dump.rdb :修改为 dbfilename dump6379.rdb

c). 从服务 6380 ……

  • port 6379 : 端口改为 port 6380;
  • pidfile /var/run/redis.pid :改为 pidfile /var/run/redis6380.pid
  • logfile “server_log.txt” : 改为 logfile “server_log6380.txt”
  • dbfilename dump.rdb :修改为 dbfilename dump6380.rdb
  • slaveof :修改为 slaveof 127.0.0.1 6379

d). 从服务 6381

  • port 6379 : 端口改为 port 6381;
  • pidfile /var/run/redis.pid :改为 pidfile /var/run/redis6380.pid
  • logfile “server_log.txt” : 改为 logfile “server_log6380.txt”
  • dbfilename dump.rdb :修改为 dbfilename dump6380.rdb
  • slaveof :修改为 slaveof 127.0.0.1 6379

(2)启动和关闭

分别切换到 三个 服务的目录下,进行启动 redis 服务:redis-server.exe redis.windows.conf

关闭服务,在 cli 客户端中输入 shutdown 再输出 exit

启动客户端,进入到服务目录下,通过 redis-cli.exe -p 6380 这种方式进行客户端的切换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ebYM9M4f-1642057063694)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220111103952684.png)]

(3)验证

打开主服务的客户端,输入 info replication 可以看到从服务的信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lw6wO2nv-1642057063695)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220111105141837.png)]

从机启动之后,成功连接到 主机后 会发送一个 sync 命令;主机在接到命令后,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后, 主机将传送整个数据文件到 从机上,并完成一次同步。

默认情况下,主服务 负责写,从 服务负责读

image-20220111151305373 image-20220111152035775

(4)主机宕掉

如果主机宕掉,我们可以使用 slaveof no one ,进行修改配置文件,让自己变成主机!其他节点可以手动连接到这个节点。

4. 哨兵模式

自动选取老大的模式(主服务);

  • redis 从 2.8 开始 正式提供了 Sentinel (哨兵)架构来解决这个问题;
  • 能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换成主库;
  • 哨兵模式是一种特殊的模式,首先 redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行。
  • 其原理是哨兵通过发送命令,等待 redis 服务器响应,从而监控运行的多个 redis 实例;
image-20220112090308049
  • 然而一个哨兵进程 对redis 服务进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这就形成了多哨兵模式。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uJQrt5r8-1642057063696)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220112092421842.png)]

    假设注解服务器宕机,哨兵1先检测到这种结果,系统不会马上进行 failover 过程,仅仅是哨兵1 主观认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用时,并且数量达到一定的值时,那么哨兵之间就会进行一次投票(投票算法),投票的结果由一个哨兵发起,进行 failover 操作,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线

(1)哨兵配置

a). 三个实例目录下分别新建sentinel.conf, 端口为6379, 6380, 6381,其他一致

port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
daemonize yes

最后的 2: 是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效;

哨兵就是个独立的进程,用来监听实例信息的变化,且对相关操作做出反应,这里对每个实例都配一个哨兵进程

b). 启动三个哨兵进程

C:\Redis-x64-3.2.100(slave6379)>redis-server.exe sentinel.conf --sentinel
C:\Redis-x64-3.2.100(slave6380)>redis-server.exe sentinel.conf --sentinel
C:\Redis-x64-3.2.100(slave6381)>redis-server.exe sentinel.conf --sentinel

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HbEmSWao-1642057063696)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220112105344879.png)]

至此,哨兵配置且启动完成.

(2)测试 停掉 6379主服务

停掉主库79的进程,这里就是在79实例的cmd(非哨兵cmd)下ctrl+c

在进行短暂的后台调整后的结果 ;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpTSNAT3-1642057063697)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220112111119342.png)]

81的日志:简单分析下日志:

  1. 老大下线了
  2. 疯狂的发送心跳进行询问(老大,你死了没?没回答,就应该是死了,哨兵进程辅助slave(s)建立新的老大)
  3. 自己成为老大 MASTER MODE enabled
  4. 其他小弟进行数据请求
  5. 新的主从建立
[15112] 09 Sep 14:57:23.699 # Connection with master lost.
[15112] 09 Sep 14:57:23.700 * Caching the disconnected master state.
[15112] 09 Sep 14:57:24.420 * Connecting to MASTER 127.0.0.1:6379
[15112] 09 Sep 14:57:24.420 * MASTER <-> SLAVE sync started
[15112] 09 Sep 14:57:25.421 * Non blocking connect for SYNC fired the event.
[15112] 09 Sep 14:57:25.421 # Sending command to master in replication handshake: -Writing to master: Unknown error
[15112] 09 Sep 14:57:25.423 * Connecting to MASTER 127.0.0.1:6379
[15112] 09 Sep 14:57:25.423 * MASTER <-> SLAVE sync started
[15112] 09 Sep 14:57:26.424 * Non blocking connect for SYNC fired the event.
[15112] 09 Sep 14:57:26.424 # Sending command to master in replication handshake: -Writing to master: Unknown error
[15112] 09 Sep 14:57:26.426 * Connecting to MASTER 127.0.0.1:6379
[15112] 09 Sep 14:57:26.426 * MASTER <-> SLAVE sync started
[15112] 09 Sep 14:57:27.427 * Non blocking connect for SYNC fired the event.
[15112] 09 Sep 14:57:27.427 # Sending command to master in replication handshake: -Writing to master: Unknown error
。。。。。。。。。。。。。。。。。。。。
[15112] 09 Sep 14:57:53.517 * Connecting to MASTER 127.0.0.1:6379
[15112] 09 Sep 14:57:53.517 * MASTER <-> SLAVE sync started
[15112] 09 Sep 14:57:54.006 * Discarding previously cached master state.
[15112] 09 Sep 14:57:54.007 * MASTER MODE enabled (user request from 'id=8 addr=127.0.0.1:51668 fd=14 name=sentinel-efc4be2b-cmd age=70 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=0 qbuf-free=32768 obl=36 oll=0 omem=0 events=rw cmd=exec')
[15112] 09 Sep 14:57:54.009 # CONFIG REWRITE executed with success.
[15112] 09 Sep 14:57:54.855 * Slave 127.0.0.1:6380 asks for synchronization
[15112] 09 Sep 14:57:54.855 * Full resync requested by slave 127.0.0.1:6380
[15112] 09 Sep 14:57:54.856 * Starting BGSAVE for SYNC with target: disk
[15112] 09 Sep 14:57:54.863 * Background saving started by pid 15712
[15112] 09 Sep 14:57:55.125 # fork operation complete
[15112] 09 Sep 14:57:55.126 * Background saving terminated with success
[15112] 09 Sep 14:57:55.129 * Synchronization with slave 127.0.0.1:6380 succeeded

redis-cli.exe -p 6380 进入到对应的客户端, info replication 看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JgfqZNgQ-1642057063698)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220112112029677.png)]

可以看到 当 6379 的挂了之后,经过哨兵的自动调整,主服务 由 6379 变成了 6381 , 6380 变成了从服务 。

(3)测试 重启 6379 主服务

这里我们再重新启动 6379 主服务,看一下会不会 哨兵会不会把主服务调成 6379, 6380 和 6381 又变成从服务;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ICpnqTvP-1642057063699)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220112112444379.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P45ssEeG-1642057063699)(C:\Users\13965\AppData\Roaming\Typora\typora-user-images\image-20220112113434427.png)]

重启之后会发现,79 的服务正常使用,但是值得注意的是,6379 的服务变成了 从服务,之前的 6381 服务,依然是主服务,并没有变成从服务。

(4)优缺点

  • 优点:基于主从复制模式,所有的主从配置优点,他全有;主从可以切换,故障可以转移,系统的可用性就会更好;
  • 缺点:redis 不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦;配置其实很麻烦;

5. redis 缓存穿透和雪崩

概念:用户想要查询一个数据,发现redis 内存没有,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求持久层的数据库,这会给持久层数据库造成很大的压力,这时候就出现了缓存穿透。

image-20220113142022234

解决方案:

(1)布隆过滤器:

是一种数据结构,对所有可能查询的参数以 hash 形式存储,在控制层先进校验,不符合则丢弃,从而避免了对底层存储系统查询压力。

image-20220113142330786

(2)缓存空对象:

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。

image-20220113142617531

但是这种方法会存在以下问题:

  • 如果空值能被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
    主服务

这里我们再重新启动 6379 主服务,看一下会不会 哨兵会不会把主服务调成 6379, 6380 和 6381 又变成从服务;

[外链图片转存中…(img-ICpnqTvP-1642057063699)]

[外链图片转存中…(img-P45ssEeG-1642057063699)]

重启之后会发现,79 的服务正常使用,但是值得注意的是,6379 的服务变成了 从服务,之前的 6381 服务,依然是主服务,并没有变成从服务。

(4)优缺点

  • 优点:基于主从复制模式,所有的主从配置优点,他全有;主从可以切换,故障可以转移,系统的可用性就会更好;
  • 缺点:redis 不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦;配置其实很麻烦;

5. redis 缓存穿透和雪崩

概念:用户想要查询一个数据,发现redis 内存没有,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求持久层的数据库,这会给持久层数据库造成很大的压力,这时候就出现了缓存穿透。

image-20220113142022234

解决方案:

(1)布隆过滤器:

是一种数据结构,对所有可能查询的参数以 hash 形式存储,在控制层先进校验,不符合则丢弃,从而避免了对底层存储系统查询压力。

image-20220113142330786

(2)缓存空对象:

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。

image-20220113142617531

但是这种方法会存在以下问题:

  • 如果空值能被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
  • 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值