ssm项目集成nginx反向代理、负载均衡和redis数据缓存
1.nginx使用以及出现相关问题
1.1.nginx下载后,找到配置文件,并修改:
#gzip on;
server {
listen 8800;//反向代理端口,地址栏访问时使用
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://localhost:8888;//反向代理转发的目的端口(ssm项目端口)
#root html;
#index index.html index.htm;
}
疑问:为什么proxy_pass http://localhost:8888;//反向代理转发的目的端口(ssm项目端口)
不写成proxy_pass http://localhost:8888/项目名称;
(因为真实访问的是: http://localhost:8888/项目名称)?
配置如下:
#gzip on;
#include vhost/*.conf;
server {
listen 8800;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://localhost:8888/xlh_ssm_web;
#root html;
#index index.html index.htm;
}
原因:写成proxy_pass http://localhost:8888/项目名称;
会发生重定向次数过多访问不了网页问题,chrom给出的解决方法是清除cookic缓存,但不管用,会发生访问超时问题。
那么为什么会发生重定向次数过多访问不了网页问题?
这也就是nginx重定向的缺点:
由上面例子发现,如果写成proxy_pass http://localhost:8888/项目名称;
,也就表明了localhost:8800
与localhost:8888/项目名称
等效,此时再地址栏上用localhost:8800/项目名称
就等于 http://localhost:8888/项目名称/项目名称
了,这就是重定向次数过多访问不了网页。但也许你会这么想如果只用http://localhost:8888
访问呢?答案也是一样的。
因此上面配置就是解决方法,访问方式就是localhost:反向代理端口/项目名称
。
因为出现重定向次数过多访问不了网页是因为 http://localhost:8800/
的时候,地址栏访问了 http://localhost:8800/
, http://localhost:8800/
到nginx服务器变成了 http://localhost:8800//
(为什么会变成这样?原因是proxy_pass中ip+port后的项目名称不加/),根据proxy_pass端口后有字符串的规律,将location在地址栏之后的字符串截取加到proxy_pass后,实际就访问了http://localhost:8888/项目名称/
,由于security配置了过滤规则,这又得重定向到${pageContext.request.contextPath}/login.do
,地址栏就去掉原先的location加上了${pageContext.request.contextPath}/login.do
,变成了 http://localhost:8800/xlh_ssm_web/login.do
,请求到了nginx后加上一个/变成http://localhost:8800//xlh_ssm_web/login.do
,然后又根据proxy_pass端口后有字符串的规律,截取/xlh_ssm_web/login.do
加到proxy_pass后访问,地址栏就剩http://localhost:8800/
,如此循环反复,这就是为什么一直是显示http://localhost:8800//
请求每次都加一个/,而实际请求路径不同的原因,真实路劲迭代
http://localhost:8888/项目名称//xlh_ssm_web/login.do/xlh_ssm_web/login.do/xlh_ssm_web/login.do.......
1.2.一直是login.jsp页面原因:
配置:
#gzip on;
#include vhost/*.conf;
server {
listen 8800;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://localhost:8888/xlh_ssm_web/;
#root html;
#index index.html index.htm;
}
其实我觉得这是nginx反向代理的特性造成的:
由于等效于http://localhost:8888/项目名称/
,这时由于<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
就会重定向到login.jsp,根据nginx302重定向特性,原链接http://localhost:8800/
去掉loaction加上/login.jsp
,就变成了http://localhost:8800/login.jsp
(http://localhost:8888/项目名称/login.jsp
),与原来不用nginx代理等效,
<!-- 定义跳转的具体的页面 ,需要添加一个成功后的跳转页面(每个人不同)-->
<security:form-login
login-page="/login.jsp"
login-processing-url="/login.do"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/pages/main.jsp"
/>
但填入密码登陆时,login.jsp代码中<form action="${pageContext.request.contextPath}/login.do" method="post">
,根据nginx302重定向特性,原链接不变http://localhost:8800${pageContext.request.contextPath}/login.do
,即http://localhost:8800/项目名称/login.do
,与http://localhost:8888/项目名称/login.do
不等效(我们期望的是http://localhost:8800/项目名称/login.do
,这才能访问),由于规则<!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="访问系统的人,必须有ROLE_USER的角色" -->/ <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
过滤/**,即包括http://localhost:8800/项目名称/login.do
中/项目名称/login.do
,其实你可以看成请求一个需要ROLE_USER的角色才能登陆的页面,可此刻我们正要登陆拿权限,还没拿到就要跳到其他页面,显然又被security拦截跳到http://localhost:8800/login.jsp
,如此循环反复,就出现了重定向次数过多访问不了页面。
1.3.总结:
- 一次请求:客户端请求-》nginx处理-》服务器
- nginx配置文件proxy_pass中ip+port后的项目名称不加/,每次请求到达nginx都要加在port后加/
- 第一次访问用到location,重定向后用跳转的页面取代location,例如
/项目名称/login.jsp
取代地址栏中port后的/,之后重定向就是页面取代页面了,也就是http://localhost:8800
不变,不再有location。 - 地址栏显示的是每次请求到nginx没有处理路径的路径(不包括nginx加上的/)
2.ssm项目使用nginx负载均衡的配置(这只是实验,为了验证而已)
:
实验:两个页面忽有忽无,多访问几次,结果如预期所想。
地址栏访问:localhost:8800
地址栏访问:localhost:8800/项目名称
学习视频
几个注意事项:
- 代码
taskkill /f /t /im nginx.exe
能够杀死nginx所有子进程,每次修改配置文件都要taskkill /f /t /im nginx.exe
一次,防止因为缓存上次修改结果使得修改结果不生效。 D:\Nginx\nginx-1.18.0>nginx -t nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in D:\Nginx\nginx-1.18.0/conf/nginx.conf:44 nginx: configuration file D:\Nginx\nginx-1.18.0/conf/nginx.conf test failed
可能因为配置文件中由中文字符如分号、斜杆等情况。
3.ssm项目集成redis
redis.properties:
redis.host=127.0.0.1
redis.port=6379
redis.maxIdle=300
redis.maxWaitMillis=1000
redis.maxTotal=600
redis.testOnBorrow=true
redis.testOnReturn=true
redis-context.xml(3.1.用此配置文件,3.2.和3.3.用3.2.的)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描redis配置文件-->
<context:property-placeholder ignore-unresolvable="true" location="classpath:redis.properties"/>
<!--设置连接池-->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
<property name="testOnReturn" value="${redis.testOnReturn}" />
</bean>
<!--设置链接属性-->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:hostName="${redis.host}"
p:port="${redis.port}"
p:pool-config-ref="poolConfig"
p:timeout="100000"/>
<!-- Jedis模板配置 -->
<bean id = "jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="keySerializer" ref="stringRedisSerializer"></property>
<property name="valueSerializer" ref="jdkSerializationRedisSerializer"></property>
</bean>
</beans>
spring-mvc.xml
新增
<!-- 引入redis配置文件 -->
<import resource="classpath:redis-context.xml"/>
3.1.用法1(默认JDK序列化存储本地Redis):
ssm项目pom.xml文件:
<!--redis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- config redis data and client jar-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<!--commons-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
使用:
Controller
@RequestMapping("/product")
public class ProductController {
@Autowired
private RedisTemplate redisTemplate; //注入
@RequestMapping("/findAll.do")
@RolesAllowed("ADMIN")
protected ModelAndView findAll(){
ModelAndView mv = new ModelAndView();
List<Product> ps = productService.findAll();
List<Product> absentValue = null;
// //redis代码
boolean absentBoolean = redisTemplate.hasKey("productFindAll");
System.out.println("通过setIfAbsent(K key, V value)方法判断变量值absentValue是否存在:" + absentBoolean);
if(!absentBoolean){
mv.addObject("productList",ps);
redisTemplate.opsForList().rightPush("productList",ps);
}else {
absentValue = (List<Product>)redisTemplate.opsForList().leftPop("productFindAll");
System.out.println("key的value:" + absentValue);
mv.addObject("productList",absentValue);
}
mv.setViewName("product-list1");
return mv;
}
}
redis命令行乱码,解决之后补上,如有小友明知,敬请告知,万分感谢。
3.2.用法2(GenericJackson2JsonRedisSerializer序列化存储非本地Redis):
ssm项目pom.xml文件:
增加了com.fasterxml.jackson.core依赖,为了能够存储其他对象而不仅仅是String,同时,尽量使用高版本,否则会爆出无法创建xxxx,没有xxxbean,应该至少有一个等等页面出错的错误
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
具体文件:
<!--redis-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- config redis data and client jar-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.6.RELEASE</version>
</dependency>
<!--commons-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
redis-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描redis配置文件-->
<context:property-placeholder ignore-unresolvable="true" location="classpath:redis.properties"/>
<!--设置连接池-->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
<property name="testOnReturn" value="${redis.testOnReturn}" />
</bean>
<!--设置链接属性-->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:hostName="${redis.host}"
p:port="${redis.port}"
p:pool-config-ref="poolConfig"
p:timeout="100000"/>
<!-- Jedis模板配置 -->
<bean id = "GenericJackson2JsonRedisSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"></bean>
<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>
<property name="valueSerializer" ref="GenericJackson2JsonRedisSerializer"></property>
</bean>
</beans>
使用:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/findById")
public ModelAndView findById(@RequestParam(name = "id",required = true) String userId){
ModelAndView mv = new ModelAndView();
BoundValueOperations<String,UserInfo> boundValueOperations =
redisTemplate.boundValueOps(userId);
UserInfo userInfo = boundValueOperations.get();
if(userInfo!=null){
System.out.println("从redis获取key"+userInfo.toString());
mv.addObject("user",userInfo);
}else{
userInfo = userService.findById(userId);
System.out.println("从数据库获取key"+userInfo.toString());
boundValueOperations.set(userInfo);
mv.addObject("user",userInfo);
}
mv.addObject("user",userInfo);
mv.setViewName("user-show");
return mv;
}
}
3.3.用法3(GenericJackson2JsonRedisSerializer序列化存储本地Redis):
相比于用法1,解决了乱码问题;相比于用法2,数据存在本地redis服务器
使用:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/findAll.do")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ModelAndView findAll(){
ModelAndView mv = new ModelAndView();
List<UserInfo> userInfoList = null;
ValueOperations<String,List<UserInfo>> valueOperations = redisTemplate1.opsForValue();
boolean absentBoolean = redisTemplate.hasKey("userFindAll");
if(!absentBoolean){
userInfoList = userService.findAll();
System.out.println("通过setIfAbsent(K key, V value)方法判断变量值absentValue是否存在:" + absentBoolean);
mv.addObject("userList",userInfoList);
valueOperations.set("userFindAll",userInfoList);
}else {
userInfoList = valueOperations.get("userFindAll");
System.out.println("从redis获取:" + absentBoolean);
mv.addObject("userList",userInfoList);
}
mv.setViewName("user-list1");
return mv;
}
}
使用jdk默认序列化和GenericJackson2JsonRedisSerializer存储非字符串对象差异: