Spring Boot分布式系统实践【扩展1】shiro+redis实现session共享、simplesession反序列化失败的问题定位及反思改进...

前言

调试之前请先关闭Favicon配置

spring:favicon:enabled: false

不然会发现有2个请求(如果用nginx+ 浏览器调试的话)
Image.png

序列化工具类【fastjson版本1.2.37】

```public class FastJson2JsonRedisSerializer implements RedisSerializer {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class clazz;

    public FastJson2JsonRedisSerializer(Class clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return (T) JSON.parseObject(str, clazz);

    }
}

`org.apache.shiro.session.mgt.SimpleSession存储到redis中会发现已经丢失了所有属性`![Image [1].png](https://upload-images.jianshu.io/upload_images/231328-ab9c9ca3c2b43710.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)#### 查看SimpleSession源码:

public class SimpleSession implements ValidatingSession, Serializable {

    private transient Serializable id;
    private transient Date startTimestamp;
    private transient Date stopTimestamp;
    private transient Date lastAccessTime;
    private transient long timeout;
    private transient boolean expired;
    private transient String host;
    private transient Map<Object, Object> attributes;
/* Serializes this object to the specified output stream for JDK Serialization.

  • @param out output stream used for Object serialization.
  • @throws IOException if any of this object's fields cannot be written to the stream.
  • @since 1.0
    */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        short alteredFieldsBitMask = getAlteredFieldsBitMask();
        out.writeShort(alteredFieldsBitMask);
        if (id != null) {
            out.writeObject(id);
        }
        if (startTimestamp != null) {
            out.writeObject(startTimestamp);
        }
        if (stopTimestamp != null) {
            out.writeObject(stopTimestamp);
        }
        if (lastAccessTime != null) {
            out.writeObject(lastAccessTime);
        }
        if (timeout != 0l) {
            out.writeLong(timeout);
        }
        if (expired) {
            out.writeBoolean(expired);
        }
        if (host != null) {
            out.writeUTF(host);
        }
        if (!CollectionUtils.isEmpty(attributes)) {
            out.writeObject(attributes);
        }
    }

/*

  • Reconstitutes this object based on the specified InputStream for JDK Serialization.
  • @param in the input stream to use for reading data to populate this object.
  • @throws IOException            if the input stream cannot be used.
  • @throws ClassNotFoundException if a required class needed for instantiation is not available in the present JVM
  • @since 1.0
    */
    @SuppressWarnings({"unchecked"})
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {

***发现transient修饰,所以Fastjson不会对这些transient属性进行持久化,所以有了方案二,重写可以json序列化的对象
同时发现有writeObject()方法写着“ Serializes this object to the specified output stream for JDK Serialization.”,
所以有了方案一,修改序列化工具( 默认使用JdkSerializationRedisSerializer,这个序列化模式会将value序列化成字节码)
问题我们就好对症下药了***
## 方案一:修改序列化工具类 (`这个方式其实有问题`)

public class FastJson2JsonRedisSerializer implements RedisSerializer {
    private Class clazz;
    public FastJson2JsonRedisSerializer(Class clazz) {
        super();
        this.clazz = clazz;
    }
    @Override
    public byte[] serialize(T t) {
        return ObjectUtils.serialize(t);
    }
    @Override
    public T deserialize(byte[] bytes) {
        return (T) ObjectUtils.unserialize(bytes);
    }
}

### ObjectUtils的方法如下:

/**

  • 序列化对象
  • @param object
  • @return
    */
    public static byte[] serialize(Object object) {
       ObjectOutputStream oos = null;
       ByteArrayOutputStream baos = null;
       try {
          if (object != null){
             baos = new ByteArrayOutputStream();
             oos = new ObjectOutputStream(baos);
             oos.writeObject(object);
             return baos.toByteArray();
          }
       } catch (Exception e) {
          e.printStackTrace();
       }
       return null;
    }

/**

  • 反序列化对象
  • @param bytes
  • @return
    */
    public static Object unserialize(byte[] bytes) {
       ByteArrayInputStream bais = null;
       try {
          if (bytes != null && bytes.length > 0){
             bais = new ByteArrayInputStream(bytes);
             ObjectInputStream ois = new ObjectInputStream(bais);
             return ois.readObject();
          }
       } catch (Exception e) {
          e.printStackTrace();
       }
       return null;
    }

***`此方案会严重依赖对象class,如果反序列化时class对象不存在则会报错
修改为: JdkSerializationRedisSerializer
`***![Image [2].png](https://upload-images.jianshu.io/upload_images/231328-900964ebbd4757e2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/900)### 方案二:***继承SimpleSession并重写
让相关的字段可以被序列化(不被transient修饰)
重写之后一定要重写SessionManager里的方法***

@Override
protected Session newSessionInstance(SessionContext context) {
SimpleSession session = new MyRedisSession(context.getHost());
// session.setId(IdGen.uuid());
session.setTimeout(SessionUtils.SESSION_TIME);
return session;
}

#### 由方案二引发的另一个问题就是:
**`在微服务开发过程中,为了使用方便经常会将频繁访问的信息如用户、权限等放置到SESSION中,便于服务访问,而且,微服务间为了共享SESSION,通常会使用Redis共享存储。但是这样就会有一个问题,在封装Request对象时会将当前SESSION中所有属性对象反序列化,反序列化都成功以后,将SESSION对象生成。如果有一个微服务将本地的自定义Bean对象放置到SESSION中,则其他微服务都将出现反序列化失败,请求异常,服务将不能够使用了,这是一个灾难性问题。`**
##### 以下是为了解决下面问题提出来的一种思路。
反序列化失败在于Attribute中添加了复杂对象,由此推出以下解决方案:1. 将复杂对象的(即非基本类型的)Key进行toString转换(转换之后再MD5缩减字符串,或者用类名代替)
2. 将复杂对象的(即非基本类型的)Value进行JSON化(不使用不转换的懒加载模式)`注意:
日期对象的处理(单独处理)`

  /**
     * 通过类型转换,将String反序列化成对象
     * @param key
     * @param value
     * @return
     */
    public Object getObjectValue(String key,String value){
        if(key == null || value == null){
           return null;
        }
        String clz = key.replace(FLAG_STR,"");
        try {
           Class aClass = Class.forName(clz);
           if(aClass.equals(Date.class)){
               return DateUtils.parseDate(value);
           }
          return   JSONObject.parseObject(value,aClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
//        如果反序列化失败就进行json化处理
        return JSONObject.parseObject(value);
    }

```
经过如此处理可以在所有系统里共享缓存
唯一缺点就是太复杂了,可能引起其他系统的修改导致反序列化失败(这个下次再讨论或者实验,因为有这么复杂的功夫,就可以考虑用JWT)

还有一种方案是将复杂对象放到redis中去,实行懒加载机制(不用的复杂对象,不从redis里获取,暂未实现测试)

转载于:https://www.cnblogs.com/Halburt/p/10552582.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/463130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

svn: E200033: database is locked, executing statement 'RELEASE   s0' 问题解决办法

前几天svn迁移到其他路径之后&#xff0c;今天早上更新代码时&#xff0c;出现了下面的问题&#xff1a; svn: E200033: database is locked, executing statement RELEASE s0 稍后执行了 svn cleanup svn up 等命令之后都不好使 网上查找资料尝试整了一遍之后是可以的&…

Apache + Tomcat 配置多个应用

转自&#xff1a;http://www.360doc.com/content/11/0415/11/1531394_109787714.shtml转载于:https://www.cnblogs.com/ribavnu/archive/2012/11/06/2757390.html

哈希(hash)表查找速度为什么那么快?快在哪里了?

先看数组存储数据是怎么样的。 现在有一个数组&#xff0c;它里面每个单元存储的是数据的地址 这叫指针数组吧&#xff0c;假设它有100个单元 我们称他为p[100] 现在我想把一百个数据&#xff08;地址&#xff09;放到里面 我们想把某个数据放到p的第几个单元完全是由 我…

Getting Started with the Table Component

为什么80%的码农都做不了架构师&#xff1f;>>> Home Wiki Getting Started with the Table Component Getting Started with the Table Component Table of Contents [hide] Creating Your First Report using the Table Component Step 1: Create a datasourc…

.NET Framewrok 4.0新增类库

转载于:https://www.cnblogs.com/tweet/archive/2010/02/08/1665805.html

在构造函数/析构函数中调用virtual函数带来的影响

在构造函数/析构函数中调用virtual函数&#xff0c;那么调用的一定是本类中的virtual函数。 先看一段代码&#xff1a; #include<iostream>class Base { public:Base() {print();}~Base() {print();}virtual void print() {std::cout << "Base::print"…

编程之美-第3章 结构之法

3.1. 字符串移位包含问题 方法1: 分别对字符串进行循环移1位,2位,3位…,来判断给定的字符串是否是其中一个字串. 复杂度是O(n^3) 方法2: 这也是一种利用空间换时间的方法. 代码如下, 为了简便实现,采用了C库中的字符串操作函数: #if 0 /** 3.1*/ bool isRotate(char *s1,char* …

每天学习一点点(2010年二月)

2010/2/8号 星期一 1.决定记录下每天学到的东西和感悟 2.看老赵的博客&#xff0c;学到一句话&#xff1a;Apple告诉我们的铁律是&#xff1a;表面功夫一定要做足。 3.看到一个笑话&#xff1a;你属什么&#xff1f;我属 于你。 2010/2/9号 星期二 1.减少页面中独立的请求数&…

InnoDB一棵B+树可以存放多少行数据?

一个问题&#xff1f; InnoDB一棵B树可以存放多少行数据&#xff1f;这个问题的简单回答是&#xff1a;约2千万。为什么是这么多呢&#xff1f;因为这是可以算出来的&#xff0c;要搞清楚这个问题&#xff0c;我们先从InnoDB索引数据结构、数据组织方式说起。 我们都知道计算…

调整路由的AD值

实验&#xff1a;调整路由的AD值【实验名称】调整路由的AD值 (注意&#xff1a;PT有可能不支持distance 99 192.168.1.2 0.0.0.0这条命令&#xff0c;所以我们做实验的时候最好用小凡模拟器)【实验目的】通过调整路由的管理距离值&#xff0c;实现路由的管理和控制【实验背景】…

C#学习日志三(流程控制语句)

if条件语句&#xff1a;根据某个条件对成都的执行进行两路分支。语法&#xff1a;if(条件){语句块1}else{语句块2}*else部分并不是必须存在的。 switch...case条件选择语句&#xff1a;当分支条件很多时&#xff0c;使用。语法&#xff1a;switch(控制表达式){case 常量表达式1…

MySQL索引的一些问题

MySQL索引的一些问题 注意&#xff1a;本文基于MySQL的InnoDB引擎说明。 一、什么是最左前缀原则 对于该表&#xff0c;如果按照name字段来建立索引的话&#xff0c;采用B树结构&#xff0c;大概的索引如下&#xff1a; 如果要进行模糊查找&#xff0c;查找name 以“张"…

线程间操作无效: 从不是创建控件“Control Name'”的线程访问它问题的解决方案及原理分析...

最近&#xff0c;在做一个使用线程控制下载文件的小程序&#xff08;使用进度条控件显示下载进度&#xff09;时&#xff0c;遇到这样的问题&#xff0c; 错误显示&#xff1a; 未处理的“System.InvalidOperationException”类型的异常出现在 System.Windows.Forms.dll 中。 其…

大家狂欢吧,我的Google帐号悲剧了

大家狂欢吧&#xff0c;我的Google帐号悲剧了 今早开始&#xff0c;鄙人在Google Code上突然被强行杯具&#xff0c;出现如下图。 最无奈的是询问Google Code管理组后得到回复如下。 然后管理员就睡觉去了|||…… 继续等待中&#xff0c;能恢复的话我会发个解决心得以警后人&am…

Hive简单实际操作(二)

两个常用的交互命令 不用启动hive就可以执行的命令 1. bin/hive -e "select * from default.animal;" 可以直接显示指定命令后的内容 2. 创建一个sql文件 vi hivef.sql 在文件内部添加你希望执行的命令例如: select * from default.animal; bin/hive -f /opt/module/…

在数据库中, 不用max()/min()找出一个列中最大/最小值的记录

不用max()/min()找出c1列中最大/最小值的记录 // 找出c1列中&#xff0c;c1是最小值的那条记录&#xff0c;不能用min() select * from t1 where c1 < all(select c1 from t1); // 找出c1列中&#xff0c;c1是最大值的那条记录&#xff0c;不能用max() select * from t1 …

C眼看J - 初窥JAVA

最近一直在学习JAVA&#xff0c;出发点并不是像当初学C那样&#xff0c;而只是想把JAVA作为下学期参加比赛的工具&#xff0c;带着这种“浮躁”的心态&#xff0c;使得我总是在想“这个用看么&#xff1f;”、“那个用看么&#xff1f;”。 这是第一次在掌握了一门语言&#xf…

为Mac OS X添加用Firefox搜索服务

为Mac OS X添加用Firefox搜索服务 在Mac OS X上&#xff0c;Firefox这种移植过来的程序往往不提供服务&#xff0c;比如只有safari才能利用服务搜索&#xff0c;经过一番实验终于自己做了一个服务&#xff1a; 第一步&#xff0c;打开Automator 第二部&#xff0c;新建一个服务…

jmeter持续集成测试中mongodb版本问题

jmeter测试mongodb&#xff0c;采用的是JSR223 Sampler脚本连接数据库&#xff0c;其中连接数据库用到了SCRAM-SHA1认证机制&#xff0c;代码如下&#xff1a; MongoCredential credential MongoCredential.createScramSha1Credential("username", "databaseN…

速度之王 — LZ4压缩算法与其他算法的比较

LZ4 (Extremely Fast Compression algorithm) 项目&#xff1a;http://code.google.com/p/lz4/ 作者&#xff1a;Yann Collet 本文作者&#xff1a;zhangskd csdn blog 简介 LZ4 is a very fast lossless compression algorithm, providing compression speed at 400MB/…