X Tutup
package RedisDataBase; import RedisFuture.RedisFuture; import RedisServer.RedisServer; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * todo 关于GC的调优 * 首先cache类型的应用就决定了会有大量的数据存在于老年代,所以把老年代的数据调大是必须的 * 但是也存在一些问题: * 对于某些类型的应用(秒杀,分布式锁等),会导致短时间类有大量对象产生或者消失 * 这里又分为两种情况: * 1 这些对象会持续一段时间(比如20-30分钟,从而进入老年代,然后导致老年代空间不足) * 2 这些对象很快就消失: * 那么将导致Minor GC发生的非常频繁 * * 针对情况1: * 如果是CMS那么就需要增加参数,在进行remark之前进行一次young GC * 如果是G1处理器,那么就不需要进行过多处理 * 针对情况2: * 对象立刻消失的类型 * 则符合MinorGC的情况,G1 会直接整个young Generation * 但是由于Minor GC设置的比较小,所以扫描+复制其实也比较快 * * 另外由于存在对象池,所以针对情况2: 大量的数据进来和删除,这样可以直接复用 * 针对情况1: 最开始的几十分钟可能存在问题,但是后面由于进入和删除平衡,也会被对象池解决 * * 关于对象池产生的时候,一般来说肯定进入老年代了 * 这种情况不能轻易的将它释放,即便满足了释放条件而是应该等待至少 T(30min) 以上 * 由于老年代存活对象比较多,所以拷贝起来会导致比较慢,而且释放的效果也不好 * 所以应该在晚上定时进行一次GC * * **/ /** * todo 实现手动内存管理,构造对象池 * Cache应用,存在时间太长,GC没有价值 * 转发应用,短时间生成/销毁的数量太多,导致GC压力太大 * 对于Redis,如果短时间有大量的删除对象的行为,测试结果是目前每0.5s会有40-80ms以上,也就每半s就有一部分命令延迟比较高 * 一个解决的思路就是使用对象池,这样就可以避免短时间生成大量的进行回收内存,造成GC压力过大 * 比如分布式锁就会导致GC比较大,另外一部分压力是来自于每次生成的临时对象 * * 但是使用对象池又会导致内存没有办法立刻就被释放,所以一个策略是将删除的元素放入DelayRemovedQueue里面 * 然后采取定时任务,按周期进行平缓的释放,目的是不让GC过大 * 但是如果遇到的是那种一瞬间失效,然后很久又不新增的情况,也是无能为力的 * * 由于大部分情况都是String 特别多,所以我们这里需要实现RedisString的对象池 * 关于RedisString * int len * byte[] str * * 为了分配对象速度足够快,一个思路是用长度 s sizeForTable来进行定位 * 然后往小对方向进行搜索找到一个可以满足大小 * 构造一个 Array[8,16,24,32,48,64,90,128,192,256,384,512,768,1024] * 每一个Array的对象都是 一个对象池,这个对象池只分配这么大的RedisString * 对象池的构造: * 1 初始化, 由于小对象最多, 所以8 - 64 都分配 2048个小对象,128 - 1024分配256个对象,1536-4096分配64个对象 * 2 然后根据定位的 sizeForTable来获取对应的对象池,如果该对象池没有数据了,那么就往前移动,直到找到满足条件的,最多找1次(比如17就最多找到32,24) * 3 如果没有找到,就临时生成一个对象 * 4 对象池怎么obtain()元素? 很简单,我们将对象池设定为只能单线程操作的,这样就可以用一个固定大小ArrayQueue来构造出来 * 5 如何针对对象池返回元素.这个也是线程安全的,所以只能有一个线程单独往里面放元素,也就是说,异步线程返回的元素需要想办法同步放回 * 什么时候使用对象池: * 需要进行模拟参数: * 规则是: 假设每一个对应大小的slot每1s申请次数等于M,假设每1s释放次数等于N * 令衰减积累等于 S1 = S1 * 0.8 + M, S2 = S2 * 0.8 + N * 这样如果连续TS发现 S1 和 S2很接近,且都大于一个值(太小了对GC的压力并不大,开启不划算)那么就可以开启对象池模式 * 然后将申请一个大小为 (S1 + S2)/2 的对象池 * * * 什么时候销毁对象池: * 如果连续ks都发现规则不满足,那么就应该将这个对象池销毁 * * * */ public class RedisDb { public static RedisDict RedisMap = new RedisDict<>(); public static RedisDict ExpiresDict = new RedisDict<>();// 用来存储所有过期key的过期时间,方便快速查找/判断/修改 static final RedisObject[] RedisIntegerPool = new RedisObject[10000]; static final RedisTimerWheel ExpiresTimer = new RedisTimerWheel(); static { // 初始化数字常量池 for(int i = 0; i < 10000; i++){ RedisIntegerPool[i] = new RedisObject(RedisObject.REDIS_INTEGER,i); } } public static boolean set(RedisString key,RedisObject val,boolean nx) { if(nx){ if(RedisMap.get(key) == null){ RedisMap.put(key,val); return true; } }else{ RedisMap.put(key,val); return true; } return false; } // 从对应的key获取 object // todo key => RedisString public static RedisObject get(RedisString key){ return getIfValid(key); } // 删除 // todo 还需要写日志 // todo 需要考虑对象释放的问题,在remove里面释放value,最后释放key public static void del(RedisString key){ if(key != null) { RedisMap.remove(key); ExpiresDict.remove(key); key.release(); } } // 设置超时 public static void expire(RedisString key,int expireDelay){ if(getIfValid(key) != null) { ExpiresTimer.enqueue(key, expireDelay); ExpiresDict.put(key, RedisTimerWheel.getSystemSeconds() + expireDelay);// 重新设置过期key } } // 只有过期的key才会被移除 public static void removeIfExpired(RedisString key){ if(isExpired(key)){ del(key); } } public static boolean isExpired(RedisString key){ Long time = ExpiresDict.get(key); return (time != null) && time < RedisTimerWheel.getSystemSeconds(); } // 清理所有过期的元素 public static void processExpires(){ ExpiresTimer.processExpires(); } // 模仿redis hset public static void hset(RedisString key, RedisString field, RedisObject val){ RedisObject h = getIfValid(key); if(h == null){ RedisDict hash = new RedisDict<>(); hash.put(field,val); RedisMap.put(key,RedisObject.redisHashObject(hash)); }else{ RedisDict hash = (RedisDict)h.getData(); hash.put(field,val); } } // 模仿redis hget public static RedisObject hget(RedisString key, RedisString field){ RedisObject h = getIfValid(key); if(h != null){ return ((RedisDict) h.getData()).get(field); } return null; } // 构造hyperLogLog public static void pfadd(RedisString key, List valList){ RedisObject robj = getIfValid(key); HyperLogLog hloglog; if(robj == null){ hloglog = new HyperLogLog(); RedisMap.put(key,new RedisObject(RedisObject.REDIS_HYPERLOGLOG,hloglog)); }else{ hloglog = (HyperLogLog) robj.getData(); } for(String val : valList){ hloglog.pfadd(val); } } // pfcount命令,获取基数估计 public static long pfcount(RedisString key){ RedisObject robj = getIfValid(key); if(robj == null){ return 0; } HyperLogLog hloglog = (HyperLogLog)robj.getData(); return hloglog.pfcount(); } /***private module***/ // 检查是否过期,过期则删除 /* * 线程不安全导致的问题: * 0 已经在isExpired里面 * 1 isExpired返回false * 2 接下来直接返回数据 * 所以根源就是expired字典出现了线程不安全的问题 * * * */ private static RedisObject getIfValid(RedisString key){ if(isExpired(key)){ del(key); return null; } return RedisMap.get(key); } }
X Tutup