> For the complete documentation index, see [llms.txt](https://jun-wang.gitbook.io/learnjava/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/web-zhong-jian-jian-xue-xi/redis/redis-ru-men.md).

# Redis入门

## 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. 应用场景

* 缓存
* 任务队列
* 应用排行榜
* 网站访问统计
* 数据过期处理
* 分布式集群架构中的session分离

## 3. Linux下安装

```
#安装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. 启动

```
cd src
#前端启动，进去以后不能操作linux了
./redis-server
#后端启动，需要配置${REDIS_HOME}/redis.conf，将 daemonize no 改为 daemonize yes，设置为后台运行
# 然后加上配置项启动
cd ..
./src/redis-server redis.conf
```

## 5. 停止

```
./src/redis-cli shutdown
```

## 6. 进入redis客户端

```
./src/redis-cli
#进入客户端了，可以看到端口号6379
127.0.0.1:6379>
```

apt-get 安装

```
# 一键安装，安装完成后自动启动，占用端口：6379
sudo apt-get install redis
# 启动客户端
redis-cli
```

## 7. 增删查改数据

```
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：回滚事务。

  ```
  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配置来启用认证。比如：

```
requirepass test123
```

需要重启redis才能生效。也可以在cli中进行配置密码：

```
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：

```
redis-cli -a test123
```

也可以先登录后验证。

实现访问控制还可以通过绑定ip的办法。同样在redis.conf中：

```
bind ip1 ip2 ip3
# 表示只允许这些ip访问。需要重启redis
```

## 9. 在java中使用redis

### 9.1 引入jedis依赖

在pom.xml中添加redis的依赖

```markup
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
```

### 9.2 在java文件中使用redis

```java
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下运行的结果：

```
Redis一秒钟写入次数：21831
Redis一秒钟读取次数：23526
```

这里每秒只操作了2万多次，而事实上Redis的执行速度比这个操作要快的多，这是因为我们只是一条一条的将命令发送给redis去执行，如果使用流水线技术会快的多。将可以达到10万次每秒的速度，十分有利于系统性能的提升。

## 10. 在SpringMVC中使用redis

### 10.1 引入pom依赖

```markup
<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 配置连接池，连接工厂，序列化方式

```markup
<!-- 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**

```java
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**

```java
@Autowired
private RedisTemplate<String, Role> redisTemplate;
```

**3）redis写入和读取**

```java
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());
```

运行程序后查看日志：

```
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写入和读取的代码可以改成：

```java
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());
```

日志打印：

```
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为什么采用单线程

1. 使用单线程模型能带来更好的可维护性，方便开发和调试；
2. 使用单线程模型也能并发的处理客户端的请求；
3. 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>
