이 글의 내용은 Java에서 높은 동시성을 해결하는 방법에 관한 것입니다. Java의 높은 동시성 솔루션은 특정 참고 가치가 있습니다. 도움이 필요한 친구가 참고할 수 있기를 바랍니다.
우리가 개발하는 웹사이트의 경우, 웹사이트 방문 횟수가 매우 많을 경우 관련 동시 접속 문제를 고려해야 합니다. 동시성 문제는 프로그래머들 대부분의 골칫거리다,
그런데 또 피할 수 없으니 냉정하게 대처하자~ 오늘은 함께 공부해보자 공통 동시성과 동기화.
동시성과 동기화를 더 잘 이해하려면 먼저 두 가지 중요한 개념을 이해해야 합니다. 동기화 및 비동기
1 . 동기식과 비동기식의 차이점과 연결
이때, 소위 동기화는 시스템이 함수나 메소드를 실행한 후 값이나 메시지를 반환할 때까지 기다리는 것으로 이해될 수 있습니다. , 프로그램이 차단되고 다른 명령은
에서 반환된 값이나 메시지를 받은 후에만 실행할 수 있습니다.
비동기적으로, 함수나 메소드를 실행한 후에는 반환 값이나 메시지를 차단적으로 기다릴 필요가 없습니다. 그런 다음 시스템이 반환을 받을 때에만 비동기 프로세스를 위임하면 됩니다. 값이나 메시지가 있으면 시스템은 위임된 비동기 프로세스를 자동으로 트리거하여 전체 프로세스를 완료합니다.
동기화는 어느 정도 단일 스레드로 간주될 수 있습니다. 이 스레드는 메서드를 요청한 후 메서드가 응답할 때까지 기다립니다. 그렇지 않으면 실행을 계속하지 않습니다. .
Asynchronous는 어느 정도 멀티 스레드로 간주될 수 있습니다. (말도 안되는 소리, 어떻게 스레드를 비동기라고 부를 수 있습니까?) 메서드를 요청한 후에는 무시되고 계속해서 다른 메서드를 실행합니다.
동기화는 한 번에 하나씩 수행되는 작업입니다.
비동기란 다른 작업을 수행하지 않고 한 작업만 수행하는 것을 의미합니다.
예:
입이 하나이기 때문에 먹고 말하는 것은 한 번에 하나씩만 가능합니다. 그러나 음악을 듣는 것과 먹는 것은 비동기적입니다. 왜냐하면 음악을 듣는 것이 우리를 먹게 하는 것이 아니기 때문입니다.
Java 프로그래머의 경우 동기화 모니터링 개체가 클래스인 경우 개체가 클래스의 동기화 메서드에 액세스하면 다른 개체가 계속하려는 경우 동기화 키워드를 자주 듣습니다. 클래스의 동기화 메서드에 액세스하려면 이전 개체가 동기화 메서드 실행을 마친 후에만 현재 개체가 메서드를 계속 실행할 수 있습니다. 이것이 동기화입니다. 반대로 메서드 이전에 동기화 키워드 수정이 없으면 여러 개체가 동시에 동일한 메서드에 액세스할 수 있습니다. 이는 비동기식입니다.
추가하자면(더티 데이터 및 반복 불가능 읽기 관련 개념):
더티 데이터
더티 읽기는 트랜잭션이 액세스할 때를 의미합니다. 데이터가 수정되었지만 수정 사항이 아직 데이터베이스에 제출되지 않은 상태입니다. 이때 다른 트랜잭션도 해당 데이터에 액세스한 후 해당 데이터를 사용합니다. 이 데이터는 아직 커밋되지 않았기 때문에 다른 트랜잭션에서 읽은 데이터는 더티 데이터이며 더티 데이터를 기반으로 한 작업은 올바르지 않을 수 있습니다.
반복 불가능 읽기
반복 불가능 읽기는 트랜잭션 내에서 동일한 데이터를 여러 번 읽는 것을 의미합니다. 이 트랜잭션이 끝나기 전에 다른 트랜잭션도 동일한 데이터에 액세스합니다. 그러면 첫 번째 트랜잭션에서 읽은 두 개의 데이터 사이에 두 번째 트랜잭션의 수정으로 인해 첫 번째 트랜잭션에서 두 번 읽은 데이터가 다를 수 있습니다. 이렇게 트랜잭션 내에서 두 번 읽는 데이터가 다르기 때문에 반복 불가능 읽기라고 합니다
2. 동시성 및 동기화 처리 방법
# 🎜🎜# 오늘은 주로 잠금 메커니즘을 통해 동시성 및 동기화 문제를 처리하는 방법에 대해 이야기하겠습니다. 잠금 메커니즘에는 두 가지 수준이 있다는 점을 이해해야 합니다. 하나는 Java의 동기화 잠금과 같은 코드 수준입니다. 일반적인 것은 동기화 키워드입니다. The 다른 하나는 데이터베이스 수준에 있으며, 더 일반적인 것은 비관적 잠금과 낙관적 잠금입니다. 여기서 초점을 맞추는 것은 비관적 잠금(전통적인 물리적 잠금)과 낙관적 잠금입니다. 비관적 잠금: 비관적 잠금은 이름에서 알 수 있듯이 외부 세계(이 시스템의 다른 현재 트랜잭션 포함)에 의한 데이터 잠금을 의미합니다. 시스템의 트랜잭션 처리) 수정은 보수적이므로 전체 데이터 처리 프로세스 동안 데이터가 잠겨 있습니다. 비관적 잠금 구현은 데이터베이스에서 제공하는 잠금 메커니즘에 의존하는 경우가 많습니다(데이터베이스 계층에서 제공하는 잠금 메커니즘만이 데이터 액세스의 독점성을 실제로 보장할 수 있습니다. 그렇지 않으면 잠금 메커니즘이 이 시스템에 구현되어 있지만 외부 시스템이 데이터를 수정하지 않는다는 보장은 없습니다.) 데이터베이스에 의존하는 일반적인 비관적 잠금 호출:select * from account where name=”Erica” for update이 SQL 문은 검색 조건(name="Erica")을 충족하는 계정 테이블의 모든 레코드를 잠급니다. .
本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
Hibernate 的悲观锁,也是基于数据库的锁机制实现。
下面的代码实现了对查询记录的加锁:
String hqlStr ="from TUser as user where user.name='Erica'"; Query query = session.createQuery(hqlStr); query.setLockMode("user",LockMode.UPGRADE); // 加锁 List userList = query.list();// 执行查询,获取数据
query.setLockMode 对查询语句中,特定别名所对应的记录进行加锁(我们为 TUser 类指定了一个别名 “user” ),这里也就是对返回的所有 user 记录进行加锁。
观察运行期 Hibernate 生成的 SQL 语句:
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex from t_user tuser0_ where (tuser0_.name='Erica' ) for update
这里 Hibernate 通过使用数据库的 for update 子句实现了悲观锁机制。
Hibernate 的加锁模式有:
? LockMode.NONE : 无锁机制。
? LockMode.WRITE : Hibernate 在 Insert 和 Update 记录的时候会自动获取
? LockMode.READ : Hibernate 在读取记录的时候会自动获取。
以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update
过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。
? LockMode.UPGRADE :利用数据库的 for update 子句加锁。
? LockMode. UPGRADE_NOWAIT : Oracle 的特定实现,利用 Oracle 的 for
update nowait 子句实现加锁。
上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查询开始之前(也就是 Hiberate 生成 SQL 之前)设定加锁,才会 真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含 for update子句的 Select SQL 加载进来,所谓数据库加锁也就无从谈起。
为了更好的理解select... for update的锁表的过程,本人将要以mysql为例,进行相应的讲解
1、要测试锁定的状况,可以利用MySQL的Command Mode ,开二个视窗来做测试。
表的基本结构如下:
表中内容如下:
开启两个测试窗口,在其中一个窗口执行select * from ta for update0
然后在另外一个窗口执行update操作如下图:
等到一个窗口commit后的图片如下:
到这里,悲观锁机制你应该了解一些了吧~
需要注意的是for update要放到mysql的事务中,即begin和commit中,否者不起作用。
至于是锁住整个表还是锁住选中的行。
至于hibernate中的悲观锁使用起来比较简单,这里就不写demo了~感兴趣的自己查一下就ok了~
乐观锁(Optimistic Locking):
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依 靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进 行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过 程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作 员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几 百上千个并发,这样的情况将导致怎样的后果。 乐观锁机制在一定程度上解决了这个问题。
乐观锁,大多是基于数据版本 Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来 实现。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。 2 在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并 从其帐户余额中扣除 $20 ( $100-$20 )。 3 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣 除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大 于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。 4 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数 据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的 数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记 录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。 这样,就避免了操作员 B 用基于version=1 的旧数据修改的结果覆盖操作 员 A 的操作结果的可能。 从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系 统整体性能表现。 需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局 限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户 余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在 系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如 将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途 径,而不是将数据库表直接对外公开)。 Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数 据库的更新操作,利用 Hibernate 提供的透明化乐观锁实现,将大大提升我们的 生产力。
User.hbm.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.xiaohao.test"> <class name="User" table="user" optimistic-lock="version" > <id name="id"> <generator class="native" /> </id> <!--version标签必须跟在id标签后面--> <version column="version" name="version" /> <property name="userName"/> <property name="password"/> </class> </hibernate-mapping>
注意 version 节点必须出现在 ID 节点之后。
这里我们声明了一个 version 属性,用于存放用户的版本信息,保存在 User 表的version中
optimistic-lock 属性有如下可选取值:
? none无乐观锁
? version通过版本机制实现乐观锁
? dirty通过检查发生变动过的属性实现乐观锁
? all通过检查所有属性实现乐观锁
其中通过 version 实现的乐观锁机制是 Hibernate 官方推荐的乐观锁实现,同时也 是 Hibernate 中,目前唯一在数据对象脱离 Session 发生修改的情况下依然有效的锁机 制。因此,一般情况下,我们都选择 version 方式作为 Hibernate 乐观锁实现机制。
2 、配置文件hibernate.cfg.xml和UserTest测试类
hibernate.cfg.xml
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 指定数据库方言 如果使用jbpm的话,数据库方言只能是InnoDB--> <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property> <!-- 根据需要自动创建数据表 --> <property name="hbm2ddl.auto">update</property> <!-- 显示Hibernate持久化操作所生成的SQL --> <property name="show_sql">true</property> <!-- 将SQL脚本进行格式化后再输出 --> <property name="format_sql">false</property> <property name="current_session_context_class">thread</property> <!-- 导入映射配置 --> <property name="connection.url">jdbc:mysql:///user</property> <property name="connection.username">root</property> <property name="connection.password">123456</property> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <mapping resource="com/xiaohao/test/User.hbm.xml" /> </session-factory> </hibernate-configuration>
UserTest.java
package com.xiaohao.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; public class UserTest { public static void main(String[] args) { Configuration conf=new Configuration().configure(); SessionFactory sf=conf.buildSessionFactory(); Session session=sf.getCurrentSession(); Transaction tx=session.beginTransaction(); // User user=new User("小浩","英雄"); // session.save(user); // session.createSQLQuery("insert into user(userName,password) value('张英雄16','123')") // .executeUpdate(); User user=(User) session.get(User.class, 1); user.setUserName("221"); // session.save(user); System.out.println("恭喜您,用户的数据插入成功了哦~~"); tx.commit(); } }
每次对 TUser 进行更新的时候,我们可以发现,数据库中的 version 都在递增。
下面我们将要通过乐观锁来实现一下并发和同步的测试用例:
这里需要使用两个测试类,分别运行在不同的虚拟机上面,以此来模拟多个用户同时操作一张表,同时其中一个测试类需要模拟长事务
UserTest.java
package com.xiaohao.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; public class UserTest { public static void main(String[] args) { Configuration conf=new Configuration().configure(); SessionFactory sf=conf.buildSessionFactory(); Session session=sf.openSession(); // Session session2=sf.openSession(); User user=(User) session.createQuery(" from User user where user=5").uniqueResult(); // User user2=(User) session.createQuery(" from User user where user=5").uniqueResult(); System.out.println(user.getVersion()); // System.out.println(user2.getVersion()); Transaction tx=session.beginTransaction(); user.setUserName("101"); tx.commit(); System.out.println(user.getVersion()); // System.out.println(user2.getVersion()); // System.out.println(user.getVersion()==user2.getVersion()); // Transaction tx2=session2.beginTransaction(); // user2.setUserName("4468"); // tx2.commit(); } }
UserTest2.java
package com.xiaohao.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; public class UserTest2 { public static void main(String[] args) throws InterruptedException { Configuration conf=new Configuration().configure(); SessionFactory sf=conf.buildSessionFactory(); Session session=sf.openSession(); // Session session2=sf.openSession(); User user=(User) session.createQuery(" from User user where user=5").uniqueResult(); Thread.sleep(10000); // User user2=(User) session.createQuery(" from User user where user=5").uniqueResult(); System.out.println(user.getVersion()); // System.out.println(user2.getVersion()); Transaction tx=session.beginTransaction(); user.setUserName("100"); tx.commit(); System.out.println(user.getVersion()); // System.out.println(user2.getVersion()); // System.out.println(user.getVersion()==user2.getVersion()); // Transaction tx2=session2.beginTransaction(); // user2.setUserName("4468"); // tx2.commit(); } }
操作流程及简单讲解: 首先启动UserTest2.java测试类,在执行到Thread.sleep(10000);这条语句的时候,当前线程会进入睡眠状态。在10秒钟之内启动UserTest这个类,在到达10秒的时候,我们将会在UserTest.java中抛出下面的异常:
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.xiaohao.test.User#5] at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1932) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2576) at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2476) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2803) at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:113) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216) at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383) at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133) at com.xiaohao.test.UserTest2.main(UserTest2.java:21)
UserTest2代码将在 tx.commit() 处抛出 StaleObjectStateException 异 常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我 们就可以在乐观锁校验失败时进行相应处理
( 동시 읽기 및 쓰기 문제로 확장 가능)문제는 10,000명이 방문한다면 티켓이 공개되기 전에 모두가 볼 수 있도록 보장해야 한다는 것입니다. 티켓을 보면 다른 사람은 볼 수 없습니다. 누가 그것을 잡을 수 있는가는 그 사람의 "운"(네트워크 속도 등)에 달려 있습니다. 두 번째 고려 사항은 동시성입니다. 10,000명이 동시에 구매를 클릭하면 누가 거래를 성사시킬 수 있습니까? 티켓은 총 1장뿐입니다.
우선, 동시성과 관련된 여러 솔루션을 쉽게 생각할 수 있습니다. 잠금 동기화는 애플리케이션 수준을 더 의미합니다. 여러 스레드가 들어와 하나씩만 액세스할 수 있습니다. Java에서는 동기화된 키워드를 나타냅니다. . 잠금에는 두 가지 수준이 있습니다. 하나는 스레드 동기화에 사용되는 Java에서 언급된 개체 잠금이고, 다른 하나는 분산 시스템인 경우 데이터베이스 잠금을 통해서만 달성할 수 있습니다. 옆. 동기화 메커니즘이나 데이터베이스의 물리적 잠금 메커니즘을 사용한다고 가정하면, 10,000명이 동시에 티켓을 볼 수 있도록 보장하는 방법은 분명히 성능을 희생하게 되며, 이는 동시성이 높은 웹 사이트에서는 권장되지 않습니다. 최대 절전 모드를 사용한 후 우리는 낙관적 잠금과 비관적 잠금(예: 기존 물리적 잠금)이라는 또 다른 개념을 생각해 냈습니다. 낙관적 잠금은 테이블을 잠그지 않고 비즈니스 제어를 사용하여 동시성 문제를 해결하는 것을 의미합니다. 이는 데이터의 동시 가독성과 저장된 데이터의 독점성을 보장하는 동시에 동시성으로 인해 발생하는 더티 데이터 문제를 해결합니다. 최대 절전 모드에서 낙관적 잠금을 구현하는 방법: 전제: 기존 테이블에 중복 필드 추가, 버전 번호, 긴 유형 원칙: 1) 현재 버전 번호만 》 = 데이터베이스 테이블 버전 번호, 제출 가능 2) 성공적으로 제출된 후 버전 번호 버전 ++은 구현이 매우 간단합니다. ormapping에 optimistic-lock="version" 속성을 추가하기만 하면 됩니다. 다음은 샘플 조각 fcbe3e9142a5aba58f59285a7d211ac7입니다. 984c7eeb661c6d4daa09c2ed053ce4c5사례 2, 주식 거래 시스템, 은행 시스템, 대용량 데이터 , 당신은 생각하는 방법 우선 주식 거래 시스템의 호가 테이블은 몇 초마다 호가 기록을 생성하며 하루에 한 개가 발생합니다 (3 초마다 호가를 가정). 주식 × 20 × 60 * 6개 레코드, 한 달에 이 테이블에는 몇 개의 레코드가 있습니까? Oracle 테이블의 레코드 수가 100만 개를 초과하면 쿼리 성능이 매우 저하됩니다. 시스템 성능을 보장하는 방법은 무엇입니까? 또 다른 예로, 차이나 모바일에는 수억 명의 사용자가 있습니다. 테이블을 디자인하는 방법은 무엇입니까? 모든 것을 하나의 테이블에 넣으시겠습니까? 따라서 시스템 수가 많은 경우 테이블 분할을 고려해야 합니다. (테이블 이름은 다르지만 구조는 정확히 동일합니다.) 몇 가지 일반적인 방법이 있습니다. (상황에 따라) 1) 비즈니스별로, 예를 들어 휴대폰 번호 테이블과 같이 130으로 시작하는 테이블을 테이블로 간주하고, 다른 테이블은 131로 시작하는 등을 고려할 수 있습니다2) Oracle의 테이블 분할 메커니즘을 사용하여 하위 테이블을 만듭니다3) If 이는 트레이딩 시스템이므로 타임라인 분할로 나누어서 오늘 날짜의 데이터가 하나의 테이블에 있고 과거 데이터가 다른 테이블에 있는 것을 고려할 수 있습니다. 여기에 있는 과거 데이터에 대한 보고 및 쿼리는 당일 거래에 영향을 미치지 않습니다. 물론, 테이블이 분할된 후에는 그에 따라 애플리케이션을 조정해야 합니다. 단순 또는 매핑을 변경해야 할 수도 있습니다. 예를 들어, 일부 기업에서는 저장 프로시저 등을 거쳐야 합니다. 또한 캐싱도 고려해야 합니다. 여기서 캐시는 최대 절전 모드를 의미할 뿐만 아니라 최대 절전 모드 자체가 1단계 및 2단계 캐시를 제공합니다. 여기의 캐시는 애플리케이션과 독립적이며 여전히 메모리 읽기입니다. 데이터베이스에 대한 빈번한 액세스를 줄일 수 있다면 확실히 시스템에 큰 이점이 될 것입니다. 예를 들어, 전자상거래 시스템의 상품 검색에서 특정 키워드가 포함된 상품이 자주 검색된다면, 상품 목록 중 이 부분을 캐시(메모리 내)에 저장하는 것을 고려해 볼 수 있습니다. 매번 데이터베이스에 액세스하면 성능이 크게 향상됩니다. 간단한 캐싱은 해시맵을 직접 만들고, 자주 접근하는 데이터에 대한 키를 만드는 것으로 이해하면 됩니다. 값은 처음 데이터베이스에서 검색한 값이므로 읽지 않고도 다음 번에 맵에서 읽을 수 있습니다. 데이터베이스. ; 보다 전문적인 것들은 현재 캐시 서버로 독립적으로 배치될 수 있는 memcached와 같은 독립적인 캐싱 프레임워크를 가지고 있습니다. 4. 높은 동시성에서 액세스 효율성을 높이는 일반적인 방법우선, 높은 동시성의 병목 현상이 어디에 있는지 이해해야 합니다. 1. 서버 네트워크 대역폭이 충분하지 않을 수 있습니다2. 웹 스레드 연결 수가 충분하지 않을 수 있습니다.3. 데이터베이스 연결 쿼리에 액세스할 수 없습니다. 상황에 따라 해결책도 다릅니다. 첫 번째 경우와 마찬가지로 네트워크 대역폭을 늘리고 DNS 도메인 이름 확인을 여러 서버에 배포할 수 있습니다. 로드 밸런싱, 프런트 엔드 프록시 서버 nginx, apache 등 데이터베이스 쿼리 최적화, 읽기-쓰기 분리, 테이블 샤딩 등 마지막으로 높은 동시성에서 자주 처리해야 하는 일부 콘텐츠 복사: 사용자 캐시, 정보 캐시 등을 포함하여 캐시를 최대한 사용하십시오. 캐싱에 더 많은 메모리를 사용하면 데이터베이스와의 상호 작용이 크게 줄어들고 성능이 향상될 수 있습니다.jprofiler와 같은 도구를 사용하여 성능 병목 현상을 찾고 추가 오버헤드를 줄입니다.
최대 절전 모드 및 기타 도구를 사용하여 데이터베이스 쿼리 문을 최적화하고 직접 생성되는 명령문 수를 줄입니다(장기 쿼리만 최적화됩니다).
데이터베이스 구조를 최적화하고 더 많은 인덱스를 생성하며 쿼리 효율성을 향상시킵니다.
통계 기능은 최대한 캐시에 저장하거나, 필요할 때 통계가 나오지 않도록 하루에 한 번 또는 정기적으로 통계를 수집해야 합니다.
컨테이너 구문 분석을 줄이기 위해 가능하면 정적 페이지를 사용하세요(표시할 동적 콘텐츠에 대해 정적 HTML을 생성해 보세요).
위의 문제를 해결한 후 서버 클러스터를 사용하여 단일 서버의 병목 현상 문제를 해결합니다.
java 높은 동시성, 해결 방법, 해결 방법
높은 동시성에 대한 해결책이 스레드라고 잘못 생각하기 전에는 또는 대기열. 높은 동시성 중에 액세스하는 사용자가 많아 잘못된 시스템 데이터 및 데이터 손실이 발생하기 때문에 생각해 보았습니다. 대기열은 문제를 해결하는 데 사용됩니다. 예를 들어 제품에 대한 입찰, Weibo에 대한 댓글 전달 또는 제품의 플래시 세일을 판매할 때 동시에 방문 횟수도 문제를 해결하는 데 사용됩니다. 여기서는 대기열이 특별한 역할을 합니다. 모든 요청은 대기열에 들어가고 밀리초 단위로 순서대로 처리되므로 데이터 손실이나 잘못된 시스템 데이터가 발생하지 않습니다.
오늘 정보를 확인한 결과 높은 동시성을 위한 두 가지 솔루션이 있음을 발견했습니다.
하나는 캐싱을 사용하는 것이고, 다른 하나는 정적 페이지를 생성하는 것입니다. 가장 기본적인 최적화는 불필요한 리소스 낭비를 줄이기 위한 코드를 작성하는 것입니다. (
1. 새 개체를 자주 사용하지 마십시오. 전체에서 하나의 인스턴스만 필요한 클래스에는 싱글톤 모드를 사용하세요. 문자열 연결 작업의 경우 유틸리티 유형 클래스의 경우 정적 메서드
2를 통해 액세스합니다. 예를 들어, Exception은 메서드 시작을 제어할 수 있지만 성능을 소비하기 위해 Exception은 스택 추적을 유지해야 합니다. Instanceof의 경우 조건부 판단을 위해서는 비율의 조건부 판단 방법을 사용해 보세요. JAVA에서는 Vector보다 성능이 좋은 ArrayList와 같은 효율적인 클래스를 사용하세요. )
우선, 캐싱 기술을 사용해 본 적이 없습니다. 다음에 사용자가 요청할 때 캐시에 데이터를 저장해야 한다고 생각합니다. 여러 요청을 방지하기 위해 캐시에 데이터를 저장하면 서버 성능이 저하되고 심각한 서버 충돌이 발생할 수 있습니다. 이는 아직은 자세한 정보를 온라인으로 수집해야 한다고 생각합니다. 페이지가 요청되면 "http://developer.51cto.com/art/201207/348766.htm"과 같이 페이지 끝이 변경된 웹사이트를 많이 보았습니다. htm으로 변환하면 정적 페이지 때문에 액세스 속도가 빨라집니다. 여기에는 서버 구성 요소가 없습니다. 자세히 소개하겠습니다:
1. 페이지 정적화란 무엇입니까:
짧은 간단히 말해서, 링크를 방문하면 ,서버의 해당 모듈은 이 요청을 처리하고 해당 jsp 인터페이스로 이동한 후 마지막으로 우리가 보고 싶은 데이터를 생성합니다. 이것의 단점은 명백합니다: 서버에 대한 모든 요청이 처리되기 때문입니다. 동시 요청이 너무 많으면 애플리케이션 서버에 대한 부담이 증가하고 서버가 다운될 수도 있습니다. 그렇다면 그것을 피하는 방법은 무엇입니까? test.do 쌍을 넣으면 요청 후의 결과는 html 파일로 저장되어 사용자가 매번 접속하게 되면 애플리케이션 서버에 대한 부담이 줄어들지 않을까요?
그럼 정적 페이지는 어디서 오는 걸까요? 각 페이지를 수동으로 처리하도록 할 수는 없겠죠? 여기에는 우리가 설명할 정적 페이지 생성 솔루션이 포함됩니다. 사용자가 방문하면 test.html이 자동으로 생성되어 사용자에게 표시됩니다.
2. 페이지 정적 구성표를 마스터하기 위해 마스터해야 할 지식 포인트를 간략하게 소개하겠습니다:
1 기본 - URL 재작성
#🎜🎜 #URL 재작성은 무엇인가요? 문제를 설명하기 위해 간단한 예를 사용하겠습니다. URL을 입력하고 실제로 abc.com/test.action에 액세스하면 URL이 다시 작성되었다고 말할 수 있습니다. 이 기술은 널리 사용되며 이 기능을 수행할 수 있는 오픈 소스 도구가 많이 있습니다. 2. 기본 - 서블릿 web.xmlweb.xml에서 요청과 서블릿이 어떻게 일치하는지 모르는 경우 서블릿 문서를 검색하세요. . 이것은 말도 안되는 소리가 아닙니다. 많은 사람들은 매칭 방법 /xyz/*.do가 효과적일 수 있다고 생각합니다. 아직 서블릿 작성 방법을 모른다면 서블릿 작성 방법을 검색해 보세요. 농담이 아닙니다. 오늘날 다양한 통합 도구가 떠돌고 있기 때문입니다. 처음부터 시작하지 마십시오.3. 기본 솔루션 소개
그 중 URL Rewriter 부분은, 유료 또는 오픈 소스 도구를 사용하여 구현할 수 있습니다. URL이 특별히 복잡하지 않은 경우 서블릿에서 구현하는 것을 고려할 수 있으며 다음과 같습니다.总 结:其实我们在开发中都很少考虑这种问题,直接都是先将功能实现,当一个程序员在干到1到2年,就会感觉光实现功能不是最主要的,安全性能、质量等等才是 一个开发人员最该关心的。今天我所说的是高并发。
我的解决思路是:
1、采用分布式应用设计
2、分布式缓存数据库
3、代码优化
Java高并发的例子:
具体情况是这样: 通过java和数据库,自己实现序列自动增长。
实现代码大致如下:
id_table表结构, 主要字段:
id_name varchar2(16); id_val number(16,0); id_prefix varchar2(4);
//操作DB public synchronized String nextStringValue(String id){ SqlSession sqlSess = SqlSessionUtil.getSqlSession(); sqlSess.update("update id_table set id_val = id_val + 1 where id_name="+id); Map map = sqlSess.getOne("select id_name, id_prefix, id_val from id_table where id_name="+ id); BigDecimal val = (BigDecimal) map.get("id_val"); //id_val是具体数字,rePack主要是统一返回固定长度的字符串;如:Y0000001, F0000001, T0000001等 String idValue = rePack(val, map); return idValue; } //公共方法 public class IdHelpTool{ public static String getNextStringValue(String idName){ return getXX().nextStringValue(idName); } }
具体使用者,都是通过类似这种方式:IdHelpTool.getNextStringValue("PAY_LOG");来调用。
问题:
(1) 当出现并发时, 有时会获取重复的ID;
(2) 由于服务器做了相关一些设置,有时调用这个方法,好像还会导致超时。
为了解决问题(1), 考虑过在方法getNextStringValue上,也加上synchronized , 同步关键字过多,会不会更导致超时?
跪求大侠提供个解决问题的大概思路!!!
解决思路一:
1、推荐 https://github.com/adyliu/idcenter
2、可以通过第三方redis来实现。
解决思路一:
1、出现重复ID,是因为脏读了,并发的时候不加 synchronized 比如会出现问题
2、但是加了 synchronized ,性能急剧下降了,本身 java 就是多线程的,你把它单线程使用,不是明智的选择,同时,如果分布式部署的时候,加了 synchronized 也无法控制并发
3、调用这个方法,出现超时的情况,说明你的并发已经超过了数据库所能处理的极限,数据库无限等待导致超时
基于上面的分析,建议采用线程池的方案,支付宝的单号就是用的线程池的方案进行的。
数据库 update 不是一次加1,而是一次加几百甚至上千,然后取到的这 1000个序号,放在线程池里慢慢分配即可,能应付任意大的并发,同时保证数据库没任何压力。
위 내용은 Java에서 높은 동시성을 해결하는 방법은 무엇입니까? Java 높은 동시성 솔루션의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!