C#客户端Redis服务器的分布式缓存
介绍
在这篇文章中,我想介绍我知道的一种最紧凑的安装和配置Redis服务器的方式。另外,我想简短地概述一下在.NET / C#客户端下Redis hash(哈希类型)和list(链表)的使用。
在这篇文章主要讲到:
安装Redis服务器(附完整的应用程序文件设置)
Redis服务器保护(配置身份验证)
配置服务器复制
从C#应用程序访问缓存
使用Redis ASP.NET会话状态
Redis 集合(Set)、列表(List)和事务处理用法示例
说明附加的源(Redis Funq LoC MVC项目:举例)
缓存的优化思路
背景
Redis是最快也是功能最丰富的内存Key-Value数据存储系统之一。
缺点
没有本地数据缓存(如在Azure缓存同步本地数据缓存)
没有完全集群化的支持(不过,可能今年年底会实现)
优点
易于配置
使用简单
高性能
支持不同的数据类型(如hash(哈希类型)、list(链表)、set(集合)、sorted set(有序集))
ASP.NET会话集成
Web UI用于浏览缓存内容
下面我将简单说明如何在服务器上安装和配置Redis,并用C#使用它。
Redis的安装
从https://github.com/dmajkic/redis/downloads(win32 win64直接链接)下载二进制文件,解包档案到应用程序目录(如C:\Program Files\Redis)
下载从https://github.com/kcherenkov/redis-windows-service/downloads编译的Redis服务,然后复制到程序文件夹(如C:\Program Files\Redis)。如果配置文件丢失,也可以下载复制到应用程序目录。有效的Redis配置文件的范例在https://raw.github.com/antirez/redis/2.6/redis.conf。
Redis应用程序的完整文件也可以从压缩文件(x64)得到。
当你拥有了全套的应用程序文件(如下图所示),
导航到应用程序目录,然后运行以下命令:
sc create %name% binpath= "\"%binpath%\" %configpath%" start= "auto" DisplayName= "Redis"
其中:
%name%——服务实例的名称,例如:redis-instance;
%binpath%——到项目exe文件的路径,例如:C:\Program Files\Redis\RedisService_1.1.exe;
%configpath%——到Redis配置文件的路径,例如:C:\Program Files\Redis\redis.conf;
举例:
sc create Redis start= auto DisplayName= Redis binpath= "\"C:\Program Files\Redis\RedisService_1.1.exe\
" \"C:\Program Files\Redis\redis.conf\""
即应该是这样的:
请确保有足够的权限启动该服务。安装完毕后,请检查该服务是否创建成功,当前是否正在运行:
或者,你可以使用安装程序(我没试过):https://github.com/rgl/redis/downloads。
Redis服务器保护:密码,IP过滤
保护Redis服务器的主要方式是使用Windows防火墙或活跃的网络连接属性设置IP过滤。此外,还可以使用Redis密码设置额外保护。这需要用下面的方式更新Redis配置文件(redis.conf):
首先,找到这行:
# requirepass foobared
删除开头的#符号,用新密码替换foobared:
requirepass foobared
然后,重新启动Redis Windows服务!
当具体使用客户端的时候,使用带密码的构造函数:
RedisClient client = new RedisClient(serverHost, port, redisPassword);
Redis服务器复制(主—从配置)
Redis支持主从同步,即,每次主服务器修改,从服务器得到通知,并自动同步。大多复制用于读取(但不能写)扩展和数据冗余和服务器故障转移。设 置两个Redis实例(在相同或不同服务器上的两个服务),然后配置其中之一作为从站。为了让Redis服务器实例是另一台服务器的从属,可以这样更改配 置文件:
找到以下代码:
# slaveof <masterip> <masterport>
替换为:
slaveof 192.168.1.1 6379
(可以自定义指定主服务器的真实IP和端口)。如果主服务器配置为需要密码(验证),可以如下所示改变redis.conf,找到这一行代码:
# masterauth <master-password>
删除开头的#符号,用主服务器的密码替换<master-password>,即:
masterauth mastpassword
现在这个Redis实例可以被用来作为主服务器的只读同步副本。
用C#代码使用Redis缓存
用C#代码使用Redis运行Manage NuGet包插件,找到ServiceStack.Redis包,并进行安装。
直接从实例化客户端使用Set
/Get
方法示例:
- string host = "localhost";
- string elementKey = "testKeyRedis";
- using (RedisClient redisClient = new RedisClient(host))
- {
- if (redisClient.Get<string>(elementKey) == null)
- {
- // adding delay to see the difference
- Thread.Sleep(5000);
- // save value in cache
- redisClient.Set(elementKey, "some cached value");
- }
- // get value from the cache by key
- message = "Item value is: " + redisClient.Get<string>("some cached value");
- }
类型化实体集更有意思和更实用,这是因为它们操作的是确切类型的对象。在下面的代码示例中,有两个类分别定义为Phone和Person——phone的主人。每个phone实例引用它的主人。下面的代码演示我们如何通过标准添加、删除和发现缓存项:
- public class Phone
- {
- public int Id { get; set; }
- public string Model { get; set; }
- public string Manufacturer { get; set; }
- public Person Owner { get; set; }
- }
- public class Person
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Surname { get; set; }
- public int Age { get; set; }
- public string Profession { get; set; }
- }
- using (RedisClient redisClient = new RedisClient(host))
- {
- IRedisTypedClient<phone> phones = redisClient.As<phone>();
- Phone phoneFive = phones.GetValue("5");
- if (phoneFive == null)
- {
- // make a small delay
- Thread.Sleep(5000);
- // creating a new Phone entry
- phoneFive = new Phone
- {
- Id = 5,
- Manufacturer = "Motorolla",
- Model = "xxxxx",
- Owner = new Person
- {
- Id = 1,
- Age = 90,
- Name = "OldOne",
- Profession = "sportsmen",
- Surname = "OldManSurname"
- }
- };
- // adding Entry to the typed entity set
- phones.SetEntry(phoneFive.Id.ToString(), phoneFive);
- }
- message = "Phone model is " + phoneFive.Manufacturer;
- message += "Phone Owner Name is: " + phoneFive.Owner.Name;
- }
在上面的例子中,我们实例化了输入端IRedisTypedClient,它与缓存对象的特定类型——Phone类型一起工作。
Redis ASP.NET会话状态
要用Redis提供商配置ASP.NET会话状态,添加新文件到你的Web项目,命名为RedisSessionStateProvider.cs,可以从https://github.com/chadman/redis-service-provider/raw/master/RedisProvider/SessionProvider/RedisSessionProvider.cs复制代码,然后添加或更改配置文件中的以下部分(sessionState标签已经内置于system.web标签),或者你也可以下载附加来源和复制代码。
- <sessionstate timeout="1" mode="Custom"
- customprovider="RedisSessionStateProvider" cookieless="false">
- <providers>
- <add name="RedisSessionStateProvider" writeexceptionstoeventlog="false"
- type="RedisProvider.SessionProvider.CustomServiceProvider"
- server="localhost" port="6379" password="pasword">
- </add> </providers>
- </sessionstate>
注意,此密码是可以选择的,看服务器是否需要认证。它必须被真实的值替换或删除,如果Redis服务器不需要身份验证,那么服务器属性和端口得由具体的数值代替(默认端口为6379)。然后在项目中,你才可以使用会话状态:
- // in the Global.asax
- public class MvcApplication1 : System.Web.HttpApplication
- {
- protected void Application_Start()
- {
- //....
- }
- protected void Session_Start()
- {
- Session["testRedisSession"] = "Message from the redis ression";
- }
- }
- 在Home controller(主控制器):
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- //...
- ViewBag.Message = Session["testRedisSession"];
- return View();
- }
- //...
- }
结果:
ASP.NET输出缓存提供者,并且Redis可以用类似的方式进行配置。
Redis Set(集合)和List(列表)
主要要注意的是,Redis列表实现IList<T>,而Redis集合实现ICollection<T>
。下面来说说如何使用它们。
当需要区分相同类型的不同分类对象时,使用列表。例如,我们有“mostSelling(热销手机)”和“oldCollection(回收手机)”两个列表:
- string host = "localhost";
- using (var redisClient = new RedisClient(host))
- {
- //Create a 'strongly-typed' API that makes all Redis Value operations to apply against Phones
- IRedisTypedClient<phone> redis = redisClient.As<phone>();
- IRedisList<phone> mostSelling = redis.Lists["urn:phones:mostselling"];
- IRedisList<phone> oldCollection = redis.Lists["urn:phones:oldcollection"];
- Person phonesOwner = new Person
- {
- Id = 7,
- Age = 90,
- Name = "OldOne",
- Profession = "sportsmen",
- Surname = "OldManSurname"
- };
- // adding new items to the list
- mostSelling.Add(new Phone
- {
- Id = 5,
- Manufacturer = "Sony",
- Model = "768564564566",
- Owner = phonesOwner
- });
- oldCollection.Add(new Phone
- {
- Id = 8,
- Manufacturer = "Motorolla",
- Model = "324557546754",
- Owner = phonesOwner
- });
- var upgradedPhone = new Phone
- {
- Id = 3,
- Manufacturer = "LG",
- Model = "634563456",
- Owner = phonesOwner
- };
- mostSelling.Add(upgradedPhone);
- // remove item from the list
- oldCollection.Remove(upgradedPhone);
- // find objects in the cache
- IEnumerable<phone> LGPhones = mostSelling.Where(ph => ph.Manufacturer == "LG");
- // find specific
- Phone singleElement = mostSelling.FirstOrDefault(ph => ph.Id == 8);
- //reset sequence and delete all lists
- redis.SetSequence(0);
- redisClient.Remove("urn:phones:mostselling");
- redisClient.Remove("urn:phones:oldcollection");
- }
当需要存储相关的数据集和收集统计信息,例如answer -> queustion给答案或问题投票时,Redis集合就非常好使。假设我们有很多的问题(queustion)和答案(answer ),需要将它们存储在缓存中。使用Redis,我们可以这么做:
- /// <summary>
- /// Gets or sets the Redis Manager. The built-in IoC used with ServiceStack autowires this property.
- /// </summary>
- IRedisClientsManager RedisManager { get; set; }
- /// <summary>
- /// Delete question by performing compensating actions to
- /// StoreQuestion() to keep the datastore in a consistent state
- /// </summary>
- /// <param name="questionId">
- public void DeleteQuestion(long questionId)
- {
- using (var redis = RedisManager.GetClient())
- {
- var redisQuestions = redis.As<question>();
- var question = redisQuestions.GetById(questionId);
- if (question == null) return;
- //decrement score in tags list
- question.Tags.ForEach(tag => redis.IncrementItemInSortedSet("urn:tags", tag, -1));
- //remove all related answers
- redisQuestions.DeleteRelatedEntities<answer>(questionId);
- //remove this question from user index
- redis.RemoveItemFromSet("urn:user>q:" + question.UserId, questionId.ToString());
- //remove tag => questions index for each tag
- question.Tags.ForEach("urn:tags>q:" + tag.ToLower(), questionId.ToString()));
- redisQuestions.DeleteById(questionId);
- }
- }
- public void StoreQuestion(Question question)
- {
- using (var redis = RedisManager.GetClient())
- {
- var redisQuestions = redis.As<question>();
- if (question.Tags == null) question.Tags = new List<string>();
- if (question.Id == default(long))
- {
- question.Id = redisQuestions.GetNextSequence();
- question.CreatedDate = DateTime.UtcNow;
- //Increment the popularity for each new question tag
- question.Tags.ForEach(tag => redis.IncrementItemInSortedSet("urn:tags", tag, 1));
- }
- redisQuestions.Store(question);
- redisQuestions.AddToRecentsList(question);
- redis.AddItemToSet("urn:user>q:" + question.UserId, question.Id.ToString());
- //Usage of tags - Populate tag => questions index for each tag
- question.Tags.ForEach(tag => redis.AddItemToSet
- ("urn:tags>q:" + tag.ToLower(), question.Id.ToString()));
- }
- }
- /// <summary>
- /// Delete Answer by performing compensating actions to
- /// StoreAnswer() to keep the datastore in a consistent state
- /// </summary>
- /// <param name="questionId">
- /// <param name="answerId">
- public void DeleteAnswer(long questionId, long answerId)
- {
- using (var redis = RedisManager.GetClient())
- {
- var answer = redis.As<question>().GetRelatedEntities<answer>
- (questionId).FirstOrDefault(x => x.Id == answerId);
- if (answer == null) return;
- redis.As<question>().DeleteRelatedEntity<answer>(questionId, answerId);
- //remove user => answer index
- redis.RemoveItemFromSet("urn:user>a:" + answer.UserId, answerId.ToString());
- }
- }
- public void StoreAnswer(Answer answer)
- {
- using (var redis = RedisManager.GetClient())
- {
- if (answer.Id == default(long))
- {
- answer.Id = redis.As<answer>().GetNextSequence();
- answer.CreatedDate = DateTime.UtcNow;
- }
- //Store as a 'Related Answer' to the parent Question
- redis.As<question>().StoreRelatedEntities(answer.QuestionId, answer);
- //Populate user => answer index
- redis.AddItemToSet("urn:user>a:" + answer.UserId, answer.Id.ToString());
- }
- }
- public List<answer> GetAnswersForQuestion(long questionId)
- {
- using (var redis = RedisManager.GetClient())
- {
- return redis.As<question>().GetRelatedEntities<answer>(questionId);
- }
- }
- public void VoteQuestionUp(long userId, long questionId)
- {
- //Populate Question => User and User => Question set indexes in a single transaction
- RedisManager.ExecTrans(trans =>
- {
- //Register upvote against question and remove any downvotes if any
- trans.QueueCommand(redis =>
- redis.AddItemToSet("urn:q>user+:" + questionId, userId.ToString()));
- trans.QueueCommand(redis =>
- redis.RemoveItemFromSet("urn:q>user-:" + questionId, userId.ToString()));
- //Register upvote against user and remove any downvotes if any
- trans.QueueCommand(redis =>
- redis.AddItemToSet("urn:user>q+:" + userId, questionId.ToString()));
- trans.QueueCommand(redis =>
- redis.RemoveItemFromSet("urn:user>q-:" + userId, questionId.ToString()));
- });
- }
- public void VoteQuestionDown(long userId, long questionId)
- {
- //Populate Question => User and User => Question set indexes in a single transaction
- RedisManager.ExecTrans(trans =>
- {
- //Register downvote against question and remove any upvotes if any
- trans.QueueCommand(redis =>
- redis.AddItemToSet("urn:q>user-:" + questionId, userId.ToString()));
- trans.QueueCommand(redis =>
- redis.RemoveItemFromSet("urn:q>user+:" + questionId, userId.ToString()));
- //Register downvote against user and remove any upvotes if any
- trans.QueueCommand(redis =>
- redis.AddItemToSet"urn:user>q-:" + userId, questionId.ToString()));
- trans.QueueCommand(redis =>
- redis.RemoveItemFromSet("urn:user>q+:" + userId, questionId.ToString()));
- });
- }
- public void VoteAnswerUp(long userId, long answerId)
- {
- //Populate Question => User and User => Question set indexes in a single transaction
- RedisManager.ExecTrans(trans =>
- {
- //Register upvote against answer and remove any downvotes if any
- trans.QueueCommand(redis =>
- redis.AddItemToSet("urn:a>user+:" + answerId, userId.ToString()));
- trans.QueueCommand(redis =>
- redis.RemoveItemFromSet("urn:a>user-:" + answerId, userId.ToString()));
- //Register upvote against user and remove any downvotes if any
- trans.QueueCommand(redis =>
- redis.AddItemToSet("urn:user>a+:" + userId, answerId.ToString()));
- trans.QueueCommand(redis =>
- redis.RemoveItemFromSet("urn:user>a-:" + userId, answerId.ToString()));
- });
- }
- public void VoteAnswerDown(long userId, long answerId)
- {
- //Populate Question => User and User => Question set indexes in a single transaction
- RedisManager.ExecTrans(trans =>
- {
- //Register downvote against answer and remove any upvotes if any
- trans.QueueCommand(redis =>
- redis.AddItemToSet("urn:a>user-:" + answerId, userId.ToString()));
- trans.QueueCommand(redis =>
- redis.RemoveItemFromSet("urn:a>user+:" + answerId, userId.ToString()));
- //Register downvote against user and remove any upvotes if any
- trans.QueueCommand(redis =>
- redis.AddItemToSet("urn:user>a-:" + userId, answerId.ToString()));
- trans.QueueCommand(redis =>
- redis.RemoveItemFromSet("urn:user>a+:" + userId, answerId.ToString()));
- });
- }
- public QuestionResult GetQuestion(long questionId)
- {
- var question = RedisManager.ExecAs<question>
- (redisQuestions => redisQuestions.GetById(questionId));
- if (question == null) return null;
- var result = ToQuestionResults(new[] { question })[0];
- var answers = GetAnswersForQuestion(questionId);
- var uniqueUserIds = answers.ConvertAll(x => x.UserId).ToHashSet();
- var usersMap = GetUsersByIds(uniqueUserIds).ToDictionary(x => x.Id);
- result.Answers = answers.ConvertAll(answer =>
- new AnswerResult { Answer = answer, User = usersMap[answer.UserId] });
- return result;
- }
- public List<user> GetUsersByIds(IEnumerable<long> userIds)
- {
- return RedisManager.ExecAs<user>(redisUsers => redisUsers.GetByIds(userIds)).ToList();
- }
- public QuestionStat GetQuestionStats(long questionId)
- {
- using (var redis = RedisManager.GetReadOnlyClient())
- {
- var result = new QuestionStat
- {
- VotesUpCount = redis.GetSetCount("urn:q>user+:" +questionId),
- VotesDownCount = redis.GetSetCount("urn:q>user-:" + questionId)
- };
- result.VotesTotal = result.VotesUpCount - result.VotesDownCount;
- return result;
- }
- }
- public List<tag> GetTagsByPopularity(int skip, int take)
- {
- using (var redis = RedisManager.GetReadOnlyClient())
- {
- var tagEntries = redis.GetRangeWithScoresFromSortedSetDesc("urn:tags", skip, take);
- var tags = tagEntries.ConvertAll(kvp => new Tag { Name = kvp.Key, Score = (int)kvp.Value });
- return tags;
- }
- }
- public SiteStats GetSiteStats()
- {
- using (var redis = RedisManager.GetClient())
- {
- return new SiteStats
- {
- QuestionsCount = redis.As<question>().TypeIdsSet.Count,
- AnswersCount = redis.As<answer>().TypeIdsSet.Count,
- TopTags = GetTagsByPopularity(0, 10)
- };
- }
- }
附加资源说明
项目中引用的一些包在packages.config文件中配置。
Funq IoC的相关配置,以及注册类型和当前控制器目录,在Global.asax文件中配置。
基于IoC的缓存使用以及Global.asax可以打开以下URL:http://localhost:37447/Question/GetQuestions?tag=test 查看。
你可以将tag字段设置成test3,test1,test2等。
Redis缓存配置——在web config文件(<system.web><sessionState>节点)以及RedisSessionStateProvider.cs文件中。
在MVC项目中有很多待办事项,因此,如果你想改进/继续,请更新,并上传。
如果有人能提供使用Redis(以及Funq IOC)缓存的MVC应用程序示例,本人将不胜感激。Funq IOC已经配置,使用示例已经在Question controller中。
注:部分取样于“ServiceStack.Examples-master”解决方案。
结论。优化应用程序缓存以及快速本地缓存
由于Redis并不在本地存储(也不在本地复制)数据,那么通过在本地缓存区存储一些轻量级或用户依赖的对象(跳过序列化字符串和客户端—服务端数据转换)来优化性能是有意义的。例如,在Web应用中,对于轻量级的对象使用’System.Runtime.Caching.ObjectCache
‘ 会更好——用户依赖,并且应用程序时常要用。否则,当经常性地需要使用该对象时,就必须在分布式Redis缓存中存储大量容积的内容。用户依赖的对象举例——个人资料信息,个性化信息 。常用对象——本地化数据,不同用户之间的共享信息,等等。
链接
如何运行Redis服务:
https://github.com/kcherenkov/redis-windows-service
文档:
.NET / C#示例:
https://github.com/ServiceStack/ServiceStack.Examples
关于如何用C#在Windows上使用Redis的好建议:
http://maxivak.com/getting-started-with-redis-and-asp-net-mvc-under-windows/:
http://www.piotrwalat.net/using-redis-with-asp-net-web-api/
关于Redis:
https://github.com/ServiceStack/ServiceStack.Redis
Azure缓存
http://kotugoroshko.blogspot.ae/2013/07/windows-azure-caching-integration.html
许可证
这篇文章,以及任何相关的源代码和文件,依据The Code Project Open License (CPOL)。
译文链接:http://www.codeceo.com/article/distributed-caching-redis-server.html
英文原文:Distributed Caching using Redis Server with .NET/C# Client
C#客户端Redis服务器的分布式缓存的更多相关文章
- Redis setnx命令 分布式缓存
setnx命令 将 key 的值设为 value,当且仅当 key 不存在. 若给定的 key 已经存在,则 SETNX 不做任何动作. SETNX 是SET if Not eXists的简写. re ...
- EhCache+Redis实现分布式缓存
Ehcache集群模式 由于 EhCache 是进程中的缓存系统,一旦将应用部署在集群环境中,每一个节点维护各自的缓存数据,当某个节点对缓存数据进行更新,这些更新的数据无法在其它节点中共享,这不仅会降 ...
- Redis分布式缓存剖析及大厂面试精髓v6.2.6
概述 官方说明 Redis官网 https://redis.io/ 最新版本6.2.6 Redis中文官网 http://www.redis.cn/ 不过中文官网的同步更新维护相对要滞后不少时间,但对 ...
- 从Redis分布式缓存实战入手到底层原理分析、面面俱到覆盖大厂面试考点
概述 官方说明 Redis官网 https://redis.io/ 最新版本6.2.6 Redis中文官网 http://www.redis.cn/ 不过中文官网的同步更新维护相对要滞后不少时间,但对 ...
- springboot+mybatis+redis实现分布式缓存
大家都知道springboot项目都是微服务部署,A服务和B服务分开部署,那么它们如何更新或者获取共有模块的缓存数据,或者给A服务做分布式集群负载,如何确保A服务的所有集群都能同步公共模块的缓存数据, ...
- 分布式缓存技术redis学习系列
分布式缓存技术redis学习系列(一)--redis简介以及linux上的安装以及操作redis问题整理 分布式缓存技术redis学习系列(二)--详细讲解redis数据结构(内存模型)以及常用命令 ...
- Asp.Net Core 轻松学-正确使用分布式缓存
前言 本来昨天应该更新的,但是由于各种原因,抱歉,让追这个系列的朋友久等了.上一篇文章 在.Net Core 使用缓存和配置依赖策略 讲的是如何使用本地缓存,那么本篇文章就来了解一下如何使用分 ...
- 经典面试题:分布式缓存热点KEY问题如何解决--有赞方案
有赞透明多级缓存解决方案(TMC) 一.引子 1-1. TMC 是什么 TMC ,即"透明多级缓存( Transparent Multilevel Cache )",是有赞 Paa ...
- ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用
.NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...
随机推荐
- 在Oracle Linux Server release 6.4下配置ocfs2文件系统
① 安装ocfs-tools-1.8 如果是使用RedHat Enterprise Linux 6.4,也可以安装ocfs-tools-1.8的,只是要插入Oracle Linux Server re ...
- INSTALL MYSQL IN MAC
安装好MYSQL后,在System References中找到MYSQL,启动它. 启动之后在终端中输入添加MySQL路径的命令,把MYSQL路径添加到PATH中: PATH="$PATH& ...
- 使用 jsErrLog 分析 js 报错
1. github 地址: https://github.com/Offbeatmammal/jsErrLog/tree/master/src 2. 在所有页面引入 jsErrLog,配置出错时打日志 ...
- 关于stacking context和CSS z-index的总结
HTML中决定元素叠加顺序的CSS属性最有名的应该是z-index了.但是,往往在项目中发现有些情况和我们的预期不太一致.经过研究和学习,总算搞清楚了其中的关系.简单总结如下: 只有Positione ...
- navicat 链接linux 服务器上的数据库
- Floyd-Warshall算法
Floyd也是采用动态规划的方案来解决在一个有向图G=(V,E)上每对顶点间的最短路径问题.运行时间为Θ(V3). 算法分析: 用邻接矩阵map[][]存储有向图,用dist[i][j]表示i到j的最 ...
- ArcGIS Geodatabase版本控制机制的学习总结
本文是最近内部的一个学习的自我整理,只有关键信息,如果需要详细了解,请参阅ArcGIS帮助文档: http://resources.arcgis.com/zh-cn/help/main/10.1/in ...
- 阿里前DBA的故事
别人怎么享受生活,与你无关.你怎么磨砺与你有头.引用同事周黄江的一句话,很多人努力程度还远没有到拼天赋的时候. 成功的人都是那种目标很明确的人.对于文中厨师的经历很感兴趣.不管是IT还是餐饮,哪个行业 ...
- iOS:缓存与Operation优先级问题
这篇博客来源于今年的一个面试题,当我们使用SDWebImgae框架中的sd_setImageWithURL: placeholderImage:方法在tableView或者collectionView ...
- CSS3 页面跳转的动画效果
从左侧弹出: var windowWidth = window.innerWidth; $(atlas_list).css({ "transition":"none&qu ...