1. 简介
Redis是一个高性能的并且开源的使用key-value保存数据的NoSql数据库,它的特点包括:
Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。
性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
丰富的特性,Redis还支持 publish/subscribe, 通知, key 过期等等特性。
Redis作为持久层,它存储的数据结构是半结构化的,这就意味着计算机在读入内存时有更少的规则,读入速度更快。作为缓存,它支持大数据存入内存中,只要命中率高,它就能快速响应。
举个例子,对于常用数据,第一次从数据库取出存放在nosql中,这样以后无需访问数据库,只要从nosql中读出即可,性能比数据库快得多,对于高并发的操作,可以先在nosql上完成写入,等待某一时刻再批量写入数据库,这样就满足系统的性能要求了。
2. 应用场景
3. Linux下安装
Copy #安装gcc环境,因为redis是c++写的
apt-get install gcc
# 下载最新的安装包
wget http://download.redis.io/releases/redis-4.0.9.tar.gz
# 解压
tar -zxvf redis-4.0.9.tar.gz
cd redis-4.0.9/
#编译
make
4. 启动
Copy cd src
#前端启动,进去以后不能操作linux了
./redis-server
#后端启动,需要配置${REDIS_HOME}/redis.conf,将 daemonize no 改为 daemonize yes,设置为后台运行
# 然后加上配置项启动
cd ..
./src/redis-server redis.conf
5. 停止
Copy ./src/redis-cli shutdown
6. 进入redis客户端
Copy ./src/redis-cli
#进入客户端了,可以看到端口号6379
127.0.0.1:6379>
apt-get 安装
Copy # 一键安装,安装完成后自动启动,占用端口:6379
sudo apt-get install redis
# 启动客户端
redis-cli
7. 增删查改数据
Copy 127.0.0.1:6379> set name wangjun
OK
127.0.0.1:6379> get name
"wangjun"
# 正则--start
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> keys ?ame
1) "name"
127.0.0.1:6379> keys [a-z]ame
1) "name"
127.0.0.1:6379> keys [mnj]ame
1) "name"
# 正则--end
127.0.0.1:6379> set name wangjun2
OK
127.0.0.1:6379> get name
"wangjun2"
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> flushdb(清空所有的key)
OK
8. Redis基础特性
多数据库:
一个Redis实例最多提供16个数据库(类似mysql的多个数据库),下标0-15,默认连接0数据库。
通过select n 选择数据库。
事务:
一次提交多条记录,multi:开始事务;exec:提交事务;discard:回滚事务。
Copy 127.0.0.1:6379> get age
"28"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) (integer) 29
2) (integer) 30
127.0.0.1:6379> get age
"30"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get age
"30"
redis没有实现访问控制这个功能,但是他提供了一个轻量级的认证方式,可以编辑redis.conf配置来启用认证。比如:
需要重启redis才能生效。也可以在cli中进行配置密码:
Copy 127.0.0.1:6379> config set requirepass test123
OK
127.0.0.1:6379> keys *
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth test123
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379>
这样配置后,只要不重启redis就一直是生效的。
登录有密码的redis:
也可以先登录后验证。
实现访问控制还可以通过绑定ip的办法。同样在redis.conf中:
Copy bind ip1 ip2 ip3
# 表示只允许这些ip访问。需要重启redis
9. 在java中使用redis
9.1 引入jedis依赖
在pom.xml中添加redis的依赖
Copy <dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
9.2 在java文件中使用redis
Copy import redis . clients . jedis . Jedis ;
public class RedisTest {
public static void main ( String [] args) {
Jedis jedis = new Jedis( "localhost" , 6379 ) ;
//如果需要密码的话
//jedis.auth(password);
//测试写入性能
int i = 0 ;
long start = System . currentTimeMillis ();
while ( true ) {
long end = System . currentTimeMillis ();
if (end - start > 1000 ) {
break ;
}
jedis . set ( "key" + i , i + "" );
i ++ ;
}
System . out . println ( "Redis一秒钟写入次数:" + i);
//测试读取性能
start = System . currentTimeMillis ();
i = 0 ;
while ( true ) {
long end = System . currentTimeMillis ();
if (end - start > 1000 ) {
break ;
}
String str = jedis . get ( "key1" );
i ++ ;
}
System . out . println ( "Redis一秒钟读取次数:" + i);
}
}
这是我在win下运行的结果:
Copy Redis一秒钟写入次数:21831
Redis一秒钟读取次数:23526
这里每秒只操作了2万多次,而事实上Redis的执行速度比这个操作要快的多,这是因为我们只是一条一条的将命令发送给redis去执行,如果使用流水线技术会快的多。将可以达到10万次每秒的速度,十分有利于系统性能的提升。
10. 在SpringMVC中使用redis
10.1 引入pom依赖
Copy <dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.15.RELEASE</version>
</dependency>
10.2 配置连接池,连接工厂,序列化方式
Copy <!-- redis连接池 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲数 -->
<property name="maxIdle" value="50"/>
<!-- 最大连接数 -->
<property name="maxTotal" value="100"/>
<!-- 最大等待时间 -->
<property name="maxWaitMillis" value="20000"/>
</bean>
<!-- 配置redis连接工厂 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="localhost"/>
<property name="port" value="6379"/>
<property name="poolConfig" ref="poolConfig"/>
</bean>
<!-- 配置redis的key和value的序列化方式 -->
<bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="keySerializer" ref="stringRedisSerializer"/>
<property name="valueSerializer" ref="jdkSerializationRedisSerializer"/>
</bean>
10.3 在java文件中使用redis
1)序列化类Role
Copy class Role implements Serializable {
private static final long serialVersionUID = 1L ;
private String name;
private int age;
private String email;
public Role ( String name , int age , String email) {
super();
this . name = name;
this . age = age;
this . email = email;
}
public String getName () {
return name;
}
public void setName ( String name) {
this . name = name;
}
public int getAge () {
return age;
}
public void setAge ( int age) {
this . age = age;
}
public String getEmail () {
return email;
}
public void setEmail ( String email) {
this . email = email;
}
public static long getSerialversionuid () {
return serialVersionUID;
}
}
2)自动注入RedisTemplate
Copy @ Autowired
private RedisTemplate < String , Role > redisTemplate;
3)redis写入和读取
Copy Role role = new Role( "wangjun" , 25 , "scuangjun@hotmail.com" ) ;
redisTemplate . opsForValue () . set ( "roleTest" , role);
logger . debug ( "redis-role:" + redisTemplate . opsForValue () . get ( "roleTest" ));
logger . debug ( "redis-role-name:" + redisTemplate . opsForValue () . get ( "roleTest" ) . getName ());
logger . debug ( "redis-role-age:" + redisTemplate . opsForValue () . get ( "roleTest" ) . getAge ());
运行程序后查看日志:
Copy 2018-10-08 14:34:49.206 DEBUG com.timeline.controllers.CalalogController.queryCatalog(CalalogController.java:58) http-apr-8080-exec-3 [msg]redis-role:com.timeline.controllers.Role@6306cf0e
2018-10-08 14:34:49.206 DEBUG com.timeline.controllers.CalalogController.queryCatalog(CalalogController.java:59) http-apr-8080-exec-3 [msg]redis-role-name:wangjun
2018-10-08 14:34:49.207 DEBUG com.timeline.controllers.CalalogController.queryCatalog(CalalogController.java:60) http-apr-8080-exec-3 [msg]redis-role-age:25
可以看到使用RedisTemplate可以很好的实现java对象的序列化存储和读取。
4)使用同一个连接池操作
以上的使用都是基于redisTemplate、基于连接池的操作,换句话说,并不能保证每次使用RedisTemplate都是操作同一个Redis的连接,比如上面的set和get方法。使用同一个连接可以节省资源,对于有很多操作的时候我们往往希望通过使用同一个连接操作。
为了保证所有的操作都来自同一个连接,可以使用SessionCallback或者RedisCallback这两个接口,RedisCallback是比较底层的封装,其使用不是很友好,所以更多的时候会使用SessionCallback这个接口,通过这个接口就可以把多个命令放入到同一个Redis连接中去执行。比如上面redis写入和读取的代码可以改成:
Copy SessionCallback < Role > sessionCallback = new SessionCallback < Role >() {
public Role execute ( RedisOperations operations) throws DataAccessException {
operations . boundValueOps ( "roleTest" ) . set (role);
return (Role) operations . boundValueOps ( "roleTest" ) . get ();
}
};
Role role2 = redisTemplate . execute (sessionCallback);
logger . debug ( "redis-role2:" + role2);
logger . debug ( "redis-role2-name:" + role2 . getName ());
logger . debug ( "redis-role2-age:" + role2 . getAge ());
日志打印:
Copy 2018-10-09 10:29:13.072 DEBUG com.timeline.controllers.CalalogController.queryCatalog(CalalogController.java:72) http-apr-8080-exec-7 [msg]redis-role2:com.timeline.controllers.Role@42f6a3e7
2018-10-09 10:29:13.073 DEBUG com.timeline.controllers.CalalogController.queryCatalog(CalalogController.java:73) http-apr-8080-exec-7 [msg]redis-role2-name:wangjun
2018-10-09 10:29:13.073 DEBUG com.timeline.controllers.CalalogController.queryCatalog(CalalogController.java:74) http-apr-8080-exec-7 [msg]redis-role2-age:25
11. redis为什么采用单线程
使用单线程模型能带来更好的可维护性,方便开发和调试;
Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU;
redis4.0之后引入多线程执行一些非阻塞的删除操作等;
redis6.0使用I/O多线程进行数据包的解析;
https://draveness.me/whys-the-design-redis-single-thread/ 这篇文章写得很好。
参考:
http://www.runoob.com/redis/redis-tutorial.html
https://www.imooc.com/video/14932/0