概述
安装Redis
安装
启动Rocky Linux 9.0,在浏览器中打开web console.
如果没有安装Web console,按以下步骤安装启用:
安装命令:
# dnf install cockpit
启用并运行服务
# systemctl enable --now cockpit.socket
开通防火墙:
# firewall-cmd --add-service=cockpit --permanent
# firewall-cmd --reload
rocky的软件仓库里自带了redis,直接yum安装就可以了。点击Web console左边菜单中的终端或Terminal,进入控制台模式。
然后敲入命令:
# dnf install redis -y
安装完成后,启用服务:
# systemctl enable --now redis
测试安装结果:
# redis-cli ping
如果返回PONG,说明安装成功。
配置
允许外部访问
用vi打开配置文件:
# vi /etc/redis/redis.conf
修改ip4绑定地址为任意,即0.0.0.0
按Esc,输入:wq退出。重启Redis:
# systemctl restart redis
添加防火墙:
# firewall-cmd --add-port 6379/tcp --permanent
# firewall-cmd --reload
验证一下安装,找另外一台机器,打开命令行输入:
telnet 192.168.0.128 6379
如果屏幕被清空,说明连接成功,输入quit退出。
写个程序测试一下是否配置成功。
SpringBoot里的spring-boot-starter-data-redis里使用的是lettuce客户端,所以我们这里也用这个。
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.1.RELEASE</version>
</dependency>
写段测试代码:
package redis;
import java.time.LocalDateTime;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class DemoMain {
public static void main(String[] args) {
RedisClient redisClient = RedisClient.create("redis://192.168.0.128:6379/0");
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisCommands<String, String> syncCommands = connection.sync();
syncCommands.set("key", "Hello, Redis!"+LocalDateTime.now());
connection.close();
redisClient.shutdown();
System.out.println("ok!");
}
}
进入服务器查下结果:
redis-cli get key
“Hello, Redis!2022-12-01T11:44:55.654705500”
限制访问
为了不让别的应用随便接入redis,我们可以修改redis.conf加上访问密码,找到这一行改成这样:
保存后重启redis服务。此时运行上边的程序,发现报错,错误提示:
NOAUTH HELLO must be called with the client already authenticated,
otherwise the HELLO AUTH <user> <pass> option can be used to
authenticate the client and select the RESP protocol version at the
same time
大概意思就是没发起验证命令。然后我们修改一下,先加入错误的密码:
package redis;
import java.time.LocalDateTime;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class DemoMain {
public static void main(String[] args) {
RedisClient redisClient = RedisClient.create("redis://111111@192.168.0.128:6379/0");
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisCommands<String, String> syncCommands = connection.sync();
syncCommands.set("key", "Hello, Redis!"+LocalDateTime.now());
connection.close();
redisClient.shutdown();
System.out.println("ok!");
}
}
运行程序后提示无效的用户名密码:
WRONGPASS invalid username-password pair or user is disabled.
换成正确的密码后,程序正常执行。
启用加密传输
- 生成证书
生成密钥:
openssl genrsa -out server.key 4096
生成证书请求文件:
注意,证书需要配合域名使用。
openssl req -new -key server.key -out server.csr
生成CA证书:
openssl x509 -req -in server.csr -out server.crt -signkey server.key -days 3650
- 服务端配置
端口:
port 0
tls-port 6379
关闭客户端证书验证
tls-auth-clients no
指定证书路径:
tls-cert-file /etc/redis/server.crt
tls-key-file /etc/redis/server.key
tls-ca-cert-file /etc/redis/server.crt
- 客户端导入证书
keytool -import -alias demo -file server.crt -keystore E:\openjdk\jdk-17\lib\security\cacerts -storepass changeit
- 修改代码
package redis;
import java.io.File;
import java.net.URL;
import java.time.LocalDateTime;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.SslOptions;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class DemoMain {
public static void main(String[] args) throws Throwable {
RedisURI redisUri = RedisURI.Builder.redis("demo.com", 6379).withDatabase(0)
.withSsl(true).withPassword("123456")
.withVerifyPeer(false)
.build();
RedisClient redisClient = RedisClient.create(redisUri);
SslOptions sslOptions = SslOptions.builder()
.jdkSslProvider()
.protocols("TLSv1.2")
.truststore(new File("E:\\openjdk\\jdk-17\\lib\\security\\cacerts"), "changeit")
.build();
ClientOptions clientOptions = ClientOptions.builder().sslOptions(sslOptions).build();
redisClient.setOptions(clientOptions);
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisCommands<String, String> syncCommands = connection.sync();
syncCommands.set("key", "Hello, Redis!" + LocalDateTime.now());
connection.close();
redisClient.shutdown();
System.out.println("ok!");
}
}
项目实战
创建Springboot项目
先用IDE生成Maven项目,再往pom.xml里加入springboot的相关依赖。pom.xml内容如下:
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>java-demo</groupId>
<artifactId>root</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>redis-practice</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<lombok.version>1.18.24</lombok.version>
<spring-boot.version>2.7.4</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<executable>true</executable>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
配置redis
为了使用密码验证及启用加密传输,我们要自定义 Lettuce的配置类:
package demo;
import java.io.File;
import java.time.Duration;
import java.util.Optional;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.SslOptions;
import io.lettuce.core.resource.ClientResources;
public class LettuceSslClientConfiguration implements LettuceClientConfiguration {
@Override
public boolean isUseSsl() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isVerifyPeer() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isStartTls() {
// TODO Auto-generated method stub
return false;
}
@Override
public Optional<ClientResources> getClientResources() {
// TODO Auto-generated method stub
return Optional.empty();
}
@Override
public Optional<ClientOptions> getClientOptions() {
SslOptions sslOptions = SslOptions.builder()
.jdkSslProvider()
.protocols("TLSv1.2")
.truststore(new File("E:\\openjdk\\jdk-17\\lib\\security\\cacerts"), "changeit")
.build();
ClientOptions clientOptions = ClientOptions.builder().sslOptions(sslOptions).build();
return Optional.of(clientOptions);
}
@Override
public Optional<String> getClientName() {
// TODO Auto-generated method stub
return Optional.empty();
}
@Override
public Optional<ReadFrom> getReadFrom() {
// TODO Auto-generated method stub
return Optional.empty();
}
@Override
public Duration getCommandTimeout() {
// TODO Auto-generated method stub
return Duration.ZERO;
}
@Override
public Duration getShutdownTimeout() {
// TODO Auto-generated method stub
return Duration.ZERO;
}
@Override
public Duration getShutdownQuietPeriod() {
// TODO Auto-generated method stub
return Duration.ZERO;
}
}
然后配置实例Lettuce客户端实例:
package demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration
class AppConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
var conf = new RedisStandaloneConfiguration("demo.com", 6379);
conf.setDatabase(0);
conf.setPassword("123456");
return new LettuceConnectionFactory(conf, new LettuceSslClientConfiguration());
}
@Bean
StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
编写测试代码:
package demo;
import java.io.IOException;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
@RestController
@RequestMapping("/api/stat")
public class StatisticsController {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("{productId}")
public ProductStat salesStat(@PathVariable UUID productId) throws IOException {
ProductStat result = new ProductStat();
var jsonStr = redisTemplate.opsForValue().get("product:" + productId);
ObjectMapper mapper = new ObjectMapper();
//从缓存中获取统计数据,如果没则云数据库中查询。
if (jsonStr != null) {
result = mapper.createParser(jsonStr).readValueAs(ProductStat.class);
} else {
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from sale_order_item where product_id=?",
Integer.class, productId);
result.setSaleCount(rowCount);
rowCount = this.jdbcTemplate.queryForObject("select count(*) from product_comment where product_id=?",
Integer.class, productId);
result.setCommentCount(rowCount);
redisTemplate.opsForValue().set("product:" + productId, mapper.writeValueAsString(result));
}
return result;
}
}
Postman验证
总结
Redis本身概念不是很难,就是将一些数据放在内存中,这样可以避开一些耗时的磁盘IO操作,以提升应用程序的性能。其难点在于安装配置,以及框架集成。如果用的Linux,安装过程中涉及操作系统的一些基础命令,如果命令不熟的话,会造成一定的困扰。如果配置加密传输的话,对于证书操作不理解,也会提升安装的难度。同样,框架集成这块,像Springboot这种框架是做了高度封装的,隐藏了很多细节,如果对框架低层不熟的话,也会加高使用的难度。