Redis6笔记
1. Redis简介
官网:https://redis.io/
1. 技术分类:
- 功能类:
- 拓展类:
- 性能类:
2. NoSQL(Not only SQL)数据库:非关系型数据库
- 作用:
- 将经常使用的数据缓存到内存中,减少数据库io次数,缓解数据库压力
- 分类:memcache、Redis、MongoDB
- memcache
- Redis:
- MongoDB:文档型数据库,解构更加复杂
- 特点:
- 以键值对存储
- 不支持ACID
- 性能比sql高很多(高并发)
- 单线程+多路io复用:每个操作都是原子性的,不会被线程调度机制打断
2. Redis的安装
1. windows
- 直接解压就行
2. Linux
3. 常用指令以及数据类型
1.常用指令
- select 数据库号:切换数据库(0~15号,共16个数据库,默认为0号数据库)
- clear:清空界面
- dbsize : 查看当前数据库key的数量
- flushdb :清空数据库
- keys * :查询所有键值对
- type key : 查看键值对(数据结构)的类型
- exitis key : 是否存在key
- del key : 删除键值对
- unlink key : 根据value非阻塞删除(先冲keyspace元数据删除,在后续异步删除操作)
- expire key 10 : 设置key10秒后过期
- ttl key : 查看还有多少秒过期(-1:永不过期,-2:已过期)
2. 字符串(String):基本数据类型
- String类型是二进制安全的,意味着Redis的字符串可以存储任何数据,比如图片,或者序列化对象
- String类型是Redis中最基本数数据类型,一个key的值最多可以存储512M
-
set key value :添加键值对(值覆盖)
-
get key :获取键值
-
append key value : 追加在值末尾
-
strlen key : 获取值长度
-
setnx key value : 当key不存在的时候才·执行,存在不执行。只产生,不覆盖
-
incr key : 将key中存储的数字值+1(只能操作数字值)
-
decr key: 将key中存储的数字值 -1
-
incrby / decrby key num: 将key中存储的数字值 +/ - num
下面的操作都是原子性
- mset k1 v1 k2 v2 k3 v3 : 设置多个值
- mget k1 k2 k3 : 获取多个键值
- msetnx k1 v1 k2 v2 k3 v3 : (产生式)设置多个值
- getrange key 起始位置 结束位置 : 前包,后包
- setrange key 起始位置 value : 在起始位置上插入值(会替换掉位置原来的值)
- setex key 过期时间 value :设置键值对同时设置过期时间
- gets et key value : 取新值换旧值
3. 队列(List)
- lpush key v1 v2 v3 :从左边入队(先入队的右边)
- rpush key v1 v2 v3: 从右边入队(先入队的左边)
- lrange key start stop:从key这个队列中取出start ~ stop位置的元素(0 ~ -1表示取出全部元素)
- lpop key:从key队列左边弹出元素
- rpop key :从key队列右边弹出元素
- lindex key:获取指定下标的元素(坐标从左到右)
- linsert key before v1 v2 :在v1前面插入元素v2
- lrem key n value : 从左边删除n个value值
- lset key index value :替换下标为index的值为value
3.1.Listd底层的数据结构:
- 底层的数据:QuickList
- 当数据比较少的时候:redis在内存中开辟一个连续的空间,一个zipList
- 当数据比较多的时候,将一个个小的ZipList串联起来,变成一个ziplist链表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZB8siNq8-1627063427979)(Redis6.assets/ziplist.png)]
- 优点:
- 传统的链表需要附加指针,比较浪费空间,Redis将ziplist组合串成链表,大大减少了指针的数量,节约了空间,又兼顾了快速查询的性能。
4.集合(Set)
- sadd key v1 v2 v3: 将v1 v2 v3 加入到key这个集合中
- smembers key :取出key集合中的所有值
- sismember key value :判断value是否在key这个集合中
- scard key:获取集合中元素的个数
- srem key v1 v2:移除集合中的v1 v2
- spop key count:随机从集合中弹出count个元素
- srandmember key count :随机返回集合count各元素,不删除
- smove source distination member : 从source集合中将member这个元素移至destination集合
- sinter set1 set2:去两个集合的交集元素
- sunion set1 set2:取两个集合的并集
- sdiff set1 set2 : 取两个集合的差集
4.1 底层数据库结构:
- **底层的数据结构:**字典(哈希表),所有的value指向同一个对象
- 性能:查询插入等操作都是O(1)级别
5. 哈希(Hash)
- 理解:键值对中的值是一个map( Map < String,Map < field, value > > )
- hset key field value : 存一个hash
- hget key field :获取hash中field对应的值
- hmset key field value field value……:批量存储键值对
- hgetall key: 获取key中所有的·键值对
- hexists key field : 查看key中field是否存在
- hkeys key:获取key中所有的field
- hvals key :获取key中所有的值
- hincrby key field increment:将key中field对应的value数字值增加increment
- hsetnx key field value:在key中存一个键值对,当且仅当field不存在时
5.1. Hash底层数据结构
- 底层数据解结构有两种:
- ziplist:当数量比较少的时候使用ziplist
- hashtable:当数量比较多的时候使用哈希表
6. 有序集合(Zset)
- 在set基础上加上一个值:score 分数
-
zadd key score value : 添加一个待分数(权重)的键值对
-
zrange key start stop :取出从start 到 stop的所有元素,按照分数排序(0 ~-1代表取出所有元素)。
-
zrangebyscore key min max:升序,查询出score在min~max之间 的元素
-
zrangebyscore key -inf +inf withscres:根据分数升序(从小到大)查询,而且带上分数值
-
zrem key member ……[member]:批量移除元素
-
zcard key :zset中元素的数量
-
zrevrange myzset start stop [withscores]:
127.0.0.1:6379> zrevrange myzset 0 -1 withscores #倒序取出集合中的元素
1) "liliu"
2) "6"
3) "wangwu"
4) "3"
5) "lisi"
6) "2"
7) "zhangsan"
8) "1"
- zcount key min max:获取指定区间的元素数量
4. 三种特殊的数据类型
1. geospaial 地理位置
支持经纬度范围
- Valid longitudes are from -180 to 180 degrees.
- Valid latitudes are from -85.05112878 to 85.05112878 degrees.
GEOADD 指令 :添加地理位置
# 语法: geoadd key 值(经度 纬度 地理名称)
127.0.0.1:6379> geoadd china 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china 121.47 31.32 shanghai
(integer) 1
127.0.0.1:6379> geoadd china 106.50 29.53 chongqin 114.05 22.52 shengzhen
(integer) 2
127.0.0.1:6379> geoadd china 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
GEOPOS 指令 :查询地理位置
# 语法:GEOPOS key 地理名称值…… [地理名称值]
127.0.0.1:6379> GEOPOS china shengzhen
1) 1) "114.04999762773513794"
2) "22.5200000879503861"
127.0.0.1:6379> GEOPOS china shengzhen hangzhou
1) 1) "114.04999762773513794"
2) "22.5200000879503861"
2) 1) "120.1600000262260437"
2) "30.2400003229490224"
127.0.0.1:6379>
GEODIST 指令: 获取城市之间的距离
# 语法:geodist key 地理名称 地理名称 单位
# 单位取值:m 米 km 千米 mi 英里 ft 英尺
127.0.0.1:6379> geodist china shengzhen xian
"1396958.2931"
GEORADIUS 指令:方圆范围内的城市
# 语法: GEORADIUS china 圆心经度 圆心纬度 半径距离 单位 [withcoord] [withdist]
127.0.0.1:6379> GEORADIUS china 110 50 5000 km withcoord withdist count 2
1) 1) "beijing"
2) "1230.0579"
3) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "xian"
2) "1752.7568"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
GEORADIUSBYNUMBER 指令:查询地理位置成员方圆内的城市
# 语法:GEORADIUS china 圆心城市 半径距离 单位 [withcoord]
127.0.0.1:6379> GEORADIUSBYMEMBER china xian 5000 km withdist withcoord count 2
1) 1) "xian"
2) "0.0000"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
2) 1) "chongqin"
2) "575.0470"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
GEOHASH 指令:返回两个11位的字符串,字符串越接近,距离越近
127.0.0.1:6379> GEOHASH china beijing chongqin
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
geo类型底层是用Zset来实现的,可以用zset来操作geo数据
# 查询所有地理位置
127.0.0.1:6379> zrange china 0 -1
1) "chongqin"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
# 移除地理位置
127.0.0.1:6379> zrem china hangzhou
(integer) 1
2. Hyperloglog 基数统计(Redis 2.8.9)
-
什么是基数:不重复元素,可接受误差(0.81%错误率,可接受)
- A{1, 3, 5,7,8,7} = 5
- B{1, 3, 5,7,8} = 5
-
优点:
- 占用内存是固定的,只需要12kb
-
需求:网页Uv(一个人多次访问一个网站,还是算作一个人)
- 传统方式:存用户id,大量id比较麻烦,占内存
PFADD : 添加基数
# 语法:PFADD key 元素 [元素]
127.0.0.1:6379> PFADD A 1 2 3 4 4
(integer) 1
PFCOUNT : 获取基数统计结果
# 语法: PFCOUNT key
127.0.0.1:6379> PFCOUNT A
(integer) 4
PFMERGE:合并两组基数集合
# 语法: PFMERGE destkey sourcekey…… [source]
127.0.0.1:6379> PFADD A 1 2 3 4 4
(integer) 1
127.0.0.1:6379> PFADD B 1 2 3 6 7 4
(integer) 1
127.0.0.1:6379> PFMERGE C A B
OK
- 如果允许容错,可以使用Hyperloglog,如果不允许,则使用其他,比如set。
3. BitMap
位存储:只有0 1两个状态
- 需求:记录打卡,是否登录等
setBit
# 语法:setbit key offset value
127.0.0.1:6379> setbit wujiazhan 1 1
(integer) 0
127.0.0.1:6379> setbit wujiazhan 1 0
(integer) 1
127.0.0.1:6379> setbit wujiazhan 2 1
(integer) 0
getbit
# 语法:getbit key offset
127.0.0.1:6379> GETBIT wujiazhan 1
(integer) 0
127.0.0.1:6379> GETBIT wujiazhan 2
(integer) 1
bitcount key:统计值为1的数量
# 语法:bitcount key [start end]
127.0.0.1:6379> BITCOUNT wujiazhan 0 2
(integer) 1
127.0.0.1:6379> BITCOUNT wujiazhan 0 1
(integer) 1
127.0.0.1:6379> BITCOUNT wujiazhan
(integer) 1
5. 事务
-
redis的事务不支持ACID
-
redis的单条命令是原子性的,但事务不是原子性的,也就是说当出现事务中某个指令发生错误(必须是运行时异常,编译异常时事务队列会被清空并且取消事务)时,其他指令会继续执行。
1. 事务执行步骤
- 开启事务
- 指令入队
- 执行事务/取消事务
mutil 开启事务
…… 输入指令入队
EXEC 执行事务
DISCARD 清空事务队列并放弃事务
# 正常的事务流程
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 zhangsan
QUEUED
127.0.0.1:6379> set k2 lisi
QUEUED
127.0.0.1:6379> set k3 wangwu
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK
127.0.0.1:6379> get k1
"zhangsan"
127.0.0.1:6379>
# exec执行前发生错误,事务队列清空并取消队列
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 zhangsan
QUEUED
127.0.0.1:6379> set k2 lisi
QUEUED
127.0.0.1:6379> set k3
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> set k4 wangwu
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
# exec执行后发生错误,错误指令不执行,返回错误,其他指令继续执行
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k5 liliu
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) "zhangsan"
3) "lisi"
4) OK
2. Redis:使用 check-and-set 操作实现乐观锁
乐观锁:顾名思义,基于乐观的填对对待数据冲突,认为在事务执行中数据不会被其他线程修改,不需要对数据进行加锁,提交时通过一种机制来验证数据是否冲突(通常是通过版本号version来验证)。乐观锁省去了对数据加锁和解锁的过程,因此性能比较好,而对于数据的安全性一般是业务(版本号验证)来实现加锁。
悲观锁:基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作。悲观锁可以保证数据的独占性和正确性,但是对每个数据加锁和解锁太耗费性能,因此比较少用。
watch 监视
- 通过wacth命令来实现了乐观锁,使用watch指令监控某一个(或者一些)键,在执行事务的过程中,如果被监控的键被其他线程(或者其他原因)修改过,那么事务会执行失败。只有被监控的键没有被修改的情况下,事务才会被执行。
- 如果事务执行失败,需要重复之前步骤,直到监控对象数据没有被修改的时候,事务才会执行成功
unwatch :取消对所有监控对象对监控
-
当客户端断开连接时, 该客户端对键的监视也会被取消。
-
使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。
6. Jedis
1.依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.2</version>
</dependency>
2. 核心类
import redis.clients.jedis.Jedis;
public class JedisTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.set("key", "zhangsan");
System.out.println(jedis.get("key"));
}
}
7. springboot整合redis
1. 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. redisTemplate的使用
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
private RedisTemplate<String, String> template; //默认
@Autowired
private RedisTemplate<Object, Object> template1;
@Test
void contextLoads() throws JSONException {
RedisConnection connection = Objects.requireNonNull(template.getConnectionFactory()).getConnection();
connection.flushAll();
User user = new User("张三", 21);
JSONObject jsonObject = new JSONObject();
jsonObject.put("name",user);
String s = jsonObject.toString();
template.opsForValue().set("zhangsan",s);
Object name = template.opsForValue().get("zhangsan");
System.out.println(name);
}
@Test
void test(){
User user = new User("李四", 18);
template1.opsForValue().set("lisi", user);
System.out.println(template1.opsForValue().get("lisi"));
}
}
- 根据自动配置类的配置,我们从容器获取到的redisTemplate只有两种
- redisTemplate:<Object, Object>
2. stringRedisTemplate:<String , String>
3. redis配置类:RedisAutoConfiguration.class
- springboot2.x 后默认使用lettuce来实现redis连接
- 我们可以通过自定义redisConfiguration来实现自己配置redis,来解决中文序列化等问题
4. RedisProperties
5. 存儲<key, value>时,cli端获取到的key显示转义字符
- 问题原因: redis序列化时**默认使用的是jdk序列化(在RedisTemplate.class定义)**的方式,无法序列化对象
- 解决:自定义redisTemplate,设置序列化方式
package com.zhan.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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;
import java.net.UnknownHostException;
@Configuration
@SuppressWarnings("all")
public class RedisConfig {
//编写自己的RedisTemplate
//自己定义了一个RedisTemplate
//这是一个固定模板,拿去可以直接使用
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
//为了我们自己开发方便,一般直接使用<String,Object>
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
//json序列化配置
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value的序列化方式采用jackson
template.setValueSerializer(objectJackson2JsonRedisSerializer);
//hash的value序列化方式采用jackson
template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
6. cli客户端无法显示中文字符问题
- 当从redis中value的值的时候,遇到中文的时候,无法正常的显示,如图所示:
6.1 解决方案
- 这时候的解决方案就是在你启动redis的时候多加一条命令:
redis-cli --raw
6.2 显示结果
8. redis工具类—RedisUtil
package com.yang.utils;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类
*
*/
@Component
public class RedisUtil {
private StringRedisTemplate redisTemplate;
public void setRedisTemplate(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public StringRedisTemplate getRedisTemplate() {
return this.redisTemplate;
}
/** -------------------key相关操作--------------------- */
/**
* 删除key
*
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* 批量删除key
*
* @param keys
*/
public void delete(Collection<String> keys) {
redisTemplate.delete(keys);
}
/**
* 序列化key
*
* @param key
* @return
*/
public byte[] dump(String key) {
return redisTemplate.dump(key);
}
/**
* 是否存在key
*
* @param key
* @return
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*
* @param key
* @param timeout
* @param unit
* @return
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 设置过期时间
*
* @param key
* @param date
* @return
*/
public Boolean expireAt(String key, Date date) {
return redisTemplate.expireAt(key, date);
}
/**
* 查找匹配的key
*
* @param pattern
* @return
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 将当前数据库的 key 移动到给定的数据库 db 当中
*
* @param key
* @param dbIndex
* @return
*/
public Boolean move(String key, int dbIndex) {
return redisTemplate.move(key, dbIndex);
}
/**
* 移除 key 的过期时间,key 将持久保持
*
* @param key
* @return
*/
public Boolean persist(String key) {
return redisTemplate.persist(key);
}
/**
* 返回 key 的剩余的过期时间
*
* @param key
* @param unit
* @return
*/
public Long getExpire(String key, TimeUnit unit) {
return redisTemplate.getExpire(key, unit);
}
/**
* 返回 key 的剩余的过期时间
*
* @param key
* @return
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
/**
* 从当前数据库中随机返回一个 key
*
* @return
*/
public String randomKey() {
return redisTemplate.randomKey();
}
/**
* 修改 key 的名称
*
* @param oldKey
* @param newKey
*/
public void rename(String oldKey, String newKey) {
redisTemplate.rename(oldKey, newKey);
}
/**
* 仅当 newkey 不存在时,将 oldKey 改名为 newkey
*
* @param oldKey
* @param newKey
* @return
*/
public Boolean renameIfAbsent(String oldKey, String newKey) {
return redisTemplate.renameIfAbsent(oldKey, newKey);
}
/**
* 返回 key 所储存的值的类型
*
* @param key
* @return
*/
public DataType type(String key) {
return redisTemplate.type(key);
}
/** -------------------string相关操作--------------------- */
/**
* 设置指定 key 的值
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 获取指定 key 的值
* @param key
* @return
*/
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 返回 key 中字符串值的子字符
* @param key
* @param start
* @param end
* @return
*/
public String getRange(String key, long start, long end) {
return redisTemplate.opsForValue().get(key, start, end);
}
/**
* 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
*
* @param key
* @param value
* @return
*/
public String getAndSet(String key, String value) {
return redisTemplate.opsForValue().getAndSet(key, value);
}
/**
* 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
*
* @param key
* @param offset
* @return
*/
public Boolean getBit(String key, long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* 批量获取
*
* @param keys
* @return
*/
public List<String> multiGet(Collection<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
*
* @param key
* @param postion
* 位置
* @param value
* 值,true为1, false为0
* @return
*/
public boolean setBit(String key, long offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
/**
* 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
*
* @param key
* @param value
* @param timeout
* 过期时间
* @param unit
* 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES
* 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
*/
public void setEx(String key, String value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 只有在 key 不存在时设置 key 的值
*
* @param key
* @param value
* @return 之前已经存在返回false,不存在返回true
*/
public boolean setIfAbsent(String key, String value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
*
* @param key
* @param value
* @param offset
* 从指定位置开始覆写
*/
public void setRange(String key, String value, long offset) {
redisTemplate.opsForValue().set(key, value, offset);
}
/**
* 获取字符串的长度
*
* @param key
* @return
*/
public Long size(String key) {
return redisTemplate.opsForValue().size(key);
}
/**
* 批量添加
*
* @param maps
*/
public void multiSet(Map<String, String> maps) {
redisTemplate.opsForValue().multiSet(maps);
}
/**
* 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
*
* @param maps
* @return 之前已经存在返回false,不存在返回true
*/
public boolean multiSetIfAbsent(Map<String, String> maps) {
return redisTemplate.opsForValue().multiSetIfAbsent(maps);
}
/**
* 增加(自增长), 负数则为自减
*
* @param key
* @param value
* @return
*/
public Long incrBy(String key, long increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
/**
*
* @param key
* @param value
* @return
*/
public Double incrByFloat(String key, double increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
/**
* 追加到末尾
*
* @param key
* @param value
* @return
*/
public Integer append(String key, String value) {
return redisTemplate.opsForValue().append(key, value);
}
/** -------------------hash相关操作------------------------- */
/**
* 获取存储在哈希表中指定字段的值
*
* @param key
* @param field
* @return
*/
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* 获取所有给定字段的值
*
* @param key
* @return
*/
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 获取所有给定字段的值
*
* @param key
* @param fields
* @return
*/
public List<Object> hMultiGet(String key, Collection<Object> fields) {
return redisTemplate.opsForHash().multiGet(key, fields);
}
public void hPut(String key, String hashKey, String value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public void hPutAll(String key, Map<String, String> maps) {
redisTemplate.opsForHash().putAll(key, maps);
}
/**
* 仅当hashKey不存在时才设置
*
* @param key
* @param hashKey
* @param value
* @return
*/
public Boolean hPutIfAbsent(String key, String hashKey, String value) {
return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}
/**
* 删除一个或多个哈希表字段
*
* @param key
* @param fields
* @return
*/
public Long hDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* 查看哈希表 key 中,指定的字段是否存在
*
* @param key
* @param field
* @return
*/
public boolean hExists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 为哈希表 key 中的指定字段的整数值加上增量 increment
*
* @param key
* @param field
* @param increment
* @return
*/
public Long hIncrBy(String key, Object field, long increment) {
return redisTemplate.opsForHash().increment(key, field, increment);
}
/**
* 为哈希表 key 中的指定字段的整数值加上增量 increment
*
* @param key
* @param field
* @param delta
* @return
*/
public Double hIncrByFloat(String key, Object field, double delta) {
return redisTemplate.opsForHash().increment(key, field, delta);
}
/**
* 获取所有哈希表中的字段
*
* @param key
* @return
*/
public Set<Object> hKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* 获取哈希表中字段的数量
*
* @param key
* @return
*/
public Long hSize(String key) {
return redisTemplate.opsForHash().size(key);
}
/**
* 获取哈希表中所有值
*
* @param key
* @return
*/
public List<Object> hValues(String key) {
return redisTemplate.opsForHash().values(key);
}
/**
* 迭代哈希表中的键值对
*
* @param key
* @param options
* @return
*/
public Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
return redisTemplate.opsForHash().scan(key, options);
}
/** ------------------------list相关操作---------------------------- */
/**
* 通过索引获取列表中的元素
*
* @param key
* @param index
* @return
*/
public String lIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
/**
* 获取列表指定范围内的元素
*
* @param key
* @param start
* 开始位置, 0是开始位置
* @param end
* 结束位置, -1返回所有
* @return
*/
public List<String> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 存储在list头部
*
* @param key
* @param value
* @return
*/
public Long lLeftPush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
*
* @param key
* @param value
* @return
*/
public Long lLeftPushAll(String key, String... value) {
return redisTemplate.opsForList().leftPushAll(key, value);
}
/**
*
* @param key
* @param value
* @return
*/
public Long lLeftPushAll(String key, Collection<String> value) {
return redisTemplate.opsForList().leftPushAll(key, value);
}
/**
* 当list存在的时候才加入
*
* @param key
* @param value
* @return
*/
public Long lLeftPushIfPresent(String key, String value) {
return redisTemplate.opsForList().leftPushIfPresent(key, value);
}
/**
* 如果pivot存在,再pivot前面添加
*
* @param key
* @param pivot
* @param value
* @return
*/
public Long lLeftPush(String key, String pivot, String value) {
return redisTemplate.opsForList().leftPush(key, pivot, value);
}
/**
*
* @param key
* @param value
* @return
*/
public Long lRightPush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
*
* @param key
* @param value
* @return
*/
public Long lRightPushAll(String key, String... value) {
return redisTemplate.opsForList().rightPushAll(key, value);
}
/**
*
* @param key
* @param value
* @return
*/
public Long lRightPushAll(String key, Collection<String> value) {
return redisTemplate.opsForList().rightPushAll(key, value);
}
/**
* 为已存在的列表添加值
*
* @param key
* @param value
* @return
*/
public Long lRightPushIfPresent(String key, String value) {
return redisTemplate.opsForList().rightPushIfPresent(key, value);
}
/**
* 在pivot元素的右边添加值
*
* @param key
* @param pivot
* @param value
* @return
*/
public Long lRightPush(String key, String pivot, String value) {
return redisTemplate.opsForList().rightPush(key, pivot, value);
}
/**
* 通过索引设置列表元素的值
*
* @param key
* @param index
* 位置
* @param value
*/
public void lSet(String key, long index, String value) {
redisTemplate.opsForList().set(key, index, value);
}
/**
* 移出并获取列表的第一个元素
*
* @param key
* @return 删除的元素
*/
public String lLeftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
*
* @param key
* @param timeout
* 等待时间
* @param unit
* 时间单位
* @return
*/
public String lBLeftPop(String key, long timeout, TimeUnit unit) {
return redisTemplate.opsForList().leftPop(key, timeout, unit);
}
/**
* 移除并获取列表最后一个元素
*
* @param key
* @return 删除的元素
*/
public String lRightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
/**
* 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
*
* @param key
* @param timeout
* 等待时间
* @param unit
* 时间单位
* @return
*/
public String lBRightPop(String key, long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPop(key, timeout, unit);
}
/**
* 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
*
* @param sourceKey
* @param destinationKey
* @return
*/
public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey);
}
/**
* 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
*
* @param sourceKey
* @param destinationKey
* @param timeout
* @param unit
* @return
*/
public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey, timeout, unit);
}
/**
* 删除集合中值等于value得元素
*
* @param key
* @param index
* index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;
* index<0, 从尾部开始删除第一个值等于value的元素;
* @param value
* @return
*/
public Long lRemove(String key, long index, String value) {
return redisTemplate.opsForList().remove(key, index, value);
}
/**
* 裁剪list
*
* @param key
* @param start
* @param end
*/
public void lTrim(String key, long start, long end) {
redisTemplate.opsForList().trim(key, start, end);
}
/**
* 获取列表长度
*
* @param key
* @return
*/
public Long lLen(String key) {
return redisTemplate.opsForList().size(key);
}
/** --------------------set相关操作-------------------------- */
/**
* set添加元素
*
* @param key
* @param values
* @return
*/
public Long sAdd(String key, String... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* set移除元素
*
* @param key
* @param values
* @return
*/
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
/**
* 移除并返回集合的一个随机元素
*
* @param key
* @return
*/
public String sPop(String key) {
return redisTemplate.opsForSet().pop(key);
}
/**
* 将元素value从一个集合移到另一个集合
*
* @param key
* @param value
* @param destKey
* @return
*/
public Boolean sMove(String key, String value, String destKey) {
return redisTemplate.opsForSet().move(key, value, destKey);
}
/**
* 获取集合的大小
*
* @param key
* @return
*/
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
/**
* 判断集合是否包含value
*
* @param key
* @param value
* @return
*/
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 获取两个集合的交集
*
* @param key
* @param otherKey
* @return
*/
public Set<String> sIntersect(String key, String otherKey) {
return redisTemplate.opsForSet().intersect(key, otherKey);
}
/**
* 获取key集合与多个集合的交集
*
* @param key
* @param otherKeys
* @return
*/
public Set<String> sIntersect(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().intersect(key, otherKeys);
}
/**
* key集合与otherKey集合的交集存储到destKey集合中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sIntersectAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().intersectAndStore(key, otherKey,
destKey);
}
/**
* key集合与多个集合的交集存储到destKey集合中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sIntersectAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,
destKey);
}
/**
* 获取两个集合的并集
*
* @param key
* @param otherKeys
* @return
*/
public Set<String> sUnion(String key, String otherKeys) {
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**
* 获取key集合与多个集合的并集
*
* @param key
* @param otherKeys
* @return
*/
public Set<String> sUnion(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**
* key集合与otherKey集合的并集存储到destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
}
/**
* key集合与多个集合的并集存储到destKey中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sUnionAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
}
/**
* 获取两个集合的差集
*
* @param key
* @param otherKey
* @return
*/
public Set<String> sDifference(String key, String otherKey) {
return redisTemplate.opsForSet().difference(key, otherKey);
}
/**
* 获取key集合与多个集合的差集
*
* @param key
* @param otherKeys
* @return
*/
public Set<String> sDifference(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().difference(key, otherKeys);
}
/**
* key集合与otherKey集合的差集存储到destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sDifference(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().differenceAndStore(key, otherKey,
destKey);
}
/**
* key集合与多个集合的差集存储到destKey中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sDifference(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
destKey);
}
/**
* 获取集合所有元素
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Set<String> setMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 随机获取集合中的一个元素
*
* @param key
* @return
*/
public String sRandomMember(String key) {
return redisTemplate.opsForSet().randomMember(key);
}
/**
* 随机获取集合中count个元素
*
* @param key
* @param count
* @return
*/
public List<String> sRandomMembers(String key, long count) {
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* 随机获取集合中count个元素并且去除重复的
*
* @param key
* @param count
* @return
*/
public Set<String> sDistinctRandomMembers(String key, long count) {
return redisTemplate.opsForSet().distinctRandomMembers(key, count);
}
/**
*
* @param key
* @param options
* @return
*/
public Cursor<String> sScan(String key, ScanOptions options) {
return redisTemplate.opsForSet().scan(key, options);
}
/**------------------zSet相关操作--------------------------------*/
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @param score
* @return
*/
public Boolean zAdd(String key, String value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
*
* @param key
* @param values
* @return
*/
public Long zAdd(String key, Set<TypedTuple<String>> values) {
return redisTemplate.opsForZSet().add(key, values);
}
/**
*
* @param key
* @param values
* @return
*/
public Long zRemove(String key, Object... values) {
return redisTemplate.opsForZSet().remove(key, values);
}
/**
* 增加元素的score值,并返回增加后的值
*
* @param key
* @param value
* @param delta
* @return
*/
public Double zIncrementScore(String key, String value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**
* 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @return 0表示第一位
*/
public Long zRank(String key, Object value) {
return redisTemplate.opsForZSet().rank(key, value);
}
/**
* 返回元素在集合的排名,按元素的score值由大到小排列
*
* @param key
* @param value
* @return
*/
public Long zReverseRank(String key, Object value) {
return redisTemplate.opsForZSet().reverseRank(key, value);
}
/**
* 获取集合的元素, 从小到大排序
*
* @param key
* @param start
* 开始位置
* @param end
* 结束位置, -1查询所有
* @return
*/
public Set<String> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
/**
* 获取集合元素, 并且把score值也获取
*
* @param key
* @param start
* @param end
* @return
*/
public Set<TypedTuple<String>> zRangeWithScores(String key, long start,
long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
/**
* 根据Score值查询集合元素
*
* @param key
* @param min
* 最小值
* @param max
* 最大值
* @return
*/
public Set<String> zRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**
* 根据Score值查询集合元素, 从小到大排序
*
* @param key
* @param min
* 最小值
* @param max
* 最大值
* @return
*/
public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
double min, double max) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
}
/**
*
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
double min, double max, long start, long end) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
start, end);
}
/**
* 获取集合的元素, 从大到小排序
*
* @param key
* @param start
* @param end
* @return
*/
public Set<String> zReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* 获取集合的元素, 从大到小排序, 并返回score值
*
* @param key
* @param start
* @param end
* @return
*/
public Set<TypedTuple<String>> zReverseRangeWithScores(String key,
long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
end);
}
/**
* 根据Score值查询集合元素, 从大到小排序
*
* @param key
* @param min
* @param max
* @return
*/
public Set<String> zReverseRangeByScore(String key, double min,
double max) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
/**
* 根据Score值查询集合元素, 从大到小排序
*
* @param key
* @param min
* @param max
* @return
*/
public Set<TypedTuple<String>> zReverseRangeByScoreWithScores(
String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
min, max);
}
/**
*
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<String> zReverseRangeByScore(String key, double min,
double max, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
start, end);
}
/**
* 根据score值获取集合元素数量
*
* @param key
* @param min
* @param max
* @return
*/
public Long zCount(String key, double min, double max) {
return redisTemplate.opsForZSet().count(key, min, max);
}
/**
* 获取集合大小
*
* @param key
* @return
*/
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
/**
* 获取集合大小
*
* @param key
* @return
*/
public Long zZCard(String key) {
return redisTemplate.opsForZSet().zCard(key);
}
/**
* 获取集合中value元素的score值
*
* @param key
* @param value
* @return
*/
public Double zScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
/**
* 移除指定索引位置的成员
*
* @param key
* @param start
* @param end
* @return
*/
public Long zRemoveRange(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**
* 根据指定的score值的范围来移除成员
*
* @param key
* @param min
* @param max
* @return
*/
public Long zRemoveRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}
/**
* 获取key和otherKey的并集并存储在destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
}
/**
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForZSet()
.unionAndStore(key, otherKeys, destKey);
}
/**
* 交集
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, String otherKey,
String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
destKey);
}
/**
* 交集
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
destKey);
}
/**
*
* @param key
* @param options
* @return
*/
public Cursor<TypedTuple<String>> zScan(String key, ScanOptions options) {
return redisTemplate.opsForZSet().scan(key, options);
}
}
9. Redis配置文件:Redis.Conf
网址:https://raw.githubusercontent.com/antirez/redis/2.8/redis.conf
- 配置对大小写不敏感
- redis通过这个配置文件来启动
配置多个配置文件
连接地址
持久化
- 900秒有1读写操作,执行持久化
- 300秒有10读写操作,执行持久化
- 60秒有10000读写操作,执行持久化
日志文件
10. 持久化(重点)
持久化的两种方式
- RDB方式:在指定时间间隔内对你的数据进行快照储存
- AOF方式:记录每次对服务器的写操作,当服务器重启的时候,会重新执行这些命令来恢复原始的数据
RDB(Redis DataBase)方式:
- 文件名:
dump.db
(默认) - 保存过程
- Redis调用fork,有父线程和子线程
- 子线程将内存中的数据全部写入新文件
- 写完毕之后替换旧文件
- 注意:,每次快照持久化都是将内存数据完整写入到磁盘一次,并不 是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。
- 优点:
- RDB在指定时间间隔内会对数据进行储存,所以RDB文件会保存很多个时间点的数据,因此非常适合来做数据备份,你可以很容易的恢复某一个时间点版本的数据集。
- RDB文件非常紧凑的单一文件,所以非常适合远程传输,做灾难恢复
- RDB保存RDB文件时,父进程只需要分出一个子进程,接下来的保存工作全部由子进程来完成,父进程不需要做其他的io工作,最大化Redis的性能。
- 相比AOF方式,RDB恢复数据更快
- 缺点:
- 如果redis意外停止工作(例如电源中断),那么你恢复的数据只能是上一个时间点保存的数据集,也就是说会丢失几分钟内的数据,如果你希望丢失的数据最少,RDB就不合适。
- RDB需要经常分出子线程存储数据,当数据集太大的时候,子线程非常耗时,不过可以修改日志文件调节数据的保存频率。
- 相关日志文件配置:
- save:save 900 1
- stop-writes-on-bgsave-error:持久化过程失败是否停止所有持久化操作,默认是yes
- rdbcompression:存储是是否压缩存储,默认yes。压缩算法:LZF算法
- dbfilename:持久化文件的名字,默认是
dump.db
(文件名,不包含路径) - dir:持久化文件的保存路径
- 相关日志文件配置:
- RDB持久化触发机制
- 退出Redis触发
- flushall触发
- save触发
- RDB常用命令
命令 | 注释 |
---|---|
save | 执行save命令时,redis客户端会阻塞客户请求,同步进行快照操作(BIO) |
bgsave | 执行save命令时,redis客户端会阻塞客户请求,异步进行快照操作(NIO) |
lastsave | 获取最后一次save操作的时刻 |
flushall | 清空数据库,在一定条件下会执行快照操作 |
# 生成rdb文件
127.0.0.1:6379> set name zhangsan OK
127.0.0.1:6379> get name "zhangsan"
127.0.0.1:6379> save OK
(0.86s)
- 如何使用rdb恢复数据
- 只需要将rdb文件放在redis启动目录就可以,Redis启动时会自动检查dump.db恢复数据。
AOF(Append Only File)方式:
-
以追加的形式向磁盘上写入数据,对历史数据只追加而不修改。
-
默认文件:
appendonly.aof
-
fsync 策略
- 完全没有fsync(
no
): - 每秒fsync(
everysec
默认):也就是说最多丢失一秒的数据,即使是everysec策略,性能依旧很高 - 每个查询fsync(
always
):
appendonly yes //启用aof持久化方式
# appendfsync always //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no //完全依赖os,性能最好,持久化没保证
-
保存过程
- Redis调用fork,于是就有父线程和子线程两个线程
- 子线程根据内存中的数据快照,往新aof文件中写入重建数据库状态的命令
- 父进程继续处理client的请求,把请求中的写命令缓存起来并写入原来的aof文件中。如果子线程重写失败时,数据是安全的
- 当子线程重写完数据之后,给父线程发送一个信号,并将缓存中的命令追加到新aof文件的末尾
- 最后将新文件替换旧文件并重命名,在此之后的新的数据将写到新文件中。
- 优点:
- 默认fsync策略下,最多只丢失一秒数据,且性能依旧很好(fsync 是使用后台线程执行的,当没有 fsync 正在进行时,主线程将努力执行写入)。
- AOF日志是仅追加日志,即使发生断电故障,也不会出现磁盘寻道和损坏问题。即使由于某种原因(磁盘已满或其他)导致日志错误也可以使用redis-check-aof工具修复
- AOF 日志恢复
如果在追加日志时,恰好遇到磁盘空间满或断电等情况,导致日志写入不完整,也没有关系,Redis 中提供了redis-check-aof工具,可以用来进行日志修复,基本步骤如下:- 备份被写坏的AOF文件
- 运行redis-check-aof –fix filename 命令进行修复
- 使用diff -u 命令来查看两个文件的差异,定位问题
- 将修复完成的AOF文件移动到Redis安装目录下
- 重启Redis ,加载修复后的AOF文件
- AOF 日志恢复
- 缺点:
- 相同数据集,AOF文件比RDB文件大
- AOF的性能可能比RDB差,具体取决于使用哪个fsync策略。在每秒sync策略下,即使是高负载的情况下,AOF的性能与RDB一样快。但是如果是always策略,随着集群负载增大,AOF记录的内越来越多,文件越来越大,数据恢复也会越来越慢。
11. Redis发布订阅
- SUBSCRIBE:订阅
- UNSUBSCRIBE:取消订阅
- PUBLISH:发布
1. 简介:
其实就是消息队列。发布者不需要去考虑订阅者(接受者)是谁,有多少个,只需要将他们推送的消息发送到某一个(或多个)消息队列,订阅者想要接受什么类型的消息就订阅哪一个(或多个)频道(消息队列),这样可以实现发送者与订阅者的解耦,实现更大的可拓展性和更动态的网络拓扑。
2. 命令
命令 | 备注 |
---|---|
psubscribe channel [channel ] | 订阅一个或多个频道 |
unsubscribe channel [channel ] | 取消订阅一个或多个频道 |
punsubcribe [pattern [pattern]] | 取消订阅所有频道 |
publish channel message | 发送消息频道 |
pubsub subcommand [argument [argument]] | 查看订阅与发布系统状态 |
4. 底层
redis-server底层维护了一个字典,字典的键是频道,值是一个链表,链表内是一个个订阅者的信息,发送者publish信息之后,server会根据频道查找字典,找到频道对应的接受者链表,遍历链表,将消息发送给接受者。
11. Redis主从复制(重点)
11.1 简介
Redis-replication是一种master-slaver模式的复制模式,这种模式可以使得slaver节点编程与master节点完全相同的副本,主从复制的架构一般有一主多从和级联架构两种。在主从复制下的redis集群,读写分离,也就是只有master节点才有写数据的权限,slaver节点只有读数据的权限。
每一台redis,默认配置是master节点,因此主从复制只需要配置从节点------从节点认定主节点。
11.2 配置多台redis服务器
-
复制修改redis.conf配置文件,需要多少个redis就需要多少个redis.conf文件
- 端口号:6380
- pid: pidfile /var/run/redis_6380.pid
- 日志名字:logfile “6380.log”
- RDB持久化文件命名:dump6380.rdb
-
指令启动配置的redis
# 指定某个配置文件,启动对应的redis服务器 redis-server.exe redis.conf
查看主从配置指令
info replication
配置指令:
slaveof 主节点ip 主节点端口
# 6380服务器
127.0.0.1:6380> slaveof 127.0.0.1 6379 #认主节点 OK
127.0.0.1:6380> info replication # 查看配置信息 # Replication
role:slave #角色: 从节点
master_host:127.0.0.1 # 主节点地址
master_port:6379 # 主节点端口
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:112
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:5bd3f16f3e18c9be55d18d453ba3c37d3d016ca9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:112
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:112
# 6379服务器
127.0.0.1:6379> info replication # 认主前 # Replication
role:master
connected_slaves:0
master_replid:714e95da9845340568644c33f4793cd19d4f0c5e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> info replication # 认主后 # Replication
role:master
connected_slaves:1 # 从节点个数
slave0:ip=127.0.0.1,port=6380,state=online,offset=0,lag=1 #从节点信息
master_replid:5bd3f16f3e18c9be55d18d453ba3c37d3d016ca9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0
-
手动设置回主节点🦌:将自己设置为主节点
slaveof on one
11.3 主从复制特点
- 在主从复制下的redis集群,读写分离,也就是只有master节点才有写数据的权限,slaver节点只有读数据的权限。因此数据同步是单向的,也就是主节点向从节点
- 全量复制:slaver节点启动成功连接master节点的时候,会发送一个sync同步命令给master节点,会触发数据的全量复制
- 全量复制两方式:
- RDB_CHILD_TYPE_DISK:将主节点在内存中的数据持久化到磁盘文件(RDB或AOF文件)让后发送给从节点
- RDB_CHILD_TYPE_SOCKET:将数据通过socket方法直接发送给从节点
- 全量复制两方式:
- 增量复制:当从节点同步完主节点的数据之后,此后数据的同步使用增量复制
- 全量复制:slaver节点启动成功连接master节点的时候,会发送一个sync同步命令给master节点,会触发数据的全量复制
- 注:只要是重新连接主节点,就会自动触发全量复制
- 在手动配置的redis集群下,一旦redis重启,配置恢复默认配置,主节点挂了的话,从节点不会自动转为主节点,但是数据依然可以读取(数据同步的原因),当master节点重启时,依然会是从节点服务器的主节点。如果想要自动配置,那么就需要配置哨兵模式
- Redis主从复制不阻塞master服务器。也就是说当若干个从服务器在进行初始同步时,主服务器仍然可以处理外界请求。
11.4 哨兵模式
1. 简介
当主节点redis服务器意外停止或者宕机的时候,可以通过手动命令slaver on one
重新设置主节点,但是这不仅会花费人力,还会会导致服务器会有一段时间不可用(可读不可写)。自redis2.8开始正式出现的(Sentinel)哨兵模式,便解决了这个问题。
哨兵模式一种自动主从切换技术,它设置了一个哨兵集群,这些哨兵集群会监控多个redis服务器。当redis服务器可能出现问题时,他们会共同确定出问题的服务器是否有问题。当哨兵集群确定master服务器宕机时,哨兵集群会通过投票的方式从slaver节点中投出一个新的master节点,然后通过发布订阅的方式通知其他slaver节点修改master节点信息。即使原来的master节点恢复服务,也只能当新master节点的slaver节点。
2. 哨兵配置
-
配置sentinel.conf文件
# 1 表示多少个哨兵认为主机有问题主机就有问题 sentinel monitor mymaster 127.0.0.1 6379 1
-
当主节点宕机时,过一会(时间可以配置)会自动切换主机
-
哨兵模式的具体配置
# Example sentinel.conf # 哨兵sentinel实例运行的端口 默认26379 port 26379 # 哨兵sentinel的工作目录 dir /tmp # 哨兵sentinel监控的redis主节点的 ip port # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。 # quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了 # sentinel monitor <master-name> <ip> <redis-port> <quorum> sentinel monitor mymaster 127.0.0.1 6379 1 # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码 # 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒 # sentinel down-after-milliseconds <master-name> <milliseconds> sentinel down-after-milliseconds mymaster 30000 # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步, 这个数字越小,完成failover所需的时间就越长, 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。 # sentinel parallel-syncs <master-name> <numslaves> sentinel parallel-syncs mymaster 1 # 故障转移的超时时间 failover-timeout 可以用在以下这些方面: #1. 同一个sentinel对同一个master两次failover之间的间隔时间。 #2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。 #3.当想要取消一个正在进行的failover所需要的时间。 #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了 # 默认三分钟 # sentinel failover-timeout <master-name> <milliseconds> sentinel failover-timeout mymaster 180000 # SCRIPTS EXECUTION #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。 #对于脚本的运行结果有以下规则: #若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10 #若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。 #如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。 #一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。 #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本, #这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数, #一个是事件的类型, #一个是事件的描述。 #如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。 #通知脚本 # sentinel notification-script <master-name> <script-path> sentinel notification-script mymaster /var/redis/notify.sh # 客户端重新配置主节点参数脚本 # 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。 # 以下参数将会在调用脚本时传给脚本: # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port> # 目前<state>总是“failover”, # <role>是“leader”或者“observer”中的一个。 # 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的 # 这个脚本应该是通用的,能被多次调用,不是针对性的。 # sentinel client-reconfig-script <master-name> <script-path> sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
3.哨兵模式的优缺点
- 优点:
- 在主服务器宕机的情况下可以童年各国哨兵集群重新选出新的主节点,提高服务的高可靠性
- 缺点:
- redis集群如果达到上限,再扩容就十分麻烦
- 哨兵配置较为复杂
12. 缓存穿透、缓存击穿、雪崩击穿(重点)
请求数据的过程:后台首先从缓存中获取数据,如果缓存中获取不到数据,那么就从数据库中获取,如果数据库也获取不到,就返回空值。在这个看似简单的过程中,其实隐藏着我们不得不考虑的风险:缓存穿透、缓存击穿、雪崩击穿
12.1 缓存穿透
问题描述:
缓存穿透指的是缓存和数据库都没有数据。在请求数据的过程中,在缓存获取数据时,没有命中,于是回去数据库中获取,但是数据库也没有数据,于是返回空值,但是空值不会被写入缓存。如果用户不断这种请求,那么每次都会去访问数据库,缓存也就好像被穿透了一样,失去了缓存的意义,在高并发的情况下还会导致数据库挂了。(如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。)
![image-20210723215233998](https://gitee.com/yell1874/TyporaImg/raw/master/img/image-20210723215233998.png)
解决方案:
- 接口层增加校验:比如用户鉴权校验,对请求数据的id做基础校验,拦截不符合规则的id(例如id<0),限制接口单位时间内的访问次数
- 缓存空对象:对于缓存和数据库中的值都为空的key,可以设置key-value为key-null,设置较短的过期时间比如30秒(设置太长会导致正常情况无法使用)
-
布隆过滤器
-
原理:布隆过滤器通过k个哈希函数计算出key对应的k个下标的值,修改底层位数组对应坐标值为1,当缓存不命中时,查询布隆过滤器(将请求的key进行hash函数运算,比对位数组对应坐标的值,全为1则存在,否则不存在),如果布隆过滤器不存在这个key就直接拦截,不访问数据库。
-
参数:
- 插入key的个数
- 容错率(与hash函数个数、位数组长度有关),容错率越小,效率越低。
-
实现:
-
谷歌框架:Guava
- 地址:https://wizardforcel.gitbooks.io/guava-tutorial/content/1.html
- 类:BoolmFilter
- 缺点:
- 只支持单机版,不支持Redis集群
- 谷歌框架的布隆过滤器底层位数组长度:21亿(int类型的长度) ,而且储存在jvm内存中,性能较差
-
Redis实现布隆过滤器
- 通过Redis底层的bit数组实现布隆过滤器:
- 使用String类型,创建一个key,value是String(实质是一个位数组),这个位数组作为Redis布隆过滤器的存储容器,存储数据库key时,通过hash函数计算出对应下标,再修改值
# 位数组相关指令 setbit key value index 0(或1) # index大于value长度时,会自动扩容,扩充位置默认值为0,index最大可以到42亿(Redis下的String最大长度是512M,相当于42亿位) getbit key value index
- 优点:
- 支持redis集群,而且位数组占用的是redis内存,性能更好
- 通过Redis底层的bit数组实现布隆过滤器:
-
-
12.2 缓存击穿
问题描述:
在高并发情况下**,持续请求一个key,当缓存中的key过期的时候,大量缓存直接击穿缓存层,直接打在数据库上**,数据库压力骤增,可能会面临数据库挂了的问题。
问题解决:
- key永不过期
- 互斥锁:
- 当缓存中的key过期时,给key加上互斥锁,只有获得锁的请求去访问数据库,然后将值更新到数据库中,后面的请求虽然也需要竞争锁,但是不会请求数据库。
12.3 缓存雪崩
问题描述
Redis大量的key同时过期,大量请求直接打在数据库上,数据库压力变大。高并发情况下,数据库可能会宕机。
问题解决
- key过期时间错开:使用随机数设置过期时间(难设置,不太现实)
- 使用熔断机制。当流量到达一定的阈值时,就直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上。至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。
- 提高数据库的容灾能力,可以使用分库分表,读写分离的策略。
- 为了防止Redis宕机导致缓存雪崩的问题,可以搭建Redis集群,提高Redis的容灾性。