Redis学习笔记
一、Redis安装及其配置
1.1 下载地址
https://redis.io/download.html
1.2 查看redis版本信息
redis-server -v
redis-cli + info
1.3 修改redis配置文件redis.conf
# 后台运行
daemonize yes
protected-mode no
#bind 127.0.0.1
二、Redis的发展演变
2.1 Redis为什么是单线程的?
Redis的版本有很多3.X、4.X、6.X,版本不同架构也是不同的,不限定版本问是否单线程不严谨。
- 版本3.x,最早版本,redis是单线程的
- 版本4.x,严格意义上来说也不是单线程,而是负责处理客户端请求的线程是单线程,但是开始加了点多线程的东西(异步删除)
- 最新版本的6.0.x之后,告别了大家印象中的单线程,用一种全新的多线程来解决问题
2.2 Redis是单线程
主要是指Redis的网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求时包括获取(socket读)、解析、执行、内容返回(socket写)等都由一个顺序串行的主线程处理,这就是所谓的"单线程"。这也是Redis对外提供键值存储服务的主要流程。
但Redis的其它功能,比如持久化、异步删除、集群数据同步等等,其实是由额外的线程执行的。Redis工作线程是单线程的,但是,整个Redis来说,是多线程的。
2.3 文件事件处理器
Redis 基于 Reactor 模式开发了自己的网络事件处理器: 这个处理器被称为文件事件处理器(file event handler):
- 文件事件处理器使用 I/O 多路复用(multiplexing) 程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
- 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
文件事件处理器的组成
图 IMAGE_CONSTRUCT_OF_FILE_EVENT_HANDLER 展示了文件事件处理器的四个组成部分, 它们分别是套接字、 I/O 多路复用程序、 文件事件分派器(dispatcher)、 以及事件处理器。
2.4 一些问题
1. Redis3.x单线程时代但性能依然很快的主要原因
- 基于内存操作: Redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以他的性能比较高;
- 数据结构简单: Redis的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是O(1),因此性能比较高;
- 多路复用和非阻塞I/O: Redis使用I/O多路复用功能来监听多个socket连接客户端,这样就可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,同时也避免了I/O阻塞操作
- 避免上下文切换: 因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的消耗。
2. Redis之父对Redis4.0之前采用单线程的回复
- 使用单线程模型使Redis得开发和维护更简单,因为单线程模型方便开发和测试
- 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是多路复用和非阻塞IO
- 对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非CPU
3. 为什么Redis4.X开始引入多线程的内容和功能
答: 正常情况下使用del指令可以很快的删除数据,而当被删除的key是一个非常大的对象时,例如包含了成千上万个元素的hash集合时,那么del指令就会造成Redis主线程卡顿。
这就是redis3.x单线程时代最经典的故障,大key删除的头疼问题
解决方案
Redis4.0中新增了多线程的模块,当然此版本中的多线程主要是为了解决删除数据效率比较低的问题的。
异步删除的命令
unlink key
flushdb async
flushall async
把删除工作交给了后台的小弟(子线程)异步来删除数据了。
三、Redis6的多线程和IO多路复用入门篇
对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非CPU
**内存: **花钱即可解决
最后Redis的瓶颈可以初步定为: 网络IO
3.1 Unix网络编程中的5中IO模型
Blocking IO-阻塞IO
NoneBlocking IO-非阻塞IO
IO multiplexing - IO多路复用
这时IO模型的一种,即经典的Reactor设计模式
I/O多路复用,简单来说就是通过监测文件的读写事件再通知线程执行相关操作,保证Redis的非阻塞I/O能够顺利执行完成的机制。
多路指的是多个socket连接
复用指的是复用一个线程。多路复用主要有三种技术: select, poll, epoll。
epoll是最新的也是目前最好的多路复用技术。采用多路I/O复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。
I/O的读和写本身是堵塞的,比如当socket中有数据时,Redis会通过调用先将数据从内核态空间拷贝到用户态空间,再交给Redis调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所需要的时间就越多,而这些操作都是基于单线程完成的。
在Redis6.0中新增了多线程的功能来提高I/O的读写性能,他的主要实现思路是将主线程的IO读写任务拆分给一组独立的线程去执行,这样就可以使多个socket的读写可以并行化了,采用多路I/O复用技术可以让单个线程高效的处理多个连接请求(尽量减少网路IO的时间消耗),将最耗时的Socket的读取、请求解析、写入单独外包出去,剩下的命令执行仍然由主线程串行执行并和内存的数据交互
结合上图可知,网络IO操作就变成多线程化了,其它核心部分仍然是线程安全的,是个不错的这种方法。
signal driven IO-信号驱动IO
asynchronous IO - 异步IO
3.2 Redis6中的多线程
Redis将所有数据放在内存中,内存的响应时间大约为100纳秒,对于小数据包,Redis服务器可以处理2W到10W的QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够用了。
Redis6中的多线程默认是关闭的,可以从redis.conf中看到
如果需要使用多线程功能,需要在redis.conf中完成两个设置
# 开启多线程
io-threads-do-reads yes
# 设置线程个数
io-threads 4
关于线程数的设置,官方的建议是如果4核的CPU,建议线程数设置为2或3.如果为8和CPU建议线程数设置为6,线程数一定要小于机器核数,线程数不是越大越好。
在Redis6.0中引入I/O多线程的读写,这样就可以更加高效的处理更多的任务了,Redis只是将I/O读写编程多线程,而命令的执行依旧是由主线程串行执行的,因此在多线程下操作Redis不会有线程安全的问题。
四、 使用TKMaper来生成CRUD
4.1 创建mysql表t_user
create table t_user (
id int(10) unsigned not null auto_increment,
username varchar(50) not null default '' comment '用户名',
password varchar(50) not null default '' comment '密码',
sex tinyint(4) not null default '0' comment '性别 0=女 1=男',
deleted tinyint(4) unsigned not null default '0' comment '删除标志,默认0不删除,1删除',
update_time timestamp not null default current_timestamp on update current_timestamp comment '更新时间',
create_time timestamp not null default current_timestamp comment '创建时间',
primary key(id)
) auto_increment=1 default charset=utf8 comment='用户表';
4.2 构建maven工程
4.3 改pom
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
</parent>
<groupId>com.hmx</groupId>
<artifactId>mybatis_generator</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<hutool.version>5.5.8</hutool.version>
<druid.version>1.1.18</druid.version>
<mapper.version>4.1.5</mapper.version>
<pagehelper.version>5.1.4</pagehelper.version>
<mysql.version>8.0.25</mysql.version>
<swagger2.version>3.0.0</swagger2.version>
<mybatis.spring.version>2.1.3</mybatis.spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Mybatis-->
<!--<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>-->
<!--mybatis-spring-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!--Mybatis Generator-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!--通用Mapper tk单独使用,自己带着版本号-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
<!--persistence-->
<!--<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
4.4 src\main\resources目录下新建配置文件
config.properties(配置数据库连接信息,以及一些其它信息
# User表名
package.name=com.hmx.redis
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://47.98.134.37:3307/redis?serverTimezone=Asia/Shanghai
jdbc.user=root
jdbc.password=root
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<properties resource="config.properties" />
<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<property name="beginningDelimiter" value=""/>
<property name="beginningDelimiter" value=""/>
<!-- 生成的pojo,将implements Serializable-->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
<property name="caseSensitive" value="true"/>
</plugin>
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<!--<property name="suppressAllComments" value="true" />-->
</commentGenerator>
<!-- 数据库链接URL、用户名、密码 -->
<jdbcConnection driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.url}"
userId="${jdbc.user}"
password="${jdbc.password}">
</jdbcConnection>
<!--
默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer
true,把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal
-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--
生成model模型,对应的包路径,以及文件存放路径(targetProject),targetProject可以指定具体的路径,如./src/main/java,
也可以使用“MAVEN”来自动生成,这样生成的代码会在target/generatord-source目录下
-->
<!--<javaModelGenerator targetPackage="com.joey.mybaties.test.pojo" targetProject="MAVEN">-->
<javaModelGenerator targetPackage="${package.name}.entity" targetProject="./src/main/java">
<property name="enableSubPackages" value="true"/>
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--对应的mapper.xml文件 -->
<sqlMapGenerator targetPackage="${package.name}.mapper" targetProject="./src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- 对应的Mapper接口类文件 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="${package.name}.mapper" targetProject="./src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 列出要生成代码的所有表,这里配置的是不生成Example文件 -->
<table tableName="t_user" domainObjectName="User">
<generatedKey column="id" sqlStatement="JDBC"/>
</table>
</context>
</generatorConfiguration>
4.5 逆向工程一键生成dao层
双击插件mybatis-generator:generate,一键生成entity+mapper接口+mapper.xml实现SQL
五、SRM(SpringBoot+redis+mybatis)案例
5.1 创建maven工程
5.2 改Pom
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>redis</artifactId>
<groupId>com.hmx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>redis_20211026</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<hutool.version>5.5.8</hutool.version>
<druid.version>1.1.18</druid.version>
<mapper.version>4.1.5</mapper.version>
<pagehelper.version>5.1.4</pagehelper.version>
<mysql.version>8.0.25</mysql.version>
<swagger2.version>3.0.0</swagger2.version>
<mybatis.spring.version>2.1.3</mybatis.spring.version>
<junit.version>4.13.2</junit.version>
</properties>
<dependencies>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!--SpringBoot与redis整合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--SpringCache Spring框架的缓存支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--SpringCache连接池依赖包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.1</version>
</dependency>
<!--springboot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mybatis和springboot整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!--springboot整合RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--通用基础配置junit/devtools/test/log4j/lombok/hutool-->
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.7</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>${junit.version}</version>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--通用Mapper tk单独使用,自己带着版本号-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
5.3 将上一部分逆向工程生成的dao层都拉到新的工程中
5.4 编写配置文件
application.properties
# 项目启动端口
server.port=5555
# 项目名
spring.application.name=redis0511
# ======================Logging 日志相关的配置======================
# 系统默认,全局root配置的日志形式,可以注释掉
logging.level.root=warn
# 开发人员自己设置的包结构,对那个package进行什么级别的日志监控
logging.level.com.atguigu.redis=info
# 开发人员自定义日志路径和日志名称
logging.file.name=E:/mylog/logs/redis1026.log
#%d{HH:mm:ss.SSS} - 日志输出时间
#%thread - 输出日志的进程名字,这在web应用以及异步任务处理中很有用
#%-5level - 日志级别,并且使用5个字符串靠左对齐
#%Logger - 日志输出者的名字
#%msg - 日志消息
#%n - 平台的换行符
#Logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %Logger- %msg%n
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %Logger- %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %Logger- %msg%n
# =====================druid相关配置=======================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://47.98.134.37:3306/redis?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle=false
# ====================redis相关配置======================
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=yourip
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制) 默认-1,记得加入单位ms,不然idea报红色
spring.redis.lettuce.pool.max-active=9
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1,记得加入单位ms,不然idea报红色
spring.redis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接 默认8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认0
spring.redis.lettuce.pool.min-idle=0
# =========mybatis相关配置===============
mybatis.mapper-locations=classpath:com/hmx/redis/mapper/*.xml
mybatis.type-aliases-package=com.hmx.redis.entity
# ==========swagger================
spring.swagger2.enabled=true
# ================rabbitmq相关配置===============
#spring.rabbitmq.host=127.0.0.1
#spring.rabbitmq.port=5672
#spring.rabbitmq.username=guest
#spring.rabbitmq.password=guest
#spring.rabbitmq.virtual-host=/
# ================redis 布隆过滤器相关配置====================
#redis.bloom.url=192.168.111.147
#redis.bloom.post=6379
#redis.bloom.init-capacity=10000
#redis.bloom.error-rate=0.01
5.5 主启动类
MainApplication.java
@SpringBootApplication
//不是spring和mybatis整合的那个MapperScan
@MapperScan("com.hmx.redis.mapper") //import tk.mybatis.spring.annotation.MapperScan
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class);
}
}