这两天把服务器的热更新实现了下。对于无状态服务器要做热更新相对简单,如果数据在别的进程,简单粗暴的重启也是可行的,我个人也更倾向于这么做。在上一家公司,我们就常常重启AppServer,数据都在DataServer。甚至在做副本Server的时候,为了做到可随时重启,在停服的时候我把所有的组队数据和刷怪进度保存到了Memcached,重启的时候会先从Memcached读数据,从ProfileServer把玩家数据拉过来,恢复度队伍数据和刷怪进度,这样玩家会感觉到卡一下,但影响不大。现在我们的数据库VinyStorage虽然在架构上是独立的,但是物理上却是与业务逻辑同进程,这导致我们不能常常重启服务器(因为停服的时候VinyStorage把内存的数据写到磁盘可能需要较长时间)。而且我们在内存中维护了很多份房间数据,我们只希望热更新Services,而不去更新Models。所以我们不能用Tomcat等容器那样定时去检测指定目录的Class是否有修改的做法,当然我们是用scala写服务器,Nginx那样fork进程的做法更不可行.
比较适合我们需求的做法是用正则是去匹配那些类需要热更新,需要热更新的类用自定义的ClassLoader去加载,不需要热更新的类还是用SystemClassLoader去加载。自定义的OverridenClassLoader代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| final class OverridenClassLoader(
urls: Array[URL],
parent: ClassLoader,
overridenClasses: Seq[Pattern] = Seq(Pattern.compile(""".*""")),
excludeClasses: Seq[Pattern] = Seq.empty,
overridenResources: Seq[Pattern] = Seq(Pattern.compile(""".*""")),
excludeResources: Seq[Pattern] = Seq.empty) extends URLClassLoader(urls, parent) {
override final def getResources(name: String) = {
if (overridenResources.exists { _.matcher(name).find } &&
!excludeResources.exists { _.matcher(name).find }) {
findResources(name)
} else {
super.getResources(name)
}
}
override final def getResource(name: String): URL = {
if (overridenResources.exists { _.matcher(name).find } &&
!excludeResources.exists { _.matcher(name).find }) {
findResource(name)
} else {
super.getResource(name)
}
}
override protected final def loadClass(name: String,
resolve: Boolean): Class[_] = {
if (overridenClasses.exists { _.matcher(name).find } &&
!excludeClasses.exists { _.matcher(name).find }) {
getClassLoadingLock(name).synchronized {
{
findLoadedClass(name) match {
case null =>
findClass(name)
case c => c
}
} match {
case null =>
null
case c =>
if (resolve) {
resolveClass(c)
}
c
}
}
} else {
super.loadClass(name, resolve)
}
}
}
|
以下是创建OverridenClassLoader的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| val urls = getClass.getClassLoader match {
case systemClassLoader if systemClassLoader == ClassLoader.getSystemClassLoader =>
val classpath = System.getProperty("java.class.path")
classpath.split(File.pathSeparatorChar) map {
new File(_).toURI.toURL
}
case urlClassLoader: URLClassLoader =>
urlClassLoader.getURLs
case _ =>
sys.error("Cannot find current classpath.")
}
new OverridenClassLoader(
urls,
getClass.getClassLoader,
Seq(
Pattern.compile("""^com\.dongxiguo\.rftw\.roomServer\.Administer$"""),
Pattern.compile("""^com\.dongxiguo\.rftw\.roomServer\.services\."""),
Pattern.compile("""^com\.dongxiguo\.rftw\.utils\."""))
overridenClasses = ,
excludeClasses = Seq.empty,
overridenResources = Seq.empty,
excludeResources = Seq(Pattern.compile(""".*""")))
|
需要注意的是,需要用OverridenClassLoader来加载入口Main,要不然热更新之后旧的service还被main引用回收不掉,会造成内存泄漏,同样需要被卸载的class需要用OverrideClassLoader来加载,因为只有classLoader回收,加载的class才会被卸载。OverridenClassLoader加载类可以引用SystemClassLoader加载的类,但SystemClassLoader加载的类绝不可以引用OverridenClassLoader加载的类。