本文是redis学习系列的第五篇,点击下面链接可回看系列文章

《redis简介以及linux上的安装》

《详细讲解redis数据结构(内存模型)以及常用命令》

《redis高级应用(主从、事务与锁、持久化)》

《redis高级应用(集群搭建、集群分区原理、集群操作》

本文我们继续学习redis与spring的整合,整合之后就可以用redisStringTemplate的setNX()和delete()方法实现分布式锁了。

Redis与spring的整合

相关依赖jar包

spring把专门的数据操作独立封装在spring-data系列中,spring-data-redis是对Redis的封装

  1. <dependency>
  2. <groupId>org.springframework.data</groupId>
  3. <artifactId>spring-data-redis</artifactId>
  4. <version>1.4.2.RELEASE</version>
  5. </dependency>
  6.  
  7. <dependency>
  8. <groupId>redis.clients</groupId>
  9. <artifactId>jedis</artifactId>
  10. <version>2.6.2</version>
  11. </dependency>
  12.  
  13. <dependency>
  14. <groupId>org.apache.commons</groupId>
  15. <artifactId>commons-pool2</artifactId>
  16. <version>2.4.2</version>
  17. </dependency>

Spring 配置文件applicationContext.xml

  1. <!--命令空间中加入下面这行-->
  2. xmlns:p="http://www.springframework.org/schema/p"
  3.  
  4. <!-- redis连接池配置文件 -->
  5. <context:property-placeholder location="classpath:redis.properties" />
  6.  
  7. <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
  8. <property name="maxIdle" value="${redis.maxIdle}" />
  9. <property name="maxTotal" value="${redis.maxTotal}" />
  10. <property name="MaxWaitMillis" value="${redis.MaxWaitMillis}" />
  11. <property name="testOnBorrow" value="${redis.testOnBorrow}" />
  12. </bean>
  13.  
  14. <bean id="connectionFactory" class="org.springframework.data. redis.connection.jedis.JedisConnectionFactory"
  15. p:host-name="${redis.host}" p:port="${redis.port}"
  16. p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
  17.  
  18. <bean id="redisTemplate" class="org.springframework.data. redis.core.RedisTemplate">
  19. <property name="connectionFactory" ref="connectionFactory" />
  20. </bean>

  

注意新版的maxTotal,MaxWaitMillis这两个字段与旧版的不同。

redis连接池配置文件redis.properties

  1. redis.host=192.168.2.129
  2. redis.port=6379
  3. redis.pass=redis129
  4.  
  5. redis.maxIdle=300
  6. redis.maxTotal=600
  7. redis.MaxWaitMillis=1000
  8. redis.testOnBorrow=true

好了,配置完成,下面写上代码

测试代码

User

  1. @Entity
  2. @Table(name = "t_user")
  3. public class User {
  4. //主键
  5. private String id;
  6. //用户名
  7. private String userName;
  8. //...省略get,set...
  9. }

BaseRedisDao

  1. @Repository
  2. public abstract class BaseRedisDao<K,V> {
  3.  
  4. @Autowired(required=true)
  5. protected RedisTemplate<K, V> redisTemplate;
  6.  
  7. }

IUserDao

  1. public interface IUserDao {
  2.  
  3. public boolean save(User user);
  4.  
  5. public boolean update(User user);
  6.  
  7. public boolean delete(String userIds);
  8.  
  9. public User find(String userId);
  10.  
  11. }

UserDao

  1. @Repository
  2. public class UserDao extends BaseRedisDao<String, User> implements IUserDao {
  3.  
  4. @Override
  5. public boolean save(final User user) {
  6. boolean res = redisTemplate.execute(new RedisCallback<Boolean>() {
  7. public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
  8. RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
  9. byte[] key = serializer.serialize(user.getId());
  10. byte[] value = serializer.serialize(user.getUserName());
  11. //set not exits
  12. return connection.setNX(key, value);
  13. }
  14. });
  15. return res;
  16. }
  17.  
  18. @Override
  19. public boolean update(final User user) {
  20. boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
  21. public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
  22. RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
  23. byte[] key = serializer.serialize(user.getId());
  24. byte[] name = serializer.serialize(user.getUserName());
  25. //set
  26. connection.set(key, name);
  27. return true;
  28. }
  29. });
  30. return result;
  31. }
  32.  
  33. @Override
  34. public User find(final String userId) {
  35. User result = redisTemplate.execute(new RedisCallback<User>() {
  36. public User doInRedis(RedisConnection connection) throws DataAccessException {
  37. RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
  38. byte[] key = serializer.serialize(userId);
  39. //get
  40. byte[] value = connection.get(key);
  41. if (value == null) {
  42. return null;
  43. }
  44. String name = serializer.deserialize(value);
  45. User resUser = new User();
  46. resUser.setId(userId);
  47. resUser.setUserName(name);
  48. return resUser;
  49. }
  50. });
  51. return result;
  52. }
  53.  
  54. @Override
  55. public boolean delete(final String userId) {
  56. boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
  57. public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
  58. RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
  59. byte[] key = serializer.serialize(userId);
  60. //delete
  61. connection.del(key);
  62. return true;
  63. }
  64. });
  65. return result;
  66. }
  67.  
  68. }

Test

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
  3. public class RedisTest extends AbstractJUnit4SpringContextTests {
  4.  
  5. @Autowired
  6. private IUserDao userDao;
  7.  
  8. @Test
  9. public void testSaveUser() {
  10. User user = new User();
  11. user.setId("402891815170e8de015170f6520b0000");
  12. user.setUserName("zhangsan");
  13. boolean res = userDao.save(user);
  14. Assert.assertTrue(res);
  15. }
  16.  
  17. @Test
  18. public void testGetUser() {
  19. User user = new User();
  20. user = userDao.find("402891815170e8de015170f6520b0000");
  21. System.out.println(user.getId() + "-" + user.getUserName() );
  22. }
  23.  
  24. @Test
  25. public void testUpdateUser() {
  26. User user = new User();
  27. user.setId("402891815170e8de015170f6520b0000");
  28. user.setUserName("lisi");
  29. boolean res = userDao.update(user);
  30. Assert.assertTrue(res);
  31. }
  32.  
  33. @Test
  34. public void testDeleteUser() {
  35. boolean res = userDao.delete("402891815170e8de015170f6520b0000");
  36. Assert.assertTrue(res);
  37. }
  38.  
  39. }

  

String类型的增删该查已完成,Hash,List,Set数据类型的操作就不举例了,和使用命令的方式差不多。如下

  1. connection.hSetNX(key, field, value);
  2. connection.hDel(key, fields);
  3. connection.hGet(key, field);
  4.  
  5. connection.lPop(key);
  6. connection.lPush(key, value);
  7. connection.rPop(key);
  8. connection.rPush(key, values);
  9.  
  10. connection.sAdd(key, values);
  11. connection.sMembers(key);
  12. connection.sDiff(keys);
  13. connection.sPop(key);

  

整合可能遇到的问题

1.NoSuchMethodError

  1. java.lang.NoSuchMethodError: org.springframework.core.serializer.support.DeserializingConverter.<init>(Ljava/lang/ClassLoader;)V
  2.  
  3. Caused by: java.lang.NoSuchMethodError: redis.clients.jedis.JedisShardInfo.setTimeout(I)V

  

类似找不到类,找不到方法的问题,当确定依赖的jar已经引入之后,此类问题多事spring-data-redis以及jedis版本问题,多换个版本试试,本文上面提到的版本可以使用。

1.No qualifying bean

  1. No qualifying bean of type [org.springframework.data.redis.core.RedisTemplate] found for dependency

  

找不到bean,考虑applicationContext.xml中配置redisTemplate bean时实现类是否写错。例如,BaseRedisDao注入的是RedisTemplate类型的对象,applicationContext.xml中配置的实现类却是RedisTemplate的子类StringRedisTemplate,那肯定报错。整合好后,下面我们着重学习基于redis的分布式锁的实现。

基于redis实现的分布式锁

我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源。锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标识,因此基于redis实现的分布式锁主要依赖redis的SETNX命令和DEL命令,SETNX相当于上锁,DEL相当于释放锁,当然,在下面的具体实现中会更复杂些。之所以称为分布式锁,是因为客户端可以在redis集群环境中向集群中任一个可用Master节点请求上锁(即SETNX命令存储key到redis缓存中是随机的),不像传统的synchronized锁只能锁住本机,分布式锁则用于分布式环境中对集群其他节点上锁。

现在相信你已经对在基于redis实现的分布式锁的基本概念有了解,需要注意的是,这个和前面文章提到的使用WATCH 命令对key值进行锁操作没有直接的关系。java中synchronized和Lock对象都能对共享资源进行加锁,下面我们将学习用java实现的redis分布式锁。

java中的锁技术

在分析java实现的redis分布式锁之前,我们先来回顾下java中的锁技术,为了直观的展示,我们采用“多个线程共享输出设备”来举例。

不加锁共享输出设备

  1. public class LockTest {
  2. //不加锁
  3. static class Outputer {
  4. public void output(String name) {
  5. for(int i=0; i<name.length(); i++) {
  6. System.out.print(name.charAt(i));
  7. }
  8. System.out.println();
  9. }
  10. }
  11. public static void main(String[] args) {
  12. final Outputer output = new Outputer();
  13. //线程1打印zhangsan
  14. new Thread(new Runnable(){
  15. @Override
  16. public void run() {
  17. while(true) {
  18. try{
  19. Thread.sleep(1000);
  20. }catch(InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. output.output("zhangsan");
  24. }
  25. }
  26. }).start();
  27.  
  28. //线程2打印lingsi
  29. new Thread(new Runnable(){
  30. @Override
  31. public void run() {
  32. while(true) {
  33. try{
  34. Thread.sleep(1000);
  35. }catch(InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. output.output("lingsi");
  39. }
  40. }
  41. }).start();
  42.  
  43. //线程3打印wangwu
  44. new Thread(new Runnable(){
  45. @Override
  46. public void run() {
  47. while(true) {
  48. try{
  49. Thread.sleep(1000);
  50. }catch(InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. output.output("huangwu");
  54. }
  55. }
  56. }).start();
  57. }
  58. }

上面例子中,三个线程同时共享输出设备output,线程1需要打印zhangsan,线程2需要打印lingsi,线程3需要打印wangwu。在不加锁的情况,这三个线程会不会因为得不到输出设备output打架呢,我们来看看运行结果:

  1. huangwu
  2. zhangslingsi
  3. an
  4. huangwu
  5. zlingsi
  6. hangsan
  7. huangwu
  8. lzhangsan
  9. ingsi
  10. huangwu
  11. lingsi

  

从运行结果可以看出,三个线程打架了,线程1没打印完zhangsan,线程2就来抢输出设备......可见,这不是我们想要的,我们想要的是线程之间能有序的工作,各个线程之间互斥的使用输出设备output。

使用java5中的Lock对输出设备加锁

现在我们对Outputer进行改进,给它加上锁,加锁之后每次只有一个线程能访问它。

  1. //使用java5中的锁
  2. static class Outputer{
  3. Lock lock = new ReentrantLock();
  4. public void output(String name) {
  5. //传统java加锁
  6. //synchronized (Outputer.class){
  7. lock.lock();
  8. try {
  9. for(int i=0; i<name.length(); i++) {
  10. System.out.print(name.charAt(i));
  11. }
  12. System.out.println();
  13. }finally{
  14. //任何情况下都有释放锁
  15. lock.unlock();
  16. }
  17. //}
  18. }
  19. }

  

看看加锁后的输出结果:

  1. zhangsan
  2. lingsi
  3. huangwu
  4. zhangsan
  5. lingsi
  6. huangwu
  7. zhangsan
  8. lingsi
  9. huangwu
  10. zhangsan
  11. lingsi
  12. huangwu
  13. zhangsan
  14. lingsi
  15. huangwu
  16. ......

  

从运行结果中可以看出,三个线程之间不打架了,线程之间的打印变得有序。有个这个基础,下面我们来学习基于Redis实现的分布式锁就更容易了。

Redis分布式锁

实现分析

从上面java锁的使用中可以看出,锁对象主要有lock与unlock方法,在lock与unlock方法之间的代码(临界区)能保证线程互斥访问。基于redis实现的Java分布式锁主要依赖redis的SETNX命令和DEL命令,SETNX相当于上锁(lock),DEL相当于释放锁(unlock)。我们只要实现Lock接口重写lock()和unlock()即可。但是这还不够,安全可靠的分布式锁应该满足满足下面三个条件:

l 互斥,不管任何时候,只有一个客户端能持有同一个锁。

l 不会死锁,最终一定会得到锁,即使持有锁的客户端对应的master节点宕掉。

l 容错,只要大多数Redis节点正常工作,客户端应该都能获取和释放锁。

那么什么情况下会不满足上面三个条件呢。多个线程(客户端)同时竞争锁可能会导致多个客户端同时拥有锁。比如,

(1)线程1在master节点拿到了锁(存入key)

(2)master节点在把线程1创建的key写入slave之前宕机了,此时集群中的节点已经没有锁(key)了,包括master节点的slaver节点

(3)slaver节点升级为master节点

(4)线程2向新的master节点发起锁(存入key)请求,很明显,能请求成功。

可见,线程1和线程2同时获得了锁。如果在更高并发的情况,可能会有更多线程(客户端)获取锁,这种情况就会导致上文所说的线程“打架”问题,线程之间的执行杂乱无章。

那什么情况下又会发生死锁的情况呢。如果拥有锁的线程(客户端)长时间的执行或者因为某种原因造成阻塞,就会导致锁无法释放(unlock没有调用),其它线程就不能获取锁而而产生无限期死锁的情况。其它线程在执行lock失败后即使粗暴的执行unlock删除key之后也不能正常释放锁,因为锁就只能由获得锁的线程释放,锁不能正常释放其它线程仍然获取不到锁。解决死锁的最好方式是设置锁的有效时间(redis的expire命令),不管是什么原因导致的死锁,有效时间过后,锁将会被自动释放。

为了保障容错功能,即只要有Redis节点正常工作,客户端应该都能获取和释放锁,我们必须用相同的key不断循环向Master节点请求锁,当请求时间超过设定的超时时间则放弃请求锁,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,应该尽快尝试下一个master节点。释放锁比较简单,因为只需要在所有节点都释放锁就行,不管之前有没有在该节点获取锁成功。

Redlock算法

根据上面的分析,官方提出了一种用Redis实现分布式锁的算法,这个算法称为RedLock。RedLock算法的主要流程如下:

RedLock算法主要流程

 

Java实现

结合上面的流程图,加上下面的代码解释,相信你一定能理解redis分布式锁的实现原理

  1. public class RedisLock implements Lock{
  2.  
  3. protected StringRedisTemplate redisStringTemplate;
  4.  
  5. // 存储到redis中的锁标志
  6. private static final String LOCKED = "LOCKED";
  7.  
  8. // 请求锁的超时时间(ms)
  9. private static final long TIME_OUT = 30000;
  10.  
  11. // 锁的有效时间(s)
  12. public static final int EXPIRE = 60;
  13.  
  14. // 锁标志对应的key;
  15. private String key;
  16.  
  17. // state flag
  18. private volatile boolean isLocked = false;
  19.  
  20. public RedisLock(String key) {
  21. this.key = key;
  22. @SuppressWarnings("resource")
  23. ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:applicationContext.xml");
  24. redisStringTemplate = (StringRedisTemplate)ctx.getBean("redisStringTemplate");
  25. }
  26.  
  27. @Override
  28. public void lock() {
  29. //系统当前时间,毫秒
  30. long nowTime = System.nanoTime();
  31. //请求锁超时时间,毫秒
  32. long timeout = TIME_OUT*1000000;
  33. final Random r = new Random();
  34. try {
  35. //不断循环向Master节点请求锁,当请求时间(System.nanoTime() - nano)超过设定的超时时间则放弃请求锁
  36. //这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间
  37. //如果一个master节点不可用了,应该尽快尝试下一个master节点
  38. while ((System.nanoTime() - nowTime) < timeout) {
  39. //将锁作为key存储到redis缓存中,存储成功则获得锁
  40. if (redisStringTemplate.getConnectionFactory().getConnection().setNX(key.getBytes(),
  41. LOCKED.getBytes())) {
  42. //设置锁的有效期,也是锁的自动释放时间,也是一个客户端在其他客户端能抢占锁之前可以执行任务的时间
  43. //可以防止因异常情况无法释放锁而造成死锁情况的发生
  44. redisStringTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
  45. isLocked = true;
  46. //上锁成功结束请求
  47. break;
  48. }
  49. //获取锁失败时,应该在随机延时后进行重试,避免不同客户端同时重试导致谁都无法拿到锁的情况出现
  50. //睡眠3毫秒后继续请求锁
  51. Thread.sleep(3, r.nextInt(500));
  52. }
  53. } catch (Exception e) {
  54. e.printStackTrace();
  55. }
  56. }
  57.  
  58. @Override
  59. public void unlock() {
  60. //释放锁
  61. //不管请求锁是否成功,只要已经上锁,客户端都会进行释放锁的操作
  62. if (isLocked) {
  63. redisStringTemplate.delete(key);
  64. }
  65. }
  66.  
  67. @Override
  68. public void lockInterruptibly() throws InterruptedException {
  69. // TODO Auto-generated method stub
  70.  
  71. }
  72.  
  73. @Override
  74. public boolean tryLock() {
  75. // TODO Auto-generated method stub
  76. return false;
  77. }
  78.  
  79. @Override
  80. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  81. // TODO Auto-generated method stub
  82. return false;
  83. }
  84.  
  85. @Override
  86. public Condition newCondition() {
  87. // TODO Auto-generated method stub
  88. return null;
  89. }
  90. }

 

好了,RedisLock已经实现,我们对Outputer使用RedisLock进行修改

  1. /使用RedisLock
  2. static class Outputer {
  3. //创建一个名为redisLock的RedisLock类型的锁
  4. RedisLock redisLock = new RedisLock("redisLock");
  5. public void output(String name) {
  6. //上锁
  7. redisLock.lock();
  8. try {
  9. for(int i=0; i<name.length(); i++) {
  10. System.out.print(name.charAt(i));
  11. }
  12. System.out.println();
  13. }finally{
  14. //任何情况下都要释放锁
  15. redisLock.unlock();
  16. }
  17. }
  18. }

  

看看使用RedisLock加锁后的的运行结果

  1. lingsi
  2. zhangsan
  3. huangwu
  4. lingsi
  5. zhangsan
  6. huangwu
  7. lingsi
  8. zhangsan
  9. huangwu
  10. lingsi
  11. zhangsan
  12. huangwu
  13. lingsi
  14. zhangsan
  15. huangwu
  16. ......

  

可见,使用RedisLock加锁后线程之间不再“打架”,三个线程互斥的访问output。

问题

现在我无法论证RedLock算法在分布式、高并发环境下的可靠性,但从本例三个线程的运行结果看,RedLock算法确实保证了三个线程互斥的访问output(redis.maxIdle=300 redis.maxTotal=600,运行到Timeout waiting for idle object都没有出现线程“打架”的问题)。我认为RedLock算法仍有些问题没说清楚,比如,如何防止拥有锁的Master节点宕机而未来得及同步key到Slave节点时导致其他线程(客户端)获得锁?RedLock算法在释放锁的处理上,不管线程是否获取锁成功,只要上了锁,就会到每个master节点上释放锁,这就会导致一个线程上的锁可能会被其他线程释放掉,这就和每个锁只能被获得锁的线程释放相互矛盾。这些有待后续进一步交流学习研究。

参考文档

http://redis.io/topics/distlock

http://ifeve.com/redis-lock/

分布式缓存技术redis学习系列(五)——redis实战(redis与spring整合,分布式锁实现)的更多相关文章

  1. 分布式缓存技术memcached学习系列(五)—— memcached java客户端的使用

    Memcached的客户端简介 我们已经知道,memcached是一套分布式的缓存系统,memcached的服务端只是缓存数据的地方,并不能实现分布式,而memcached的客户端才是实现分布式的地方 ...

  2. 分布式缓存技术memcached学习(五)—— memcached java客户端的使用

    Memcached的客户端简介 我们已经知道,memcached是一套分布式的缓存系统,memcached的服务端只是缓存数据的地方,并不能实现分布式,而memcached的客户端才是实现分布式的地方 ...

  3. 分布式缓存技术memcached学习系列(四)—— 一致性hash算法原理

    分布式一致性hash算法简介 当你看到"分布式一致性hash算法"这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前, ...

  4. 分布式缓存技术memcached学习系列(二)——memcached基础命令

    上文<linux环境下编译memcahed>介绍了memcahed在linux环境下的安装以及登录,下面介绍memcahed的基本命令的使用. Add 功能:往内存增加一条新的缓存记录 语 ...

  5. 分布式缓存技术memcached学习系列(三)——memcached内存管理机制

    几个重要概念 Slab memcached通过slab机制进行内存的分配和回收,slab是一个内存块,它是memcached一次申请内存的最小单位,.在启动memcached的时候一般会使用参数-m指 ...

  6. 分布式缓存技术memcached学习系列(一)——linux环境下编译memcahed

    安装依赖工具 [root@localhost upload]# yum  install gcc  make  cmake  autoconf  libtool 下载并上传文件 memcached 依 ...

  7. 分布式缓存技术redis学习系列(四)——redis高级应用(集群搭建、集群分区原理、集群操作)

    本文是redis学习系列的第四篇,前面我们学习了redis的数据结构和一些高级特性,点击下面链接可回看 <详细讲解redis数据结构(内存模型)以及常用命令> <redis高级应用( ...

  8. 分布式缓存技术redis学习系列(一)——redis简介以及linux上的安装

    redis简介 redis是NoSQL(No Only SQL,非关系型数据库)的一种,NoSQL是以Key-Value的形式存储数据.当前主流的分布式缓存技术有redis,memcached,ssd ...

  9. 分布式缓存技术redis学习系列(三)——redis高级应用(主从、事务与锁、持久化)

    上文<详细讲解redis数据结构(内存模型)以及常用命令>介绍了redis的数据类型以及常用命令,本文我们来学习下redis的一些高级特性. 安全性设置 设置客户端操作秘密 redis安装 ...

  10. Redis学习系列五Set(集合)

    一.简介 Redis中的Set(集合)相当于C#中的HashSet,它内部的键值对时无序的.唯一的.用过Dictionary的都知道,Dictionary都知道,里面的每个键值对肯定是唯一的,因为键不 ...

随机推荐

  1. lua c api

    #include <stdio.h> #include <string.h> extern "C"{ #include <lua.h> #inc ...

  2. 调试使用windows堆程序遇到的问题

    今天测试我的api hook demo,中间有个单向链表,我对他进行遍历的时候,通过判断链表当前元素是否为NULL(即0)来进行循环控制,在cmd下正常运行,输出的是:,struct addr is ...

  3. 数据库性能优化常用sql脚本总结

    最近闲来无事,正好抽出时间,来总结总结 sql性能优化方面的一下小技巧,小工具.虽然都是些很杂的东西,但是我个人觉得,如果真的清楚了里面的一下指标,或许真的能抵半个DBA. 有些时候,找不到DBA或者 ...

  4. mysql优化记录

    老板反应项目的反应越来越慢,叫优化一下,顺便学习总结一下mysql优化. 不同引擎的优化,myisam读的效果好,写的效率差,使用场景 非事务型应用只读类应用空间类应用 Innodb的特性,innod ...

  5. mysql中binlog_format模式与配置详解

    mysql复制主要有三种方式:基于SQL语句的复制(statement-based replication, SBR),基于行的复制(row-based replication, RBR),混合模式复 ...

  6. 为什么为 const 变量重新赋值不是个静态错误

    const 和 let 的唯一区别就是用 const 声明的变量不能被重新赋值(只读变量),比如像下面这样就会报错: const foo = 1 foo = 2 // TypeError: Assig ...

  7. 使用JVMTI创建调试和监控代理

    Java 虚拟机工具接口(JVMTI)提供了一个编程接口,允许你(程序员)创建software agent 来监视和控制你的Java应用. JVMTI 代替了原来的Java Virtual Machi ...

  8. make 和 makefile 的关系

    程序的 编译 和 链接 要先总结 make 和 makefile,就需要先了解下面这个过程: 预编译:也叫预处理,进行一些文本替换工作,比如将 #define 定义的内容,在代码中进行替换: 编译:将 ...

  9. Linux 执行文件查找命令 which 详解

    某个文件不知道放在哪里了,通常可以使用下面的一些命令来查找: which  查看可执行文件的位置 whereis 查看文件的位置 locate   配合数据库查看文件位置 find   实际搜寻硬盘查 ...

  10. 相同根域名下跨域共享session的解决方案

    https://code.msdn.microsoft.com/CSASPNETShareSessionBetween-021daa39