分散セッションに一貫性がない場合はどうすればよいですか?次の記事では、redis における分散セッションの不一致の解決策を紹介します。
1. セッションの役割は何ですか?
Redis ビデオ チュートリアル ]
2. 分散セッションにはどのような問題がありますか?
単一サーバーの Web アプリケーションでは、セッション情報はサーバーに保存するだけで済みます。これは、過去数年間でセッション情報にアクセスする最も一般的な方法ですしかし、近年、分散システムの普及により、単一のシステムでは何百万ものユーザーの増大するニーズに対応できなくなり、多くの企業でクラスターに展開されたサーバーが使用されています。同時実行性の高いリクエストがサーバーに到着し、クラスター内のサーバーに分散されたロード バランシングが使用されます。これにより、同じユーザーからの複数のリクエストがクラスター内の異なるサーバーに分散される可能性があり、セッション データが取得できないため、セッション共有が行われます。が一つの質問になります。 3. サービスのクラスタリングは一般的にどのように行われますか?SpringBoot プロジェクトを作成し、ポート番号を変更していくつかのポートを開始し、nginx を統合リバース プロキシとして使用します。
nginx 負荷分散はサーバー側の負荷です負荷分散アルゴリズムに基づいて、1 つのアドレスに均一にアクセスし、どのサーバーにアクセスするかを決定します。
1. セッション レプリケーション (同期)
アイデア: 複数のサーバーが相互にセッションを同期し、各サーバーにすべてのセッションが含まれるようにします。
利点: サーバーによってサポートされる機能と、アプリケーションがサポートする機能コードを変更する必要はありません
欠点:
セッション同期にはデータ送信が必要であり、イントラネット帯域幅を占有し、遅延が発生しますすべて サーバーにはすべてのセッションが含まれますデータ量はメモリによって制限されており、水平方向に拡張することはできませんアイデア: サーバーはすべてのユーザーのセッションを保存しますが、これは多くのメモリを消費します。セッションはブラウザの Cookie に保存できます。各クライアントは 1 人のユーザーのデータのみを保存する必要があります。
利点: サーバーは保存する必要がありません
欠点:
各 http リクエストはセッションを伝送し、これにより外部ネットワーク帯域幅が考慮されます。データは端末内やネットワーク通信に保存されるため、漏洩、改ざん、盗難などのセキュリティリスクがあります。セッションに保存されるデータサイズとドメイン名Cookieの数には制限がありますアイデア: 高可用性を確保するために、サーバーには複数の冗長リバース プロキシがあります。同じユーザーからのリクエストが同じサーバーに送られるようにするための何かはありますか? オプション 1: 4 層プロキシ ハッシュ
オプション 2: 7 層プロキシ ハッシュ
リバース プロキシは、ハッシュに http プロトコルの特定のビジネス属性 (sid、city_id、user_id など) を使用します。これにより、ハッシュ戦略をより柔軟に実装して、同じブラウザ ユーザーからのリクエストが同じサーバーに送られるようにすることができます。
利点:
欠点:
セッションには通常、有効期間があります。この 2 つの欠点は、部分的なセッションの失敗に相当すると考えられます。通常、問題は大きくありません。
4 層ハッシュまたは 7 層ハッシュについては、個人的には前者をお勧めします: 専門的なソフトウェアに専門的なことをさせ、リバース プロキシが転送を担当します。 (ビジネス属性に応じて、複数のコンピュータ ルームおよび複数のサーバーを異なるコンピュータ ルームのサーバーにルーティングする必要がある場合など)。
4 層ロード バランシングと 7 層ロード バランシングの違い
4. バックエンドの統合集中ストレージ
利点:
概要
セッションの一貫性を確保するためのアーキテクチャ設計の一般的な方法:# #6. ケースプラクティス: SpringSession redis は分散セッションの不整合の問題を解決します
##ステップ 1: SpringSession と redis の依存関係パッケージを追加します<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
# 为某个包目录下 设置日志
logging.level.com.ljw=debug
# 设置session的存储方式,采用redis存储
spring.session.store-type=redis
# session有效时长为10分钟
server.servlet.session.timeout=PT10M
## Redis 配置
## Redis数据库索引(默认为0)
spring.redis.database=0
## Redis服务器地址
spring.redis.host=127.0.0.1
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=
@Configuration
public class SessionConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SecurityInterceptor())
//排除拦截的2个路径
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/logout")
//拦截所有URL路径
.addPathPatterns("/**");
}
}
@Configuration
public class SecurityInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
HttpSession session = request.getSession();
//验证当前session是否存在,存在返回true true代表能正常处理业务逻辑
if (session.getAttribute(session.getId()) != null){
log.info("session拦截器,session={},验证通过",session.getId());
return true;
}
//session不存在,返回false,并提示请重新登录。
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write("请登录!!!!!");
log.info("session拦截器,session={},验证失败",session.getId());
return false;
}
}
HandlerInterceptor
@RestController
@RequestMapping(value = "/user")
public class UserController {
Map<String, User> userMap = new HashMap<>();
public UserController() {
//初始化2个用户,用于模拟登录
User u1=new User(1,"user1","user1");
userMap.put("user1",u1);
User u2=new User(2,"user2","user2");
userMap.put("user2",u2);
}
@GetMapping(value = "/login")
public String login(String username, String password, HttpSession session) {
//模拟数据库的查找
User user = this.userMap.get(username);
if (user != null) {
if (!password.equals(user.getPassword())) {
return "用户名或密码错误!!!";
} else {
session.setAttribute(session.getId(), user);
log.info("登录成功{}",user);
}
} else {
return "用户名或密码错误!!!";
}
return "登录成功!!!";
}
/**
* 通过用户名查找用户
*/
@GetMapping(value = "/find/{username}")
public User find(@PathVariable String username) {
User user=this.userMap.get(username);
log.info("通过用户名={},查找出用户{}",username,user);
return user;
}
/**
*拿当前用户的session
*/
@GetMapping(value = "/session")
public String session(HttpSession session) {
log.info("当前用户的session={}",session.getId());
return session.getId();
}
/**
* 退出登录
*/
@GetMapping(value = "/logout")
public String logout(HttpSession session) {
log.info("退出登录session={}",session.getId());
session.removeAttribute(session.getId());
return "成功退出!!";
}
}
@Data
public class User implements Serializable{
private int id;
private String username;
private String password;
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
}
最初にログインします: http://127.0.0.1:8080/user/login?username=user1&password=user1
もう一度確認してくださいhttp://127.0.0.1:8080/user/find/user1
7. SpringSession の Redis 原理を分析するステップ 1: SpringSession の Redis データ構造を分析するSpringSession
127.0.0.1:6379> keys *
1) "spring:session:sessions:9889ccfd-f4c9-41e5-b9ab-a77649a7bb6a"
2) "spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b"
3) "spring:session:expirations:1635413520000"
4) "spring:session:sessions:expires:9889ccfd-f4c9-41e5-b9ab-a77649a7bb6a"
5) "spring:session:expirations:1635412980000"
6) "spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"
共通点: 3 つのキーはすべて SpringSession の Redis データを表す spring:session: で始まります。
127.0.0.1:6379> type spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b hash
127.0.0.1:6379> hgetall spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b // session的创建时间 1) "creationTime" 2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01|\xc5\xdb\xecu" // sesson的属性,存储了user对象 3) "sessionAttr:d3434f61-4d0a-4687-9070-610bd7790f3b" 4) "\xac\xed\x00\x05sr\x00\x1ecom.ljw.redis.controller.User\x16\"_m\x1b\xa0W\x7f\x02\x00\x03I\x00\x02idL\x00\bpasswordt\x00\x12Ljava/lang/String;L\x00\busernameq\x00~\x00\x01xp\x00\x00\x00\x01t\x00\x05user1q\x00~\x00\x03" //最后的访问时间 5) "lastAccessedTime" 6) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01|\xc5\xe1\xc7\xed" //失效时间 100分钟 7) "maxInactiveInterval" 8) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x17p"ステップ 2: SpringSession の Redis 有効期限戦略を分析する
期限切れの削除には、通常 3 つのタイプがあります。データ戦略:
スケジュールされた削除。つまり、キーの有効期限を設定しながらタイマーを作成し、キーの有効期限が到来したらすぐに削除します。定期删除,即每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
redis删除过期数据采用的是懒性删除+定期删除组合策略,也就是数据过期了并不会及时被删除。
但由于redis是单线程,并且redis对删除过期的key优先级很低;如果有大量的过期key,就会出现key已经过期但是未删除。
为了实现 session 过期的及时性,spring session 采用了定时删除+惰性删除的策略。
定时删除
127.0.0.1:6379> type spring:session:expirations:1635413520000 set 127.0.0.1:6379> smembers spring:session:expirations:1635413520000 1) "\xac\xed\x00\x05t\x00,expires:d3434f61-4d0a-4687-9070-610bd7790f3b"
2) "spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b" 3) "spring:session:expirations:1635413520000" 6) "spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"
惰性删除
127.0.0.1:6379> type spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b string 127.0.0.1:6379> get spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b "" 127.0.0.1:6379> ttl spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b (integer) 3143 127.0.0.1:6379>
2) "spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b" 3) "spring:session:expirations:1635413520000" 6) "spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"
更多编程相关知识,请访问:编程视频!!
以上がRedis での分散セッションの不整合についてどうすればよいかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。