Spring Boot分布式系统实践【扩展1】shiro+redis实现session共享、simplesession反序列化失败的问题定位及反思改进
前言
调试之前请先关闭favicon配置
spring: favicon: enabled: false
不然会发现有2个请求(如果用nginx+ 浏览器调试的话)
序列化工具类【fastjson版本1.2.37】
```public class fastjson2jsonredisserializer
public static final charset default_charset = charset.forname("utf-8"); private class
public fastjson2jsonredisserializer(class
@override @override return (t) json.parseobject(str, clazz); } public class simplesession implements validatingsession, serializable { private transient serializable id; /* public class fastjson2jsonredisserializer
/** /** @override /** ```
super();
this.clazz = clazz;
}
public byte[] serialize(t t) throws serializationexception {
if (t == null) {
return new byte[0];
}
return json.tojsonstring(t, serializerfeature.writeclassname).getbytes(default_charset);
}
public t deserialize(byte[] bytes) throws serializationexception {
if (bytes == null || bytes.length <= 0) {
return null;
}
string str = new string(bytes, default_charset);
}
`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源码:
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.
*/
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);
}
}
*/
@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序列化成字节码)
问题我们就好对症下药了***
## 方案一:
修改序列化工具类 (`这个方式其实有问题`)
private class
public fastjson2jsonredisserializer(class
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的方法如下:
*/
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;
}
*/
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里的方法***
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里获取,暂未实现测试)
上一篇: Python实现简单查找最长子串功能示例