apache缓存清理_深挖 Mybatis 源码:缓存模块

458637f9a9842904ea678b4374afcb08.png

作者:AmyliaY

出自:Doocs开源社区

原文:my.oschina.net/doocs/blog/4549852


MyBatis 中的缓存分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是 Cache 接口的实现。在这篇文章里,我们就来分析 Cache 接口以及多个实现类的具体实现。

1、Cache 组件

MyBatis 中缓存模块相关的代码位于 org.apache.ibatis.cache 包 下,其中 Cache 接口 是缓存模块中最核心的接口,它定义了所有缓存的基本行为。

public interface Cache {/*** 获取当前缓存的 Id*/String getId();/*** 存入缓存的 key 和 value,key 一般为 CacheKey对象*/void putObject(Object key, Object value);/*** 根据 key 获取缓存值*/Object getObject(Object key);/*** 删除指定的缓存项*/Object removeObject(Object key);/*** 清空缓存*/void clear();/*** 获取缓存的大小*/int getSize();/*** !!!!!!!!!!!!!!!!!!!!!!!!!!* 获取读写锁,可以看到,这个接口方法提供了默认的实现!!* 这是 Java8 的新特性!!只是平时开发时很少用到!!!* !!!!!!!!!!!!!!!!!!!!!!!!!!*/default ReadWriteLock getReadWriteLock() {return null;}
}

如下图所示,Cache 接口 的实现类有很多,但大部分都是装饰器,只有 PerpetualCache 提供了 Cache 接口 的基本实现。

bb97233d750284bf0673b5bd4eebcb3d.png

PerpetualCache

PerpetualCache(Perpetual:永恒的,持续的)在缓存模块中扮演着被装饰的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap 对象 的方法实现的 Cache 接口 中定义的相应方法。

public class PerpetualCache implements Cache {// Cache对象 的唯一标识private final String id;// 其所有的缓存功能实现,都是基于 JDK 的 HashMap 提供的方法private Map<Object, Object> cache = new HashMap<>();public PerpetualCache(String id) {this.id = id;}@Overridepublic String getId() {return id;}@Overridepublic int getSize() {return cache.size();}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}/*** 其重写了 Object 中的 equals() 和 hashCode()方法,两者都只关心 id字段*/@Overridepublic boolean equals(Object o) {if (getId() == null) {throw new CacheException("Cache instances require an ID.");}if (this == o) {return true;}if (!(o instanceof Cache)) {return false;}Cache otherCache = (Cache) o;return getId().equals(otherCache.getId());}@Overridepublic int hashCode() {if (getId() == null) {throw new CacheException("Cache instances require an ID.");}return getId().hashCode();}
}

下面来看一下 cache.decorators 包 下提供的装饰器,它们都直接实现了 Cache 接口,扮演着装饰器的角色。这些装饰器会在 PerpetualCache 的基础上提供一些额外的功能,通过多个组合后满足一个特定的需求。

BlockingCache

BlockingCache 是阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据。

public class BlockingCache implements Cache {// 阻塞超时时长private long timeout;// 持有的被装饰者private final Cache delegate;// 每个 key 都有其对应的 ReentrantLock锁对象private final ConcurrentHashMap<Object, ReentrantLock> locks;// 初始化 持有的持有的被装饰者 和 锁集合public BlockingCache(Cache delegate) {this.delegate = delegate;this.locks = new ConcurrentHashMap<>();}
}

假设 线程 A 在 BlockingCache 中未查找到 keyA 对应的缓存项时,线程 A 会获取 keyA 对应的锁,这样,线程 A 在后续查找 keyA 时,其它线程会被阻塞。

// 根据 key 获取锁对象,然后上锁private void acquireLock(Object key) {// 获取 key 对应的锁对象Lock lock = getLockForKey(key);// 获取锁,带超时时长if (timeout > 0) {try {boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);if (!acquired) { // 超时,则抛出异常throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());}} catch (InterruptedException e) {// 如果获取锁失败,则阻塞一段时间throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);}} else {// 上锁lock.lock();}}private ReentrantLock getLockForKey(Object key) {// Java8 新特性,Map系列类 中新增的方法// V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)// 表示,若 key 对应的 value 为空,则将第二个参数的返回值存入该 Map集合 并返回return locks.computeIfAbsent(key, k -> new ReentrantLock());}

假设 线程 A 从数据库中查找到 keyA 对应的结果对象后,将结果对象放入到 BlockingCache 中,此时 线程 A 会释放 keyA 对应的锁,唤醒阻塞在该锁上的线程。其它线程即可从 BlockingCache 中获取 keyA 对应的数据,而不是再次访问数据库。

 @Overridepublic void putObject(Object key, Object value) {try {// 存入 key 和其对应的缓存项delegate.putObject(key, value);} finally {// 最后释放锁releaseLock(key);}}private void releaseLock(Object key) {ReentrantLock lock = locks.get(key);// 锁是否被当前线程持有if (lock.isHeldByCurrentThread()) {// 是,则释放锁lock.unlock();}}

FifoCache 和 LruCache

在很多场景中,为了控制缓存的大小,系统需要按照一定的规则清理缓存。FifoCache 是先入先出版本的装饰器,当向缓存添加数据时,如果缓存项的个数已经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。

public class FifoCache implements Cache {// 被装饰对象private final Cache delegate;// 用一个 FIFO 的队列记录 key 的顺序,其具体实现为 LinkedListprivate final Deque<Object> keyList;// 决定了缓存的容量上限private int size;// 国际惯例,通过构造方法初始化自己的属性,缓存容量上限默认为 1024个public FifoCache(Cache delegate) {this.delegate = delegate;this.keyList = new LinkedList<>();this.size = 1024;}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {return delegate.getSize();}public void setSize(int size) {this.size = size;}@Overridepublic void putObject(Object key, Object value) {// 存储缓存项之前,先在 keyList 中注册cycleKeyList(key);// 存储缓存项delegate.putObject(key, value);}private void cycleKeyList(Object key) {// 在 keyList队列 中注册要添加的 keykeyList.addLast(key);// 如果注册这个 key 会超出容积上限,则把最老的一个缓存项清除掉if (keyList.size() > size) {Object oldestKey = keyList.removeFirst();delegate.removeObject(oldestKey);}}@Overridepublic Object getObject(Object key) {return delegate.getObject(key);}@Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}// 除了清理缓存项,还要清理 key 的注册列表@Overridepublic void clear() {delegate.clear();keyList.clear();}
}

LruCache 是按照"近期最少使用算法"(Least Recently Used, LRU)进行缓存清理的装饰器,在需要清理缓存时,它会清除最近最少使用的缓存项。

public class LruCache implements Cache {// 被装饰者private final Cache delegate;// 这里使用的是 LinkedHashMap,它继承了 HashMap,但它的元素是有序的private Map<Object, Object> keyMap;// 最近最少被使用的缓存项的 keyprivate Object eldestKey;// 国际惯例,构造方法中进行属性初始化public LruCache(Cache delegate) {this.delegate = delegate;// 这里初始化了 keyMap,并定义了 eldestKey 的取值规则setSize(1024);}public void setSize(final int size) {// 初始化 keyMap,同时指定该 Map 的初始容积及加载因子,第三个参数true 表示 该LinkedHashMap// 记录的顺序是 accessOrder,即,LinkedHashMap.get()方法 会改变其中元素的顺序keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {private static final long serialVersionUID = 4267176411845948333L;// 当调用 LinkedHashMap.put()方法 时,该方法会被调用@Overrideprotected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {boolean tooBig = size() > size;if (tooBig) {// 当已达到缓存上限,更新 eldestKey字段,后面将其删除eldestKey = eldest.getKey();}return tooBig;}};}// 存储缓存项@Overridepublic void putObject(Object key, Object value) {delegate.putObject(key, value);// 记录缓存项的 key,超出容量则清除最久未使用的缓存项cycleKeyList(key);}private void cycleKeyList(Object key) {keyMap.put(key, key);// eldestKey 不为空,则表示已经达到缓存上限if (eldestKey != null) {// 清除最久未使用的缓存delegate.removeObject(eldestKey);// 制空eldestKey = null;}}@Overridepublic Object getObject(Object key) {// 访问 key元素 会改变该元素在 LinkedHashMap 中的顺序keyMap.get(key); //touchreturn delegate.getObject(key);}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {return delegate.getSize();}@Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}@Overridepublic void clear() {delegate.clear();keyMap.clear();}
}

SoftCache 和 WeakCache

在分析 SoftCache 和 WeakCache 实现之前,我们再温习一下 Java 提供的 4 种引用类型,强引用 StrongReference、软引用 SoftReference、弱引用 WeakReference 和虚引用 PhantomReference。

•强引用 平时用的最多的,如 Object obj = new Object(),新建的 Object 对象 就是被强引用的。如果一个对象被强引用,即使是 JVM 内存空间不足,要抛出 OutOfMemoryError 异常,GC 也绝不会回收该对象。•软引用 仅次于强引用的一种引用,它使用类 SoftReference 来表示。当 JVM 内存不足时,GC 会回收那些只被软引用指向的对象,从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象,例如, 数据库缓存中的对象就可以从数据库中恢复,所以软引用可以用来实现缓存,下面要介绍的 SoftCache 就是通过软引用实现的。
另外,由于在程序使用软引用之前的某个时刻,其所指向的对象可能己经被 GC 回收掉了,所以通过 Reference.get()方法 来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null,来判断被软引用的对象是否还存活。•弱引用 弱引用使用 WeakReference 表示,它不会阻止所引用的对象被 GC 回收。在 JVM 进行垃圾回收时,如果指向一个对象的所有引用都是弱引用,那么该对象会被回收。所以,只被弱引用所指向的对象,其生存周期是 两次 GC 之间 的这段时间,而只被软引用所指向的对象可以经历多次 GC,直到出现内存紧张的情况才被回收。•虚引用 最弱的一种引用类型,由类 PhantomReference 表示。虚引用可以用来实现比较精细的内存使用控制,但很少使用。•引用队列(ReferenceQueue ) 很多场景下,我们的程序需要在一个对象被 GC 时得到通知,引用队列就是用于收集这些信息的队列。在创建 SoftReference 对象 时,可以为其关联一个引用队列,当 SoftReference 所引用的对象被 GC 时, JVM 就会将该 SoftReference 对象 添加到与之关联的引用队列中。当需要检测这些通知信息时,就可以从引用队列中获取这些 SoftReference 对象。不仅是 SoftReference,弱引用和虚引用都可以关联相应的队列。

现在来看一下 SoftCache 的具体实现。

public class SoftCache implements Cache {// 这里使用了 LinkedList 作为容器,在 SoftCache 中,最近使用的一部分缓存项不会被 GC// 这是通过将其 value 添加到 hardLinksToAvoidGarbageCollection集合 实现的(即,有强引用指向其value)private final Deque<Object> hardLinksToAvoidGarbageCollection;// 引用队列,用于记录已经被 GC 的缓存项所对应的 SoftEntry对象private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;// 持有的被装饰者private final Cache delegate;// 强连接的个数,默认为 256private int numberOfHardLinks;// 构造方法进行属性的初始化public SoftCache(Cache delegate) {this.delegate = delegate;this.numberOfHardLinks = 256;this.hardLinksToAvoidGarbageCollection = new LinkedList<>();this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();}private static class SoftEntry extends SoftReference<Object> {private final Object key;SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {// 指向 value 的引用是软引用,并且关联了 引用队列super(value, garbageCollectionQueue);// 强引用this.key = key;}}@Overridepublic void putObject(Object key, Object value) {// 清除已经被 GC 的缓存项removeGarbageCollectedItems();// 添加缓存delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));}private void removeGarbageCollectedItems() {SoftEntry sv;// 遍历 queueOfGarbageCollectedEntries集合,清除已经被 GC 的缓存项 valuewhile ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {delegate.removeObject(sv.key);}}@Overridepublic Object getObject(Object key) {Object result = null;@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache// 用一个软引用指向 key 对应的缓存项SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);// 检测缓存中是否有对应的缓存项if (softReference != null) {// 获取 softReference 引用的 valueresult = softReference.get();// 如果 softReference 引用的对象已经被 GC,则从缓存中清除对应的缓存项if (result == null) {delegate.removeObject(key);} else {synchronized (hardLinksToAvoidGarbageCollection) {// 将缓存项的 value 添加到 hardLinksToAvoidGarbageCollection集合 中保存hardLinksToAvoidGarbageCollection.addFirst(result);// 如果 hardLinksToAvoidGarbageCollection 的容积已经超过 numberOfHardLinks// 则将最老的缓存项从 hardLinksToAvoidGarbageCollection 中清除,FIFOif (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {hardLinksToAvoidGarbageCollection.removeLast();}}}}return result;}@Overridepublic Object removeObject(Object key) {// 清除指定的缓存项之前,也会先清理被 GC 的缓存项removeGarbageCollectedItems();return delegate.removeObject(key);}@Overridepublic void clear() {synchronized (hardLinksToAvoidGarbageCollection) {// 清理强引用集合hardLinksToAvoidGarbageCollection.clear();}// 清理被 GC 的缓存项removeGarbageCollectedItems();// 清理最底层的缓存项delegate.clear();}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {removeGarbageCollectedItems();return delegate.getSize();}public void setSize(int size) {this.numberOfHardLinks = size;}
}

WeakCache 的实现与 SoftCache 基本类似,唯一的区别在于其中使用 WeakEntry(继承了 WeakReference)封装真正的 value 对象,其他实现完全一样。

另外,还有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理缓存的装饰器,它的 clearInterval 字段 记录了两次缓存清理之间的时间间隔,默认是一小时,lastClear 字段 记录了最近一次清理的时间戳。ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。

LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit 字段 和 request 字段 记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法 中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。

SynchronizedCache 通过在每个方法上添加 synchronized 关键字,为 Cache 添加了同步功能,有点类似于 JDK 中 Collections 的 SynchronizedCollection 内部类。

SerializedCache 提供了将 value 对象 序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java 对象 进行序列化,井将序列化后的 byte[]数组 作为 value 存入缓存 。SerializedCache 在获取缓存项时,会将缓存项中的 byte[]数组 反序列化成 Java 对象。不使用 SerializedCache 装饰器 进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而使用 SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。SerializedCache 使用的序列化方式是 Java 原生序列化。

2、CacheKey

在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key 进行比较,MyBatis 中因为涉及 动态 SQL 等多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey 类 来表示缓存项的 key,在一个 CacheKey 对象 中可以封装多个影响缓存项的因素。CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象 是否相同。

public class CacheKey implements Cloneable, Serializable {private static final long serialVersionUID = 1146682552656046210L;public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();private static final int DEFAULT_MULTIPLYER = 37;private static final int DEFAULT_HASHCODE = 17;// 参与计算hashcode,默认值DEFAULT_MULTIPLYER = 37private final int multiplier;// 当前CacheKey对象的hashcode,默认值DEFAULT_HASHCODE = 17private int hashcode;// 校验和private long checksum;private int count;// 由该集合中的所有元素 共同决定两个CacheKey对象是否相同,一般会使用一下四个元素// MappedStatement的id、查询结果集的范围参数(RowBounds的offset和limit)// SQL语句(其中可能包含占位符"?")、SQL语句中占位符的实际参数private List<Object> updateList;// 构造方法初始化属性public CacheKey() {this.hashcode = DEFAULT_HASHCODE;this.multiplier = DEFAULT_MULTIPLYER;this.count = 0;this.updateList = new ArrayList<>();}public CacheKey(Object[] objects) {this();updateAll(objects);}public void update(Object object) {int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);// 重新计算count、checksum和hashcode的值count++;checksum += baseHashCode;baseHashCode *= count;hashcode = multiplier * hashcode + baseHashCode;// 将object添加到updateList集合updateList.add(object);}public int getUpdateCount() {return updateList.size();}public void updateAll(Object[] objects) {for (Object o : objects) {update(o);}}/*** CacheKey重写了 equals() 和 hashCode()方法,这两个方法使用上面介绍* 的 count、checksum、hashcode、updateList 比较两个 CacheKey对象 是否相同*/@Overridepublic boolean equals(Object object) {// 如果为同一对象,直接返回 trueif (this == object) {return true;}// 如果 object 都不是 CacheKey类型,直接返回 falseif (!(object instanceof CacheKey)) {return false;}// 类型转换一下final CacheKey cacheKey = (CacheKey) object;// 依次比较 hashcode、checksum、count,如果不等,直接返回 falseif (hashcode != cacheKey.hashcode) {return false;}if (checksum != cacheKey.checksum) {return false;}if (count != cacheKey.count) {return false;}// 比较 updateList 中的元素是否相同,不同直接返回 falsefor (int i = 0; i < updateList.size(); i++) {Object thisObject = updateList.get(i);Object thatObject = cacheKey.updateList.get(i);if (!ArrayUtil.equals(thisObject, thatObject)) {return false;}}return true;}@Overridepublic int hashCode() {return hashcode;}@Overridepublic String toString() {StringJoiner returnValue = new StringJoiner(":");returnValue.add(String.valueOf(hashcode));returnValue.add(String.valueOf(checksum));updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);return returnValue.toString();}@Overridepublic CacheKey clone() throws CloneNotSupportedException {CacheKey clonedCacheKey = (CacheKey) super.clone();clonedCacheKey.updateList = new ArrayList<>(updateList);return clonedCacheKey;}
}

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

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

相关文章

前端学习(504):垂直居中的第一种方式

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>居中布局一</title><style>.parent{wid…

HTML5游戏引擎Playcraft将于近日正式启动

HTML5游戏引擎Playcraft将于近日正式启动&#xff0c;该引擎5月时就已经发布了测试版&#xff0c;经过2个月的测试后&#xff0c;将于近日正式上线。创始人兼首席执行官马丁韦尔斯介绍说&#xff0c;playcraft是一个为游戏设计者提供的工具&#xff0c;工具优化了设计步骤&…

深度学习 相机标定_基于深度学习的多传感器标定

标定是确定不同坐标系的相互转换关系&#xff0c;是传感器数据融合的前提&#xff0c;特别是激光雷达和图像数据。这里以深度学习的方法训练CNN模型去回归坐标系转换的参数。主要是两个CNN模型&#xff1a;RegNet和CalibNet。RegNet应该是第一个深度卷积神经网络&#xff08;CN…

前端学习(505):垂直居中的第一种方式的优点和缺点

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>居中布局一</title><style>.parent{wid…

实现推拉ui样式_这推拉门隔断,我从没见过!直角设计同时划分3大功能区,太牛了...

这位业主要是说自家装修第二名&#xff0c;我想大概不会有人愿意称第一。就说这个操作我就没见过&#xff0c;推拉门隔断内部&#xff0c;再装大白墙拼接推拉门隔断&#xff0c;形成一个直角设计&#xff0c;同时划分3大功能区&#xff0c;属实是太牛了&#xff01;这和邻居想让…

前端学习(506):垂直居中的第二种方式

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>居中布局一</title><style>.parent{wid…

绘制半圆_Android Canvas 绘制小黄人

❝学习往往是枯燥的&#xff0c;如果能用一个有趣 Demo 来学习和练习技术&#xff0c;那对知识的掌握就会更牢固。我在学习 Canvas 绘制 API 的时候就是这样做的。❞截图镇楼效果图我觉得这个绘制小黄人的自定义 View 就很有意思&#xff0c;也为我后来工作中的自定义 View 实现…

利用旧手机自建anki服务器,废旧手机变身服务器,打造私人云盘

前提条件安装Linux Deploy 并安装系统1.点开左上角选择设置点击左上角进行初始设置1.勾选锁定wifi,防止休眠时wifi断开2.勾选cpu保持唤醒3.勾选开机启动 (可选)4. path变量设置&#xff0c;网上大部分都说手机上安装busybox后填写busybox安装命令包后的路径&#xff0c;其实Lin…

关于返回结构体的函数

【前言】写作本文&#xff0c;源于最近回复的 《汇编中函数返回结构体的方法》 一文。在网络上也已经有一些相关文章和相关问题&#xff0c;有的文章已经给出了一部分结果&#xff0c;但总体而言还缺少比较重要的结论。本文以分析 VC6 编译器&#xff0c;32 位架构为主来重复性…

无法确定当前的订阅失效日期_元器件失效率与失效分布

在汽车电子进行可靠性计算时&#xff0c;首先需要分析各个元器件的失效率与失效分布。元器件真实的失效率与失效分布应该是基于大量的实际数据统计得到的&#xff0c;但由于汽车电子没有专门的失效数据库&#xff0c;通常采用的是预估失效率的方法。目前业界公认的可以参考的标…

易邮服务器com组件注册失败,com组件注册失败有什么办法可以解决

COM组件实际上是系统内一些比较小的二进制可执行程序&#xff0c;它们能为应用程序&#xff0c;操作系统以及其他组件提供一定的服务。所以注册COM组件是一个重要的事情&#xff0c;但这同时也是win7系统的一个硬伤&#xff0c;win7系统注册COM组件总是失败&#xff0c;让人烦心…

前端学习(508):水平和垂直居中第一种方式

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>居中布局一</title><style>.parent{wid…

修改无效_解决docker部署gitlab时,clone地址无效和修改默认端口

部署&#xff1a;docker run -d -p 444:443 -p 88:88 -p 222:22 --name gitlab --restart always -v /usr/local/docker/gitlab/config:/etc/gitlab -v /usr/local/docker/gitlab/logs:/var/log/gitlab -v /usr/local/docker/gitlab/data:/var/opt/gitlab gitlab/gitlab-cevim …

如何实现服务器转发客户端消息,socket 怎么实现服务器与客户端不停的互发消息呢?...

在 java Socket应用的 3-4节 我把老师的代码修改了一下可以手动输入文字让对面的服务器/客户端接受&#xff0c;下面是代码&#xff1a;//客户端package 通讯;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStrea…

前端学习(509):水平和垂直居中第二种方式

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>居中布局一</title><style>.parent{wid…

pg高性能服务器,Pgpool-II 负载均衡对PG的性能影响

Pgpool-II相当于中间件&#xff0c;Pgpool-II与PG是解耦合的&#xff0c;基于这样的机制&#xff0c;Pgpool-II可以搭建在已经存在的任意版本的PG主从结构上&#xff0c;主从结构的实现与Pgpool-II无关&#xff0c;可以通过slony等工具或者PG自身的流复制机制实现。一、拓扑结构…

股上涨和下跌天数比_面对下跌,如何信心十足地逢低买入?

大众总是错的&#xff0c;对吗?也许大部分时间都是这样。但在股市“逢低买进”的想法&#xff0c;可能是金融市场中群策群力的一个罕见例子。这是一个有大量学术支持的老想法。但这并不是说股票市场总是在上涨&#xff0c;事实上&#xff0c;当下跌来临的时候&#xff0c;它们…

前端学习(511):两列布局的第一种方式

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>两列布局</title><style>.left{width: …