对于config的读取,再熟悉不过了,通常的写法如下:
//1. read from cache string configvalue = GetFromCache(configKey); if(!configValue.IsNullOrEmpty()){ return configValue; } //2. read from db configValue = GetFromDb(configKey); if(configValue.IsNullOrEmpty()){ // set to cache 30s retrun config; }
这种写法在普通的场景下,完全是可行的 先从cache中读,再从db中读取,设置30s缓存时间
但当遇到高并发的场景下: 即如果瞬时有10000个或更多的请求,来读取这个config,恰巧碰到,30s过期的临界值。此时全部的请求压力就会转向db,缓存就会miss。
这里有个概念:
滑动过期 :即config 从本地内存中读取,同时开启一个定时器,定时从cache中同步config到本地内存中
public class ConfigFacade { private const int FRESHEN_INTERVAL = 30 * 1000; private static readonly ConcurrentDictionary<string, string> localCache = new ConcurrentDictionary<string, string>(); private static Timer _timer; static ConfigFacade() { Console.WriteLine("timer start ..."); _timer = new Timer(state => LoadConfig(), null, FRESHEN_INTERVAL, FRESHEN_INTERVAL); } private static void LoadConfig() { //sync configvalue to localcache foreach (var key in localCache.Keys) { var configValue = GetConfigFromClient(key); var tryUpdateValue = localCache.TryUpdate(key, configValue, localCache[key]); Console.WriteLine("now : {0}", DateTime.Now); Console.WriteLine("update key :{0} , {1}", key, tryUpdateValue); } } public static string GetConfig(string key) { string value; if (localCache != null && localCache.Count > 0 && localCache.ContainsKey(key)) { var tryGetValue = localCache.TryGetValue(key, out value); if (tryGetValue) { return value; } } value = GetConfigFromClient(key); localCache.TryAdd(key, value); return value; } private static string GetConfigFromClient(string key) { // 1. from redis // 2. from db Console.WriteLine("key => {0} ,get value form client", key); return string.Format("{0}_value", key); } }
优点:
- 每次并发的读取都能保证是读取本地内存,定时器会定时的去同步缓存到本地内存
- 这种处理问题的思路 类似于将config 放到一个池子里,永远保证这个池子里有值