文章目录
redis
数据格式
redis是k-v结构的,key与Value都是String类型的数据
数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
操作
redi数据类型的操作
String(字符串)类型的操作
incr按照默认步长(默认为1)进行递增
语法:ncr key
incrby 按照指定步长进行递增
语法:incrby key increment(数值)
decr/decrby
语法:decr key 按照默认步长(默认为1)进行递减
语法:decrby key decrement 按照指定步长进行递减
append向尾部追加值
语法:append key value
strlen字符串长度,返回数据的长度,如果键不存在则返回0。
语法: strlen key字符串长度
mset/mget同时设置/获取多个键值
语法:MSET key value [key value …]MGET key [key …]
hash
hash用于存储对象信息,存储了字段(field)和字段值的映射
hset/hget
hset key field value #
hset key field
hmset key field value [field value…]
例子: hmset 人类 姓名 jack 年龄 22 sex 男
hmget key field [field] #
hgetall key #获取所有的值
hexists
格式: hexists key field
hdel
格式: hdel field value
hkeys/hvals 只获取字段名hkeys或字段值hvals
格式: hkeys/hvals/hlen key
list
lpush 在key对应list的头部添加字符串元素
格式: lpush key "value"
rpush 在key对应list的尾部添加字符串元素
格式: rpush key "value"
del 清空集合元素
linsert 在key对应list的特定位置之前或之后添加字符串元素
格式: linsert key before/after "参照数据" "被添加元素"
lset 设置list中指定下标的元素值(一般用于修改操作)
格式:lset key 0(下标) "要添加的数据
lrem 从key对应list中删除count个和value相同的元素,count>0时,按从头到尾的顺序删除
格式: lrem key 3(个数) "被删除的值"
**ltrim 保留指定key 的值范围内的数据 **
格式: ltrim key 0 -1
lpop从list的头部删除元素,并返回删除元素
lpop list的名 删除list集合的头部元素
rpop从list的尾部删除元素,并返回删除元素:
rpop list集合的名字---删除list集合的尾部元素
llen返回key对应list的长度:
llen list集合的名字---返回list集合的长度
lindex返回名称为key的list中index位置的元素:
lindex list集合的名字 0(list集合的元素位置)----查询位置为0的元素名称
** rpoplpush从第一个list的尾部移除元素并添加到第二个list的头部,最后返回被移除的元素值,整个操作是原子的.如果第一个list是空或者不存在返回nil:**
rpoplpush lst1 lst1
rpoplpush lst1 lst2
set
sadd添加元素,重复元素添加失败,返回0
sadd key value --向set集合中添加元素
smembers获取集合中成员
smember key---获取集合中的成员
spop移除并返回集合中的一个随机元素
spop key ---随机删除set集合中的元素
scard获取集合中的成员个数
scard key --获取set集合中的成员个数
smove移动一个元素到另外一个集合
smove 被移动元素所在集合key 移动后元素所在集合的key 被移动的元素
sunion实现集合的并集操作
sunion 第一个集合的key 第二个集合的key---将两个集合中的元素合并起来
java操作
RedisTemplate为SpringBoot工程中操作redis数据库的一个Java对象,此对象封装了对redis的一些基本操作。
stringredistemplate
StringRedisTemplate 是一个专门用于操作redis字符串类型数据的一个对象
特点: 可以实现与redis数据库的交互,会以字符串序列化的存储方式k/v
- 写配置文件
spring:
redis:
host: 192.168.126.129 #自己redis的IP地址
port: 6379 #默认存在
- 需要写启动类
需要在启动类添加一个注解
- 使用
stringredistemplate
的时候需要注入
@SpringBootTest
public class StringRedisTemplateTests {
/**
* 可以基于此对象实现与redis数据库的交互,它
* 会以字符串序列化的方式存储key/value
* 序列化/反序列化
* 1)狭义:
* 序列化:将对象转换为字节
* 反序列化:将字节转换为对象
* 2)广义:
* 序列化:将对象转换为json格式字符串或字节
* 反序列化:将字节或json串转换对象
*/
@Autowired
private StringRedisTemplate redisTemplate;
@Test
void testHash01(){
//1.获取Hash类型操作对象
HashOperations<String, Object, Object> vo =
redisTemplate.opsForHash();
//2.读写redis数据
vo.put("blog","id","100");
vo.put("blog","createdTime",new Date().toString());
Map<Object, Object> blog = vo.entries("blog");
System.out.println(blog);
}
/**
* 测试字符串操作
*/
@Test
void testString01(){
//1.获取字符串操作对象
ValueOperations<String, String> vo =
redisTemplate.opsForValue();
//2.读写redis数据
vo.set("name", "redis");
vo.set("author","tony", Duration.ofSeconds(10));//10表示有效时长
String name=vo.get("name");
System.out.println(name);
String author=vo.get("author");
System.out.println(author);
}
/**
* 测试与redis数据的连接
*/
@Test
void testGetConnection(){
RedisConnection connection =
redisTemplate.getConnectionFactory().getConnection();
String ping = connection.ping();
System.out.println(ping);
}
}
StringRedistemplate 与Redistemplate的区别
- 序列化的方式不同,Redistemplate的序列化方式为jdk默认的序列化方式
而StringRedistemplate序列化方式为String字符串的序列化方式 - StringRedistemplate继承了Redistemplate
redistemplate
RedisTemplate是一个专门用于实现对远端redis中复杂数据的操作的对应
@SpringBootTest
public class RedisTemplateTests {
/**
* RedisTemplate 是StringRedisTemplate的父类类型,
* 默认采用JDK的序列化,反序列化方式存取数据
*/
@Autowired
private RedisTemplate redisTemplate;
@Test
void testBlog01(){
//1.获取Blog对象操作对象
// redisTemplate.setKeySerializer(RedisSerializer.string());
// redisTemplate.setValueSerializer(RedisSerializer.json());
ValueOperations vo = redisTemplate.opsForValue();
//2.执行Blog对象读写操作
Blog blog=new Blog();
blog.setId(100);
//blog.setTitle("redis in java");
vo.set("blog",blog);//底层自动执行序列化
//blog= (Blog) vo.get("blog");
//System.out.println(blog);
}
@Test
void testHash01(){
//1.获取Hash类型操作对象
HashOperations<String, Object, Object> vo =
redisTemplate.opsForHash();
//2.读写redis数据
vo.put("blog","id","100");
vo.put("blog","createdTime",new Date().toString());
Map<Object, Object> blog = vo.entries("blog");
System.out.println(blog);
}
@Test
void testString01(){
//修改序列化方式
//redisTemplate.setKeySerializer(RedisSerializer.string());
//1.获取字符串操作对象
ValueOperations<String, String> vo =
redisTemplate.opsForValue();
//2.读写redis数据
vo.set("name", "redis");
vo.set("author","tony", Duration.ofSeconds(10));//10表示有效时长
String name=vo.get("name");
System.out.println(name);
String author=vo.get("author");
System.out.println(author);
}
}
序列化
定制redisTemplate
- 书写配置类
@Configuration
public class RedisConfig {//RedisAutoConfiguration
//自定义json序列化
public RedisSerializer jsonSerializer(){
//1.定义Redis序列化,反序列化规范对象(此对象底层通过ObjectMapper完成对象序列化和反序列化)
Jackson2JsonRedisSerializer serializer=
new Jackson2JsonRedisSerializer(Object.class);
//2.创建ObjectMapper(有jackson api库提供)对象,基于此对象进行序列化和反序列化
//2.1创建ObjectMapper对象
ObjectMapper objectMapper=new ObjectMapper();
//2.2设置按哪些方法规则进行序列化
objectMapper.setVisibility(PropertyAccessor.GETTER,//get方法
JsonAutoDetect.Visibility.ANY);//Any 表示任意方法访问修饰符
//对象属性值为null时,不进行序列化存储
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//2.2激活序列化类型存储,对象序列化时还会将对象的类型存储到redis数据库
//假如没有这个配置,redis存储数据时不存储类型,反序列化时会默认将其数据存储到map
objectMapper.activateDefaultTyping(
objectMapper.getPolymorphicTypeValidator(),//多态校验分析
ObjectMapper.DefaultTyping.NON_FINAL,//激活序列化类型存储,类不能使用final修饰
JsonTypeInfo.As.PROPERTY);//PROPERTY 表示类型会以json对象属性形式存储
serializer.setObjectMapper(objectMapper);
return serializer;
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//设置key的序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
//设置值的序列化方式
template.setValueSerializer(jsonSerializer());
template.setHashValueSerializer(jsonSerializer());
//更新一下RedisTemplate对象的默认配置
template.afterPropertiesSet();
return template;
}
分布式缓存原理
哈希环
一致性哈希(分片)
业务类
1.穿透
1.什么是缓存穿透
一般的缓存系统,都是按照key值去缓存查询,如果不存在对应的value,就应该去DB中查找 。这个时候,如果请求的并发量很大,就会对后端的DB系统造成很大的压力。这就叫做缓存穿透。关键词:缓存value为空;并发量很大去访问DB。
2.造成的原因
1.业务自身代码或数据出现问题;2.一些恶意攻击、爬虫造成大量空的命中,此时会对数据库造成很大压力。
解决方法
1.设置布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,
从避免了对底层存储系统的查询压力。
2. 如果一个查询返回的数据为空,不管是数据不存在还是系统故障,我们仍然把这个结果进行缓存,但是它的过期时间会很短
最长不超过5分钟。
2.雪崩
1.什么是雪崩?? 雪崩可能产生的原因是缓存服务器重启或大量缓存集中在某一时间段失效,大量数据就会去直接访问数据库
因为缓存层承载了大量的请求,有效的保护了存储层,但是如果缓存由于某些原因,整体不能够提供服务,于是所有的请求,就会到达存储层,存储层的调用量就会暴增,造成存储层也会挂掉的情况。缓存雪崩的英文解释是奔逃的野牛,指的是缓存层当掉之后,并发流量会像奔腾的野牛一样,大量后端存储。
存在这种问题的一个场景是:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,大量数据会去直接访问DB,此时给DB很大的压力。
2.解决方法
(1)设置redis集群和DB集群的高可用,如果redis出现宕机情况,可以立即由别的机器顶替上来。这样可以防止一部分的风险。
(2)使用互斥锁
在缓存失效后,通过加锁或者队列来控制读和写数据库的线程数量。比如:对某个key只允许一个线程查询数据和写缓存,其他线程等待。单机的话,可以使用synchronized或者lock来解决,如果是分布式环境,可以是用redis的setnx命令来解决。
(3)不同的key,可以设置不同的过期时间,让缓存失效的时间点不一致,尽量达到平均分布。
(4)永远不过期
redis中设置永久不过期,这样就保证了,不会出现热点问题,也就是物理上不过期。
(5)资源保护
使用netflix的hystrix,可以做各种资源的线程池隔离,从而保护主线程池。
3.使用
四种方案,没有最佳只有最合适, 根据自己项目情况使用不同的解决策略。