在redis盛行的今天很少有人还会选择本地缓存,但是在一次面试中有人问了,我就学习一下,为防止以后忘记,写篇博客记录一下。
使用本地缓存,需要使用一个路由策略,保证同一个单号的数据永远路由到同一台机器,这个可以使用Hash算法生成一个hashcode,然后和集群数量做取模运算。 直接记录代码,这个代码是借鉴的别人的。
import org.apache.commons.lang3.StringUtils;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
public class ConsistentHashWithVirtualNode {
/**
* 虚拟节点信息
* key:hash值
* value:真实节点+"&"+序号
* */
private static SortedMap<Integer, String> virtualNodeMap = new TreeMap<>();
//单机虚拟节点
private static final int VIRTUAL_NODE_NUM = 5;
//服务器配置信息(可配置)
private static String[] servers = {"192.168.56.120:6379",
"192.168.56.121:6379",
"192.168.56.122:6379",
"192.168.56.123:6379",
"192.168.56.124:6379"};
/**
* 初始化
* */
static{
for(int i=0; i< servers.length; i++){
for(int j=0; j<VIRTUAL_NODE_NUM; j++){
String virtualNodeName = servers[i] + "&" + j;
virtualNodeMap.put(getHash(virtualNodeName), virtualNodeName);
}
}
System.out.println("带虚拟节点的Hash环初始化完成!");
}
/**
* 经典的Time33 hash算法
* */
public static int getHash(String key) {
if(StringUtils.isEmpty(key))
return 0;
try{
MessageDigest digest = MessageDigest.getInstance("MD5");
key = new String(digest.digest(key.getBytes()));
}catch(NoSuchAlgorithmException e){
e.printStackTrace();
}
int hash = 5381;
for (int i = 0; i < key.length(); i++) {
int cc = key.charAt(i);
hash += (hash << 5) + cc;
}
return hash<0 ? -hash : hash;
}
/**
* 缓存路由算法
* */
public static String getServer(String key){
int hash = getHash(key);
//得到大于该Hash值的所有Map
SortedMap<Integer, String> subMap = virtualNodeMap.tailMap(hash);
if(subMap.isEmpty()){
int index = virtualNodeMap.firstKey();
System.out.printf("%s被路由到虚拟节点[%s]真实节点[%s]\n", key, virtualNodeMap.get(index),
virtualNodeMap.get(index).substring(0, virtualNodeMap.get(index).indexOf("&")));
return virtualNodeMap.get(index).substring(0, virtualNodeMap.get(index).indexOf("&"));
}else{
int index = subMap.firstKey();
System.out.printf("%s被路由到虚拟节点[%s]真实节点[%s]\n", key, virtualNodeMap.get(index),
virtualNodeMap.get(index).substring(0, virtualNodeMap.get(index).indexOf("&")));
return virtualNodeMap.get(index).substring(0, virtualNodeMap.get(index).indexOf("&"));
}
}
/**
* 使用UUID模拟随机key
* */
public static void main(String[] args) {
// for(int i=0; i<20; i++){
// String str = UUID.randomUUID().toString();
// getServer(str);
// }
String user = new String("Java4ye");
ReferenceQueue<String> userReferenceQueue = new ReferenceQueue<>();
// 创建User对象的虚引用
PhantomReference<String> phantomReference = new PhantomReference<>(user, userReferenceQueue);
System.out.println(userReferenceQueue.poll().get());
// 去掉强引用
user = null;
System.out.println(phantomReference.get());
System.gc();
System.out.println("GC: " + phantomReference.get());
Reference<? extends String> reference = null;
try {
reference = userReferenceQueue.remove(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (reference != null) {
System.out.println("对象User被回收了:");
}
}
}
Spring Cloud Gateway 自定义负载均衡
重写LoadBalancerClientFilter
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
import org.springframework.web.server.ServerWebExchange;
public class UserLoadBalancerClientFilter extends LoadBalancerClientFilter {
public UserLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
super(loadBalancer, properties);
}
@Override
protected ServiceInstance choose(ServerWebExchange exchange) {
//这里可以拿到web请求的上下文,可以从header中取出来自己定义的数据。
String hashcode= exchange.getRequest().getHeaders().getFirst("hashcode");
if (hashcode== null) {
return super.choose(exchange);
}
if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
//这里使用hashcode做为选择服务实例的key
return client.choose(serviceId, hashcode);
}
return super.choose(exchange);
}
}
添加自定义的负载规则
import java.util.List;
import org.apache.commons.lang.math.RandomUtils;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.Server;
/**
*
* @ClassName: GameCenterBalanceRule
* @Description: 根据userId对服务进行负载均衡。同一个用户id的请求,都转发到同一个服务实例上面。
* @author: wgs
* @date: 2019年3月15日 下午2:17:06
*/
public class GameCenterBalanceRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {//这里的key就是过滤器中传过来的hashcode
List<Server> servers = this.getLoadBalancer().getReachableServers();
if (servers.isEmpty()) {
return null;
}
if (servers.size() == 1) {
return servers.get(0);
}
if (key == null) {
return randomChoose(servers);
}
return hashKeyChoose(servers, key);
}
/**
*
* <p>Description:随机返回一个服务实例 </p>
* @param servers
* @return
* @author wgs
* @date 2019年3月15日 下午2:25:23
*
*/
private Server randomChoose(List<Server> servers) {
int randomIndex = RandomUtils.nextInt(servers.size());
return servers.get(randomIndex);
}
/**
*
* <p>Description:使用key的hash值,和服务实例数量求余,选择一个服务实例 </p>
* @param servers
* @param key
* @return
* @author wgs
* @date 2019年3月15日 下午2:25:36
*
*/
private Server hashKeyChoose(List<Server> servers, Object key) {
//此处可以使用上面所说的一致性hash 算法
int hashCode = Math.abs(key.hashCode());
if (hashCode < servers.size()) {
return servers.get(hashCode);
}
int index = hashCode % servers.size();
return servers.get(index);
}
@Override
public void initWithNiwsConfig(IClientConfig config) {
}
}
添加Bean
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class LoadBalancedBean {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public UserLoadBalancerClientFilter userLoadBalanceClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
return new UserLoadBalancerClientFilter(client, properties);
}
}
本地缓存框架有很多 如ehcache、GuavaCache、Caffeine等,因为这些框架使用都比较简单,网上有很多例子,我就不写了
Java高性能本地缓存框架Caffeine_C.-CSDN博客_java本地缓存框架
中介绍了Caffeine 的使用
java中使用Ehcache缓存数据 - shuaiflying - 博客园
中介绍了ehcache 的使用
Java Guava Cache 使用_编程战五渣-CSDN博客
中介绍了GuavaCache的使用