目录
前言
带着问题学java系列博文之java基础篇。从问题出发,学习java知识。
Redis
来自redis官网——Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
下载安装
linux上redis安装配置
1.下载 link:http://www.redis.cn/download.html
2.上传解压 rz -y tar -zxvf redis-5.0.7.tar.gz
3.编译安装 cd redis-5.0.7 make
进入src cd redis-5.0.7/src make install PREFIX=/usr/local/redis
(个人喜好安装在/usr/local/redis下)
4.安装成功redis路径下会生成bin,一系列命令都在里面
5.创建etc文件夹 复制配置文件 redis.conf
mkdir /usr/local/redis/etc
mv ~/redis-5.0.7/redis.conf /usr/local/redis/etc (将配置文件移动到etc)
6.修改配置文件
vi /usr/local/redis/etc/redis.conf
修改三个地方:
①NETWORK栏目中:开放IP限制,外网访问
注释掉 #bind 127.0.0.1 或者修改为 bind 0.0.0.0
②GENERAL栏目中:配置后台启动
修改 daemonize no 为 daemonize yes
③SECURITY栏目中:设置访问密码
添加 requirepass ***** (注释掉 requirepass foobared,redis默认无密码)
至此redis安装配置完成,可以开启redis测试下:
启动server服务: /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
ps查看:
启动client进入: /usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 6379(端口默认是6379) -a ****(之前设置的密码)
7.加入开机启动
修改开机启动文件:
vi /etc/rc.local
在文件最后添加下面内容(启动redis server的命令):
redis的基本概念
redis是NoSql非关系型数据库,存储在内存中,高性能。存储的是key-value型,类似一个大的map。如果没有做相应的持久化,当redis服务器重启,或者电脑重启,数据会丢失。redis可存储的主要数据类型:字符串 string、哈希类型hash(map)、列表 list、集合set、有序集合 sortedset。
redis高并发,高性能的原因:
1.Redis是基于内存的,内存的读写速度非常快;
2.Redis是单线程的,省去了很多上下文切换线程的时间,也没有加锁性能消耗;
3.Redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间;
4.Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
***:Redis为什么设计成单线程的?
官方回答是:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。虽然Redis设计成单线程,但性能却非常高,普通笔记本部署都可以轻松处理每秒数十万并发请求。此外,Redis使用了良好的io多路复用技术epoll,采用事件驱动、reactor模型,拥有很大的吞吐量。
***:解释IO多路复用、reactor模型
举一个实际场景:老师组织一次随堂测验,30名学生即时作答,老师当堂检查答题结果。老师有三种方式进行测验结果检查:
1.按照学生座位顺序,逐个检查;(单线程,for循环处理所有客户端请求)
2.叫来全校其他的老师帮忙,30名老师分别检查30名学生的答卷;(多线程,一个线程处理一个客户端请求)
3.老师让学生自己提交,谁完成答题,谁就上讲台提交,然后老师进行检查;(多路复用技术,单线程处理所有客户端请求)
第一种方式,虽然简单,而且也是单线程;但是有个致命问题:当检查到某个学生时,他还在作答,这时候老师就得等他作答完毕,才能继续,意味着所有的学生都得等这位同学。
第二种方式,虽然并发很好,可以快速完成检查;但是很显然,资源消耗太大,一场随堂测验居然要找来30位老师进行检查。
第三种方式,就是我们要讲的多路复用技术,虽然只有一位老师进行检查(单线程),但是却没有第一种方式存在的等待问题。
redis服务端处理线程被动等待redis客户端随机到来的请求,有请求到来就进行处理。自己实现的话,可以这样设计:所有客户端socketclient与服务端socketserver连接,然后socketserver接收所有的客户端发送过来的数据,socketserver对这些事件不作任何处理,直接存入阻塞队列;注册了该事件的处理线程则尝试从阻塞队列获取,获取到就进行处理。当然redis底层采用epoll模型的多路复用技术,要比我们设计的优越很多。
redis的持久化机制
redis的数据存取都是直接内存操作,如果不做持久化,一旦机器重启或者异常崩溃等,数据将全部丢失。所以redis也支持几种持久化机制,对内存中的数据进行持久化,保存到本地文件中。
1.RDB:默认的方式,一定的时间间隔中,检测key的变化情况,然后持久化数据。(可以修改配置,自定义检查的时间间隔,以及key变化的数量。比如,自定义检查间隔10分钟,key变化数量100条。则redis每隔10分钟会检查一次,如果发现key变化的数量超过100,则进行一次持久化。)
2.AOF:日志记录的方式,可以记录每一条命令的操作,每一次命令操作后都持久化数据.
*:修改conf文件, appendonly no(关闭 aof) appendonly yes(开启aof)
appendfsync always:每一次都进行持久化
appendfsync everysec:每隔一秒进行一次持久化
appendfsync no:不进行持久化
从这两种持久化机制可以看出,其实redis是无法保证数据一定不丢失的,不管是RDB还是AOF都存在一定程度的数据丢失风险。
SpringBoot集成Redis
spring框架对redis进行了集成封装,spring-data-redis模块,封装了RedisTemplate模板类,便于我们使用。
1.依赖导入:
<!--redis启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lettuce pool 缓存连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--jackson序列化器,实现自定义template-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
2.信息配置(application.yml):
spring:
redis:
database: 0 #数据库索引(默认是0)
host: 192.168.218.130 #redis服务器地址
port: 6379 #端口
password: xxx #密码
timeout: 10000 #连接超时时间(ms)
# lettuce连接池
lettuce:
pool:
max-active: 8 #连接池最大的连接数(负值表示没限制)
max-wait: 10000 #最大的阻塞等待时间(负值表示没限制 ms)
max-idle: 8 #最大的空闲连接数
min-idle: 0 #最小的空闲连接数
shutdown-timeout: 100 #关闭超时时间(ms)
3.自定义模板(可选)
因为spring为我们封装的redistemplate默认是redistemplate<String,String>类型,使用起来不是很方便(存储非string的数据,还需要先进行对象转为字符串的操作)。所以一般都是自定义一个模板,redistemplate<String,Object>。如下,借助configuration注解,向spring框架注入自定义的redistemplate实例。
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
//设置key序列化器 string序列化器
template.setKeySerializer(new StringRedisSerializer());
//设置hash的key序列化器
template.setHashKeySerializer(new StringRedisSerializer());
//此种序列化方式结果清晰、容易阅读、存储字节少、速度快,所以推荐使用
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);
//设置value的序列化器(jackson序列化器,object转换为json)
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
4.使用自定义的redistemplate模板,封装一系列的数据存储操作工具类:(可选)
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
//=======================common============================
/**
* 指定缓存时间
* @param key 键
* @param time 时间(s)
* @return
*/
public boolean expire(String key,long time){
try {
if (time>0){
redisTemplate.expire(key,time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取过期时间
* @param key 键
* @return 时间(s)返回0代表永久有效
*/
public long getExpire(String key){
return redisTemplate.getExpire(key);
}
/**
* 判断key是否存在
* @param key 键
* @return true存在,false不存在
*/
public boolean hasKey(String key){
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param keys 可以传递多个key
*/
@SuppressWarnings("unchecked")
public void del(String... keys){
if (keys != null && keys.length>0){
if (keys.length == 1){
redisTemplate.delete(keys[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(keys));
}
}
}
//=======================String============================
/**
* 普通缓存获取值
* @param key 键
* @return 值
*/
public Object get(String key){
return key == null ? null:redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存存入
* @param key 键
* @param value 值
* @return true成功,false失败
*/
public boolean set(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 时间(s)time要大于0,否则将使用默认时长(无限期)
* @return true成功,false失败
*/
public boolean set(String key,Object value,long time){
try {
if (time > 0){
redisTemplate.opsForValue().set(key,value,time);
} else {
set(key,value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增(要求key对应的value必须可以转换成Double型)
* @param key 键
* @param delta 递增数值
* @return 递增之后的数值
*/
public double incr(String key,double delta){
if (delta < 0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key,delta);
}
/**
* 递减(要求key对应的value必须可以转换成Double型)
* @param key 键
* @param delta 递减数值
* @return 递减后的数值
*
* 由于decrement方法仅支持long类型,所以这里还是使用increment,取反因子
*/
public double decr(String key,double delta){
if (delta < 0){
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key,-delta);
}
//=======================Map============================
/**
* hashGet(获取map中的值)
* @param key 键
* @param item 项
* @return 值
*/
public Object hGet(String key,String item){
return redisTemplate.opsForHash().get(key,item);
}
/**
* 向hash表中存入一条数据,不存在将创建hash表
* @param key 键
* @param item 项
* @param value 值
* @return
*/
public boolean hSet(String key,String item,Object value){
try {
redisTemplate.opsForHash().put(key,item,value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向hash表中存入一条数据,不存在将创建hash表
* @param key 键
* @param item 项
* @param value 值
* @param time 时长
* @return
*/
public boolean hSet(String key,String item,Object value,long time){
try {
redisTemplate.opsForHash().put(key,item,value);
if (time > 0){
expire(key,time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取hashkey对应的map
* @param key 键
* @return map
*/
public Map<Object, Object> hmGet(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* hashset 保存map
* @param key 键
* @param map 数据
* @return
*/
public boolean hmSet(String key,Map<String,Object> map){
try {
redisTemplate.opsForHash().putAll(key,map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* hashset 保存map
* @param key 键
* @param map 数据
* @param time 时长
* @return
*/
public boolean hmSet(String key,Map<String,Object> map,long time){
try {
redisTemplate.opsForHash().putAll(key,map);
if (time > 0){
expire(key,time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hashmap中的item项
* @param key 键
* @param item 项,可以多个
*/
public void hDel(String key,Object... item){
redisTemplate.opsForHash().delete(key,item);
}
/**
* 判断hashmap中是否有该项
* @param key 键
* @param item 项
* @return
*/
public boolean hHasItem(String key,Object item){
return redisTemplate.opsForHash().hasKey(key,item);
}
/**
* hash递增,如果不存在会创建一个,再返回新增之后的值
* @param key 键
* @param item 项
* @param delta 递增因子
* @return
*/
public double hIncr(String key,String item,double delta){
return redisTemplate.opsForHash().increment(key,item,delta);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param delta 递减因子
* @return
*/
public double hDecr(String key,String item,double delta){
return redisTemplate.opsForHash().increment(key,item,-delta);
}
//=======================Set============================
/**
* 根据key获取set中所有值
* @param key 键
* @return
*/
public Set<Object> sGet(String key){
return redisTemplate.opsForSet().members(key);
}
/**
* 存入set集合
* @param key 键
* @param values 值
* @return 成功个数
*/
public long sSet(String key,Object... values){
return redisTemplate.opsForSet().add(key,values);
}
/**
* 存入set集合,并设定有效时长
* @param key 键
* @param time 时长
* @param values 值
* @return 成功个数
*/
public long sSet(String key,long time,Object... values){
long count = redisTemplate.opsForSet().add(key,values);
if (time > 0){
expire(key,time);
}
return count;
}
/**
* 判断value是否存在
* @param key
* @param value
* @return
*/
public boolean sHasItem(String key,Object value){
return redisTemplate.opsForSet().isMember(key,value);
}
/**
* 获取set的size
* @param key 键
* @return
*/
public long sSize(String key){
return redisTemplate.opsForSet().size(key);
}
/**
* 移除set中的value
* @param key
* @param values
* @return
*/
public long sRemove(String key,Object... values){
return redisTemplate.opsForSet().remove(key,values);
}
//=======================list============================
/**
* 获取list中的内容
* @param key 键
* @param start 开始下标
* @param end 结束下标 (0,-1)代表整个list
* @return
*/
public List<Object> lRange(String key,long start,long end){
return redisTemplate.opsForList().range(key,start,end);
}
/**
* 获取list的size
* @param key 键
* @return
*/
public long lSize(String key){
return redisTemplate.opsForList().size(key);
}
/**
* 取索引index的值
* @param key
* @param index 0表头,1第二个元素,-1表尾,-2倒数第二个元素
* @return
*/
public Object lIndex(String key,long index){
return redisTemplate.opsForList().index(key,index);
}
/**
* 向list中存入值(从列表尾部添加)
* @param key 键
* @param value 值
*/
public void lRightPush(String key,Object value){
redisTemplate.opsForList().rightPush(key, value);
}
/**
* 向list中存入值,并设定有效时长(从列表尾部添加)
* @param key 键
* @param value 值
* @param time 时长
*/
public void lRightPush(String key,Object value,long time){
redisTemplate.opsForList().rightPush(key,value);
expire(key,time);
}
/**
* 向list中存入多个值(从列表尾部添加)
* @param key 键
* @param values 值
*/
public void lRightPushAll(String key,List<Object> values){
redisTemplate.opsForList().rightPushAll(key, values);
}
/**
* 向list中存入值,并设定有效时长(从列表尾部添加)
* @param key 键
* @param values 值
* @param time 时长
*/
public void lRightPushAll(String key,List<Object> values,long time){
redisTemplate.opsForList().rightPushAll(key,values);
expire(key,time);
}
/**
* 修改index下标对应的值
* @param key 键
* @param index 下标
* @param value 值
*/
public void lUpdateIndex(String key,long index,Object value){
if (Math.abs(index) < lSize(key)){
redisTemplate.opsForList().set(key,index,value);
} else {
//下标越界
}
}
/**
* 向list中存入值(从列表头部添加)
* @param key 键
* @param value 值
*/
public void lLeftPush(String key,Object value){
redisTemplate.opsForList().leftPush(key, value);
}
/**
* 向list中存入值,并设定有效时长(从列表头部添加)
* @param key 键
* @param value 值
* @param time 时长
*/
public void lLeftPush(String key,Object value,long time){
lLeftPush(key,value);
expire(key,time);
}
/**
* 向list中存入多个值(从列表头部添加)
* @param key 键
* @param values 值
*/
public void lLeftPushAll(String key,List<Object> values){
redisTemplate.opsForList().leftPushAll(key, values);
}
/**
* 向list中存入值,并设定有效时长(从列表头部添加)
* @param key 键
* @param values 值
* @param time 时长
*/
public void lLeftPushAll(String key,List<Object> values,long time){
redisTemplate.opsForList().leftPushAll(key,values);
expire(key,time);
}
/**
* 移除列表中count个值为value的元素
* @param key 键
* @param value 值
* @param count 限定个数
* @return 实际移除的个数
*/
public long lRemove(String key,Object value,long count){
return redisTemplate.opsForList().remove(key, count, value);
}
}
至此,springboot集成redis完成。
Redis使用场景分析
redis使用key-value模式存储,数据直接存储在内存中,数据操作简单、高效;但是由于查询数据全都依赖key,给复杂查询带来诸多不便。因此适用场景主要是数据量小,需要频繁、高速读写数据,数据结构简单,查询条件简单;比如用户权限认证的token等系统需要频繁读写的运行期关键数据的存储,就非常适合用redis。
redis还被用于缓存介质,比如mybatis、hibernate框架都支持二级缓存,如果不进行存储配置,它们的二级缓存默认都是存储在当前后台服务内存中,可以使用redis作为存储介质,减轻当前后台服务的压力。
在分布式系统中,redis还被用来做为分布式锁的一种实现方式:某个系统建立一个分布式锁(在redis中存入指定key,value为随机值,并设置key不允许重复创建),其他系统都不断轮询尝试获取锁(创建key),获得锁的系统执行任务,执行完毕则释放锁(删除key)。
Redis高级特性
redis哨兵、分区、高可用;
redis事务;
利用redis作为mybatis、hibernate的二级缓存存储介质;
未完待续……
以上系个人理解,如果存在错误,欢迎大家指正。原创不易,转载请注明出处!