# 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>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/web-zhong-jian-jian-xue-xi/redis/redis-ru-men.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
