Redis简介
https://redis.io/docs/data-types/
Redis(Remote Dictionary Server )远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存也可持久化的日志型、Key-Value(NoSQL)数据库。
Redis的特点
- 性能极高,基于内存,读的速度是110000次/s,写的速度是81000次/s
- 丰富的数据类型,支持string、hash、list、set及zset多种数据类型
- 原子性,所有操作都是原子性的,支持事务
- 丰富的特性,支持发布订阅、通知、过期策略等
- 支持持久化,可以将内存中的数据保存在磁盘中,重启后再次加载
- 支持分布式,理论上可以无限扩展
- 单线程,没有线程并发问题,Redis5.0后也支持多线程
应用场景:
1. 做为缓存,保存热点数据
2. 分布式锁、分布式ID、分布式Session
3. 消息队列
4. ...
安装Redis
Linux安装
安装c编译器
yum -y install gcc
下载redis
cd /usr/local
wget http://download.redis.io/releases/redis-3.2.5.tar.gz
解压redis
tar -xvf redis-3.2.5.tar.gz
mv redis-3.2.5 redis
编译redis
cd redis
make
配置redis
修改redis.conf
# bind 127.0.0.1 去掉绑定本机IP,让其它机器访问
protected mode no 关闭保护模式
启动redis服务器
切换到src中
./redis-server ../redis.conf
启动redis客户端
./redis-cli
后台启用
在启用的redis目录下输入以下命令,进行下图修改
vim redis.conf
启动
src/redis-server ./redis.conf
密码设置
可设置-也可省略
设置了之后要记得重新启动
Windows安装
从官网下载redis的windows版本
https://github.com/tporadowski/redis/releases
解压后,双击redis-server.exe,就完成了Redis启动
Redis的数据类型
Redis数据类型有:
- string 字符串
- hash 哈希
- list 列表
- set 集合
- zset 有序集合
string
字符串是基本的key-value结构,key和value最大512m,key不建议超过1k,值可以是各种格式的字符、JSON、二进制、编码后的图片。
应用场景:
- 缓存
- 分布式ID
- 分布式锁
- 全局Session
- 全局计数器
设置值
set 键 值
set name zhagnsan
set name zhagnsan EX 60 //EX是过期时间,单位是秒
setex name 60 zhangsan
set 键 值 NX //NX键如果不存在就设置成功,存在就失败
读取值
get 键
get name
删除键
del 键名
递增/递减(值必须为数字)
incr 键 递增1
decr 键 递减1
incrby 键 递增量
decrby 键 递减量
hash
一个key下有多个key-value
应用场景:保存对象的多个属性
和string的对比,优势是:可以灵活读写对象的部分属性
设置对象属性
student是对象名称,name和age是属性名称
hmset student name zhangsan age 20
hset student name zhangsan age 20
读取对象属性
hmget student name
也阔以获取多个
hmget student name age
读取对象所有属性
hgetall student
删除对象属性
hdel student age
list
list采用链表结构保存多个数据,是有序的、可重复的。
应用场景:
- 模拟数组、栈、队列等数据结构
- 线型结构,如:粉丝、点赞列表
- 消息队列
左边入栈
lpush students zhangsan
lpush students lisi
lpush students wangwu
左边出栈
lpop students
右边入栈
rpush students zhangsan
右边出栈
rpop students
列表长度
llen key
读取列表,0和2是开始和结束位置(-1 代表全部)
lrange students 0 2
set
set是无序的、不可重复的集合。
应用场景:
- 点赞数(避免重复)
- 社交关系(共同关注、可能认识)
添加数据
sadd students zhangsan
sadd students lisi
sadd students wangwu zhaoliu
读取数据
smembers students
移除一个元素
srem key mamber1 mamber2
是否是其中元素
sismember students zhangsan
取交集
sinter key1 key2 ...
取差集
sdiff key1 key2 ...
取并集
sunion key1 key2 ...
zset
zset是有序的、不可重复的集合。
应用场景:
- 排行榜
添加数据,要添加一个score数字,按score排序
zadd key score value
读取数据
- zrangebyscore ,start和end是score最小和最大值
zrevrangebyscore 反向读取zrangebyscore
zrangebyscore key start end
- zrange ,start和end是开始和结束位置
zrevrange 反向读取zrange
zrange key start end
事务
Redis提供的事务是将多个命令打包,然后一次性、按照先进先出的顺序(FIFO)有序的执行。在执行过程中不会被打断(在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中),当事务队列中的所以命令都被执行(无论成功还是失败)完毕之后,事务才会结束。
事务相关命令
- multi 启动事务
- exec 提交事务
- discard 放弃事务
- watch 监视一个或多个键,如果有其他客户端修改键的值,事务将失败
事务操作
案例1:开启事务,正常执行,提交事务
案例2:开启事务,放弃事务,事务中的操作没有执行
案例3:开始事务,出现了语法错误,提交事务后,操作都没有执行
案例4:开始事务,出现数值错误(k1是字符串不能递增),提交事务后,其它的操作可以执行
案例5:监视score键,没有其它客户端修改,事务正常执行
案例6:监视score键,提交前打开另一个客户端修改score,提交事务后incrby score没有执行
Redis持久化
Redis数据保存在内存中,为避免关闭程序后数据的丢失,就需要将数据保存到磁盘文件上。
持久化策略
持久化策略包含
- AOF:默认每秒对数据进行持久化
- RDB:按条件触发持久化操作,满足任意一个条件
- 900 1 900秒中修改1次
- 300 10 300秒中修改10次
- 60 10000 60秒中修改10000次
配置方法
可以在redis.conf中配置持久化
如:RDB
启动AOF的配置
appendonly yes 开启AOF
appendfsync everysec 每秒保存
选择持久化策略
我们如何选择RDB和AOF呢?视业务场景而定:
- 允许少量数据丢失,性能要求高,选择RDB
- 只允许很少数据丢失,选择AOF
- 几乎不允许数据丢失,选择RDB + AOF
Redis淘汰策略
Redis中的数据太多可能导致内存溢出,Redis会根据情况淘汰一些数据。
Redis的内存上限:64位系统,上限就是内存上限;32位系统,最大是4G
配置最大内存:
max-memory 配置0就是无上限(默认)
淘汰策略
配置淘汰策略
maxmemory-policy
值:
-
noevication(默认)不淘汰
-
volatile-ttl 在过期键中淘汰存活时间短的键
-
allkeys-lru (推荐)使用LRU算法淘汰比较少使用的键
LRU算法:Least Recently Used 最近最少使用算法,淘汰长期不用的缓存LFU算法:Least Frequently Used 频率最少使用算法,淘汰使用频率少的缓存
-
volatile-lru 在过期的键中淘汰较少使用的
-
allkeys-random 在所有键中随机淘汰
-
volatile-random 在过期键中随机淘汰
-
allkeys-lfu
-
volatile-lfu
Redis开发
编程式缓存
通过SpringBoot整合Redis的方式来实现缓存商品。
1)导入Redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2)添加配置文件
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-wait=100ms
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=10
3)配置类
配置RedisTemplate
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
// 配置序列化器
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
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;
}
}
4)使用RedisTemplate
常用方法:
- ValueOperations opsForValue() 获得string类型的操作对象
- HashOperations opsForHash 获得hash类型的操作对象
- …
ValueOperations操作对象
- set(key,value) 保存key-value
- Object get(key) 读取value
使用缓存的步骤:
- 先查询缓存
- 如果查到直接返回
- 如果查不到,查询数据库
- 数据库查到,保存缓存中
- 数据库查不到返回null
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
public static final String KEY = "User-";
//注入RedisTemplate对象
@Autowired
private RedisTemplate<String,Object> redisTemplate;
// @Override
// public User getUserById(Long id) {
// //获得string类型的操作对象
// ValueOperations<String, Object> ops = redisTemplate.opsForValue();
// //查询redis中的用户
// User user = (User) ops.get(KEY + id);
// if(user == null){
// log.info("如果redis没有该用户,就查数据库");
// //如果redis没有该用户,就查数据库
// user = this.getById(id);
// if(user != null){
// log.info("如果数据库查到,就保存到redis中");
// //如果数据库查到,就保存到redis中
// ops.set(KEY + id,user);
// return user;
// }
// }else {
// log.info("如果redis查到,就返回对象");
// //如果redis查到,就返回对象
// return user;
// }
// log.info("如果数据库没有查到,就返回null");
// return null;
// }
// key-->User::1
@Cacheable(cacheNames = "User",key = "T(String).valueOf(#id)")
@Override
public User getUserById(Long id) {
return this.getById(id);
}
}
声明式缓存
编程式缓存使用复杂,代码侵入性高,推荐使用声明式缓存,通过注解来实现热点数据缓存。
1)在启动类上添加注解
//启动缓存
@EnableCaching
2)Redis的配置类
@Configuration
public class RedisConfig {
@Bean
public RedisCacheConfiguration provideRedisCacheConfiguration(){
//加载默认配置
RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig();
//返回Jackson序列化器
return conf.serializeValuesWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
}
3)缓存相关注解
- @CacheConfig 使用在Service类上,可以配置缓存名称,如:
@CacheConfig(cacheNames = “books”) - @Cacheable 使用在查询方法上,让方法优先查询缓存
- @CachePut 使用在更新和添加方法上,数据库更新和插入数据后同时保存到缓存里
- @CacheEvict 使用在删除方法上,数据库删除后同时删除缓存
注意:缓存的实体类必须实现序列化接口
案例:Reids缓存品牌
//配置缓存名称为brand
@CacheConfig(cacheNames = "brand")
@Service
public class BrandServiceImpl extends ServiceImpl<BrandMapper, Brand> implements IBrandService {
@Autowired
private BrandMapper brandMapper;
//按分类查询品牌时进行缓存,缓存名称是brand-category,键是分类id
@Cacheable(cacheNames = "brand-category",key = "T(String).valueOf(#cid)")
@Override
public List<Brand> findBrandsByCategory(Integer cid) {
return brandMapper.selectBrandsByCategory(cid);
}
//缓存单个品牌
@Cacheable(key = "T(String).valueOf(#id)")
@Override
public Brand findBrandById(Long id) {
return this.getById(id);
}
//修改品牌时更新缓存
@CachePut(key = "T(String).valueOf(#brand.id)")
@Override
public Brand saveBrand(Brand brand) {
this.saveOrUpdate(brand);
return brand;
}
//删除品牌时删除缓存
@CacheEvict(key = "T(String).valueOf(#id)")
@Override
public void deleteBrand(Long id) {
this.removeById(id);
}
//缓存分页
@Cacheable(cacheNames = "brand-page",key = "T(String).valueOf(#page)")
@Override
public IPage<Brand> pageBrands(Long page) {
return this.page(new Page<>(page,10));
}
}
组合注解@Cacing
同时完成多个查询、更新、删除的操作
@CacheConfig(cacheNames = "person")
@Service
public class PersonServiceImpl extends ServiceImpl<PersonMapper, Person> implements IPersonService {
public static final String PREFIX = "Person:";
@Autowired
private PersonMapper personMapper;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Cacheable(cacheNames = "person",key = "T(String).valueOf(#id)")
@Override
public Person getPersonById(Long id) {
return personMapper.selectById(id);
}
@Cacheable(cacheNames = "person-page",key = "T(String).valueOf(#current)")
@Override
public IPage<Person> getPersonPage(Long current, Long size) {
return personMapper.selectPersonPage(new Page<Person>(current,size));
}
@Caching(put = @CachePut(cacheNames = "person",key = "T(String).valueOf(#person.id)"),
evict = @CacheEvict(cacheNames = "person-page",allEntries = true))
public Person addPerson(Person person){
personMapper.insert(person);
return person;
}
//同时更新person+id,和删除所有person-page缓存
@Caching(put = @CachePut(cacheNames = "person",key = "T(String).valueOf(#person.id)"),
evict = @CacheEvict(cacheNames = "person-page",allEntries = true))
@Override
public Person updatePerson(Person person) {
personMapper.updateById(person);
return person;
}
//同时删除person+id,和所有person-page缓存
@Caching(evict = {@CacheEvict(cacheNames = "person",key = "T(String).valueOf(#id)"),
@CacheEvict(cacheNames = "person-page",allEntries = true)})
@Override
public void deletePerson(Integer id) {
personMapper.deleteById(id);
}
}