Java项目中集成Redis提升系统的性能

概述

安装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.

换成正确的密码后,程序正常执行。

启用加密传输

  1. 生成证书
    生成密钥:

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

  1. 服务端配置
    端口:

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

  1. 客户端导入证书

keytool -import -alias demo -file server.crt -keystore E:\openjdk\jdk-17\lib\security\cacerts -storepass changeit

  1. 修改代码
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这种框架是做了高度封装的,隐藏了很多细节,如果对框架低层不熟的话,也会加高使用的难度。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值