最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

redis中session會話共享的三種方案

 更新時(shí)間:2025年08月05日 11:44:09   作者:morris131  
本文探討了分布式系統(tǒng)中Session共享的三種解決方案,包括粘性會話、Session復(fù)制以及基于Redis的集中存儲,具有一定的參考價(jià)值,感興趣的可以了解一下

在分布式系統(tǒng)架構(gòu)中,用戶請求可能被負(fù)載均衡器分發(fā)到不同的服務(wù)器節(jié)點(diǎn)。如果用戶的第一次請求落在服務(wù)器A并創(chuàng)建了Session,而第二次請求被路由到服務(wù)器B,服務(wù)器B無法識別該用戶的Session狀態(tài),導(dǎo)致用戶需要重新登錄,這顯然是災(zāi)難性的用戶體驗(yàn)。

三種解決方案

粘性會話(Sticky Sessions)

例如在Nginx的負(fù)載均衡策略中,通過IP哈希等策略將同一個(gè)ip的用戶請求固定到同一服務(wù)器中,這樣session自然也沒有失效。

缺點(diǎn):單點(diǎn)故障風(fēng)險(xiǎn)高(服務(wù)器宕機(jī)導(dǎo)致Session丟失);擴(kuò)容時(shí)Rehash引發(fā)路由混亂。

Session復(fù)制

例如在Tomcat集群中實(shí)現(xiàn)Session復(fù)制,需通過修改配置文件使不同節(jié)點(diǎn)間自動同步會話數(shù)據(jù)。集群內(nèi)所有服務(wù)器實(shí)時(shí)同步Session數(shù)據(jù)。

缺點(diǎn):同步開銷隨服務(wù)器數(shù)量指數(shù)級增長,引發(fā)網(wǎng)絡(luò)風(fēng)暴和內(nèi)存浪費(fèi)。

redis統(tǒng)一存儲

SpringBoot整合Spring Session,通過redis存儲方式實(shí)現(xiàn)session共享。

通過集中存儲Session(如Redis),實(shí)現(xiàn):

  • 無狀態(tài)擴(kuò)展:新增服務(wù)器無需同步Session,直接訪問中央存儲。
  • 高可用性:即使單服務(wù)器宕機(jī),會話數(shù)據(jù)仍可從Redis恢復(fù),用戶無感知。
  • 數(shù)據(jù)一致性:所有服務(wù)器讀寫同一份Session數(shù)據(jù),避免狀態(tài)沖突

Spring Session + Redis集成

添加依賴

在pom.xml中引入關(guān)鍵依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

配置Redis連接

在application.properties中加上Redis的配置:

spring:
  data:
    redis:
      host: localhost
      port: 6379

redis配置類

需要注入一個(gè)名為springSessionDefaultRedisSerializer的序列化對象,用于在redis中寫入對象時(shí)進(jìn)行序列化,不然session中存入對象會拋出異常。

package com.morris.redis.demo.session;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public GenericJackson2JsonRedisSerializer springSessionDefaultRedisSerializer() {
        // 需要注入一個(gè)名為springSessionDefaultRedisSerializer的序列化對象
        // 不然session中存入對象會拋出異常
        return new GenericJackson2JsonRedisSerializer();
    }
}

不需要顯示的通過注解@EnableRedisHttpSession來開啟session共享。

使用Session

package com.morris.redis.demo.session;

import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.*;

@RestController
public class AuthController {

    @PostMapping("/login")
    public String login(HttpSession session, @RequestBody User user) {
        // 驗(yàn)證用戶憑證...
        session.setAttribute("currentUser", user);
        return "登錄成功,SessionID:" + session.getId();
    }

    @GetMapping("/profile")
    @ResponseBody
    public User profile(HttpSession session) {
        // 任意服務(wù)節(jié)點(diǎn)都能獲取到相同Session
        return (User) session.getAttribute("currentUser");
    }
}

session共享驗(yàn)證

調(diào)用登錄接口:

$ curl --location --request POST 'http://172.23.208.1:8080/login' --header 'Content-Type: application/json' --data-raw '{"name": "morris"}' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 172.23.208.1:8080...
* TCP_NODELAY set
* Connected to 172.23.208.1 (172.23.208.1) port 8080 (#0)
> POST /login HTTP/1.1
> Host: 172.23.208.1:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Set-Cookie: SESSION=ZTE0Yjc5NjItODFiZS00ZGYwLWE0NDktYTBjNmQ4ZjUxYmYy; Path=/; HttpOnly; SameSite=Lax
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 63
< Date: Tue, 24 Jun 2025 03:23:52 GMT
<
* Connection #0 to host 172.23.208.1 left intact
登錄成功,SessionID:e14b7962-81be-4df0-a449-a0c6d8f51bf2

可以看到返回的響應(yīng)頭中帶有cookie,后續(xù)請求需要帶上這個(gè)cookie去請求接口才能識別出用戶。

查詢用戶信息:

$ curl --location --request GET 'http://172.23.208.1:8080/profile' --cookie 'SESSION=ZTE0Yjc5NjItODFiZS00ZGYwLWE0NDktYTBjNmQ4ZjUxYmYy'
{"name":"morris"}

可以修改端口再啟動一個(gè)服務(wù),換個(gè)服務(wù)查詢用戶信息:

$ curl --location 'http://172.23.208.1:8082/profile' --cookie 'SESSION=ZTE0Yjc5NjItODFiZS00ZGYwLWE0NDktYTBjNmQ4ZjUxYmYy'
{"name":"morris"}

高級配置

自定義Cookie配置(支持跨域)

@Bean
public CookieSerializer cookieSerializer() {
    DefaultCookieSerializer serializer = new DefaultCookieSerializer();
    serializer.setCookieName("JSESSIONID");
    serializer.setDomainNamePattern("example.com");
    serializer.setCookiePath("/");
    return serializer;
}

Spring Session核心原理

SessionAutoConfiguration

這就是為什么不需要使用注解@EnableRedisHttpSession來開啟session共享。

SessionAutoConfiguration類中會引入RedisSessionConfiguration。

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SessionRepository.class)
@Import({ RedisSessionConfiguration.class, JdbcSessionConfiguration.class, HazelcastSessionConfiguration.class,
    MongoSessionConfiguration.class })
static class ServletSessionRepositoryConfiguration {

}

RedisSessionConfiguration類中會引入RedisHttpSessionConfiguration:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.session.redis", name = "repository-type", havingValue = "default", matchIfMissing = true)
@Import(RedisHttpSessionConfiguration.class)
static class DefaultRedisSessionConfiguration {

而注解@EnableRedisHttpSession引入的配置類也是RedisSessionConfiguration:

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ java.lang.annotation.ElementType.TYPE })
@Documented
@Import(SpringHttpSessionConfiguration.class)
public @interface EnableSpringHttpSession {

}

SessionRepositoryFilter

自定義過濾器SessionRepositoryFilter攔截所有請求,透明地替換了Servlet容器原生的HttpSession實(shí)現(xiàn)。

將請求包裝為SessionRepositoryRequestWrapper:

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
  request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

  SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
  SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
      response);

  try {
    filterChain.doFilter(wrappedRequest, wrappedResponse);
  }
  finally {
    wrappedRequest.commitSession();
  }
}

HttpServletRequestWrapper

HttpServletRequestWrapper中重寫getSession()方法實(shí)現(xiàn)session會話替換。

public HttpSessionWrapper getSession(boolean create) {
	HttpSessionWrapper currentSession = getCurrentSession();
	if (currentSession != null) {
		return currentSession;
	}
	S requestedSession = getRequestedSession();
	if (requestedSession != null) {
		if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
			requestedSession.setLastAccessedTime(Instant.now());
			this.requestedSessionIdValid = true;
			currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
			currentSession.markNotNew();
			setCurrentSession(currentSession);
			return currentSession;
		}
	}
	else {
		// This is an invalid session id. No need to ask again if
		// request.getSession is invoked for the duration of this request
		if (SESSION_LOGGER.isDebugEnabled()) {
			SESSION_LOGGER.debug(
					"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
		}
		setAttribute(INVALID_SESSION_ID_ATTR, "true");
	}
	if (!create) {
		return null;
	}
	if (SessionRepositoryFilter.this.httpSessionIdResolver instanceof CookieHttpSessionIdResolver
			&& this.response.isCommitted()) {
		throw new IllegalStateException("Cannot create a session after the response has been committed");
	}
	if (SESSION_LOGGER.isDebugEnabled()) {
		SESSION_LOGGER.debug(
				"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
						+ SESSION_LOGGER_NAME,
				new RuntimeException("For debugging purposes only (not an error)"));
	}
	S session = SessionRepositoryFilter.this.sessionRepository.createSession();
	session.setLastAccessedTime(Instant.now());
	currentSession = new HttpSessionWrapper(session, getServletContext());
	setCurrentSession(currentSession);
	return currentSession;
}

RedisSessionRepository

RedisSessionRepository負(fù)責(zé)創(chuàng)建RedisSession。

public RedisSession createSession() {
	MapSession cached = new MapSession(this.sessionIdGenerator);
	cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
	RedisSession session = new RedisSession(cached, true);
	session.flushIfRequired();
	return session;
}

RedisSession

session保存時(shí)使用的是sessionRedisOperations,其實(shí)就是RedisTemplate,這個(gè)RedisTemplate是spring session自己創(chuàng)建的,而不是使用的項(xiàng)目中的。

private void save() {
			saveChangeSessionId();
			saveDelta();
			if (this.isNew) {
				this.isNew = false;
			}
		}

private void saveDelta() {
  if (this.delta.isEmpty()) {
    return;
  }
  String key = getSessionKey(getId());
  RedisSessionRepository.this.sessionRedisOperations.opsForHash().putAll(key, new HashMap<>(this.delta));
  RedisSessionRepository.this.sessionRedisOperations.expireAt(key,
      Instant.ofEpochMilli(getLastAccessedTime().toEpochMilli())
        .plusSeconds(getMaxInactiveInterval().getSeconds()));
  this.delta.clear();
}

到此這篇關(guān)于redis中session會話共享的三種方案的文章就介紹到這了,更多相關(guān)redis session會話共享內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • Redis主從架構(gòu)和高可用性實(shí)現(xiàn)過程

    Redis主從架構(gòu)和高可用性實(shí)現(xiàn)過程

    本文詳細(xì)介紹了使用Redis主從架構(gòu)和Linux虛擬服務(wù)器(LVS)實(shí)現(xiàn)高可用性的方法,并回顧了最近完成的Redis集群遷移部署過程,主從架構(gòu)通過復(fù)制數(shù)據(jù)來提高性能和數(shù)據(jù)冗余,而LVS用于實(shí)現(xiàn)負(fù)載均衡和故障切換,感興趣的朋友跟隨小編一起看看吧
    2024-09-09
  • Redis中緩存和數(shù)據(jù)庫雙寫數(shù)據(jù)不一致的原因及解決方案

    Redis中緩存和數(shù)據(jù)庫雙寫數(shù)據(jù)不一致的原因及解決方案

    這篇文章主要介紹了Redis中緩存和數(shù)據(jù)庫雙寫數(shù)據(jù)不一致的原因及解決方案,文中通過圖文結(jié)合的方式講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-03-03
  • redis?key鍵過期刪除策略及淘汰機(jī)制探究

    redis?key鍵過期刪除策略及淘汰機(jī)制探究

    這篇文章主要為大家介紹了redis?key鍵過期刪除策略及淘汰機(jī)制探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • 了解Redis常見應(yīng)用場景

    了解Redis常見應(yīng)用場景

    Redis是一個(gè)key-value存儲系統(tǒng),現(xiàn)在在各種系統(tǒng)中的使用越來越多,大部分情況下是因?yàn)槠涓咝阅艿奶匦?,被?dāng)做緩存使用,這里介紹下Redis經(jīng)常遇到的使用場景
    2021-06-06
  • Redis實(shí)現(xiàn)UV統(tǒng)計(jì)的示例代碼

    Redis實(shí)現(xiàn)UV統(tǒng)計(jì)的示例代碼

    本文主要介紹了Redis實(shí)現(xiàn)UV統(tǒng)計(jì)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Redis高效檢索地理位置的原理解析

    Redis高效檢索地理位置的原理解析

    這篇文章主要介紹了Redis是如何高效檢索地理位置,通過geo相關(guān)的命令,可以很容易在redis中存儲和使用經(jīng)緯度坐標(biāo)信息,具體實(shí)現(xiàn)方法跟隨小編一起看看吧
    2021-06-06
  • Redis批量刪除Key的三種方式小結(jié)

    Redis批量刪除Key的三種方式小結(jié)

    本文主要介紹了Redis批量刪除Key的三種方式小結(jié),包括KEYS+DEL、SCAN+DEL、Lua腳本,需分批處理以減少風(fēng)險(xiǎn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-07-07
  • redis數(shù)據(jù)一致性之延時(shí)雙刪策略詳解

    redis數(shù)據(jù)一致性之延時(shí)雙刪策略詳解

    在使用redis時(shí),需要保持redis和數(shù)據(jù)庫數(shù)據(jù)的一致性,最流行的解決方案之一就是延時(shí)雙刪策略,今天我們就來詳細(xì)刨析一下,需要的朋友可以參考下
    2023-09-09
  • Linux服務(wù)器Redis6.x安裝、配置全過程

    Linux服務(wù)器Redis6.x安裝、配置全過程

    Redis是一個(gè)高性能的鍵值對數(shù)據(jù)庫,支持多種數(shù)據(jù)類型和持久化方式,它在緩存、分布式系統(tǒng)、聊天室、任務(wù)隊(duì)列等領(lǐng)域有廣泛應(yīng)用,本文詳細(xì)介紹了Redis的安裝、配置、高級類型和持久化機(jī)制
    2026-03-03
  • 基于redis+lua進(jìn)行限流的方法

    基于redis+lua進(jìn)行限流的方法

    這篇文章主要介紹了基于redis+lua進(jìn)行限流,通過實(shí)例代碼詳細(xì)介紹了lua+redis進(jìn)行限流的做法,開發(fā)環(huán)境使用idea+redis+lua,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07

最新評論

太白县| 巴楚县| 临潭县| 连江县| 邯郸市| 布拖县| 潼关县| 上杭县| 合江县| 徐闻县| 克山县| 阿拉善左旗| 四平市| 汕尾市| 翁源县| 大悟县| 伊春市| 盘锦市| 兴安盟| 乐清市| 松溪县| 永德县| 天长市| 盐边县| 工布江达县| 囊谦县| 天门市| 阿勒泰市| 彭州市| 手机| 绿春县| 吴桥县| 方山县| 霞浦县| 石渠县| 安宁市| 黄陵县| 三亚市| 赤水市| 墨脱县| 德格县|