cari
Rumahpangkalan dataRedisCara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson

      1. Kami menggunakan Redis untuk melaksanakan kunci yang diedarkan pada mulanya gunakan

      Kunci dan gunakan skrip Lua untuk memastikan atomicity melepaskan kunci. Pelaksanaan manual ini menyusahkan laman web rasmi Redis juga dengan jelas menyatakan bahawa versi Java menggunakan

      untuk melaksanakannya. Editor juga melihat laman web rasmi dan perlahan-lahan memikirkannya, dan mengambil jarak dekat untuk merakamnya. Daripada tapak web rasmi untuk menyepadukan Springboot kepada tafsiran kod sumber, ambil satu nod sebagai contoh. SET resource-name anystring NX EX max-lock-timeRedisson2. Mengapa menggunakan Redisson

      1 Kami membuka laman web rasmi

      laman web rasmi redis Cina

      2 untuk menggunakan lain

      Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson3 Buka syor rasmi

      Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson4 Alamat Redisson

      5 Struktur Redisson

      Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson

      3. Springboot mengintegrasikan Redisson

      Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson1 dependencies

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>
      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
      </dependency>
      <!--redis分布式锁-->
      <dependency>
          <groupId>org.redisson</groupId>
          <artifactId>redisson</artifactId>
          <version>3.12.0</version>
      </dependency>

      2 Ambil tapak web rasmi sebagai contoh untuk melihat cara mengkonfigurasi

      3. Tulis kelas konfigurasi

      import org.redisson.Redisson;
      import org.redisson.api.RedissonClient;
      import org.redisson.config.Config;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      /**
       * @author wangzhenjun
       * @date 2022/2/9 9:57
       */
      @Configuration
      public class MyRedissonConfig {
      
          /**
           * 所有对redisson的使用都是通过RedissonClient来操作的
           * @return
           */
          @Bean(destroyMethod="shutdown")
          public RedissonClient redisson(){
              // 1. 创建配置
              Config config = new Config();
              // 一定要加redis://
              config.useSingleServer().setAddress("redis://192.168.17.130:6379");
              // 2. 根据config创建出redissonClient实例
              RedissonClient redissonClient = Redisson.create(config);
              return redissonClient;
          }
      }
      Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson 4. Contoh penguncian ujian laman web rasmi

      5 Tulis

      @ResponseBody
      @GetMapping("/hello")
      public String hello(){
          // 1.获取一把锁,只要锁名字一样,就是同一把锁
          RLock lock = redisson.getLock("my-lock");
          // 2. 加锁
          lock.lock();// 阻塞试等待  默认加的都是30s
          // 带参数情况
          // lock.lock(10, TimeUnit.SECONDS);// 10s自动解锁,自动解锁时间一定要大于业务的执行时间。
          try {
              System.out.println("加锁成功" + Thread.currentThread().getId());
              Thread.sleep(30000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          } finally {
              // 3. 解锁
              System.out.println("解锁成功:" + Thread.currentThread().getId());
              lock.unlock();
          }
          return "hello";
      }
      Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson berdasarkan antara muka Pengawal yang ringkas di laman web rasmi 6. Uji

      4. lock.lock() Analisis kod sumber

      Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson1 Buka kelas pelaksanaan RedissonLock

      2. Cari kaedah pelaksanaan

      @Override
      public void lock() {
          try {
          	// 我们发现不穿过期时间源码默认过期时间为-1
              lock(-1, null, false);
          } catch (InterruptedException e) {
              throw new IllegalStateException();
          }
      }
      Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson3. Tahan Ctrl untuk memasukkan kaedah kunci

      private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
      	// 获取线程的id,占有锁的时候field的值为UUID:线程号id
          long threadId = Thread.currentThread().getId();
          // 尝试获得锁
          Long ttl = tryAcquire(leaseTime, unit, threadId);
          // lock acquired 获得锁,返回
          if (ttl == null) {
              return;
          }
      	// 这里说明获取锁失败,就通过线程id订阅这个锁
          RFuture<RedissonLockEntry> future = subscribe(threadId);
          if (interruptibly) {
              commandExecutor.syncSubscriptionInterrupted(future);
          } else {
              commandExecutor.syncSubscription(future);
          }
      
          try {
          	// 这里进行自旋,不断尝试获取锁
              while (true) {
              	// 继续尝试获取锁
                  ttl = tryAcquire(leaseTime, unit, threadId);
                  // lock acquired 获取成功
                  if (ttl == null) {
                  	// 直接返回,挑出自旋
                      break;
                  }
      
                  // waiting for message 继续等待获得锁
                  if (ttl >= 0) {
                      try {
                          future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                      } catch (InterruptedException e) {
                          if (interruptibly) {
                              throw e;
                          }
                          future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                      }
                  } else {
                      if (interruptibly) {
                          future.getNow().getLatch().acquire();
                      } else {
                          future.getNow().getLatch().acquireUninterruptibly();
                      }
                  }
              }
          } finally {
           	// 取消订阅
              unsubscribe(future, threadId);
          }
      //        get(lockAsync(leaseTime, unit));
      }

      4 Masuk dan cuba dapatkan kaedah kunci

      private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
      	// 直接进入异步方法
          return get(tryAcquireAsync(leaseTime, unit, threadId));
      }
      
      private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
          // 这里进行判断如果没有设置参数leaseTime = -1
          if (leaseTime != -1) {
              return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
          }
          // 此方法进行获得锁,过期时间为看门狗的默认时间
          // private long lockWatchdogTimeout = 30 * 1000;看门狗默认过期时间为30s
          // 加锁和过期时间要保证原子性,这个方法后面肯定调用执行了Lua脚本,我们下面在看
          RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
          // 开启一个定时任务进行不断刷新过期时间
          ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
              if (e != null) {
                  return;
              }
              // lock acquired 获得锁
              if (ttlRemaining == null) {
              	// 刷新过期时间方法,我们下一步详细说一下
                  scheduleExpirationRenewal(threadId);
          });
          return ttlRemainingFuture;

      5. Semak kaedah tryLockInnerAsync()

      <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
          internalLockLeaseTime = unit.toMillis(leaseTime);
      
          return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
          		  // 首先判断锁是否存在
                    "if (redis.call(&#39;exists&#39;, KEYS[1]) == 0) then " +
                    		// 存在则获取锁
                        "redis.call(&#39;hset&#39;, KEYS[1], ARGV[2], 1); " +
                        // 然后设置过期时间
                        "redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); " +
                        "return nil; " +
                    "end; " +
                    // hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁
                    "if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[2]) == 1) then " +
                    		// hincrby自增一
                        "redis.call(&#39;hincrby&#39;, KEYS[1], ARGV[2], 1); " +
                        	// 锁的值大于1,说明是可重入锁,重置过期时间
                        "redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); " +
                        "return nil; " +
                    "end; " +
                    // 锁已存在,且不是本线程,则返回过期时间ttl
                    "return redis.call(&#39;pttl&#39;, KEYS[1]);",
                      Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
      }

      6 Pergi ke apa yang tinggal dalam 4 kaedah ScheduleExpirationRenewal() tugas berjadual Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson

      Cari kod sumber langkah demi langkah: scheduleExpirationRenewal --->renewExpiration

      Mengikut kod sumber di bawah, masa muat semula tugas yang dijadualkan ialah: internalLockLeaseTime / 3, iaitu anjing pemerhati 1/3, yang dimuat semula setiap 10 saat

      private void renewExpiration() {
          ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
          if (ee == null) {
              return;
          }
          
          Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
              @Override
              public void run(Timeout timeout) throws Exception {
                  ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
                  if (ent == null) {
                      return;
                  }
                  Long threadId = ent.getFirstThreadId();
                  if (threadId == null) {
                      return;
                  }
                  
                  RFuture<Boolean> future = renewExpirationAsync(threadId);
                  future.onComplete((res, e) -> {
                      if (e != null) {
                          log.error("Can&#39;t update lock " + getName() + " expiration", e);
                          return;
                      }
                      
                      if (res) {
                          // reschedule itself
                          renewExpiration();
                      }
                  });
              }
          }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
          
          ee.setTimeout(task);
      }

      5. Analisis kod sumber Lock.lock(10, TimeUnit.SECONDS)

      1 Buka kelas pelaksanaan

      @Override
      public void lock(long leaseTime, TimeUnit unit) {
          try {
          	// 这里的过期时间为我们输入的10
              lock(leaseTime, unit, false);
          } catch (InterruptedException e) {
              throw new IllegalStateException();
          }
      }

      2 kod

      3. Pergi terus untuk cuba mendapatkan kunci

      kaedah

      private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
          // 这里进行判断如果没有设置参数leaseTime = -1,此时我们为10
          if (leaseTime != -1) {
          	// 来到此方法
              return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
          }
          // 此处省略后面内容,前面以详细说明。。。。
      }

      4. Buka kaedah lock()

      Tidak sukar untuk kita mencari bahawa ia adalah sama dengan kaedah tanpa melepasi masa tamat, kecuali nilai leaseTime telah berubah. tryAcquireAsync()

      <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
          internalLockLeaseTime = unit.toMillis(leaseTime);
      
          return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
          		  // 首先判断锁是否存在
                    "if (redis.call(&#39;exists&#39;, KEYS[1]) == 0) then " +
                    		// 存在则获取锁
                        "redis.call(&#39;hset&#39;, KEYS[1], ARGV[2], 1); " +
                        // 然后设置过期时间
                        "redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); " +
                        "return nil; " +
                    "end; " +
                    // hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁
                    "if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[2]) == 1) then " +
                    		// hincrby自增一
                        "redis.call(&#39;hincrby&#39;, KEYS[1], ARGV[2], 1); " +
                        	// 锁的值大于1,说明是可重入锁,重置过期时间
                        "redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[1]); " +
                        "return nil; " +
                    "end; " +
                    // 锁已存在,且不是本线程,则返回过期时间ttl
                    "return redis.call(&#39;pttl&#39;, KEYS[1]);",
                      Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
      }

      6. Analisis kod sumber Lock.unlock()

      tryLockInnerAsync()1 Pelaksanaan kaedah terbuka

      @Override
      public void unlock() {
          try {
          	// 点击进入释放锁方法
              get(unlockAsync(Thread.currentThread().getId()));
          } catch (RedisException e) {
              if (e.getCause() instanceof IllegalMonitorStateException) {
                  throw (IllegalMonitorStateException) e.getCause();
              } else {
                  throw e;
              }
          }
          
      //        Future<Void> future = unlockAsync();
      //        future.awaitUninterruptibly();
      //        if (future.isSuccess()) {
      //            return;
      //        }
      //        if (future.cause() instanceof IllegalMonitorStateException) {
      //            throw (IllegalMonitorStateException)future.cause();
      //        }
      //        throw commandExecutor.convertException(future);
      }

      kaedah

      @Override
      public RFuture<Void> unlockAsync(long threadId) {
          RPromise<Void> result = new RedissonPromise<Void>();
          // 解锁方法,后面展开说
          RFuture<Boolean> future = unlockInnerAsync(threadId);
      	// 完成
          future.onComplete((opStatus, e) -> {
              if (e != null) {
              	// 取消到期续订
                  cancelExpirationRenewal(threadId);
                  // 将这个未来标记为失败并通知所有人
                  result.tryFailure(e);
                  return;
              }
      		// 状态为空,说明解锁的线程和当前锁不是同一个线程
              if (opStatus == null) {
                  IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                          + id + " thread-id: " + threadId);
                  result.tryFailure(cause);
                  return;
              }
              
              cancelExpirationRenewal(threadId);
              result.trySuccess(null);
          });
      
          return result;
      }

      3 . Buka

      Kaedah

      protected RFuture<Boolean> unlockInnerAsync(long threadId) {
          return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
          		// 判断释放锁的线程和已存在锁的线程是不是同一个线程,不是返回空
                  "if (redis.call(&#39;hexists&#39;, KEYS[1], ARGV[3]) == 0) then " +
                      "return nil;" +
                  "end; " +
                  // 释放锁后,加锁次数减一
                  "local counter = redis.call(&#39;hincrby&#39;, KEYS[1], ARGV[3], -1); " +
                  // 判断剩余数量是否大于0
                  "if (counter > 0) then " +
                  	// 大于0 ,则刷新过期时间
                      "redis.call(&#39;pexpire&#39;, KEYS[1], ARGV[2]); " +
                      "return 0; " +
                  "else " +
                  	// 释放锁,删除key并发布锁释放的消息
                      "redis.call(&#39;del&#39;, KEYS[1]); " +
                      "redis.call(&#39;publish&#39;, KEYS[2], ARGV[1]); " +
                      "return 1; "+
                  "end; " +
                  "return nil;",
                  Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
      
      }

      Atas ialah kandungan terperinci Cara Springboot melaksanakan analisis kod sumber kunci reentrant yang diedarkan Redis berdasarkan Redisson. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

      Kenyataan
      Artikel ini dikembalikan pada:亿速云. Jika ada pelanggaran, sila hubungi admin@php.cn Padam
      Redis: Meningkatkan prestasi aplikasi dan skalabilitiRedis: Meningkatkan prestasi aplikasi dan skalabilitiApr 17, 2025 am 12:16 AM

      Redis meningkatkan prestasi aplikasi dan skalabiliti dengan data caching, melaksanakan penguncian dan ketekunan data yang diedarkan. 1) Data cache: Gunakan REDIS ke cache data yang sering diakses untuk meningkatkan kelajuan akses data. 2) Kunci yang diedarkan: Gunakan Redis untuk melaksanakan kunci yang diedarkan untuk memastikan keselamatan operasi dalam persekitaran yang diedarkan. 3) Kegigihan data: Memastikan keselamatan data melalui mekanisme RDB dan AOF untuk mencegah kehilangan data.

      Redis: Meneroka Model dan Struktur DatanyaRedis: Meneroka Model dan Struktur DatanyaApr 16, 2025 am 12:09 AM

      Model dan struktur data Redis termasuk lima jenis utama: 1. String: Digunakan untuk menyimpan teks atau data binari, dan menyokong operasi atom. 2. Senarai: Koleksi Elemen yang Diarahkan, sesuai untuk beratur dan susunan. 3. Set: unsur -unsur unik yang tidak teratur ditetapkan, menyokong operasi set. 4. Diarahkan Set (SortedSet): Satu set elemen yang unik dengan skor, sesuai untuk kedudukan. 5. Jadual Hash (Hash): Koleksi pasangan nilai utama, sesuai untuk menyimpan objek.

      Redis: mengklasifikasikan pendekatan pangkalan datanyaRedis: mengklasifikasikan pendekatan pangkalan datanyaApr 15, 2025 am 12:06 AM

      Kaedah pangkalan data Redis termasuk pangkalan data dalam memori dan penyimpanan nilai utama. 1) Redis menyimpan data dalam ingatan, dan membaca dan menulis dengan cepat. 2) Ia menggunakan pasangan nilai utama untuk menyimpan data, menyokong struktur data kompleks seperti senarai, koleksi, jadual hash dan koleksi yang diperintahkan, sesuai untuk pangkalan data cache dan NoSQL.

      Mengapa menggunakan Redis? Faedah dan kelebihanMengapa menggunakan Redis? Faedah dan kelebihanApr 14, 2025 am 12:07 AM

      REDIS adalah penyelesaian pangkalan data yang kuat kerana ia menyediakan prestasi cepat, struktur data yang kaya, ketersediaan dan skalabilitas yang tinggi, keupayaan kegigihan, dan pelbagai sokongan ekosistem. 1) Prestasi yang sangat cepat: Data Redis disimpan dalam ingatan dan mempunyai kelajuan membaca dan menulis yang sangat cepat, sesuai untuk aplikasi kesesuaian yang tinggi dan rendah. 2) Struktur data yang kaya: Menyokong pelbagai jenis data, seperti senarai, koleksi, dan lain -lain, yang sesuai untuk pelbagai senario. 3) Ketersediaan dan skalabilitas yang tinggi: Menyokong replikasi master-hamba dan mod kluster untuk mencapai ketersediaan yang tinggi dan berskala mendatar. 4) Kegigihan dan keselamatan data: Ketekunan data dicapai melalui RDB dan AOF untuk memastikan integriti dan kebolehpercayaan data. 5) Sokongan ekosistem dan komuniti yang luas: dengan ekosistem yang besar dan komuniti aktif,

      Memahami NoSQL: Ciri Utama RedisMemahami NoSQL: Ciri Utama RedisApr 13, 2025 am 12:17 AM

      Ciri -ciri utama Redis termasuk kelajuan, fleksibiliti dan sokongan struktur data yang kaya. 1) Kelajuan: Redis adalah pangkalan data dalam memori, dan membaca dan menulis operasi hampir seketika, sesuai untuk pengurusan cache dan sesi. 2) Fleksibiliti: Menyokong pelbagai struktur data, seperti rentetan, senarai, koleksi, dan lain -lain, yang sesuai untuk pemprosesan data yang kompleks. 3) Sokongan Struktur Data: Menyediakan rentetan, senarai, koleksi, jadual hash, dan lain -lain, yang sesuai untuk keperluan perniagaan yang berbeza.

      Redis: Mengenal pasti fungsi utamanyaRedis: Mengenal pasti fungsi utamanyaApr 12, 2025 am 12:01 AM

      Fungsi teras Redis adalah sistem penyimpanan dan pemprosesan data berprestasi tinggi. 1) Akses data berkelajuan tinggi: Redis menyimpan data dalam memori dan menyediakan kelajuan membaca dan menulis tahap mikrosecond. 2) Struktur Data Kaya: Menyokong rentetan, senarai, koleksi, dan lain -lain, dan menyesuaikan diri dengan pelbagai senario aplikasi. 3) Kegigihan: Data berterusan ke cakera melalui RDB dan AOF. 4) Menerbitkan langganan: boleh digunakan dalam beratur mesej atau sistem komunikasi masa nyata.

      Redis: Panduan untuk struktur data popularRedis: Panduan untuk struktur data popularApr 11, 2025 am 12:04 AM

      Redis menyokong pelbagai struktur data, termasuk: 1. String, sesuai untuk menyimpan data nilai tunggal; 2. Senarai, sesuai untuk beratur dan susunan; 3. Tetapkan, digunakan untuk menyimpan data yang tidak duplikasi; 4. Diarahkan set, sesuai untuk senarai ranking dan beratur keutamaan; 5. Jadual hash, sesuai untuk menyimpan objek atau data berstruktur.

      Cara Melaksanakan Kaunter RedisCara Melaksanakan Kaunter RedisApr 10, 2025 pm 10:21 PM

      Kaunter Redis adalah satu mekanisme yang menggunakan penyimpanan pasangan nilai utama REDIS untuk melaksanakan operasi pengiraan, termasuk langkah-langkah berikut: mewujudkan kekunci kaunter, meningkatkan tuduhan, mengurangkan tuduhan, menetapkan semula, dan mendapatkan tuduhan. Kelebihan kaunter Redis termasuk kelajuan cepat, konkurensi tinggi, ketahanan dan kesederhanaan dan kemudahan penggunaan. Ia boleh digunakan dalam senario seperti pengiraan akses pengguna, penjejakan metrik masa nyata, skor permainan dan kedudukan, dan pengiraan pemprosesan pesanan.

      See all articles

      Alat AI Hot

      Undresser.AI Undress

      Undresser.AI Undress

      Apl berkuasa AI untuk mencipta foto bogel yang realistik

      AI Clothes Remover

      AI Clothes Remover

      Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

      Undress AI Tool

      Undress AI Tool

      Gambar buka pakaian secara percuma

      Clothoff.io

      Clothoff.io

      Penyingkiran pakaian AI

      AI Hentai Generator

      AI Hentai Generator

      Menjana ai hentai secara percuma.

      Artikel Panas

      R.E.P.O. Kristal tenaga dijelaskan dan apa yang mereka lakukan (kristal kuning)
      1 bulan yang laluBy尊渡假赌尊渡假赌尊渡假赌
      R.E.P.O. Tetapan grafik terbaik
      1 bulan yang laluBy尊渡假赌尊渡假赌尊渡假赌
      R.E.P.O. Cara Memperbaiki Audio Jika anda tidak dapat mendengar sesiapa
      1 bulan yang laluBy尊渡假赌尊渡假赌尊渡假赌
      R.E.P.O. Arahan sembang dan cara menggunakannya
      1 bulan yang laluBy尊渡假赌尊渡假赌尊渡假赌

      Alat panas

      PhpStorm versi Mac

      PhpStorm versi Mac

      Alat pembangunan bersepadu PHP profesional terkini (2018.2.1).

      Hantar Studio 13.0.1

      Hantar Studio 13.0.1

      Persekitaran pembangunan bersepadu PHP yang berkuasa

      EditPlus versi Cina retak

      EditPlus versi Cina retak

      Saiz kecil, penyerlahan sintaks, tidak menyokong fungsi gesaan kod

      SublimeText3 versi Inggeris

      SublimeText3 versi Inggeris

      Disyorkan: Versi Win, menyokong gesaan kod!

      Penyesuai Pelayan SAP NetWeaver untuk Eclipse

      Penyesuai Pelayan SAP NetWeaver untuk Eclipse

      Integrasikan Eclipse dengan pelayan aplikasi SAP NetWeaver.