Session是客户端与服务器通讯会话技术,比如浏览器登录、记录整个浏览会话信息
客户端向服务器发送请求后,Session创建在服务器端,返回SessionId给客户端浏览器保存在本地,当下次再发送请求时,在请求头中传递sessionid获取对应的从服务器上获取对用的session。
(1)Session 保证在那里?——存放在服务器上
(2)关闭浏览器Session会失效吗?——不会消失
@SpringBootApplication
@RestController
public class TestSessionController {
// 创建session 会话
@RequestMapping("/createSession")
public String createSession(HttpServletRequest request, String nameValue) {
HttpSession session = request.getSession();
System.out.println("存入Session sessionid:信息" + session.getId() + ",nameValue:" + nameValue);
session.setAttribute("name", nameValue);
return "success";
}
// 获取session 会话
@RequestMapping("/getSession")
public Object getSession(HttpServletRequest request) {
HttpSession session = request.getSession();
System.out.println("获取Session sessionid:信息" + session.getId());
Object value = session.getAttribute("name");
return value;
}
public static void main(String[] args) {
SpringApplication.run(TestSessionController.class, args);
}
}
会导致Session不一致问题。服务器产生集群后,因为session是存放在服务器上,客户端会使用同一个sessionid在多个不同的服务器上获取不到对应的Session。
(1)nginx或者haproxy实现IP绑定:
用Nginx 做的负载均衡可以添加ip_hash这个配置,
用haproxy做的负载均衡可以用 balance source这个配置。
从而使同一个ip的请求发到同一台服务器。
(2)利用数据库同步session
(3)使用Session集群存放Redis
使用spring-session框架,底层实现原理是重写httpsession。
(4)基于令牌(Token)方式实现
因为Session本身就是分布式共享连接,所以靠谱
(5)Spring-Session框架解决
(1)maven配置
略
(2).yml配置文件
启动redis /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
server:
port: 8080
redis:
hostname: 192.168.212.151
port: 6379
password: 123456
(3)创建SessionConfig
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
//这个类用配置redis服务器的连接
//maxInactiveIntervalInSeconds为SpringSession的过期时间(单位:秒)
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
// 冒号后的值为没有配置文件时,制动装载的默认值
@Value("${redis.hostname:localhost}")
String HostName;
@Value("${redis.port:6379}")
int Port;
@Bean
public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory connection = new JedisConnectionFactory();
connection.setPort(Port);
connection.setHostName(HostName);
return connection;
}
}
(4)初始化Session
//初始化Session配置
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer{
public SessionInitializer() {
super(SessionConfig.class);
}
}
2.3、基于令牌(Token)方式实现,上代码(用的最多)
(1)TokenService
@Service
public class TokenService {
@Autowired
private RedisService redisService;
// 新增 返回token
public String put(Object object) {
String token = getToken();
redisService.setString(token, object);
return token;
}
// 获取信息
public String get(String token) {
String reuslt = redisService.getString(token);
return reuslt;
}
public String getToken() {
return UUID.randomUUID().toString();
}
}
(2)TokenController
@RestController
public class TokenController {
@Autowired
private TokenService tokenService;
@Value("${server.port}")
private String serverPort;
@RequestMapping("/put")
public String put(String nameValue) {
String token = tokenService.put(nameValue);
return token + "-" + serverPort;
}
@RequestMapping("/get")
public String get(String token) {
String value = tokenService.get(token);
return value + "-" + serverPort;
}
}
(1)依赖
spring session 与redis应用基本环境配置,需要开启redis后才可以使用,不然启动Spring boot会报错
org.springframework.session
spring-session-data-redis
org.apache.commons
commons-pool2
redis.clients
jedis
(2)配置
redis:
hostname: 188.131.155.46
port: 6379
password: 123456
(3)代码实现
这个类用配置redis服务器的连接
//maxInactiveIntervalInSeconds为SpringSession的过期时间(单位:秒)
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
// 冒号后的值为没有配置文件时,制动装载的默认值
@Value("${redis.hostname:localhost}")
String hostName;
@Value("${redis.port:6379}")
int port;
@Value("${redis.password:123456}")
String passWord;
@Bean
public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory connection = new JedisConnectionFactory();
connection.setPort(port);
connection.setHostName(hostName);
connection.setPassword(passWord);
return connection;
}
}
...
现在很多时候我们的服务需要7*24小时工作,假如一台机器挂了,我们希望能有其它机器顶替它继续工作。此类问题现在多采用master-salve模式,也就是常说的主从模式,正常情况下主机提供服务,备机负责监听主机状态,当主机异常时,可以自动切换到备机继续提供服务,这个切换过程中选出下一个主机的过程就是master选举。
多个服务器在启动的时候,会在ZK上创建相同的临时节点,谁如果能够创建成功,谁就为主(因为节点唯一性),如果主服务器宕机之后,会话连接也会失效,其他服务器又开始选举。(谁能创建节点成功谁就为主)
(1)Maven依赖信息
org.springframework.boot
spring-boot-starter-parent
2.0.0.RELEASE
com.101tec
zkclient
0.10
slf4j-api
org.slf4j
log4j
log4j
slf4j-log4j12
org.slf4j
org.springframework.boot
spring-boot-starter-web
(2)IndexController
@RestController
public class IndexController {
// 获取服务信息
@RequestMapping("/getServerInfo")
public String getServerInfo() {
return ElectionMaster.isSurvival ? "当前服务器为主节点" : "当前服务器为从节点";
}
//main启动类 略
}
(3)MyApplicationRunner
//启动完成后实现的方法
@Component
public class MyApplicationRunner implements ApplicationRunner {
// 创建zk连接
ZkClient zkClient = new ZkClient("127.0.0.1:2181");
//1.项目启动的时候会在ZK上创建一个相同的临时节点
private String path = "/election";
@Value("${server.port}")
private String serverPort;
//启动后执行的方法
public void run(ApplicationArguments args) throws Exception {
System.out.println("项目启动完成...");
//2.谁能够创建成功谁就为主服务器
createEphemeral();
// 创建事件监听
//3.使用服务监听节点是否被删除,如果接收到节点被删除,就重新开始选举
zkClient.subscribeDataChanges(path, new IZkDataListener() {
// 节点被删除,返回通知
public void handleDataDeleted(String dataPath) throws Exception {
// 主节点已经挂了,重新选举
System.out.println("主节点已经挂了,重新开始选举");
createEphemeral();
}
public void handleDataChange(String dataPath, Object data) throws Exception {
}
});
}
private void createEphemeral() {
try {
zkClient.createEphemeral(path, serverPort);
//选举成功
ElectionMaster.isSurvival = true;
System.out.println("serverPort:" + serverPort + ",选举成功....");
} catch (Exception e) {
//选举失败
ElectionMaster.isSurvival = false;
}
}
}
(4)ElectionMaster
@Component
public class ElectionMaster {
// 服务器info信息 是否存活
public static boolean isSurvival;
}